zoukankan      html  css  js  c++  java
  • 一款批量修改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,不依赖其他外部库,接口简单。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    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;
            }
        }
    }
  • 相关阅读:
    mysql复制那点事
    全排列问题
    56. Merge Interval
    2. Add Two Numbers
    20. Valid Parentheses
    121. Best Time to Buy and Sell Stock
    120. Triangle
    96. Unique Binary Search Trees
    91. Decode Ways
    72. Edit Distance
  • 原文地址:https://www.cnblogs.com/linnew/p/7773542.html
Copyright © 2011-2022 走看看