motion-reels は何をしますか?
コード描画モーショングラフィックスで縦型リールを生成します。各リールは1つ以上のブロックで構成されます。ブロックは、見出し、図面、写真、スウォッチを配置し、それぞれがいつ表示されるかを定義する小さなJavaScriptファイルです。すべてのタイミングは、単語のタイムライン(ビートシート(音声なし)または音声テイクの単語タイミング)に基づきます。エンジンはすべてのフレームを独立したブラウザページでレンダリングし、レイアウトをチェックした後、音声や音楽とともに結果を組み立てます。
ページを所有する人物が、ルック、文言、投稿内容を決定します。Claudeは決して投稿しません。
セットアップ(1回限り)
- Python 3.10以上、ffmpeg/ffprobeがPATHに含まれていること。その後、リポジトリの
setup.ps1(Windows)またはsetup.sh(macOS/Linux)を実行するか、pip install -r requirements.txt+python -m playwright install chromium(または環境変数MG_CHROMEに既存のChromeを指定)を実行します。 - 音声のみ:
pip install -r requirements-voice.txt(faster-whisper;初回実行で音声モデルをダウンロード)。 - テスト:
python engine/selftest.pyでSELFTEST OKと表示されることを確認(サンプルでのチェック合格、否定テストの適切な失敗、アラビア語字幕の正しい分割、短いレンダリング+フォールトスキャン+アセンブルが-14 LUFSに達することを確認)。--quickを付けるとレンダリングをスキップします。
エンジンファイル(すべてengine/内):render.py(チェック/ストーリーボード/静止画/動画)、beats.py、words.py、frames.py、faultscan.py、assemble.py、storyboard_sheet.py、make_grain.py、templates/(シェル、reel.js、reel.css、フォント)。
リールごとに1フォルダ
<リール名>/
brief.md リールの目的、1つのアイデア、コールトゥアクション、利用可能な写真
beats.txt 画面上の文言、1行に1ビート(音声がある場合はscript.txt + take.wav)
words.json タイムライン(beats.pyまたはwords.pyから生成)
photos/ 所有者の写真(権利を有するもののみ)
A.js, B.js ... ブロック
build/ チェックJSON、ストーリーボード静止画、ブロック動画、render.jsonファイル、コンタクトシート
<リール名>_REV01_<日付>.mp4 納品物;後続の修正は_REV02として納品、上書きはしない
ゲート(スキップや順序変更不可;各⛔で所有者の承認を得るまで停止)
- ブリーフィング。 1リールにつき1つのアイデア(部屋の公開、素材のストーリー、間取りの変更、ヒント)。写真とハンドルを尋ねます。各写真の内容をリスト化。最初の2秒で視聴者を引き込みます。
- ビートシート(
beats.txt):画面上の正確な文言を順に、1行に1ビート。ビートは短く(2~7語)。リストの各項目は独立した行にし、それぞれが伝わる時間を確保します。⛔ 所有者が文言を承認します。 承認後の文言変更はブロックの再タイミング(および音声がある場合は再録音)を意味します:変更前にその旨を伝えてください。 - タイムライン。
- 音声なし:
python engine/beats.py beats.txt words.json(デフォルトペース 2.6語/秒 + 各ビート後に0.7秒;行末の[+1.5]でさらに長く保持;[pause 2]で新しい要素なしに保持)。 - 音声(任意のTTSまたは録音、ブロックごとに1テイク、編集不可):
python engine/words.py take.wav words.json、その後トランスクリプトとスクリプトを単語レベルで比較。映像はテイクに合わせ、その逆はしません。
- 音声なし:
- ⛔ ストーリーボード静止画。 ブロックのレイアウト(位置のみで十分)を作成し、
checkを実行、その後render.py A.js words.json storyboard build/sbとstoryboard_sheet.py build/storyboard.png build/sbを実行。シートを自己レビュー(後述のリスト)、その後送信します。所有者がレイアウトを承認するまでアニメーションは作成しません。 - モーションの構築。 各要素を名前が登場する単語でキューイング(後述のルール)。変更のたびに
checkを実行します。 - レンダリング。 ブロックごとに
render.py A.js words.json video build/A.mp4 --workers 4(送信するものには決して--draftを使用しない)。すべてのブロックのbuild/A.mp4.render.jsonに"capture": "fresh"と他のブロックと同じブラウザが記載されている必要があります。ブラウザが変更された場合は、すべてのブロックを再レンダリングします。 - フォールトスキャン。 すべてのブロック、次に最終リールに対して
faultscan.py build/A.mp4 --sheet build/A_contact.pngを実行。フラグが立ったすべての時間を確認。黒い帯や1フレームのフラッシュはフォールトです:停止して修正します。暗い写真の内容は問題ありません。 - アセンブル。
assemble.py blocks.json <リール名>_REV01_<日付>.mp4 [--music bed.mp3]。測定されたラウドネスを表示します。ターゲット(デフォルト-14 LUFS)と、トゥルーピークが-1.5 dBTP以下である必要があります。 - ⛔ レビュー。 リールとそのコンタクトシートを送信します。画像のみの修正は変更されたブロックのみを再レンダリングし、次の_REVとして納品します。所有者が投稿します。
モーションルール(理由:落ち着いたプレミアム感を実現;ジッターや読みにくさを排除)
- 文字は決して動かない。 文字はフェードイン(0.28秒)し、その後静止します。テキストにスライド、スケール、バウンス、ドリフトはなし。
- 見出し:
hl(el, text, cue(...))で各単語をタイムライン上の該当単語でフェードイン。見出しが音声を言い換える場合は{stream:true}を使用:単語が左から右に0.1秒間隔で表示されます。 - カメラ: ショットが保持されている間は完全に静止。ショット間のみスライド(0.8秒、イーズインアウト)。9:16では、ショットをワールド内で縦に積み重ね(ショット2はy 1920、ショット3はy 3840)、下にスライド:
SHOTS=[[t,540,960,1],[t2,540,2880,1],...]。 - 写真はゆっくりズームイン可能:
push:[1,1.04]= 一定速度、+6%を超えない(MOTION.PUSH_MAX;エンジンが超過を拒否)。テキストが載った写真へのプッシュはなし。 - 図面は名前が呼ばれるときに描画。 参照線を先に:壁の前に窓とドア、床線の前にそこから測定した高さ。
at('#walls', cue('Walls'), null, 'draw', 0.9);要素あたり0.6~1.0秒。 - 寸法は1つの尺度に統一。 平面図に2つの寸法が表示される場合、両方で同じ尺度を使用。
- 文言に名前があるものにはラベルを付ける。 すべての数字はその単位を記載(「3.2 m」、裸の「3.2」は不可)。
- バウンス、オーバーシュート、エラスティック、スプリングのイージングは一切使用しない。 すべてのタイミングは
reel.jsのMOTIONから取得。変更はそこでのみ行い、ブロック内でリテラルとして記述しない。 - 裸の破線SVG要素を
at()に渡すと、そのキューの前に表示されます:<g>でラップしてください。
タイミングトークン(reel.jsのMOTION)
| トークン | 値 | 用途 |
|---|---|---|
| WORD_FADE / WORD_LEAD | 0.28 s / 0.08 s | 単語フェードイン;単語の少し前から開始 |
| STREAM | 0.10 s | ストリームモードの見出し単語間隔 |
| REVEAL / EXIT | 0.5 s / 0.4 s | ブロック、チップ、スウォッチ、写真のイン/アウト |
draw (at(...,'draw',dur)) | 0.6–1.0 s | 描画要素あたり |
| CAM_SLIDE / CAM_LEAD | 0.8 s / 0.35 s | カメラスライド;ショットのキューの少し前から開始 |
| PUSH_MAX | 0.06 | 写真プッシュの制限 |
| WIPE | 1.2 s | ビフォー/アフターワイプ |
| イージング:表示はイーズアウトキュービック、スライド、描画、ワイプはイーズインアウトキュービック。 |
レイアウトとチェック
- セーフゾーン(
reel.jsのSAFE):テキスト、ラベル、キャプションは1080×1920フレームのx 65–1015、y 269–1248内に収める(上部14%、下部35%、側面6%)。これらの数値はリールに関する広く使用されているサードパーティのガイダンスであり、Metaが公開したオーガニック投稿のルールではありません:投稿されたリールの電話スクリーンショットから再測定し、アプリが変更された場合はsafeZone({...})を設定してください。写真や図面はその範囲を超えても構いませんが、テキストは超えてはいけません。 render.py <ブロック> words.json check build/<ブロック>.check.jsonはすべてのレンダリング前に必須。0.25秒ごとにサンプリングし、個別の要素(テキスト、図面、チップ、スウォッチ、写真、キャプション)間で0.5秒以上のオーバーラップがある場合、およびカメラが静止している間にテキストがセーフゾーン外にある場合に失敗します。失敗した場合はレイアウトを修正し、決してレンダリングしないでください。新しいルールは否定テスト(既知のフォールトを再導入;チェックが失敗する必要がある)で証明してください。- 写真に属するテキストは、写真の要素内(
parent: photoDiv)にscrim()を後ろに置くか、写真を背景(bg:true)として配置します。そうしないと、チェックはテキストが写真の上にあると報告します(これは意図した動作です)。 - チェックはフレームエッジで切り取られたものを検出しません:自己レビューで確認します。
ブロックの構築(reel.js)
boot(()=>{ // すべてのブロックはboot(...)で開始
handle('@yourstudio'); // セーフゾーン上部の小さなハンドル
$('world').insertAdjacentHTML('beforeend', `<div id="h1" class="abs serif" style="left:90px;top:360px;width:900px;font-size:104px"></div>`);
hl('h1', 'A small room, *re-planned.*', cue('A small')); // *...* = アクセント斜体;~word = タイムラインに含まれない
at('#walls', cue('Walls'), null, 'draw', 0.9); // fx: 'fade' | 'pop' | 'wipe' | 'draw'
photo({src:rel('photos/living.jpg'), x:0, y:1920, w:1080, h:1350, a:cue('Light'), push:[1,1.04]});
beforeAfter({before:rel('photos/before.jpg'), after:rel('photos/after.jpg'), x:90, y:2440, w:900, h:900, a:cue('Before'), t:cue('after')});
swatch({name:'Oak', note:'floor', color:'#B08A62', x:90, y:4480, size:270, a:cue('Oak')}); // または src: rel('photos/oak.jpg')
clip({dir:rel('clips/walk'), n:240, x:0, y:5760, w:1080, h:1920, a:cue('Walk'), t0:cue('Walk')}); // frames.pyからのフレーム
captions(1120, 3); // タイムラインからの焼き込みキャプション(音声リール)
SHOTS=[[0,540,960,1],[cue('Light'),540,2880,1]];
});
rel(path)はブロックと同階層のファイルを解決します。cue('words', after)は単語がタイムラインに存在しない場合にエラーをスローするため、タイプミスは確実に検出されます。完全な例:examples/demo/demo.js。
アラビア語およびその他の右から左へのリール
boot()内で最初にrtl()を呼び出します。見出しにはNoto Naskh Arabic、テキストにはIBM Plex Sans Arabic(OFL)を使用。ラテン文字はデフォルトフォントにフォールバックします。アラビア語ではイタリック、文字間隔、大文字は使用不可(文字が結合する必要があるため);アクセント単語は色のみ変更します。- テキストボックスは
left+widthを維持;テキストは右端に揃えます。リストは右から左にレイアウト(最初に名前が挙がったものが一番右)。 - キューはダイアクリティカルマーク、タトウィール、アリフ/ヤー/ター・マルブータの異体字を無視し、アラビア・インド数字を変換します。文字のないキューはエラーをスローします。 ةとهは1文字として扱われるため、2つの単語がそこでのみ異なる場合はより長いフレーズをキューにします。数字の単語は英語でのみ一致:アラビア語では、数字または文字起こしされた単語をキューにします(
words.py --lang arで多言語モデルを選択)。 - キャプションは、 、 ; ؟およびラテン句読点で分割され、それらは表示されません。
- サンプル:
examples/arabic/。
ルック
テーマはengine/templates/reel.css(色はCSS変数、フォント)とフォントファイルのみに存在します。デフォルトの「ウォームミニマル」:紙 #F4EFE8、インク #2B2622、アクセント #9C6B4E、見出しにCormorant Garamond + テキストにJost(いずれもSIL OFL 1.1)。ルックを変更するには:変数を編集するか、OFLフォントをengine/fonts/にライセンスファイルとともに追加し、fonts.cssとwindow.FONTS(reel.js)にリストします。所有者にグリッドのスクリーンショットを3~5枚要求し、それに合わせてください。
自己レビュー(Claudeは、シートやレンダリングを送信する前にこれを行う)
- 各要素はその単語で開始されること。参照線が最初に描画されること。
- 着地後、単語は静止していること。カメラは保持中静止していること。スライド終了時にジャンプがないこと。
- フレームエッジで切り取られたものがないこと。すべてのテキストがセーフゾーン内にあること。下部35%の下に重要なものが隠れていないこと。
- 寸法は1つの尺度に統一されていること。すべての数字に単位があること。スペルが承認済みビートシートと一致していること。
- すべてのブロックでチェックがPASSしていること。
render.jsonにcapture: fresh、すべてのブロックで同じブラウザと記載されていること。 - フォールトスキャンがクリーンであること、またはフラグが立ったすべての時間が調査され説明されていること。
assemble.pyが出力するラウドネスとトゥルーピークがターゲット内であること。
権利とプライバシー
- 所有者の写真、または権利を有する写真のみを使用すること。クライアントの住宅:同意なしに顔、住所、書類を表示しないこと。
- 音楽:所有者がライセンスを持っているトラック、または投稿時にアプリで追加されたプラットフォームのライブラリのみを使用すること。
- 所有者が行っていない主張(価格、スケジュール、賞など)をしないこと。
トラブルシューティング
cue not found: "...":その単語が順序通りwords.jsonに存在しない。ビートシートまたはキューを修正してください。ERR reqfail:file:///...jpg:写真またはクリップのパスが間違っている。rel()を使用し、ファイル名を確認してください。- 以前の描画のゴーストコピーまたは黒い帯:
--draftを使用してレンダリングした。それを付けずに再レンダリングしてください。 - レンダリングが遅い:
--workers 4(最大8)をそのコア数を持つマシンで使用。チェックとストーリーボードは高速、動画は低速です。