After Effects スクリプティングメモ

After Effectsのスクリプティングにおいてのメモなどをまとめておきます。今後のAEのスクリプティングに関する情報は「After Effects Script Reference」のTipsにまとめます。このページの更新予定はありません。

INDEX
jsxbinってなに?
スクリプトでincludeファイルを使用する方法
nullとundefined
Footageが画像ファイルかどうか調べる
AfterEffectsの起動ディレクトリ
変数のスコープ
即時関数

Blog記事へのリンク
After Effects CS4 スクリプティング変更点
After Effects CS5 スクリプティング変更点
After Effects CS5.5 スクリプティング変更点
After Effects CS6 スクリプティング変更点
別サイト
After Effects スクリプトリファレンス(作業中)




jsxbinってなに?
jsxbinは文字通りJavaScriptをバイナリ化したファイルです。作成はAdobe ExtendScript Toolkit(ESTK)の「ファイル」メニューから「バイナリ形式で書き出し」を選択します。出力されたファイルはjsxbinという拡張子で保存されます。使用方法は通常のjsxと変わりありません。
注意点としては、これは通常のコンパイラのビルドと違い、エラーやワーニングを出しません。スクリプト自体に致命的なエラーがあったとしてもバイナリ化はすんなり完了してしまいます

jsxbinファイルはバイナリ形式ですので、このファイルをESTKで開いても編集作業は困難です。開発には元のjsxファイルを使用します。

jsxbin_fileview.png

jsxbin化するによってユーザーがスクリプトのコードを改造したり無意識に改変して動かなくなるというトラブルを防止できたり、ユーザーが自分で設定すべき項目を設定ファイルに独立させることでわかりやすくできたりします。
実行速度の向上はあまり目に見えて速くなるという実感はありませんが、コメント行などが削除されることでスクリプトのロード時間短縮などは期待できるでしょう。
またincludeディレクティブのファイルが統合されるので、配布する時の構成がシンプルにできます。

注意点としては、下位互換が失われるということがあります。CS5のESTKで作成したjsxbinをCS4で実行しようとするとエラーとなります。上位互換はありますので、CS4のESTKで作成したスクリプトはCS5で実行可能です。厳密にはスクリプト自体にも互換性が保証されているわけではないので、実行したいバージョンでプログミングを行なうように注意してください。

不特定多数のエンドユーザー向けにはjsxbinを使い、開発者向けにはjsxとincludeファイルを提供するのがお勧めです。

[ INDEXにもどる ]




ESTKでincludeファイルを使用する方法
After Effectsでincludeファイルを使用する場合には「jsxinc」を使用します。「jsx」も使用可能ですが、それらを使用するとAfter Effectsのスクリプトメニューにそれらが表示されてしまうので、公式にはjsxincを用いることになっています。

ディレクティブは以下のように記述します。

#include "../(include)/myScript.jsxinc"

includeディレクティブを記述した行には、行末に「; (セミコロン)」を付けないということに注意してください。
従来の「//@」で始まる書式も使用できますが、現在Adobeでは推奨されていないので、今後のサポートのことも考えると上記の書式に書き換えた方がいいと思われます。

予めincludeファイルのパスを指定しておくことも可能です。パスは絶対パスでも相対パスでも構いません。

#includepath "../(include)"
#include "myScript.jsxinc"

ちなみにincludepathは2つ以上を「; (セミコロン)」で区切って列挙できます。

#includepath "../(include); /Users/myname/AEScripts/common" #include "myScript.jsxinc"
#include "myCommon.jsxinc"

「#include」の行は「"myScript.jsxinc; myCommon.jsxinc"」というように列挙して記述できません。

includeディレクティブを使用した場合、ESTKなどではデバッグが困難になります。事前にjsxincのデバッグを充分に行なうことである程度(基本的な書式の間違いなど)は回避できますが、実際に引数を渡したりオブジェクトを継承したりした時に発生する問題は、メインファイルから正確に追跡することができません。そこでincludeファイル内の関数やオブジェクト内でデバッグ用のアラートやログ出力を行なうようにしておく必要があります。

[ INDEXにもどる ]




nullとundefined
JavaScript全般の話になりますが、nullとundefinedの関係について。
nullもundefinedも独立したデータ型です。これらに関しては、JavaScriptは明確に分けて定義していますが、スクリプト上では同じように使用することができます。

var me = null;
alert(me == undefined);
結果は「true」です。しかし先にも書いたようにJavaScriptはこの両者をデータ上明確にわけています。

alert(null === undefined);

これは「false」となります。この結果はJavaScript上定義されているので、こういうものであるということで理解するよりないと思います。
しかしnullはプロパティを持たないオブジェクトで、undefinedはグローバル変数です。よってundefinedは上書きできていまいます。

undefined = null;
alert(null === undefined);


しかもグローバル変数なのでうっかり上書きしてしまったり、そういうスクリプトを実行してしまった場合はずっとその値が残り、その後のundefinedを予期するスクリプトにことごとくあく影響を与えます。もし不安であればスクリプトの初期化時に

undefined = void 0;


などとして未定義値にしておけばいいのですが、実際にundefinedを上書きしてるなんてロクなスクリプトじゃないのでとっとと該当箇所を修正すべきです。

typeofで判定した場合にはundefinedは文字列"undefined"を返します。nullは文字列"object"を返します。

alert(typeof undefined == typeof null);


結果は「false」です。

この件に関してはJavaScript: The Good Parts』(関連エントリー)にでより詳しく解説されています。

[ INDEXにもどる ]



Footageが画像ファイルかどうか調べる
After Effectsではフッテージと言ってもコンポであったりNullであったり、Solidであったりします。スクリプトからフッテージが画像ファイルかどうか調べる必要がある場合には

app.project.item(i).mainSource instanceof FileSource

の論理値を調べることで、フッテージが画像ファイルかどうかを調べることができます。
ちなみにこのままで大丈夫な感じがしますが、実は落とし穴があって、FileSourceにはサウンドファイルも含まれてるということです。そこでFileSourceからサウンドファイルを除外する必要があります。サウンドファイルを判定するために、私の場合はwidthの値を調べて「0」ならサウンドファイルにしています。

また、このファイルが静止画かどうかを調べるには

app.project.item(i).mainSource.isStill

の戻り値で判定できます。あるいはdurationで調べてもいいかもしれませんが、先に述べた通りサウンドファイルの可能性もあるので、事前にサウンドファイルを除外していること前提です。isStillはitem().mainSource.fileが存在していないとエラーになりますので、isStillを用いる場合には事前にFileSourceの判定を行なってからにする必要があります。

[ INDEXにもどる ]



After Effectsの起動ディレクトリ
現在使用中のAfter Effectsの起動ディレクトリを調べるには

Folder.startup

です。Windowsの場合はこれで問題なくAEのスタートアップフォルダが戻ってきますが、Mac OS Xの場合にはこれの返り値は

/Applications/Adobe After Effects CS5/Adobe After Effects CS5.app/Contents/MacOS

となります。(CS5の例です)
「/Applications/Adobe After Effects CS5/Adobe After Effects CS5.app」をダブルクリックしてAfter Effectsを起動していますが、これは実行ファイル本体ではありません。そこでMac OS Xの場合、「/Applications/Adobe After Effects CS5/」の返り値を期待する場合には

Folder.startup.parent.parent.parent

となります。
さらにもし、After Effectsのバージョンが6.5であった場合には

Folder.startup.parent.parent.parent.parent

です。

[ INDEXにもどる ]




変数のスコープ
恥ずかしながら自分がかなりハマってしまった問題です。これはAfterEffectsに特化した問題ではなくJavaScript全般の問題です。
JavaScriptの変数スコープはちょっと特殊です。まず下のコード

for(var i=0; i<100; i++) {
 var myValue = 'this value';
}
alert(typeof myValue);

私はJavaScriptではブロックで変数のスコープが発生「しない」という仕様を知らなかったので、この場合alertがundefinedを返してくることを期待してました...が、実際には本人の意図とは裏腹にグローバル変数を定義してしまっていたということになります。更にJavaScriptの困った仕様として以下のようなものもあります。...

function tryThisFunction(){
  for(i=0; i<100; i++) {
   var myValue = 'this value';
  }
}
var myGlobal = tryThisFunction();
alert(i);

この例ではfor文の中で宣言なしでi=0という変数を定義しているために、iがグローバル変数になってしまってます。JavaScriptでは未宣言の変数を使用したときに自動的に「グローバルスコープ」で宣言されちゃいます。このバグが比較的複雑なコードの中に紛れると結構厄介な上に、発見したときの精神的な破壊力が半端ないです。もうこの仕様は欠陥といって差し支えないと思うんですけど...

これも『JavaScript: The Good Parts』(関連エントリー)に詳しく書かれてました。もっと早くに読んでおけばよかった..

[ INDEXにもどる ]




即時関数

本来はきちんとグローバル変数やグローバル関数の命名を管理して、必要であればオブジェクトのプロパティなどにまとめていくことなどが望ましいのですが、既に使用中のスクリプトにこれらの問題が発生し、早急に対処したい場合には即時関数化することで対処が可能です。

以下のようなコードがあったとします。

var myItem = app.project.item(1);
alert(myItem.name);

これでもかという位に不遜な名前のグローバル変数ですが、これを即時関数化することで変数のスコープをローカル化します。

(function(){
   var myItem = app.project.item(1);
   alert(myItem.name);
}());

これで即時関数化されますので、このmyItemはローカル変数になります。即時関数は、関数自体がコールされなくても定義と同時に実行されますので、普通の逐次コードを即時関数で囲むだけでグローバル変数をローカル変数にできます。この即時関数化は便利なようですが、グローバルオブジェクトのアクセス時に問題になります。AfterEffectsではScript UIなどで「this」としてグローバルオブジェクトを参照するときなどが顕著です。この場合は即時関数の引数にグローバルオブジェクトをまるごと渡してしまい、それを即時関数の自己呼出関数の引数を経由してアクセスすることができます。

(function(global){
   var myPalette = global;
   myPalette.add("statictext",[5,10,100,40],"myPalette");
}(this));

グローバルスコープでのコンフリクトを避けるという意味では簡単なのですが、一見してコードが読み取りにくくメンテナンス性にも問題が出るので、個人的にはあまりお勧めできる方法ではないと思います。一応こんな風にすればグローバルオブジェクトにアクセスできるよ、ということで書いてみました。

[ INDEXにもどる ]