モーショングラフィックス
レンダラーがフレームごとにシークするウェブページを書くことで動画を生成します。以下の2つのレーンは同じことを行いますが、記述方法が異なります。
- HyperFrames — 1つのHTMLファイル、CSSで見た目、一時停止したGSAPタイムラインでモーションを制御。
node scripts/hf.mjsでピン留めされたバージョンを実行し、コミットされたロックファイルから一度だけインストールします。Apache 2.0。 - Remotion — Reactコンポーネント。
useCurrentFrame()でフレームを取得し、計算はすべて自分で行います。成熟的で型安全、ただし無料ライセンスは3人まで。
コンポジションコードを書く前に、対応するリファレンスを読み込んでください。フレームワークのルールは推測可能ではなく、多くの失敗は静かに発生します。たとえば、タイムラインが登録されていないHyperFramesファイルは、エラーなく静止画をレンダリングします。
簡単なパス
ユーザーは自分の望む内容をどんな言葉でも説明します。ユーザーは決してブリーフ、仕様、ビートシートを書きません — Claudeがそれをまとめます。
/motion インクウェルの6秒のロゴスティングをパンチの効いた感じで
これが入力のすべてです。内部では以下の処理が行われます:
- コンパイル: リクエストを
brief.jsonに変換 — 再生時間、サイズ、fps、タイミング付きのビート、コピー(原文ママ)、イージング、パレット、assumptionsリスト。すべてのギャップはreferences/brief.mdのデフォルト表で埋められ、何も未定義のままにせず、疑問も挟みません。 - 表示: コンパクトなサマリー(1つのテーブル、約12行)を表示し、同じターンで構築します。承認ゲートはありません。
- 構築: テンプレートからビルドし、検証し、レンダリングし、フレームを確認します。
その後の反復は、再構築ではなくフィールドの編集で行います。「もっとパンチを効かせて」は feel.ease と2つの長さを変更します。kind や motion が変わらない限り、コードは移動しません。
ブリーフがこの処理を安価にする成果物です。コンポジションと同じディスク上に存在するため、修正は差分として管理でき、再レンダリングは1つのコマンドで完了します。
ルートの選択
| シグナル | レーン |
|---|---|
| 新規ビルド、フレームワーク指定なし | HyperFrames |
| 既存のHTML/CSS/GSAP/Lottieを再利用 | HyperFrames |
node_modules なし、ビルドステップ不要 | HyperFrames |
| 3人以上のチーム | HyperFrames(ライセンス) |
| ユーザーがRemotionを指定、またはリポジトリがReact/TS | Remotion |
| デザインシステム、チャートライブラリ、複雑なコンポーネントが必要 | Remotion |
| 既存のRemotionプロジェクトの移植 | Remotion(移植はコストが上回ることが多い) |
ユーザーが両方を指定した場合は、HyperFramesでビルドし、Remotionへの移植はリクエストがあれば可能と一言添えます。同じものを2回フルビルドするのは、比較が求められていない限り無駄な作業です。
ループ
1. ブリーフをコンパイルする
コードを書く前にリクエストを brief.json に変換します。スキーマ、kind/motion の文法、デフォルト表、反復のためのレバーは references/brief.md にあります。
node scripts/brief.mjs brief.json # 検証 + サマリーテーブル
node scripts/brief.mjs brief.json --json # 機械可読な結果
サマリーをユーザーに表示し、ERROR があれば修正してから、同じターンで続行します。警告は判断を要するものです — 受け入れた警告は assumptions に追加します。
ブリーフはスコアも保持します。ビートグリッドがカットを分割するように audio.bpm を選びます。bpm において、1ビートは 60 / bpm 秒であり、すべての beats[].start はそのグリッド上にあるべきです。120 BPM では1ビートが0.5秒になり、ほとんどの短い作品に適しています。brief.mjs が off_beat_grid を報告した場合は、カットを移動するかテンポを変更します。無視しないでください。これが、作品がタイミングに乗っているか、微妙にずれているかの違いです。
完了条件: brief.mjs が終了コード0で終了し、すべてのビートに時間ウィンドウ、種類、モーション、原文ママのコピーがあり、どのカットもビートグリッドから外れていない。
2. スキャフォールド
空のファイルではなく、このスキルのテンプレートから始めてください。これらは両方のコントラクトに対して正しく、2つの失敗しやすいステップ(タイムラインの登録、シーケンスのタイミング)を省きます。
# HyperFrames — プレーンなHTML。ピン留めされたエンジンが一度だけ自身をインストールします(ネットワークが必要)
cp -R assets/hyperframes/. ./my-video/
node scripts/hf.mjs lint ./my-video
# Remotion — Reactプロジェクト、ピン留めされた依存関係を一度だけインストール
cp -R assets/remotion/. ./my-video/
cd my-video && npm ci
# PowerShell
Copy-Item -Recurse -Force assets\hyperframes\* .\my-video\
Copy-Item -Recurse -Force assets\remotion\* .\my-video\
完了条件: node scripts/hf.mjs lint がパスするか、npx remotion compositions src/index.tsx がコンポジションIDをリストする。
3. ビートを構築する
ビートごとに1ファイル。タイムラインはフラットに保ちます — HyperFramesでは絶対時間、Remotionでは <Sequence> フレーム。ビルドによってタイミングが変わった場合はビートシートを更新し、その後総再生時間を再確認します。
各シーンはビートにちなんで名前を付けます。これがチェックがシーンを見つける方法です。HyperFramesのホスト要素には data-brief-beat="<id>" を、Remotionの <Sequence> には name="<id>" を設定します。Remotionのシーケンスがタイミングを計算する場合は、その内部の要素にも data-brief-beat="<id>" を設定し、タイミングを測定できるようにします。
完了条件: シート内のすべてのビートがコード内に存在し、コンポジションの再生時間がシートと一致し、TODO、スタブ、プレースホルダーのコピーがない。
4. モーションを検証する
静止画はタイミングについて嘘をつきます。フレームを確認し、その後レンダリング結果を確認します。
node scripts/check-contract.mjs --dir ./my-video --preflight # 静的、インストール不要
node scripts/check-contract.mjs --dir ./my-video --runtime # ブラウザでの検査も含む
node scripts/hf.mjs check ./my-video # レイアウト、モーション、コントラスト
node scripts/hf.mjs snapshot ./my-video --review # ビートとシームのフレームをPNGで出力
node scripts/hf.mjs keyframes ./my-video --selector "#card" --shot motion.png
npx remotion still src/index.tsx LaunchCard out/f.png --frame=45
npx remotion studio src/index.tsx # 等倍で再生を確認
check-contract.mjs は何もインストールせずに、静かな失敗をキャッチします:未登録のタイムライン、不一致の window.__timelines キー、一時停止されていないタイムライン、欠落したduration、コンポジション内の Math.random()、クランプされていない interpolate。これはプリフライトであり、実際のリンターの代わりにはなりません。
プロジェクト内に brief.json がある場合(または --brief で指定された場合)、ビルドとブリーフを比較します。すべての要件は verified、failed、または unverified になります。unverified は合格ではなく、レンダリングをブロックします。
| コード | ステータス | 意味 |
|---|---|---|
brief_format_mismatch | failed | 幅、高さ、fps、または長さが format と異なる |
brief_scene_count, brief_scene_unmapped | failed | ビートに対応するシーンがない、またはシーンに対応するビートがない。位置は同一性ではない |
brief_timing_mismatch | failed | シーンの開始または終了が、ブリーフの指定(フレーム単位)と一致しない |
brief_copy_missing | failed | 指定された行がシーンにない。コメントや未使用変数はカウントしない |
brief_copy_wrong_scene | failed | その行は別のシーンにのみ存在する |
copy_not_visible | failed | ランタイム:DOM内にあるが、非表示、透明、画面外、クリップ、ぼかし、または他の要素に隠れている |
copy_hold_short | failed | ランタイム:beats[].hold で指定された時間より短い間しか読めない |
first_frame_empty | failed | ランタイム:visibleAtStart が設定されているが、ビートの要素が何も描画されていない |
first_frame_unverified | unverified | ランタイム:最初のフレームにグラフィックのみがある、またはビートにコピーがない。フレームを確認する必要がある |
brief_timing_unverified, brief_format_unverified | unverified | ソースが計算している。ランタイムが確定できる |
brief_copy_dynamic, brief_copy_unverified | unverified | 表示されるかどうかはランタイムの問題。Remotionのコピーはランタイムまで常にこれになる |
マッチングは大文字小文字と空白を無視し、カーリー引用符を折りたたみます。他の句読点は重要であり、"12,480" は 12480 で落ち着くカウントアップとマッチします。要件が unverified のままの場合は、どれかを伝え、受け入れる前にそれらを測定可能にしてください。受け入れる場合は、--allow-unverified "beat.lines.copy[0].visible" のように名前を指定し、そのことをユーザーに報告します。包括的なパターンは決して受け入れず、失敗は受け入れられません。正確なルールと制限については references/validation.md を参照してください。
ドリフトはコードで修正するか、意図的にブリーフを変更して brief.mjs を再実行します — 両者を矛盾させたままにしてはいけません。
フレームをレビューする — すべてのチェックがパスしても、動画が壊れている可能性がある
合格したコントラクトは、タイミングとコピーが一致し、テキストが読めることを示します。パレット、イージング、トランジション、または動画が機能するかどうかについては何も言いません。このスキル自身のテンプレートは、すべてのビートで黒にカットし、カウントアップが数字に到達した瞬間に終了するものでしたが、その際にlint、レイアウト、モーション、コントラスト、クラフトのチェックはすべてパスしました。フレームを見ることだけがそれをキャッチしました。以下の方法でフレームをキャプチャします:
# HyperFrames: ビートフレーム、各カットの前後5フレームのストリップ、および電話用シート
node scripts/motion-render.mjs --brief ./my-video/brief.json --snapshot --no-render
# Remotion: 各カットc(フレーム単位)について、c-6、c、c+6、および各ビートの中間を取得
# (テンプレートは75、210、270でカット)
node scripts/motion-render.mjs --dir ./my-video --no-render \
--stills 37,69,75,81,142,204,210,216,240,264,270,276,315
次に画像を読み、各質問に答えます。コードを修正し、再キャプチャし、再度確認します:
- カットは黒いですか? カットの前後で1フレーム以上空フレームがある場合、前のビートの終了が早すぎて、次のビートが何もないところからフェードインしていることを意味します。終了がカットで終わるように移動します(
start = beat duration − exit duration)。 - 見せ場は十分に保持されていますか? カウントアップの最終的な数字、または行の最後の単語は、画面から消える前に少なくとも1ビート(
60 / bpm秒)静止している必要があります。到達した瞬間に終了すると、読まれることはありません。 - 電話画面で読めますか?
review-phone.jpgを確認するか、フレームを幅360ピクセルで想像してください。1080pでは、ラベルやタグラインが約40ピクセル未満だと通常失敗します。 - ブリーフで指定されたフォントですか? Segoe UIやArialなどのフォールバックフォントが使われている場合、フォントが読み込まれておらず、別のマシンで異なる動画がレンダリングされることを意味します。
--runtimeはfont_fallbackで警告し、レポートは実際に描画されたフォントを記録します。Remotionでは、@fontsource/*(テンプレートの選択)または@remotion/fonts(ローカルファイル用)が必要です。 - ブリーフで指定されたトランジションですか?
feel.transitionは各カットの前後のフレームに存在し、黒へのディップではありません。
完了条件: すべてのビートフレームとすべてのシームストリップを読み、上記の各質問に答え、「出荷チェックリスト」のすべての行が成立している。
5. レンダリング
node scripts/motion-render.mjs --brief ./my-video/brief.json
node scripts/motion-render.mjs --brief ./my-video/brief.json --batch rows.json --no-audio
このラッパーは、ブリーフからレーン、フォーマット、fps、スラッグを読み取り、ブリーフ、静的チェック、ツール、アセット、またはランタイムチェックが停止を要求した場合、レンダリング時間を費やす前に停止します。失敗時は終了コード1、未検証の要件がある場合は終了コード3で終了します。--no-checks は検証をスキップし、そのことをレポートに記録します。次にリンターを実行し、レンダリングし、ffprobeでファイルを測定し、<out>.report.json を書き込みます。
audio.policy が generated の場合、どちらのレーンでも、music.mjs でバックグラウンドミュージックを合成し、動画のみをレンダリングし、1回の ffmpeg 呼び出しで2つをマルチプレックスします — そのためコンポジションはエンジンのオーディオミキサーに依存しません。次に、ファイルにオーディオストリームがあり、無音でないことを確認します。
透明度が必要な場合:--format webm または --format mov — H.264はアルファチャンネルをサポートしません。--no-audio はバックグラウンドミュージックをスキップし、オーディオストリームのないファイルを生成します。GIFはオーディオをサポートできないため、--no-audio を渡さない限り、バックグラウンドミュージックがあるブリーフはGIFでは拒否されます。
--batch を使用すると、1つのブリーフと行ファイルからN個の動画が生成されます:HyperFramesは行をネイティブにレンダリングし、Remotionは行ごとに1つのプロップスファイルを取得します。バッチは動画のみであり、--no-audio を渡さない限り、バックグラウンドミュージックがあるブリーフは拒否されます。
完了条件: ファイルが存在し、レポートが rendered を示し、その notVerified リストが空であるか、ユーザーにその内容を伝えており、最初と最後のフレームが意図的なものである。
静かに壊れるコントラクト
HyperFrames — 4つの必須項目:
- ルート要素に
data-composition-id、data-width、data-height、data-duration、data-start="0"があること。 - すべてのタイミング付き要素に
id+data-start+data-durationがあること。 - GSAPタイムラインが
{ paused: true }で構築されていること。 window.__timelines[<data-composition-id>] = tl— 同じキー。トゥイーンを追加した後で行う。これを忘れると、エラーなく静止画がレンダリングされる。
Remotion — 3つの必須項目:
- エントリーファイルに
registerRoot()、ルートコンポーネントに<Composition>があること。 interpolate(..., { extrapolateLeft: "clamp", extrapolateRight: "clamp" })— 両端を毎回クランプする。- コンポジション内で
Math.random()、Date.now()を使用しない。代わりにrandom(seed)を使用する。
安っぽく見えるかどうかを決める4つのルール
これらは最も頻繁に間違えられるものです。完全なセットとコードは references/motion-craft.md にあります。
- 動いているものだけを動かす。 静止しているものは動かさない。アイドル状態の「呼吸」するようなスケールは、機械生成と読まれる。
- 待たずに重ねる。 要素自体の持続時間の約15〜30%をずらす。前の動きがまだ落ち着いている間に次の動きが始まる。
- テキストにバウンスを使わない。 オーバーシュートは質量のあるオブジェクト(カード、フラップ、キャラクター)にのみ使用する。テキスト、数字、UIラベルは
power3.out/expo.outを使用し、決してオーバーシュートしない。 - 1つの動きに1つのアイデア。
y + opacityは「到着」。回転とスケールを追加すると、単語は移動中に読めなくなる。
また、トランジションは出口であり、フェードアウトの後にフェードインが続くものではありません。そうすると、途中でディップがあるジャンプカットになります。
出荷チェックリスト
-
brief.jsonの検証:タイミングがタイリングされ、最初のフレームが意図的で、最後のホールドが2秒以上ある。 -
check-contract.mjs --runtimeがverifiedを示すか、未検証のIDが名前で受け入れられ、ユーザーに報告されている。 - コピー、数字、ハンドル、URLは記述された通り — ダミーテキストやプレースホルダーなし。
- タイムラインがコンポジションIDの下に登録されている(HyperFrames)、またはコンポジションが
npx remotion compositionsでリストされる(Remotion)。 - Lint/チェックがクリーン。
-
<out>.report.jsonがrenderedを示し、期待されるサイズ、fps、長さ、オーディオがある。 - フレームを検査済み:すべてのビート、すべてのシーム、電話幅 — カットで黒がない、
…
[翻訳のため一部省略]