motionkit は何をしますか?
1ページ(HTML)と1つのスクリプト(JavaScript)からなる映像制作エンジンです。renderFrame(f) がフレーム f(30fps、1920x1080)を描画するだけで、あとは隠れChromeがフレームを刻み、ffmpegがエンコード(mp4は60fpsに倍速、高品質、約4~5MB/秒)、サウンドトラックが合成されます。
構成要素の場所
- エンジン:このファイルと同じフォルダにある
engine/フォルダ(ムーブ、レンダラー、サウンド、チェック、フォント)。これを$Eと呼び、フルパスで参照します。読み取り専用で、この中にフィルムを置かないでください(セットアップ時に独自のnode_modulesが追加されることが想定されています)。 - 作業フォルダ:ユーザーが作業しているフォルダ。フィルム(
<name>.html、<name>.js)、assets/、ref/、out-<name>/はすべてこのフォルダに置かれ、すべてのコマンドはこのフォルダから実行されます。ページはエンジン内のlib.js、moves.js、fonts/を自動的に見つけます。作業フォルダがgitリポジトリの場合は、out-*/とref/を.gitignoreに追加してください。
最初に行うこと:セットアップ(マシンごとに1回)
sh $E/setup.shを実行します。これにより、bun、ffmpeg/ffprobe、python3、uv、Chromium系ブラウザ(Chrome、Chromium、Edge。CHROME_PATHで上書き可能)の有無をチェックし、エンジンの1つのパッケージを確認します。Readyと表示されたら先に進みます。engine packageだけが欠けている場合(新規インストールでは通常これ)は、確認なしでsh $E/setup.sh --installを実行します(スキル自身のフォルダに1つのパッケージを取得するだけ)。ツールやブラウザがMISSINGと表示された場合は、そのリストと表示されたインストールコマンドをユーザーに提示し、ソフトウェアをマシンにインストールする前に一度確認を求めます。- 承諾が得られたら、
sh $E/setup.sh --installを実行します(macOSではHomebrew、bunとuvの公式インストーラを使用)。その後、再度チェックを実行します。run this yourselfと表示された行は、ユーザーのパスワード(Linuxではsudo)または手動ダウンロードが必要です。これらは決して実行せず、ユーザーに正確なコマンドを伝えて待ちます。インストールが失敗した場合は、ユーザー自身が実行する1つのコマンドを伝えます。 - 動作確認:作業フォルダから
bun $E/build.ts helloを実行し、out-hello/hello.mp4が存在することを確認します。
macOSでのみテスト済みです。ユーザーがLinuxまたはWindowsを使用している場合は、その旨を伝えてください。
参考になる実例:$E/hello.js(ビートグリッド上のフィルム、すべてのサウンドはクリップから、背景はコードで描画)、$E/title.js(映像の上に重ねる透過タイトル)、$E/moves-demo.js(すべてのムーブを1回ずつ実行)。
フィルムを制作する:まずクリップを宣言
各ムーブの使用を、1回のクリップとして宣言します。画像、サウンド、読み上げ時間のチェックはすべて、その1行から得られます。
const M = window.MOVES, B = M.brand, G = M.grid(120); // 120 bpm:ビートは15フレーム、小節は60フレーム
const K = { line: M.clip("words", G.bar(1) + 2, 10, { text: "Copy the motion.", x: 960, y: 590, size: 110, fam: B.display, stretch: B.wide, weight: 600, color: B.paper, until: G.bar(2) }) };
// シーン内: K.line.draw(X, f) K.line.on(f) は表示されるべきときに true を返す
window.timeline = () => ({ fps: 30, total: TOTAL, bpm: G.bpm, sections: [[0, "intro"], [4, "build"], [8, "drop"], [24, "outro"]], cues: M.cues(), captions: M.captions() });
ローンチ動画や製品動画の場合は、以下の「ローンチ動画と製品動画」を先に読んでください。bun $E/new.ts <name> でフィルムを開始します。これにより、作業フォルダに <name>.html と <name>.js が作成され、helloの例からコピーされます。クリップ、シーン、コピーを書き換えます。bun $E/build.ts <name> は、以下の順序で実行します:タイムライン、サウンドトラック(sound.py)、サウンドチェック、読み上げ時間チェック(lint.py)、レンダリング、結合、同期チェック(sync.py)、そして out-<name>/<name>.mp4 を書き込みます。bpm がないタイムラインは、audio.py からよりシンプルなサウンドトラック(カットのみ)を取得します。タイムラインのスイッチ:sound: "fx"(ムーブのサウンドのみ、音楽なし)、sound: "none"、alpha: true(透過背景、.mov 出力。EditMaxのセクションを参照)。
フィルムスクリプトの構造
$E/hello.js がモデルです。一度読んでください。各部分の説明:
lib.jsからのグローバル変数:W、H(1920、1080)、X(キャンバスのコンテキスト)、clamp、lerp、TAU、hash、noise、txt(ctx, text, x, y, size, weight, color, fam, italic, align)、rr(角丸矩形)、vignette(strength, innerRadius)。M.clip(move, start, length, options):startとlengthはフレーム数。lengthはムーブの再生にかかる時間であり、表示されている時間ではありません。{ draw(ctx, f), t(f) (0..1 でムーブの進行度), on(f), o (options) }を返します。lengthが経過した後もクリップは終了状態を描画し続けます。非表示にするには、シーンを終了するか、if (K.x.on(f))を使用します(onはstartからuntilまで true、untilがない場合は永続的に true)。トランジションのfrom内で完了したテキストムーブを表示するには、t = 1で描画します:M.words(ctx, 1, K.line.o)。ワンショットエフェクト(burst、glitchCut)は、K.x.t(f) < 1も必要です。- クリップ内で呼び出すシェイプ:トランジションは options の
fromとtoを受け取ります。ラッパー(punch、shake、sweep、smear)はdrawを受け取ります。それ以外(pathDraw、burst、bars、chart、callout、logoDraw、bounce、ticker)は options のみを受け取ります。meshとparallaxは背景です:直接呼び出します(例:M.mesh(ctx, f / 240, { colors }))。tはゆっくり増加する数値です。 - テキストオプション:
xは行の中心(align: "center"がデフォルト)、yはベースライン。stretch: B.wideでワイドカット、stretch: nullでオフ。テキストの計測や折り返しは自動で行われません。サイズ150ワイドで、13文字で約1,340pxの幅になるため、スチルをレンダリングして、各行がフレーム内に余白ととも収まっていることを確認してください。 - シーンテーブル:
[最初のフレーム, draw(f), drawAfterFinish(f)]。renderFrameはコンテキスト状態をリセットし(その行は保持)、現在のシーンを描画し、次にグレインとビネット、そしてクリーンな状態を保つ必要があるもの(ロゴなど)を3番目のスロットに描画します。 window.ready:レンダラーが待機するプロミス。例:Promise.all([M.fonts(), M.levels("out-<name>/levels.json"), M.load({ ... })])。M.levelsはM.eqにのみ必要で、ファイルが存在しなくても安全です。- タイムライン:
sectionsは[ビート, 名前]。短いフィルムでは[[0, "intro"], [8, "drop"], [16, "outro"]]だけで十分です。各dropの開始は無音の半ビートを取得します(必要に応じてクリップをmute:にするか、そのままにします)。syncは、カットとベースのアタックが一致する必要があるフレームをリストします。ドロップ以降の各シーンの最初のフレームを使用します。「変更」はフィルムの通常のフレーム間差分と比較して判断されるため、トランジションが同期フレームで正確に終了しても、そこで変更するものがなく、チェックはNO CHANGEと表示します。そのフレームで短いフラッシュを追加するか、次のムーブを開始してください。cutsはすべてのシーンの開始位置です。 - 色:すべてを
M.brand(B)から読み取ります。M.rgba(B.ink, 0.7)で透明度付きの色、M.mix(a, b, t)で2色間の中間色を取得します。M.setBrandはM.brandをその場で変更します。クリップを宣言する前に呼び出し、accentOnDark(暗い背景で使用されるアクセント。accentには従いません)も設定します。ワイドカットがないフォントにはwide: nullを設定します。 - 長さ:読み上げ時間が最小値を設定します。3単語の行は、表示されてから約1.3秒必要です。そのため、120bpmでは1小節あたり1行です。サウンドトラックは最後のフレームの約0.5秒後まで再生され、そこでカットされます。
outroセクションで終了し、カットがそのテール部分に落ちるようにします。 - ビルド結果:
build.tsはチェックが失敗しても終了コード0を返します。失敗はWARNING:行として、その上に詳細を付けて表示されます。すべての警告を失敗として扱います。出力を読み(frame n/total進捗行をフィルタリング)、修正し、警告がなくなるまで再ビルドします。スチルはout-<name>/fNNNN.jpgとして書き込まれます。これらを読んで確認してください。bun $E/pure.ts <name>で、フレームが単独でも他のフレームの後でも同じようにレンダリングされることを確認します。 sound.pyは numpy を必要とし、常にuv run経由で実行されます。その他のPythonスクリプトは標準ライブラリのみを使用します。
ローンチ動画と製品動画:製品を見せる
背景に大きな文字だけのフィルムは、基本的すぎると受け取られます。効果的なローンチ動画は、製品を画面に表示し続け、奥行きとゆっくりと移動するカメラで1つの視覚的世界を構築し、小さく簡潔なテキストを使用し、ショットを2~6秒(数小節)保持し、ビートごとにカットを切り替えません。ローンチ動画や製品動画では、hello から開始しないでください:
- まずルックを決めます:1つの背景世界、1つのアクセントカラー、1つの書体ファミリー。それをユーザーに2行で伝えてください。
- 6~8ショットのストーリーボードを、実際のもので構成します:製品の実際の画面や出力、実際のコマンド、実際のコード、実際の結果。製品にないインターフェースを描いたり、数字をでっち上げたり、他社のロゴを使用したりしないでください。キャプションは小さなラベル(
txt、等幅、左下)であり、見出しではありません。 - 最初に1つのヒーローショットを構築し、スチルをレンダリングし、実際のローンチ動画と比較してから残りを構築します。
ページは moves.js の後に stage.js を読み込む必要があります(new.ts で作成されたページは読み込みます)。renderFrame を M.frame(); M.reset(X); で開始します。ステージピース(すべて MOVES 上):
| ピース | 説明 |
|---|---|
backdrop(ctx, f, { base, lights[], streak }) | ほぼ黒で、ゆっくりとした光のプールがある背景:すべてが置かれる世界 |
glass(ctx, x, y, w, h, { r, top, bottom, rim, shadow }) | 光の縁と柔らかな影を持つ、暗い角丸パネル |
camera(ctx, cam, draw) で cam(a, b, t, ease) | より大きなシーンの1点を見る:{ x, y, zoom, rot }。マクロクローズで開始し引き出すか、ゆっくりとドリフトさせる |
tilt(ctx, draw, { depth, focus, blur, fog, top, height, width, cx, roll }) | 全画面シーンを傾いた平面として描画し、遠い端は小さく、暗く、ぼやける |
tiles(ctx, f, { cells: [{ draw(ctx, t), label, bg, len, hold }], cols, x, y, w, gap }) | それぞれがムーブをライブ再生する小さな画面のグリッド(タイル内にトランジションを入れないこと) |
term(ctx, f, { x, y, size, lines: [{ text, at, speed, prompt, mark: "ok" or "run", color }] }) | フレーム上でタイプ表示または出現するターミナル行 |
code(ctx, { lines[], x, y, size, lead, hot, shown }) | 控えめなシンタックスカラーと1行のハイライトを持つJavaScript |
wave(ctx, { peaks, x, y, w, h, played, beats[], blocks[] }) | 再生ヘッド、ビートチック、クリップのレーンを備えた、実際のサウンドトラックの形状 |
ステージピースはそれ自体ではサウンドを生成しません。タイムラインに手動でキューを追加します。例:{ f, kind, gain }、種類は type、tick、pop、ding など。
実際の楽曲に合わせる
- ユーザーが音楽ファイルを提供し、使用する権利を持っている必要があります。そのことを伝え、ライセンスが要求するクレジットをエンドカードに記載します。ストリーミングサイトから楽曲をダウンロードしないでください。
uv run $E/beats.py <ファイル>でテンポと各小節の音量を表示します。小節の変更が数小節後にある小節から始まる抜粋を選択し、uv run $E/beats.py <ファイル> --from <秒> --len <秒> --out beats.jsonでbeats.jsonとbeats.jsを書き込みます。- フィルムスクリプトの前に、ページで
beats.jsを読み込みます。すべてのシーン開始をビート番号として記述します:const b = (n) => window.BEATS.frames[n]。トラックが変化する小節(4ビートごと)でカットします。 - タイムラインで
music: { file, from }、sound: "fx"(ムーブ自体のサウンドは音楽の下に残る)または"none"、bpm、sync: [実際のカットのフレーム]を設定します。ビルドはトラックを -16 LUFS でミックスし、その終了をフェードアウトし、各カットにベースのアタックが1フレーム以内にあることをチェックします。チェックを通すためだけにフラッシュを追加しないでください。
EditMax用のモーション(動画エディタのタイムライン上のクリップ)
ユーザーがEditMaxで編集しており、カット内にモーションを追加したい場合に使用します:映像の上に重ねるアニメーションタイトル、イントロ/アウトロカード、ショット間のキネティックタイプビート。同じエージェントセッションで次の2つが必要です:シェル(レンダリング用)とEditMaxのMCPツール(inspect_timeline、import_media など。EditMaxのエージェントタブから設定)。EditMaxのアプリ内チャットはシェルコマンドを拒否するため、このスキルを実行できません。その旨を伝え、Claude Codeなどのエージェントに接続するようユーザーに指示してください。EditMaxツールがないか応答しない場合は、クリップをレンダリングして、ユーザーがドラッグ&ドロップできるファイルパスを提供します。
1. ルートを選択します。 EditMaxの独自ツールでモーションをカバーできる場合は、そちらを優先します。テキストがアプリ内で編集可能なままになるからです:
| 必要なもの | 使用するもの |
|---|---|
| フェード、スライド、ポップ、タイプ、ワイプで表示されるタイトルまたはローワーサード | add_text / add_lower_third + set_text_animation |
| 2つのクリップ間のディゾルブ、プッシュ、スライド、ワイプ、アイリス | set_transition |
| 移動、拡大縮小、フェードするクリップ | set_keyframe、set_clip_transform、set_clip_fades |
| それ以外:スラム、スクランブル、スプリットフラップ、ワードホイール、クロスアウト、ハイライト、パス/ロゴ描画、チャート、カウンター、バースト、マルチムーブシーケンス、フルイントロ/アウトロ | このスキルでクリップをレンダリング |
2. プロジェクトを読み取ります。 inspect_timeline:フレームサイズ、フレームレート、トラック、モーションを配置する時間を確認します。このエンジンは1920x1080、30fpsでレンダリングします。別のサイズの16:9プロジェクトでは、綺麗にスケーリングされます。縦型や正方形のプロジェクトの場合は、クリップがレターボックスになるか、set_clip_transform で拡大・トリミングする必要があること、そしてネイティブのタイトルの方が適している可能性があることを明確に伝えてください。
3. レンダリングします。
- オーバーレイ(映像の上に重なる):
bun $E/new.ts <name> --overlay。$E/title.jsをモデルにします。タイムラインにalpha: trueとsound: "fx"を設定します。ビルドはout-<name>/<name>.mov(ProRes 4444、透過背景)を書き込みます。ルール:フレームをクリア(X.clearRect)し、塗りつぶさない。grain、vignette、mesh、parallax、全画面トランジションを使用しない。タイプの下に柔らかな影やプレートを置き、どの映像でも読みやすくする。タイプをフレームの中央90%以内に収める。最後にフェードアウトする。 - 全画面(イントロ、アウトロ、ショット間のカード):
bun $E/new.ts <name>。$E/hello.jsをモデルにします。ビルドはout-<name>/<name>.mp4を書き込みます。 - サウンド(タイムラインで
sound:を設定):フルサウンドトラック(単独で使用するイントロ/アウトロ)の場合は省略。映像や音声、ユーザーの音楽の上に再生するものには"fx"。無音には"none"。編集に既に音楽がある場合は、ユーザーにどれを希望するか尋ねてください。 - ビルドし、すべての
WARNINGを修正し、先に進む前にスチルを確認します。
4. ファイルを永続的な場所に置きます。 EditMaxはインポートされたファイルを参照し、コピーしません。完成したファイルを out-<name>/ からユーザーが保持するフォルダにコピーし(一度確認し、~/Movies/Motions/ を提案)、そこからインポートします。
5. ユーザーのカットを移動せずに配置します。 insert_clip で時間を指定すると、ユーザーのカットが破壊される可能性があります。その代わりに、import_media でメディアをプロジェクトに追加し、正しいトラックと時間をユーザーに伝えて、自分でドラッグするよう指示します。これにより、ユーザーはカットをそのまま保持できます。モーションの長さと配置を明確に伝えてください。例:「トラック2、00:15:00から2秒間、モーションが配置されます」。