映像制作ゆる Advent Calendar 2025 の13日目の記事です。
い
After EffectsはCC2019から、エクスプレッションのエンジンを従来のExtendScriptのエンジンからモダンなJavaSriptのエンジン(Windows版はV8)へ切り替えられるようになった。そこら辺の話をメインに、エクスプレッションに関して書いていく。
ExtendScriptの話
ExtendScriptはES3をベースにしているが、独自拡張も多い*1。代表例としてE4X*2があげられる。
var xml = <root><a foo="bar">1</a><b>2</b></root>; xml.a.@foo; // bar
こんな感じで、JSX*3のようにXMLを扱える代物である。おそらくFlashのAction Script 3.0でES4の要素を取り入れたので*4、それが回り回ってExtendScriptにも取り入れられたものだと思われる。
エクスプレッションに関わってくる話だと、演算子のオーバーロード*5がある。
function V(x, y) { this.x = x; this.y = y; } V.prototype["+"] = function (v) { return new V(this.x + v.x, this.y + v.y); } var v = new V(3, 4); var w = new V(4, 5); v + w; // => new V(7, 9)
のような感じで、演算子の挙動を変えられることが出来て便利である。この仕組みはAEで多分に利用されており、Objectの演算子があらかじめオーバロードされている。 そのおかげで、
2 * [3, 4, 5]; // => [6, 8, 10] [6, 7] + [8, 9]; // => [14, 16]
のように、エクスプレッションやスクリプトで配列を用いた計算が楽に出来るようになっている。
前処理の話
エクスプレッションのコードはそのままの状態では評価できない。そのため、前もって処理がなされる。
前処理の話は、上記記事に詳しく載ってあるので、詳細を見たい場合も見たくない場合も行ってみてください*6。
演算子のオーバーロードの対応
まず、JavaScriptの仕様には演算子のオーバーロードがないので、演算子が使われている箇所は別の処理に置き換える必要がある。そのために、コードをrecastというライブラリで一度AST*7 に変換し、該当する演算子があれば別の処理に置き換えるということを行っている。
たとえば、
[1, 2] + [3, 4]; a *= 3;
というコードは、
__add([1,2], [3, 4]); a = __mul(a, 3);
のように変換される*8。
with文の付加
これはExtendScript時代から行われていた処理だと思われるが、エクスプレッションでは、属性やメソッドを手軽に書けるようにするために、それらを"開いて"使えるようにしている。たとえば、本来はthisProperty.wiggle(1, 100)と書くべきところを、wiggle(1, 100)とだけ書けるようになっていて、この仕組みを実現するためにwith文が使われている。
そもそもwith文とは、
var obj = {A: 1, B: 2}; with(obj) { A = 5; // obj.A = 5; B = 2 * A; // obj.B = 2 * obj.A; }
のように、指定したオブジェクトのプロパティに簡易にアクセスするために導入されたもので、現在は非推奨となっている。
このwith文を用いて、thisLayer、thisPropertyが展開されるようになっている。
with(thisProperty) { with(thisLayer) { // ここにコードが展開される } }
のような感じである*9。そのおかげで、thisLayer、thisPropertyの属性、メソッドを簡単に書くことができる。
エクスプレッションには__preprocessという関数があり、文字列を与えてやると、実際にどのように変換されるのかが確認できる。
ヘルパーオブジェクトの話
$オブジェクトは JavaScript エンジンに切り替わったことで、一部の機能が削減された一方で、新たな可能性も秘めている。
この記事にある通り、$オブジェクトは同じインスタンスが使い回される。よって、
$.settings = { mainColor: [0.33, 0.22, 0.18, 1], accentColor: [0.75, 0.15, 0.20, 1], backgroundColor: [0.96, 0.93, 0.88, 1] };
のように$麾下に値を代入するエクスプレッションを一度でもAEに評価させることができれば、他のエクスプレッションからも$.settingsの値を参照できる。ただし、いの一番に当該エクスプレッションを評価させるというのが存外難しい*10。
しかも、この$インスタンスの使い回しはMFRがオフの時に限られる*11。MFRがオンのときは、複数の実行環境が並列に生成され、$も環境ごとに別のインスタンスになる。そのインスタンスもRAMプレビューや、レンダリングを掛けたときなどに初期化されるようである。
ただ、毎回新たに生成されるわけではないというところに、使い道に関しての一筋の光明が見えないでもない。
エクスプレッションライブラリの話
AEではデータ駆動云々のときに、json、csv、tsv、txtを読み込めるようになった。
これを用いて、
{ "mainColor": [0.33, 0.22, 0.18, 1], "accentColor": [0.75, 0.15, 0.20, 1], "backgroundColor": [0.96, 0.93, 0.88, 1] }
のようなjsonファイルを作って、AEに読み込み、エクスプレッション側で、
footage("palette.json").sourceData.accentColor
こんな感じで値を読み込むことが出来る。上で書いた$オブジェクトを橋渡しに用いるよりよほど真っ当である。関数を書きたい場合は、通常のjsonだと書くことが出来ないので、関数を書けるように拡張したjsonであるjsx*12ファイルを用いる必要がある*13。
この話もMotion Developerの上記記事が詳しい*14。
jsxで書かれた関数は、AE側で上で書いたような前処理がなされないため、thisLayer、thisPropertyをいちいち書く必要があるという点は注意である*15。
つまり、
{ getTime() { return thisLayer.time; } }
のように書く必要がある。
シンプルな関数の詰め合わせであれば、
{ func1() { }, func2() { }, func3() { }, }
と書けばいい。より複雑なのを構築したければ、
{ load() { class Vector { } class Matrix { } return { Vector, Matrix, }; }, }
のようにエントリーポイントとなる関数を一つ用意し、その関数が色んな関数などを詰め込んだオブジェクトを返すようにすればいい。更に、$オブジェクトを駆使して、
{ load() { if ($.__lib) return $.__lib; class Vector { } class Matrix { } const lib = { Vector, Matrix, }; $.__lib = lib; return lib; }, }
のように、キャッシュすることで幾分かパフォーマンスの向上を望める。
と、色々書いたが、現状エクスプレッション用のライブラリを構築できていないので、作って終わりの自己満足に終わらない、実用と思想にまみれたライブラリをいずれは作りたい。
尚々
何も例がないのはよろしくないので、それっぽいものをこしらえた。
*1:ExtendScriptについて知ってることをまとめた – TAWAMIラボに詳しい
*2:Integrating XML into JavaScript - Adobe Extendscript Scripting Guide
*3:Reactの方
*4:JavaScript: 最初の 20 年 (翻訳) - inzkyk.xyzに詳しい
*5:Operator overloading - Adobe Extendscript Scripting Guide
*6:かなり具体的なコードが掲載されているが、aelib.dllを覗けば誰でも見れる
*8:__addは単にthisLayer.addを呼び出している。__mulも同様
*9:実際はProxyを用いてひとつのwith式ですむようにしてある
*10:プロジェクトを閉じれば初期化されるので、プロジェクトを再オープンした際にも注意を払う必要がある
*11:上記記事はMFR導入前に書かれている
*13:AE15.0ではjsonに関数を書いても読み込むことが出来たが、AE15.1で厳格化したようである
*14:機械翻訳の質が向上したこのご時世に、言語の壁を利用し、ただ左から右へ流すだけの東京のようなつまらないことをしている
*15:rgbToHslやdegreesToRadiansなど、グローバル関数として定義されているかのように説明されているものも実際はLayerのメソッドで、これらもthisLayer.rgbToHsl、thisLayer.degreesToRadiansと書く必要がある