一款批量修改AE模板的工具

一、需求分析

对于视频后期剪辑及相关从业人员来说,AE(After Effects)模板效果是一个不错的开始点。在模板效果的基础上,可以很快的做出各种炫酷的后期效果。但是在网上下载的模板工程中,往往包含了非常多的模板文字、图片、图形实体、AI资源等。这些资源文件往往并不是我们需要的,在使用模板时需要手动替换或者删除。但是网上下载的模板工程往往非常大,包含的资源非常多。这样手动改动起来的话,工作量会成倍增加。那么,是否可以考虑做一个小工具来高效完成这项枯燥的工作呢?要替换模板中的文字和图片,第一步就是要定位到这些图片和文字;其次才能考虑使用程序替换。那么,如何定位模板工程中的图片和文字呢?定位到之后又如何修改呢?如果要修改的话,又要修改哪些地方呢?接下来就来分析下整个解决过程。

二、实现方案

Adobe After Effects工程使用aep格式来存储。aep格式是一种紧凑的二进制格式,工程中的所有资源及组织结构都以二进制格式保存。如果要从这种二进制的格式中来定位图片和文字,倒也不是不可能:

但是有一个致命的缺点。先不说定位的时候无法做到精确匹配,就算成功找到了文本或图片路径,替换的时候很可能还要进行位置移动。因为替换的文本可能比原文本长,如果不移动腾出空位的话,替换的内容就会覆盖掉后面的二进制数据。修改后的aep文件极有可能因此损坏。因此,直接修改aep文件是不可取的。经过一番搜索,得知AE工程还有另外一种存储格式:AEPX。

*.aepx是以XML格式进行存储的。相对于二进制格式aep而言,aepx的文件尺寸比较大,加载速度也会慢些。但是XML格式非常容易操作,而且在成熟的XML库的帮助下,修改标签和遍历标签只需要几行代码即可搞定。那么,接下来的工作就是确定XML的组织结构以及需要修改哪些字段了。首先看一个比较复杂的AEP工程:

这是一个典型的AEP工程,使用文件夹的方式来组织各种资源。那么XML中是怎么组织的呢?上面这个工程中存在8个顶级文件夹,可以在XML中看到对应8个<Item>标签:

再来分析其中的合成(Composite):

这张图是关键的:我们可以看到,文件夹中的子元素是以<Sfdr>标签来包裹的。而不管是Composite还是文件夹,都是以<Item>标签来表示的,只不过以子标签<idta>的值来区分。0001开头的表示是文件夹,0004开头的表示合成,而0007开头的则表示是其他普通资源文件,如图片、AI文件等。经过分析,文本都是以<Layr>标签包裹的,我们要替换文本的话,直接替换子标签<string>中的文本即可。那么图片是怎样一种结构呢?

图片资源的引用是封装在<Pin>标签里面的<fileReference>里面,直接以路径的形式引用。确定了这些东西,就可以开始编码来定位文本和图片了。这里采用了一个C++ XML解析库TinyXML,不依赖其他外部库,接口简单。

void XMLParser::parseTemplateItem(XMLNode* rootElement, int& index)
{
	if (rootElement == nullptr)
	{
		return;
	}

	XMLElement* str = rootElement->FirstChildElement("string");
	const char* txt = str->GetText();
	XMLElement* idtaNode = rootElement->FirstChildElement("idta");
	if (idtaNode != nullptr)
	{
		const char* idatBdata = idtaNode->Attribute("bdata");
		ItemType itemType = whichType(idatBdata);
		if (itemType == NORMAL_ITEM)
		{
			XMLElement* pinNode = idtaNode->NextSiblingElement("Pin");
			if (pinNode != nullptr)
			{
				XMLElement* sspcNode = pinNode->FirstChildElement("sspc");
				if (sspcNode == nullptr)
				{
					return;
				}
				const char* sspcBdata = sspcNode->Attribute("bdata");
				bool isNormalFormat = isImageFormat(sspcBdata);
				if (isNormalFormat)
				{
					XMLElement* Als2Node = sspcNode->NextSiblingElement("Als2");
					if (Als2Node == nullptr)
					{
						return;
					}
					XMLElement* fileReferenceNode = Als2Node->FirstChildElement("fileReference");
					if (fileReferenceNode == nullptr)
					{
						return;
					}
					const char* fullPath = fileReferenceNode->Attribute("fullpath");
					m_imageMap.insertMulti(fullPath, index);
					index++;
				}
			}
		}
		else if (itemType == COMPOSITE_ITEM)
		{
			XMLElement* LayrNode = idtaNode->NextSiblingElement("Layr");
			while (LayrNode != nullptr)
			{
				XMLElement* stringNode = LayrNode->FirstChildElement("string");
				if (stringNode)
				{
					// 文本为空的层直接跳过不要
					const char* layerStr = stringNode->GetText();
					if (layerStr != nullptr && strcmp(layerStr, ""))
					{
						XMLElement* tdgpOuter = stringNode->NextSiblingElement("tdgp");
						if (tdgpOuter)
						{
							XMLElement* tdmnOuter = tdgpOuter->FirstChildElement("tdmn");
							if (tdmnOuter)
							{
								const char* tdmnOuterBdata = tdmnOuter->Attribute("bdata");
								// ‘ADBE Text Properties‘
								if (tdmnOuterBdata != nullptr && !strcmp("4144424520546578742050726f706572746965730000000000000000000000000000000000000000", tdmnOuterBdata))
								{
									XMLElement* tdgpInner = tdmnOuter->NextSiblingElement("tdgp");
									if (tdgpInner != nullptr)
									{
										XMLElement* tdmnInner = tdgpInner->FirstChildElement("tdmn");
										if (tdmnInner != nullptr)
										{
											const char* tdmnInnerBdata = tdmnInner->Attribute("bdata");
											// ‘ADBE Text Document‘
											if (tdmnInnerBdata != nullptr || !strcmp("41444245205465787420446f63756d656e7400000000000000000000000000000000000000000000", tdmnInnerBdata))
											{
												m_textMap.insertMulti(layerStr, index);
												index++;
											}
										}
									}
								}
							}
						}
					}
				}
				LayrNode = LayrNode->NextSiblingElement("Layr");
			}
		}
		else if (itemType == FOLDER_ITEM)
		{
			XMLElement* SfdrNode = idtaNode->NextSiblingElement("Sfdr");
			if (SfdrNode == nullptr)
			{
				return;
			}
			XMLElement* tempItem = SfdrNode->FirstChildElement("Item");
			while (tempItem != nullptr)
			{
				parseTemplateItem(tempItem, index);
				tempItem = tempItem->NextSiblingElement("Item");
			}
		}
		else
		{
			return;
		}
	}
}

三、修改字段

完成了图片和文字的解析工作之后,剩下的就是替换了。不妨先来观察下使用AE修改资源时,XML文件会发生哪些变化。这样,我们用程序修改时,把相关的字段也修改掉就可以了。对于图片修改可以看下图:

总共需要修改三个地方。其中,"4a504547"是JPEG这八个字符的十六进制表示,有两个地方需要同时修改。如果是替换成其他格式的图片,也要修改成对应格式的十六进制表示。如:

‘706e6721‘  -> PNG format
‘4a504547‘  -> JPEG or JPG format
‘5449465f‘   -> TIF or TIFF format
‘424d5020‘  -> BMP format

  另外一个要修改的就是<fileReference>的属性fullpath值了。也就是图片资源的路径。文本的修改就要稍显复杂一点了。如下图:

这里采用了一个小技巧,使用了文本层的一个属性:text.sourceText=name。给了这个属性之后,文本层的内容和名称保持一致。也即是说,我们只要修改文本层的名称,就能达到修改文本层内容的目的。这个技巧需要修改两个地方。一个是<tdb4>标签值的倒数第七位置1;另一个就是增加一个<tdb4>的兄弟标签<expr>,其值为“746578742e736f75726365546578743d6e616d6500”,也就是"text.sourceText=name"的十六进制表示。这样就实现了文本层和文本内容的同步设置了。

此外,Layr层不光只有text在里面,还有色块(Solid)、过渡效果、动画等内容。因此还需要根据<tdmn>标签的值来过滤。条件就是<tdmn>的值:

4144424520546578742050726f706572746965730000000000000000000000000000000000000000 // ‘ADBE Text Properties‘
41444245205472616e73666f726d2047726f75700000000000000000000000000000000000000000 // ‘ADBE Transform Group‘
41444245204c61796572205374796c65730000000000000000000000000000000000000000000000 // ‘ADBE Layer Styles‘
414442452045787472736e204f7074696f6e732047726f7570000000000000000000000000000000 // ‘ADBE Extrsn Options Group‘
41444245204d6174657269616c204f7074696f6e732047726f757000000000000000000000000000 // ‘ADBE Material Options Group‘
4144424520417564696f2047726f7570000000000000000000000000000000000000000000000000 // ‘ADBE Audio Group‘
414442452047726f757020456e640000000000000000000000000000000000000000000000000000 // ‘ADBE Group End‘
41444245205465787420446f63756d656e7400000000000000000000000000000000000000000000 // ‘ADBE Text Document‘
4144424520546578742050617468204f7074696f6e73000000000000000000000000000000000000 // ‘ADBE Path Options‘
414442452054696d652052656d617070696e67000000000000000000000000000000000000000000 // ‘ADBE Time Remapping‘
4144424520506c616e65204f7074696f6e732047726f757000000000000000000000000000000000 // ‘ADBE Plane Options Group‘
41444245204566666563742050617261646500000000000000000000000000000000000000000000 // ‘ADBE Effect Parade‘

  只有内层<tdmn>和外层<tdmn>的值分别是‘ADBE Text Properties‘和‘ADBE Text Document‘的时候,<Layr>中包含的才是文本。这种过滤条件,能够过滤掉其他的干扰数据,让我专注于处理模板中的文本内容。

四、最终效果

时间: 2024-10-13 07:56:12

一款批量修改AE模板的工具的相关文章

C#代码生成工具:文本模板初体验 使用T4批量修改实体框架(Entity Framework)的类名

转自:http://www.cnblogs.com/huangcong/archive/2011/07/20/1931107.html 在之前的文本模板(T4)初体验中我们已经知道了T4的用处,下面就看看如何用它来实现批量修改实体框架(Entity Framework)中的类名.我们都知道ADO.NET 实体数据模型中有一种方式是以数据库模型来生成数据模型的,这是个很简便的实体数据模型生成的方式,但是因为微软提供的自定义接口不足,我们无法实现对生成的数据模型实体类批量进行修改(至少我上网找了很久

一款批量linux管理工具batchshell

BatchShell是什么? BatchShell是一款基于SSH2的批量文件传输及命令执行工具,它可以同时传输文件到多台远程服务器以及同时对多台远程服务器执行命令.BatchShell基于原生的shell命令或python命令,无需二次学习成本,上手即用.如果需要随时随地发现.操作很多台Linux,又不想要做太多的配置,那这应该就是你想要的工具. 具备以下主要功能:      1. 多服务器批量文件传送.接收(一键完成)      2. 多服务器远程命令交互(一键完成)      3. 快速远

Unity3D 批量修改贴图导入设置工具脚本

这个Unity3D 批量修改贴图导入设置工具脚本十分小巧,但是威力大.特别针对大批量贴图要调整尺寸等等的时候作用尤为明显.在菜单中添加"Custom→Texture"的方式来批量改变所选的贴图导入设置.Unity本身只能一次打开一张图片进行导入设置,目前这个脚本可以批量更改贴图格式,是否开启MipMap,调整纹理最大尺寸,是否可读等等. 用法是把脚本放在你项目的资源目录的Editor文件夹下.然后选择你要批处理的纹理.到菜单中选择要处理的类型就可以了.ChangeTextureImpo

【Unity小工具】批量修改原始资源设置

需求:项目中导入了近200个音效文件,我需要批量修改设置,但是编辑器下无法多选修改设置. 解决办法:重写OnPreprocessAudio方法 using UnityEngine; using System.Collections; using UnityEditor; public class AudioSet : AssetPostprocessor { public void OnPreprocessAudio(){ AudioImporter audioImport=assetImpor

MFC批量修改文件名工具

1批量修改文件名描述 1.1功能描述 批量修改同一文件夹下文件名字,可以定义一个新名字,后面接着文件从0开始的序号. 1.2所需技术 CFileDialog,CString方法操作得到所需,rename 2批量修改文件名运行流程 3批量修改文件名详细设计 3.1添加文件按钮响应OnAddFile 按下"添加文件"按钮后,打开一个文件对话框objFileDlg.但是要设置objFileDlg最大文件名缓冲区.然后获得第一个文件的起始位置,依次把全部的文件完整名添加到列表控件中,这里列表控

批量Linux、Windows管理工具BatchShell 1.2(最新版)

简介: BatchShell是什么: BatchShell是一款基于SSH2的批量文件传输及命令执行工具,它可以同时传输文件到多台远程服务器以及同时对多台远程服务器执行命令.具备以下主要功能:    ... BatchShell是什么: BatchShell是一款基于SSH2的批量文件传输及命令执行工具,它可以同时传输文件到多台远程服务器以及同时对多台远程服务器执行命令. 具备以下主要功能:     1. 多服务器批量文件传送.接收     2. 多服务器远程命令交互     3. 快速远程桌面

推荐一款优秀的web自动化测工具

在业务使用的自动化测试工具很多.有开源的,有商业化的,各有各得特色,各有各得优点!下面我就介绍几个我用过的一款非常优秀的国产自动化测试工具.在现有的自动化软件当中,都是以元素的name.id.xpath.class.tag.link.partial_link.partial_link中的一种作为对元素进行定位.现在现实的测试环境经常不是我们想象的那样平静.页面中出现id,name,tag 重复的很多.也许在我们写用例的不重复,经过一轮版本修改后很可能变得重复了.xpath是最不可能失效的,但是经

gitlab连接并批量修改数据库账号邮箱地址(上)

最近,由于公司的域名发生变化,导致需要批量修改gitlab的账号.这个工作对于运维人员来说,工作量还是相对较大的.因此,尝试通过脚本修改数据库的方式进行批量修改. 整个过程大致分为几个阶段: 1)搞清楚gitlab的数据库结构 2)使用Python对相关的字段进行修改,若发现以@xxx结尾的域名,自动替换为@aaa的域名 注:本文中所采用的gitlab安装方式为官方默认的yum安装 1)gitlab的数据库采用的是postgresql,连接库时需要修改pg_hba.conf文件的相关配置 vim

如何批量修改200台以上web服务器密码

作为一个运维工作人员,有时候我们需要对自己负责的web服务器批量修改密码,如果一台一台改,会相对来说较为麻烦,所以在这里我们可以使用NIS账户统一认证. 我们假设有一部账号主控服务器来管理网域中所有主机的账号, 当其他的主机有用户登入的需求时,才到这部主控服务器上面要求相关的账号.密码等用户信息,如此一来,如果想要增加.修改.删除用户数据,只要到这部主控服务器上面处理即可, 这样就能够降低重复设定使用者账号的步骤了! NIS=Network Information Service   网络信息服