After Effectsの拡張機能を作ってみよう！【Adobe ExtendScript】

# #1 誰？
お世話になっております！
株式会社MIXIの開発本部 CTO室 映像開発グループの「まんてら」と申します！

前回のアドベントカレンダーに参加してから体感２～３か月くらいしか経ってないんですが、もう年末なんですね…\_(:3」∠)\_
時間の過行く速さに驚愕しながら今年も元気にアドベントカレンダーに参加していきます！

去年の記事はこちらから！
 - [私がUnityプロジェクトを作成したら必ず入れるアセット４選！](https://qiita.com/manntera_studio/items/3ce686865823865f40fc)

本記事は「[MIXI DEVELOPERS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/mixi)」の２日目の記事です！

# #2 何やるの？
今回はAdobe After Effects(以下、AE)の拡張ツールを作るため必要な最低限の知識を共有したいと思います！
これらを読めば、後はググれば簡単なツール制作はなんとかなるくらいにはなるかと思います！
本記事の内容を極めていけば、普段煩雑に感じている作業を簡略化出来たり、やろうと思えば作成したレイヤー構造を他のタイムラインを使えるツールにエクスポート出来ちゃったり…？
AEの基本機能に満足していない人は是非触ってみて欲しいです！

# #3 とりあえず動かしてみようか
## 気になる開発言語はなぁに!?
Adobe関係のソフトウェアを拡張する際には「Adobe ExtendScript」なるものを使用します。
「Adobe ExtendScript」はES3相当のJavaScriptをAdobe社が魔改造したものです。

:::note
WEB屋さんとかでJavaScriptに精通している方は上記の記述でOh…と思った事でしょう。
そうです。ES3が基準なのです…ES3は1999年に策定された超絶古いJavaScriptです。
後々これがまんてらさんを苦しめる事になるとは知る由も無かったのです。
:::

## エディタは何を使おうか!?
基本的にVSCodeを使用します。
VSCodeの拡張機能に「ExtendScript Debugger」をインストールする事で、VSCode上でコードのデバッグを行える様になります。
VSCodeってなんぞや？って人は[ここから](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)ダウンロードしよう！
VSCodeの拡張機能ってなんぞや？って人は[この記事](https://blog-and-destroy.com/21376)を見ると分かりやすいですよ！

:::note warn
ちなみにAdobe ExtendScriptについて色々調べていると、「ExtendScript ToolKit」なるものを見つけるかと思います。
こちらはAdobe社が作成していたコードエディタツールなのですが、現在は開発が終了しておりサポートがなされておりません。
なので、特別な理由が無い限りはVSCodeを使用した開発をオススメします！
:::

## 環境構築しよう!!
### ワークスペースの作成
まずは拡張機能を作る為に、ワークスペースを作成しておくと便利です！
ワークスペースについては[このサイト](https://www.javadrive.jp/vscode/file/index4.html)が詳しいので、こちらを参考にして任意のフォルダにワークスペースを作成しておいて下さい。
### ExtendScript Debuggerの有効化
1. Ctrl+Shift+Dで「実行とデバッグ」のウィンドウを表示させます。
1. 「launch.jsonファイルを作成します」を選択。
1. 上の方に選択肢っぽいやつが出てくるので、[作成したワークスペース名]→ExtendScriptを選択
1. ワークスペース内に「.vscode→launch.json」が作成されているので、その中身を下記に従って編集します。ここで編集した内容は、デバッグ開始ボタンのリストに反映されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362540/02d52d33-7dc3-2f90-50d4-ff763963bf4a.png)
 - version
     - 「0.2.0」で固定
 - configurations
     - リストを増やすとデバッグ開始ボタンのリストが増える
 - type
     - 「extendscript-debug」で固定
 - request
     - 「launch」だと即時コードを実行させる。
     - 「attach」だと一旦アプリケーションと接続してから、ウィンドウ下部に表示される「Eval…」のボタンから手動で再度開始させる感じになる。こっちの方がデバッグしやすいので推奨
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362540/cafa5b6e-46fa-156c-af11-b74abe0c5316.png)
 - name
     - デバッグ開始ボタンの名前
 - hostAppSpecifier
     - 接続するアプリケーションを指定する。AEのデバッグを行う場合は、「"hostAppSpecifier": "aftereffects-23.0"」の様に記載する。「-」の後は、AEのバージョンを指定する。
 - engineName
     - 大体mainでおｋ。意味は分かってない
 - debugLevel
     - 0,1,2でデバッグ時の挙動を指定します。未記入の場合は自動で1が指定されます。
     - 0
         - ブレークポイントや例外処理を無視して実行されます。
     - 1
         - ブレークポイントや例外処理で一時停止します。
     - 2
         - 1に加えてコードの1行目で一時停止します。
 - script
     - どのスクリプトファイルのデバッグを走らせるかを指定します。未記入の場合はVSCode上でアクティブになっているファイルが指定されます。

私はこんな具合で設定しております！
```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "extendscript-debug",
            "request": "attach",
            "name": "Attach-AE23",
            "engineName": "main",
            "hostAppSpecifier": "aftereffects-23.0",
        },
        {
            "type": "extendscript-debug",
            "request": "launch",
            "name": "Launch-AE23",
            "engineName": "main",
            "hostAppSpecifier": "aftereffects-23.0",
        },
    ]
}
```
## さぁ！コードを書くぞ！
まずは、ワークスペース内の任意のディレクトリに、[任意の名前.jsx]のファイルを作成して下さい。
### プロジェクトを作成しよう！
とりあえず、先ほど作成したファイルに下記コードを記述してみて下さい。
```jsx
// プロジェクトを作成する
// appってのは、ExtendScriptによって接続されているアプリケーションの事
var mainProject= app.newProject();
// プロジェクトに新規コンポジションを追加する。
// 引数は、コンポジション名,width解像度,height解像度,ピクセルアスペクト比,デュレーション(秒)
var mainComp = mainProject.items.addComp("MainComp", 1920, 1080, 1.0, 15, 29.97);
// コンポジションにテキストレイヤーを追加
var textLayer = mainComp.layers.addText("Hello World");
// コンポジションに平面レイヤーを追加
// 引数は、色,レイヤー名,width解像度,height解像度,ピクセルアスペクト比,デュレーション(秒)
var redSolid = mainComp.layers.addSolid([1, 0, 0],"RedSolid", 300, 300, 1.0, 15);
// 平面レイヤーのトランスフォームプロパティを取得
var transformProp = redSolid.property("ADBE Transform Group");
// トランスフォームのローテーションプロパティを取得し、
// エクスプレッション(AE内の簡易的なスクリプト)を追加
transformProp.property("Rotation").expression = "time*100";
```
## とりあえず実行してみよう！
「ExtendScript Debuggerの有効化」で紹介した、デバッグ開始ボタンのリストで任意の選択をした状態で、再生アイコンを押下してみましょう！
上記のスクリプトを実行してみると…？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362540/054c08a8-8e1a-d979-f3f6-15935630efea.png)
こんな具合で後ろにでかでかとHello Worldが表示され、赤い平面がくるくる回っているかと思います！
## AE内で使うには？
実際にAEで使ってもらう際に、毎回アーティストにVSCodeから実行して貰うのは非常に面倒です。
そのため、AEのツールバーから、「ファイル>スクリプト>スクリプトファイルの実行」を選択して実行して貰います。
しかし、これでもまぁまぁ面倒な作業かと思いますので、一般的には後述する「#4 GUIを作成するぞ！」の方法で実行する事が多いです。

# #4 AEでScriptを書く際の知識について
基本的にAEに何かしらの手を加える際は、AEのデータに対して何かしらのアクセスを行って処理をします。
AEのデータ構造は下記表の様になっております。
最初にappオブジェクトからProjectを取得し、ProjectからItemを取得し、ItemからLayerを取得し・・・の様に、欲しい情報を深堀りしていきます。
具体的なデータのアクセス取得方法に関しては、[このサイト](https://aejsx.hiroshisaito.net/home)を参考にして下さい！
![アドベントカレンダー資料.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362540/b9819bb4-1d48-5486-8f33-9d1d6ce3d25d.png)
 - app
AEのアプリケーション自身です。
アプリのバージョン等の状態確認やアプリの終了等が行えます。
このインスタンス変数はグローバルで定義されています。
 - Project
プロジェクトの操作を行います。
プロジェクトの保存や、プロジェクト内のフッテージやコンポジション等の映像を作る際にかかせない要素にアクセスする事が出来ます。
 - Item(s)
Project内で使用している素材やコンポジション等が配列で格納されています。
AEのプロジェクトウィンドウにリストされているものが対象のItem達です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362540/825113b9-248b-a668-f988-a25c4d648e13.png)
また、各Itemは下記の3つのItemの継承が存在します。
(正確にはFootageItemとCompItemは更にAVItemが継承されている。)
   - FolderItem
       - プロジェクトウィンドウでItem達を格納しているフォルダ
   - FootageItem
       - 画像や動画、平面等の素材です。
   - CompItem
       - コンポジションの事。レイヤー情報がたくさん詰まっている。
 - Layer(s)
コンポジション内のレイヤー情報が配列で格納されています。
各Layerは下記のLayerの継承が存在します。
     - CameraLayer
     - LightLayer
     - AVLayer
         - AVItemを含んだレイヤー。基本はCameraとLight以外全部。TextLayerに関してはAVLayerが継承されています。
 - Property
各レイヤーに含まれているプロパティ情報を取得します。各Propertyは階層化されています。
例えば、Transform情報はその子に位置情報や回転情報が存在したりする様に、欲しい情報に対して階層的にアクセスします。
ただし、このPropertyにアクセスする際に目的のPropertyの文字列を入力するのですが、AEの言語設定によってその入力するべき文字列が変化します。例えば日本語設定の場合は「トランスフォーム」で、英語設定の場合は「Transform」で入力してPropertyを取得する事になります。
この様な地獄の言語対応を行わない様にする為、「matchName」というものが存在します。プロパティにアクセスする際は、基本的にmatchNameを入力する事を推奨します…が、中には対応していないものもあるので、それに関しては頑張って対応させましょう…
具体的にどんなmatchNameが存在するかは、[ここ](https://aejsx.hiroshisaito.net/matchname/effects-matchnames-cc2014)と[ここ](https://aejsx.hiroshisaito.net/matchname/layer-property-matchnames-cc2014)を参照して下さい。

# #5 GUIを作成するぞ！
## とりあえず動かしてみよう！
 - 先ほどの「#3 とりあえず動かしてみようか」で紹介したコードを下記の様に改造してみましょう。
```jsx
function createProject(){
    var mainProject= app.newProject();
    var mainComp = mainProject.items.addComp("MainComp", 1920, 1080, 1.0, 15, 29.97);
    var textLayer = mainComp.layers.addText("Hello World");
    var redSolid = mainComp.layers.addSolid([1, 0, 0],"RedSolid", 300, 300, 1.0, 15);
    var transformProp = redSolid.property("ADBE Transform Group");
    transformProp.property("Rotation").expression = "time*100";
}

// thisには状況に合わせて何かしらのクラスが入ってくる。
// 後述する、ScriptUI Panelフォルダに入れて、
// ウィンドウメニューから起動した場合は、object Panelが入ってくる。
var wnd = this;

// Panelウィンドウに何か表示させたい場合は、.add()を使用する。
// .add()の引数は、
// 入れたい要素,要素の座標(モニター左上を始点として[左,上,右,下]),要素の描画内容

// Panelウィンドウにテキストを追加する。
wnd.add("statictext",[30,10,220,30],"プロジェクト作成");
// Panelウィンドウにボタンを追加する。
var button = wnd.add("button",[30,50,220,70],"作成開始");

// 作成開始ボタンがクリックされると、createProjectの関数が実行される。
button.onClick = createProject;

// Panelウィンドウを表示させる
wnd.show();
```
 - 作成したファイルを、「C:\Program Files\Adobe\Adobe After Effects [バージョン名]\Support Files\Scripts\ScriptUI Panels」(インストールディレクトリによっては異なる場合があります)にコピーしましょう。
 - ファイルをコピーしたらAEを立ち上げ、ツールバーの「ウィンドウ>[コピーしたスクリプトファイル名]」をクリックしてみましょう！すると、下記の様なウィンドウが表示され、作成開始を押下するとプロジェクトが作成されるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362540/66815c36-4c8d-0fa3-61ab-c0c0cb16f7ce.png)
## 気軽にデバッグ出来る様にしてみる！
さて、上記コードをVSCodeから起動してみるとどうなるでしょうか？
恐らく「関数wnd.addは未定義です」と例外が吐かれるかと思います。
これは、スクリプトを起動した媒体によって、thisに入るクラスが異なる為に起こる現象です。
AEのウィンドウメニューから起動させた場合はthisはPanelウィンドウが入って来ますが、その他の媒体で起動させた場合はobject globalという謎のクラスが入ってきます。これだとPanelウィンドウの取得が出来ません。
この場合は、自分でウィンドウを作成する必要が出てくるため、下記の様にコードを変更してみましょう。
```jsx
function createProject(){
    var mainProject= app.newProject();
    var mainComp = mainProject.items.addComp("MainComp", 1920, 1080, 1.0, 15, 29.97);
    var textLayer = mainComp.layers.addText("Hello World");
    var redSolid = mainComp.layers.addSolid([1, 0, 0],"RedSolid", 300, 300, 1.0, 15);
    var transformProp = redSolid.property("ADBE Transform Group");
    transformProp.property("Rotation").expression = "time*100";
}

var wnd = this;

// this(wnd)がPanelでない場合、
if(!(wnd instanceof Panel)){
    // wndに自作のウィンドウを作成する。
    // 引数は、Windowの種類,Window名,Windowの座標
    // Windowの種類には、dialog/palette/windowの3種類がある。
    // しかし、paletteじゃないとappから正しい値が取得できない！！なんでや！！謎！！
    wnd = new Window("palette", "Test Window", [100, 100, 600, 600]);
}
wnd.add("statictext",[30,10,220,30],"プロジェクト作成");
var button = wnd.add("button",[30,50,220,70],"作成開始");

button.onClick = createProject;

// wndを自作した場合は、show()は不要
if(wnd instanceof Window)
{
    wnd.show();
}
```
すると、無事にVSCodeから実行させた場合でもウィンドウが表示される様になったと思います。
## 詳細な資料について
GUI周りは色々と複雑な事が多い為、ここでは全て紹介しきれません。
もっと込み入った事をしたい場合は、下記サイトを参照してみて下さい！
 - [各引数やパラメーターについて詳しい説明が載っている](http://ae-users.com/jp/tutorials/2011/10/after-effects%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%80%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%85%A5%E9%96%80%E3%80%80%E3%81%9D-11/)
 - [GUIを丁寧に実装してみたい場合の解説](https://sites.google.com/view/youuu4/HowToWrite/scriptuipanels)
# #6 開発をもっと楽にしたい！
## コードの自動補完したい！！
1. ワークスペース内に任意の名前のフォルダを作成する(typesとか)
1. [このリポジトリ](https://github.com/aenhancers/Types-for-Adobe)をダウンロードとかクローンとかします。
1. 1.で作成したフォルダに、「shaderd」と「AfterEffects」のフォルダをコピーします。
1. ワークスペースの直下に、「jsconfig.json」を作成する。(中身は空で良いらしい。ようわからん)
1. これで、コードの自動補完が出来るはず！
## JSONクラス使えないの？
標準だと使えないんですよ…
なので、外部ライブラリを用意しましょう。
1. [このリポジトリ](https://github.com/douglascrockford/JSON-js)をダウンロードとかクローンとかします。
1. 「json2.js」をワークスペース内の任意の場所にコピーし、ファイル名を「json2.jsxinc」にします。
1. jsonを使いたいコードの先頭に、``#include ~/json2.jsxinc``と記述します。(~/json2.jsxincの部分はファイルの場所に応じて変更して下さい。)
1. 下記のコードの様にJSONが使えます！
```jsx
// オブジェクトをJson式の文字列に変換
var jsonString = JSON.stringify(myObj);
// Json式の文字列をオブジェクトに変換
var obj=JSON.parse(myObjStr)
```
## JavaScript_ES3準拠で書くのとてもつらい…
いや、本当に辛いんですよ。
foreach使えなかったり、JSONクラス使うのに外部ライブラリ使ったり…
実は私はまだ対応まで出来てないので別途記事で紹介する事になりますが、どうやらES6やTypeScriptから変換出来たりするみたいですね～
変換できた人はやり方を教えてくれると嬉しいです…！

# #7 最後に
こんな具合で後はググれば大体コードは書けるかと思います！
もし何か記事に関するミスや質問があれば、[TwitterのDM](https://twitter.com/manntera)に問い合わせて頂けると嬉しいです！


また、MIXIでは様々な事業/ポジションを積極採用中です！
是非MIXIの採用情報をご覧ください！

https://mixigroup-recruit.mixi.co.jp/

最後まで見て下さりありがとうございました！