Pages

2013年5月23日木曜日

お手軽AEスクリプト(その一)

書いた本人も半分忘れていたこのブログですが、気がつけば1年以上更新していなかった…となると急に気になりだすのが人情というもの、ひと月ぶりにお休み貰ったことだし、何か書いてみることにしました。

と、ちょうど手元に良いネタがあったので、今回はコレをご紹介。先日社内で別チームのスタッフに頼まれて作った、After Effects用単機能スクリプトです。
わずか数十行とネタとしても手頃ですし、春の新番ラッシュを乗り越え、5月病をクリアし、そろそろスクリプトにでも手を出してみようかな~という殊勝な業界若人のヒントにでもなるかな?と。
ある程度書ける人には今更な内容かと思いますが、まあ、こんなもんでも役に立つんだよ、という事例紹介みたいなノリで行きたいと思います。


◆その1:指定エフェクトを使用しているレイヤー一覧を書き出す ”searchFx.jsx"
短いのでそのまま書いてみます。

(function(){
    // ---------------------------------------------------
    var p=app.project;
    var txt="";
    var idx=1;
    // ---------------------------------------------------
    if(p.file.exists){
        var defaultPath=p.file.parent.fsName+"/";        
        main();
    }else{
        alert("プロジェクトを保存してからスクリプトを実行して下さい");
    }
    // ---------------------------------------------------
    function main(){
        var tgtFxName=prompt("検索するエフェクト名を入れて下さい 完全一致オプションは「^プラグイン名$」", "プラグイン名");
        var result="";
  if(tgtFxName!=null){
   for (var i=1; i<=p.numItems; i++){
    if(p.item(i) instanceof CompItem){
     result=searchFxLayer(p.item(i), tgtFxName); 
    }
   }
   dumpTxt(result, tgtFxName);
   alert("完了しました!(`・∀・´)"+"\n"+defaultPath+" 直下を確認して下さい");
  }
    }
    //
    function searchFxLayer(tgtComp, srcStr){
        for(var j=1; j<=tgtComp.numLayers; j++){
            var tLayer=tgtComp.layer(j);
            if(tLayer.matchName!="ADBE Camera Layer" && tLayer.matchName!="ADBE Light Layer"){
                var m=tLayer.property("Effects").numProperties;
            
                for(var k=1; k<=m; k++){
                    if(tLayer.effect(k).name.match(srcStr)!=null){
                        txt=txt+"\n"+idx+" :使用コンポ名: "+tLayer.containingComp.name+" // 使用レイヤー名: "+tLayer.index+" ; "+tLayer.name;
                        idx=idx+1;
                    }
                }
            }
        }
        return txt;
    }
    //
    function dumpTxt(rslt, sfxn){
        var logObj=new File(defaultPath+"searchResult_"+p.file.name.split(".")[0]+"_"+sfxn.replace(/\s| /g,"")+".txt");

        if(logObj.open("w")){
            logObj.writeln("<<< 検索対象プロジェクト名:"+decodeURI(p.file.name)+"   /   検索対象エフェクト名 : "+sfxn.replace(/\s| /g,"")+" >>>");
            logObj.write(rslt);
            logObj.close();
        }else{
            alert("ログファイルを開けませんでした");
            return;
        }
    }
})();

こんな感じで。
順に見えてもらえばやっていることはまあ分かるかと思いますが、一応簡単な解説をば。


◆その前に
まず大前提として、このスクリプトではログとしてテキストファイルを書き出すのでAE側で「編集」⇒「環境設定」⇒「一般設定」⇒”スクリプトによるファイルへの書き込みと~”にチェックを入れておく必要があります。


* * *
では頭から
1行目、及び最終57行目で使用している”(function(){})();”は、所謂無名関数というものです。要は変数のスコープをこの内側に限定する目的で使用されてます(たぶん)。

7行目で、実行時のプロジェクトがファイルとして保存されているか否かを以下のスクリプト実行のスイッチとしています。プロジェクトファイルが存在すれば、そこからファイル名やプロジェクトファイルがあるフォルダパスなど様々な情報を取得出来ます。

14-26行目がメインとなる関数部分。ダイアログで検索するエフェクト名を取得し、その「名前」のエフェクトを使っているレイヤーを、プロジェクト内全てのコンポから洗い出します。
入力されたエフェクト名と、実際にプロジェクト内で使用されているエフェクト名の照会は、正規表現を用いたmatchメソッドを使用しているため、入力時の検索オプションとして(手入力による ><; )前方、後方完全一致オプションを使用できるようになっています。

出来れば、きちんとUIをデザインしてボタンオプションなどで実装したいところですが、AEは非常にUIデザインに向いていないのでとりあえずコレでお茶を濁します。隣でデザイナーがまだかまだかと期待の眼差しを向けてくる状況では、優雅さよりスピードだったりもするのです。


閑話休題。
19行目でプロジェクト内のデータを頭から虱潰しにしていきながらコンポアイテムに当たった場合、そのコンポアイテムと、検索エフェクト名を次の「コンポ内のすべてのレイヤーから、指定した名前のエフェクトを使用しているレイヤーを洗い出す」関数(”searchFxLayer”)に渡します。

この関数も、基本は上の「プロジェクト内のすべてのアイテムから、コンポアイテムを洗い出す」main関数の機能と同じです。

ここで特徴的なのは、31行目の”matchName”と32行目の”property("ADBE Effect Parade")”でしょうか。

”matchName”はAEのUI上で表示される名称とは別にAE内部で各プロパティを認識するための名前で、使用言語による変化が無いため、スクリプトでプロパティを指定する際には(大抵の場合)通常のname属性よりオススメされています。

2つ目の”property("ADBE Effect Parade")”は、エフェクトのmatchNameを使用したエフェクトプロパティへのアクセスで、そのレイヤーの総エフェクト数を取得しています。
(カメラ、ライトオブジェクトはエフェクトを持てないので、ここでは最初に除外している訳です。matchNameを使用することで、カメラ、ライトレイヤーの名前を変更していても抽出に影響ないのがミソです)

この辺りのProperty/PropertyGroup objectについて話しだすと長くなる上に、恐らく僕の手には余るので、既にネット上で詳しく説明されているページを紹介して逃げたいと思います。
After Effectsユーザーのための、プログラミング入門 その7 スクリプト作成補佐スクリプト Property/PropertyGroupについて


さて、気を取り直して、そのレイヤーのエフェクト総数が分かったらまたしても同型の処理、「エフェクトの数だけ総当りで調べて、指定エフェクト名と一致するエフェクトを洗い出す」のが、34-35行目の処理になります。
見つかったら、そのコンポ名、レイヤー名を内部的にメモして、次のエフェクトへ。
一つのレイヤーを調べ尽くしたら、そのコンポ内の次のレイヤーへ、そのコンポ内も全て調べつくしたら、元のmain関数に戻って、プロジェクト内の次のコンポへ…
と渡って、プロジェクト内の全てのレイヤーを調べ尽くします。
人の手と眼だと気が遠くなる上に見逃しもありえますが、機械に任せておけばその心配はありません。エラーも込みで、書かれたとおりに実行してくれます。


最後に、見つけたコンポ名、レイヤー名をメモしたデータを”dumpTxt”関数でテキストデータとして書き出します。
日本語エフェクトに含まれる空白を除去したり(46行目)、日本語ファイル名での文字化けを回避したり(49行目)しつつ、作業完了となります。
完了メッセージの顔文字にイラッとしたら、是非ともエディターを開いてソースを書き換えてしまいましょう。千里の道も一歩から。
* * *


◆post script
スクリプト習得の学習曲線がどのようなものか、まあ人によってそれぞれなのでしょうが、書いてみたら思いがけずいろいろな要素が混在していて、ちょっと学習ネタとしては不適切だったかもしれません。その場合は、遠慮せず職場に一人はいるはずの3度の飯よりスクリプトが好きな人や、勿論僕当てでも構いませんので、質問して頂ければと思います。
仕事中はたいていTwitter(@hachinogy)にいます。あそこの記述が変だ、解説が怪しい、などのツッコミもお待ちしております(どうぞお手柔らかに)。
検索対象を変更すれば、他所から来た巨大なプロジェクトファイルの何処かに、見たこともないフォントを使用した謎のテキストレイヤーが隠れている…なんて事態にも対応出来ますね。


さて、ツッコミが入る前に軽く自己弁護しておきますと、今回のスクリプト、ざっと見た人ならきっと誰もが「何故matchNameでエフェクトも検索しないのか」と疑問に思われたことでしょう。エフェクト名による検索は、エフェクトの名前を変更していた場合効果が半減します。
正確を期すならば、検索時には見慣れたname属性によるエフェクト名を入力してもらい、内部でそれをmatchNameに変換する関数を通すことで可能な限り、matchName検索をかける、というのが最善なのでしょう。
今回は、たまたまデフォルトのエフェクト名を変更していなかったパターンだったので、実装速度最優先ということで上記のような内容になったまでです。
ちょうど”hiroshisaito.net”の斉藤さん(After Effects Plugins matchNames)や”AfterEffects compZero”の吉岡さん(Plugin matchName)がステキなリストを上げてくださっているのを発見したので、自分でもバージョンアップを図りたいと思います。


あ、その1と言っておきながらその2を書くには長すぎた気がするので、分割して次回へ続く…。
次回、「コメントされたレイヤーを削除するスクリプト(”deleteCommentedLayer”)」




一応、ファイルもアップロード。