AfterEffects:外部jsonファイルで動画字幕をコントロールする#2

AfterEffects:外部jsonファイルで動画字幕をコントロールする#2

下記記事の続き。今回は文字が表示・非表示するだけだった部分に演出を付ける。

完成動画

利用したエフェクト

アニメーションプリセットのテキストカテゴリからIN、OUTとも拝借した。つまり、アニメーションプリセットと組み合わせられるような仕組みに作ってある。

Adobeブリッジを使うと、、基本的にブリッジ使いにくいと思ってしまうんだが、それぞれのアニメーションプリセットのプレビュー動画がみられるので便利だ。INにはフィギーというものを、OUTには、、おや何だったか忘れてしまった。。

なお、INのエフェクトも逆再生するとOUTになるので、目当ての物がない場合は逆側を探してみてもいいかもしれない。またプリセットなのでそれぞれエフェクトの調整が効く。

仕組み

字幕の内容はjsonテキストに書いてある。値は調整したが、内容的には前回と同じ。字幕開始時間(秒)、字幕継続時間(秒)、字幕の内容、のリスト。

{
    "telops":[
        [1, 4, "Hello world"],
        [8, 4, "Hello computer"],
        [15, 4, "Hello tommorow"],
        [22, 4, "fin"]
    ]
}

jsxは結構記述が増えた。機能的に増えたのは、今の時間は字幕が表示される演出・消えていく演出だとどのくらいの位置なのか?を得られるところ。

{
	remap: function(val, min, max, value_for_out_of_range=null) {
		/*
		* 0~1の値を指定した範囲にリマップする
		* @param val リマップする値
		* @param min リマップ前の最小値
		* @param max リマップ前の最大値
		* @param value_for_out_of_range 0~1範囲外の値に対する値
		* 	指定がない場合はminまたはmaxの値を返す
		*/
		if (val < 0) {
			val = 0;
			if (value_for_out_of_range !== null) {
				return value_for_out_of_range;
			}
		}
		else if (val > 1) {
			val = 1;
			if (value_for_out_of_range !== null) {
				return value_for_out_of_range;
			}
		}
		val = min + (max - min) * val;
		return val;
	},
	_get_telop_object: function(telops, time) {
		for(var i in telops) {
			var o = telops[i];
			var start = o[0];
			var duration = o[1]
			if(start <= time && time < start + duration){
				return telops[i];
			}
		}
		return null;
	},
	get_telop: function(telops, time=null) {
		/*
		* 特定の時間に対応するテロップデータを返す
		* 見つからなければnullを返す
		* @param telops テロップデータの配列
		* @param time 時間 指定がない場合は現在の時間を使用
		*/
		if(time === null){
			time = thisLayer.time;
		}
		var telop = this._get_telop_object(telops, time);
		if (telop === null) {
			return "";
		}
		return telop[2];
	},
	get_in_effect_ratio: function(telops, effect_sec=0.5, time=null) {
		/*
		* 特定の時間に対応するテロップデータのフェードイン率を返す
		* 見つからなければ-1を返す
		* @param telops テロップデータの配列
		* @param effect_sec テロップが表示されるまでの演出時間
		* @param time 時間 指定がない場合は現在の時間を使用
		*/
		if(time === null){
			time = thisLayer.time;
		}
		var telop = this._get_telop_object(telops, time);
		if(telop === null){
			return -1;
		}
		// 0 ~ 1
		var effect_ratio = Math.min((time - telop[0]) / effect_sec, 1.0);
		return effect_ratio;
	},
	get_out_effect_ratio: function(telops, effect_sec=0.5, time=null) {
		/*
		* 特定の時間に対応するテロップデータのフェードイン率を返す
		* 見つからなければ-1を返す
		* @param telops テロップデータの配列
		* @param effect_sec テロップが消えるまでの演出時間
		* @param time 時間 指定がない場合は現在の時間を使用
		*/
		if(time === null){
			time = thisLayer.time;
		}
		var telop = this._get_telop_object(telops, time);
		if(telop === null){
			return -1;
		}
		// 0 ~ 1
		var effect_ratio = Math.min((telop[0] + telop[1] - time) / effect_sec, 1.0);
		return 1 - effect_ratio;
	}
}

多分雑に配列に全部の値を入れた返すみたいな仕様にするともっとコードは短くなる。だが、このjsxはいろいろなAEプロジェクトで使いまわすつもりなので丁寧に作った。INまたはOUTの演出の進行度の割合が0から1で返るので、コンポ側のアニメーションプリセットではそれを良しなに使えばいい。

レイヤーの設定

上記のjsonとjsxはプロジェクト素材に読み込んでおく。レイヤーに配置する必要はない。自動でコンポジションができた場合は無視するか削除する。

メインのコンポジションにテキストを適当に配置する。

テキストには前回とほぼ同じエクスプレッションを設定する。

var sd = footage('telop2.jsx').sourceData;
sd.get_telop(footage('telop2.json').sourceData.telops);

テキストに、アニメーションプリセットを2つ設定する。処理がケンカしないものがいいが、多分けんかしても何とかなる。

IN演出としたフォギーの場合、オフセットというプロパティがエフェクトの開始時に-100、終了時に100と設定する仕様だった。ここをキーフレーム2つの設定ではなく、エクスプレッションに置き換える。

var sd = footage('telop2.jsx').sourceData;
var ratio = sd.get_in_effect_ratio(footage('telop2.json').sourceData.telops);
sd.remap(ratio, -100, 100);

get_in_effect_ratioメソッドで文字が出現する演出の進行度が0から1の間で返ってくる。これをremapメソッドで-100から100に分布しなおす。

これだけで、字幕文字が表示される際にフォギー演出が毎回表示されるようになる。

OUT演出のアニメーションプリセットは適用するとたくさんのエクスプレッションやレイヤーが作成される。どこでコントロールするのか。

var sd = footage('telop2.jsx').sourceData;
var ratio = sd.get_out_effect_ratio(footage('telop2.json').sourceData.telops);
sd.remap(ratio, 0, 100);

アニメーションエフェクトの代替文字アウトという項目だった。複雑なアニメーションの場合、だいたい親パラメータが存在し、そこをコントロールすれば意図通りのタイミングで演出を制御できるようだ。

エクスプレッションはこのように設定。さっきと違うのは0~1の値を0~100にマッピングしなおした所。このアニメーションプリセットの仕様がそうだったから。

これで2つのアニメーションプリセットの演出タイミングをを外部テキストからコントロールすることができるようになる。

補足・考察など

演出のタイミングに関しては。jsxでたくさん自動でキーフレームを打ったほうが人によってはその後の微調整がやりやすいかもしれない。テキストチクチク直すのはたしかに感覚的ではないのだが、自分の場合はAEプロジェクトを開かないでデータを流し込んで動画生成というのを推し進めたいのでこのような作りになった。

前回同様の注意点だが、2022年末時点ではaerenderコマンドやメディアエンコーダーでの動画出力ではjsonファイルの動的データ流し込みに対応できていないようだ。afterfxコマンドかAE内でのレンダリングを行う必要がある。

ダウンロード

https://github.com/logicalmodelin/LGMLtools/tree/d9f6105b8e10527f9f47ca25fc46524328aa75cd/blog20221227_ae_caption

AEプロジェクトファイルはここに。

Previous post AfterEffects:外部jsonファイルで動画字幕をコントロールする#1
Next post Houdini:FloodFillをSOPソルバで実装してみた#2 パラメータ調整で面白い結果に