Pages

2014年3月23日日曜日

帰ってきたお手軽AEスクリプト

気がつけば2014年ももう1/4近くが過ぎてしまい、幾ら何でもおめでたい御挨拶やら新年の抱負を語る空気でもなくなってしまいました…

今年は、新年早々某アイドルアニメ映画のコンポジット手伝いをしていたり、担当していた1年もののTVシリーズアニメが最終回を迎えたりと何だかんだで忙しい第一四半期でした。(反動で今はデータ整理しかしてませんが)
会社もスタッフが増えたり、それに伴って引っ越しを考えたりと慌ただしく、なかなかブログネタも思い浮かばない…という言い訳がすぐ口をついて出て来るのですががが。

以前公開したAfter Effectsのスクリプトがいまだにそこそこ需要があるようなので、これに気を良くして2014最初のネタはこの続編としてみました。

***

今回のネタは、
「選択レイヤの長さをアクティブコンポの尺に合わせるスクリプト」
です。

これはもともと、別に作っていた一連の作業自動化スクリプトの一部なのですが、なかなかに重宝するツールなのです。
(似たような作業をしている方なら思い当たるかもしれませんが、主にAE上でコンポジションとして読み込んだPSDとその内包するレイヤ群の尺を親コンポの尺に合わせる際に使います。
所謂美術素材の扱いですね)

今回のポイントは「再帰処理」を上手く使うことです。
まあ、見た方が早いので早速サンプル行ってみましょう。

/*---------------------------------------------------------------------------------------------------------*/
    function fitDuration(comp){
        var tgtList=comp.selectedLayers;    //選択したレイヤ(配列)
        var sList=[];                                 //処理をスキップしたレイヤリスト(配列)
        var txt="skipped layers\n";            //スキップしたレイヤリスト
        var ct=1;                                      //スキップしたレイヤ数
        
        for(var i=0; i<tgtList.length; i++){
            if(tgtList[i].source instanceof CompItem){
                sList[i]=recSetDur(tgtList[i].source, comp);
                
                if(sList[i]!=undefined){
                    for(var l=0; l<sList[i].length; l++){
                        txt=txt+(ct).toString()+" : "+sList[i][l][0].toString()+" > #"+sList[i][l][1].toString()+" "+sList[i][l][2].toString()+"\n";
                        ct=ct+1;
                    }
                }else{
                    sList.pop();
                }
            }
            tgtList[i].outPoint=comp.duration;
        }
    
        if(txt!="skipped layers\n"){
            alert(txt);            
        }        
    }
    
    function recSetDur(cC, pC){
        var dD=cC.duration;              //渡されたコンポの元尺(変更前の尺)
        var nD=pC.duration;              //親コンポの尺(変更後の尺)
        
        cC.frameRate=pC.frameRate;
        cC.duration=nD;
        
        var lFlag=false;                    //レイヤロックの有無
        var slList=[];                        //処理をスキップしたレイヤリスト(配列)
        var tmpSlList=[];                  //プリコンポ内での処理をスキップしたレイヤリスト(配列)
        
        for(var j=1; j<=cC.numLayers; j++){
            if(cC.layer(j).locked){
                cC.layer(j).locked=false;
                lFlag=true;
            }
        
            if(cC.layer(j).source instanceof CompItem){
                tmpSlList=recSetDur(cC.layer(j).source, cC);
                if(tmpSlList!=""){
                    for(var k=0; k<tmpSlList.length; k++){
                        if(tmpSlList[k]!=""){
                            slList.push(tmpSlList[k]);
                        }
                    }
                }
            }
        
            if(cC.layer(j).outPoint==dD){
                cC.layer(j).outPoint=nD;
            }else{
                slList.push([cC.name, cC.layer(j).index, cC.layer(j).name]);
            }
            if(lFlag){
                cC.layer(j).locked=true;
            }
        }
        return slList;
    } 

/*---------------------------------------------------------------------------------------------------------*/
(ちょっと今回SyntaxHighlighterで上手く貼れていないようなんですが、如何でしょう?)


 それでは、恒例の要領を得ない解説をば。

 今回はざっくり2つの関数に分かれています。


 一つ目の”fitDuration”が基準となる親コンポを渡され、その中の選択レイヤに対して尺調整をする関数です。 
変数定義時に簡単にコメントしてありますが、変数tgtListに選択レイヤを格納し、その数だけ繰り返し処理を回しています。 
もし単純にプリコンポされたレイヤを含まない状況であれば、
”tgtList[i].outPoint=comp.duration;”を繰り返すだけです。

が、もし選択レイヤがプリコンポ状態でその内部に更にレイヤ要素を含む場合、上述の再帰処理を使ってその奥まで確認をする必要があります。
それが、2つ目の関数”recSetDur”の役割です。


この関数では、まず尺の基準になる親コンポと処理対象のコンポの2つが渡されます。
対象コンポの尺(とフレームレート)を合わせるのは簡単なのですが、問題はその内包するレイヤ群です。
対象レイヤはロックされているかもしれないし、コンポ尺の一部でしか使われていないかもしれない。
更には、もう一段プリコンポされて更に深い構造を持っているかもしれない…


そこで今回の処理では、
☆ロックされたレイヤも処理対象とする(ロックを外して尺変更処理をし、再度ロックをかける)
☆コンポ尻まで尺のあるレイヤのみを処理対象とする(連番ファイルだと尺を伸ばしようがないレイヤもありますが)
☆プリコンポされたレイヤは更にその奥までチェックする

★処理対象外のレイヤは、その名前と親コンポ名をチェックして処理完了後に警告表示する

という方針にしてみました。


ロックされたレイヤまで処理されては困る、余計なアラートは要らない、という方は適宜条件を調整してみてください。
(そもそも、ここで見逃している条件もあるかもしれませんし)


ちなみにこのアラート処理は今回用に思いついた機能なのですが、思いの外面倒くさく、コードも冗長になってしまいました。
自分だけが使うツールの場合は、もっとシンプルなコードにします。


閑話休題。
recSetDur関数中程の”tmpSlList=recSetDur(cC.layer(j).source, cC)”が再帰処理と言われる部分ですね。
対象レイヤがプリコンプされたものの場合、渡してやる変数をちょっと変えて、再度同じ関数(自分自身)に処理を投げています。
この辺使えると、簡単なスクリプトでも一気に処理の汎用性が広がります。
してやったりの瞬間です。

その後の配列(slList,tmpSlList)を使ってのやりとりは、処理を飛ばしたレイヤ情報の受け渡し部分になります。
それらをまとめて、元のfitDuration関数に戻してやったら、最後に該当レイヤの尺を調整して(内⇒外の順番を間違えると予想外の結果になってしまいます)処理は終了です。


処理を飛ばしたレイヤがあれば、最後に一覧表示します。
以上の処理をいつものテンプレにまとめたjsxファイルをアップしておきましたので、宜しければ見てみてください。




今回のスクリプトはコンポの内容を変更してしまうのでundoGroupの登録は必須です。
これをしておかないと、間違った処理をした後に何度Ctrl+Zしなくてはいけないか分かりません。

上記アップロードファイルは対応済みですが、念の為ご使用前にプロジェクトを保存しておいて頂けると良いかと思います。(一応いつものご使用は自己責任というやつです)

という訳で、例によってツッコミ宜しくお願いします。Twitter辺りが手っ取り早いかと。

こういうちょっとした機能でも、必要とした時にササッと書けるとなかなか重宝すると思いますので(経験談)、皆様お気軽に取り組んで頂き、そしてあわよくばネットにバンバン公開していって頂けると良いのではないでしょうか。