C4Dでの音合わせが少し楽になるかもしれないAEスクリプト紹介

More than 1 year has passed since last update.

これはAfterEffects Advent Calendar 2016の5日目の記事です。

昨日の記事は、 @tetsuoh さんによる アニメ撮影でErgoDox使ってみた です。


はじめに

映像作ってます。Shutoです。

BGAやボーカロイドMVなどのモーショングラフィックス作品の制作でよく使われるCinema 4D(通称C4D)は、ドイツに本社を置くMAXONが開発した3DCG制作ソフトです。

最近のAfterEffects CCには「Cineware」といった機能がついていてC4D単体製品を買わなくても「Cinema 4D Lite」が使えるためにその名前をご存知の方も多いでしょう。

さて、本題に入りますが、今回はC4Dを用いて音楽に合ったCGシーンを制作する際に、音合わせが少し楽になるかもしれない、そんな気休め程度のスクリプトをご紹介します。

お気づきの方も多いでしょうが、本記事はC4DかつAEのユーザを想定しています。C4Dユーザで無い方はごめんなさい。一応AEのスクリプトなので許してくださいなんでもry

ちなみに環境ですが、こちらはWindows 10のCS6(11.0.4.2)を使用しています。


C4Dでの音合わせの問題

C4Dではシーンが重くなってくると、作業画面のプレビューの再生がままならなくなります。音声再生がガタガタになったり、プレビューのフレームレートが1fps以下にもなったりします。

ここでAEユーザーなら「じゃあRAMプレビューだ!」と思うわけですが、C4DにはRAMプレビューがありません。代わりにある簡易的にレンダリングしてから再生させるプレビューが「プレビュ作成」の「ハードウェア(ソフトウェア)プレビュ」にあたるわけですが、シーンが複雑になってくると、この「プレビュ作成」も非常に重くなってくることが多いです。

qiita_advent2016_1_1.png

qiita_advent2016_1_2.png

映像制作の音合わせでは基本的には何度も試行錯誤を行って気持ちよく当てはまる動きを探すことでしょう。

ところがこの重い「プレビュ作成」を何度もしていたら試行錯誤をするのに疲弊してしまいます。

効率よく制作するには1度のプレビュ作成でどれだけ理想の音ハメのタイミングに近づけるかが重要になってきます。


解決策とその手順

最初からいきなりC4Dで音合わせをするのでは、タイミングを当てるのが難しいです。

AEには.c4dファイルを書き出す機能があるので、それをうまく活用して、AEで作成した音合わせの目印をC4Dに持っていくことにしましょう。

手順を示すとこんな感じです。1~4はすべてAEでの作業になります。

なお、題名にもあるスクリプトについては最後に記します。


1. AE側で音楽ファイルを読み込み、RAMプレビューを用いてタイミングを確認する

普通にAEにmp3やら音楽ファイルを読み込んでRAMプレビューをさせます。ここである程度タイミングを確認しておきます。


2. 音ハメのキーポイントとなる特徴的な音のタイミングで、AEのマーカーを設置する

qiita_advent2016_1_3.png

このように音楽ファイルのレイヤーなどにマーカーを打っていきます。

一例ですが、2秒間にこんなに細かい動きを作りたいとしたら、C4Dだと音合わせが大変ですよね。


3. ライトレイヤーを作成し、2.で設置したマーカーと同じタイミングで座標などにキーフレームを打つ

qiita_advent2016_1_5.png

2で打ったマーカーをライトレイヤーの位置プロパティのキーフレームに変換します。手打ちでも構いませんが、大変なのでここはスクリプトで自動化します。

なお、「ライトレイヤー」は「カメラレイヤー」でもかまいません。C4Dにキーフレームを持っていければなんでもいいのです。

※キーフレーム補間法が「停止」になっている点については次項で説明します。


4. コンポジションを .c4d 形式にエクスポートする

qiita_advent2016_1_6.png

打たれたキーフレームの情報も含めてAEのライトをC4Dに持っていけます。これもスクリプトで自動化してしまいましょう。

メニューの「ファイル>書き出し>CINEMA 4D Exporter」から手動でもエクスポートができます。

ただし、一点だけ注意が必要で、3のライトレイヤーに打つキーフレームの補完法は「停止」にします。他の補完法でもエクスポートは出来るのですが、C4Dに対応していない補完法は何でもかんでも勝手にベイクされてしまうようで、どこがマーカーのタイミングなのか分からなくなってしまいます。(補間法リニアでエクスポートした図)

qiita_advent2016_1_7.png


5. エクスポートされた .c4d ファイルをC4Dで読み込み、ライトレイヤーのキーフレームのタイミングを参考に音ハメをする

キーフレーム補完法を「停止」にして.c4dファイルとしてエクスポートすると、C4Dで読み込むときちんとこのように必要なタイミングでのみキーフレームが打たれた状態でライトを持っていくことが出来ます。

qiita_advent2016_1_8.png

制作にあたっては、当然何度かはハードウェア(ソフトウェア)プレビュを作成してきちんと動きとタイミングが合っているかを確認する必要はありますが、この方法で音の目印を持っていくことで、だいたいのタイミングが合った状態で動きをつけていくことが出来ます。

余談ですが、最近制作したkanoneさんのCO5M1C R4ILR0ADのBGAでもこのように活用しました。

qiita_advent2016_1_13.PNG


スクリプトについて

ようやくスクリプトの紹介ですが、このように「AEで作成したマーカー情報を元に、C4Dへ持っていけるライトレイヤーにキーフレームを打ち、C4Dファイルにエクスポートしてくれるスクリプト」を書きました。どのようなスクリプトを書いたとか、なんでそんなスクリプト書いたのとかっていう説明の上手い手順が思い浮かばなかったので回りくどい書き方をしてしまいました。

なお、最近は実装されたそうですが、コンポマーカーにアクセスするメソッドが用意されていなかったので、noriaki iwataniさんのコンポジションマーカーを取得するスクリプトをお借りしています。

以下作成したスクリプト。

コピペ保存は面倒くさいと思うので、こちらをCtrl+Sもしくは⌘+Sで保存してお使いください。

保存形式が.jsxになるように注意してください。


markerToC4DasLight.js


// コンポかレイヤーを選択した状態で使うと、新たにライトレイヤーを生成して、コンポかレイヤーのマーカーと同じ時間にキーフレームを打つ。
// レイヤーが1枚選択されていればそのレイヤーのマーカーを使う。
// コンポが選択されていれば(レイヤーが選択されていなければ)、そのコンポのマーカーを使う。
// CS6ではコンポのマーカーを取得するメソッドが用意されていないのでnariakiwataniさんのスクリプトをそのままお借りした。

var activeComp = app.project.activeItem; // プロジェクトパネルでアクティブなアイテムを取得

function main() {
var markers = null;

if (activeComp instanceof CompItem == false) { // アクティブなアイテムがコンポ以外ならはじく
alert("コンポが選択されていません。");
return;
}
if (activeComp.selectedLayers.length > 1) { // activeComp内で選択されているレイヤーが2枚以上ならはじく
alert ("レイヤーを1つだけ選択するか、コンポのみ選択してください。\nレイヤーを選択していない場合はアクティブなコンポのマーカーが使われます。");
return;
} else if (activeComp.selectedLayers.length == 1) { // 選択されているレイヤーが1枚ならそのレイヤーのマーカーを取得
markers = activeComp.selectedLayers[0].property("Marker");
} else if (activeComp.selectedLayers.length == 0) { // レイヤーが選択されていないならばそのコンポのマーカーを取得
markers = getCompMarker(activeComp);
}
if (markers.numKeys < 1) { // マーカーが0ならはじく
alert ("選択されているレイヤーかコンポにマーカーがありません。");
return;
}

var newLight = activeComp.layers.addLight("Marker_Light", [0, 0]); // ライトレイヤーを生成して、newLightに格納
newLight.property("position").setValue([0, 0, 0]); // ライトレイヤーの座標が初期だとキモいので変える
newLight.property("pointOfInterest").setValue([0, 0, 100]);
for (var i=0; i<=markers.numKeys; i++) { // 取得したマーカーの分だけキーフレームを打つ
var keyIdx = newLight.property("position").addKey((i==0)?0:markers.keyTime(i)); // キーフレームを打って、そのキーフレームのインデックスをkeyIdxに格納
newLight.property("position").setValueAtKey(keyIdx, [0, 0, i*-10]); // 格納したインデックスを元に、そのキーの座標値を変更
// 同様に補間方法を変更
newLight.property("position").setInterpolationTypeAtKey(keyIdx, KeyframeInterpolationType.HOLD, KeyframeInterpolationType.HOLD);
}

var version = parseInt(app.version.split(".")[0]);
var msg_export = "Marker_Light を生成しました。\n続けて.c4dファイルに出力しますか?";
var code_export = (version>11)?5022:5006;
var execute = confirm(msg_export); // 確認ダイアログを表示
if (execute) app.executeCommand(code_export); // yesならexporter起動

}

app.beginUndoGroup("markerToC4DasLight"); // ここから
main();
app.endUndoGroup(); // ここまでの処理がUndo1回で戻せる

/*
AfterEffectsでコンポジションマーカーを取得するスクリプト
2013.10.31 by nariakiiwatani
MIT License - http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
*/

// マーカーの情報を取得したいコンポジションを渡す
function getCompMarker(comp) {

// プロジェクトに含まれるコンポジションのリストを取得
function getCompAll(proj) {
var ret = [];
for(var i = 1; i <= proj.numItems; ++i) {
if(proj.item(i) instanceof CompItem) {
ret.push(proj.item(i));
}
}
return ret;
}
// 配列の差分を取得
function getArrayDiff(a, b) {
var ret = [];
for(var _a in a) {
var found = false;
for(var _b in b) {
if(a[_a] === b[_b]) {
found = true;
break;
}
}
if(!found) {
ret.push(a[_a]);
}
}
var tmp = a;
a = b;
b = tmp;
for(var _a in a) {
var found = false;
for(var _b in b) {
if(a[_a] === b[_b]) {
found = true;
break;
}
}
if(!found) {
ret.push(a[_a]);
}
}
return ret;
}

// プロジェクトウィンドウの選択情報を操作するので、操作前の状態を保存しておく
var selected = app.project.selection;
var selection = [];
for(var i = 0; i < selected.length; ++i) {
selection.push(selected[i].selected);
selected[i].selected = false;
}

// ここから処理の本体
comp.selected = true; // 書き出し対象のコンポジションを選択状態にする
// 「複数アイテムから新規コンポジション」はプロジェクトウィンドウにフォーカスしていないと使えないので強制的にアクティブにする
app.project.showWindow(false);
app.project.showWindow(true);

var allComps = getCompAll(app.project); // コマンド実行前のコンポジションのリスト
app.executeCommand(2796); // 「複数アイテムから新規コンポジション」を実行
var added = getArrayDiff(getCompAll(app.project), allComps)[0]; // コマンド実行によって追加されたコンポジションを探す
var marker = added.layer(1).marker; // これが欲しかったマーカー!

// ここから後片付け
added.remove(); // 増やしたコンポジションを削除
comp.selected = false; // 選択状態を解除
for(var i = 0; i < selected.length; ++i) { // スクリプト実行前の選択状態に戻す
selected[i].selected = selection[i];
}
// マーカー情報を返す
return marker;
}



オマケのC4Dスクリプト

C4Dで音楽を読み込む方法はググって貰えれば出てくる通り、オブジェクト(ヌルなどなんでもよい)に「サウンド」という特殊トラックを追加すればよいのですが、これがまたUIからのアクセス性が悪く、「サウンド」のパラメータを変える場合は毎回タイムラインを開いて「サウンド」トラックを選択して属性マネージャに表示してやる必要があります。

qiita_advent2016_1_14.PNG

↑追加した特殊トラック「サウンド」を選択してアクティブにしないと↓このプロパティを表示できない

qiita_advent2016_1_11.PNG

座標やその他のタグなど、他のパラメータは基本的にオブジェクトマネージャ(ツリー)からそのオブジェクトやタグ自身を選択すれば属性マネージャに表示されますが、「サウンド」の属性を表示させるにはタイムラインの「サウンド」を選択する必要があります。

qiita_advent2016_1_10.PNG

↑オブジェクトマネージャの「null」オブジェクトを選択しても「サウンド」のプロパティは表示されない。

そこで、新規ヌルオブジェクト(Musicという名前)に「サウンド」トラックを追加(実際この手順も結構面倒)して、さらにそのMusicオブジェクトを選択すればこのように「サウンド」のプロパティに簡単にアクセスできるスクリプトを書きました。

qiita_advent2016_1_12.PNG

仕組みとしてはPythonタグを使って常に属性マネージャの↑の「Sound」タブの値を見て、「サウンド」の特殊トラックの値に入れています。つまり↑の「Use Sound」や「Start Time」の値をいじれば自動的にそれが特殊トラックの方にも反映されるわけです。

※使い方にご注意ください!Pythonスクリプトタグにコピペせずに、必ずスクリプトファイルとして保存して、メニューの「スクリプト>ユーザスクリプト>スクリプトを実行」から実行してください。

qiita_advent2016_1_15.png

スクリプトの保存はAEスクリプト同様、こちらから保存してお使いください。

jsx同様に保存形式が.pyになるように注意してください。


addMusic.py

import c4d

import time

def main():
obj = c4d.BaseObject(c4d.Onull)
obj.SetName("Music")
doc.InsertObject(obj)
doc.SetSelection(obj)

tr = c4d.CTrack(obj,c4d.DescID(c4d.DescLevel(c4d.CTsound, c4d.CTsound, 0)))
obj.InsertTrackSorted(tr)

pyTag = obj.MakeTag(c4d.Tpython)
pyTag.SetName("Music")

rootGroup = c4d.GetCustomDataTypeDefault(c4d.DTYPE_GROUP)
rootGroup[c4d.DESC_NAME] = "Sound Properties"
rootGroup[c4d.DESC_SHORT_NAME] = "Sound"
rootGroup[c4d.DESC_TITLEBAR] = 1
rootGroup[c4d.DESC_GUIOPEN] = 1
rootGroup[c4d.DESC_PARENTGROUP] = c4d.DescID()
descId = obj.AddUserData(rootGroup)

secondGroup = c4d.GetCustomDataTypeDefault(c4d.DTYPE_GROUP)
secondGroup[c4d.DESC_NAME] = "Basic Properties"
secondGroup[c4d.DESC_SHORT_NAME] = "Basic"
secondGroup[c4d.DESC_TITLEBAR] = 1
secondGroup[c4d.DESC_GUIOPEN] = 1
secondGroup[c4d.DESC_PARENTGROUP] = descId
descId = obj.AddUserData(secondGroup)

label = ["Use Sound", "Start Time", "Sound"]
default = [1, c4d.BaseTime(), ""]
typ = [c4d.DTYPE_BOOL, c4d.DTYPE_TIME, c4d.DTYPE_FILENAME]

for i in range(3):
bc = c4d.GetCustomDataTypeDefault(typ[i])
bc[c4d.DESC_NAME] = label[i]
bc[c4d.DESC_SHORT_NAME] = label[i]
bc[c4d.DESC_PARENTGROUP] = descId
bc[c4d.DESC_DEFAULT] = default[i]
bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_OFF
descId2 = obj.AddUserData(bc)
obj[descId2] = default[i]

pyTag[c4d.TPYTHON_CODE] = 'import c4d\n#Welcome to the world of Python\n\n\ndef main():\n obj = op.GetObject()\n tr = obj.GetCTracks()[0]\n tr[c4d.CID_SOUND_ONOFF] = obj[(c4d.ID_USERDATA, 3)]\n tr[c4d.CID_SOUND_START] = obj[(c4d.ID_USERDATA, 4)]\n tr[c4d.CID_SOUND_NAME] = obj[(c4d.ID_USERDATA, 5)]\n'

if __name__=='__main__':
main()
c4d.EventAdd()


※公開直後追記

やりたさん 曰く、C4D標準UIから「サウンド」プロパティへのアクセスは、MoGraphの「サウンドエフェクタ」をシーンに追加するだけで簡単にできるそうです。

ただし、「サウンドエフェクタ」はハードウェアプレビュなど「プレビュ作成」においては音声が再生されないので、そのあたり使い分けが必要になりそうです。

プレビュ作成をしないのならばそちらだけでも十分楽になると思いますが、僕のように基本的に必ずプレビュ作成する場合はこのようなスクリプトが便利かと思います。

追記ここまで


最後に

この時点で既に公開予定の0時15分を過ぎています。ごめんなさい。

駆け足で書いたのでとっても読みにくかったと思いますが、何か誤りや分かりにくいところがあればコメントまたはTwitterまでご連絡ください!

※公開直後追記

なお、記事公開後にそもそもC4Dのシーンを軽くする方法をやりたさんに教えて頂きました。

つきましては こちらのくだり が参考になるかと思いますので、こちらも一読して貰えるとよいかと思います。

追記ここまで

それでは明日は @kickbase さんの マルチバイトフォルダ名許さいないマンのためのTips です!

僕もマルチバイトフォルダ名許さないマンなのでとても楽しみです! ※2016/12/6更新↑リンク追加しました