一款批量修改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>中包含的才是文本。這種過濾條件,能夠過濾掉其他的干擾數據,讓我專注於處理模板中的文本內容。

四、最終效果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM