make-ai-short — 生成系ショート動画(エンドツーエンド) は何をしますか?
ピクセルはビデオモデルから生成され、Remotionはクリップとキャプションの合成のみを行います。ブルーマン #1「The Door」で実証済み。/make-short(100% TSX)の兄弟版で、同じ規律に加え、TSXが決して必要としなかった3つのゲートがあります:生成前にコストを明示、character.jsonからキャラクターの固定、終端フレームの条件付けによるループの固定。
リポジトリルートから実行します。.env に FAL_KEY(クリップ+画像)と ELEVENLABS_API_KEY(音声)が必要です。
3つの鉄則
- テキストからキャラクターを2度生成してはいけません。
ai-shorts/<char>/character.json+character.pngが固定された参照です。キャラクターが必要な静止画はすべてgen_image.py --ref character.pngで生成し、すべてのクリップは既にキャラクターを含むフレームからの画像→動画変換です。キャラクターシートがまだ存在しない場合、1枚作成すること(そしてユーザーの確認を得ること)は、他の何よりも先に行う独立したステップです。 - コストを支払う前に明示してください。 引用ではなく計算:Seedance 1080p 音声オフ =
h×w×24×dur/1024トークン × $1.2/1M ≈ $0.0583/秒(fal価格API 2026-07-14検証 — 完全なテーブル+Wan/Veo-Liteの注意点は ai-shorts/IDEAS.md を参照)。常にgenerate_audio:false— 音声はElevenLabs、効果音は当社のSFXパイプラインです。 - ループは運任せではなく、制約によって実現します。 ショット1の真のフレーム0(プロンプト画像ではなくクリップから抽出)がループのターゲットです。最終ショットはこれを
end_image_url(Seedance/Kling)として渡し、最終的な合成は最後の約10フレームにわたってその正確な静止画に落ち着きます(remotion/src/shots/ai-1/Ai1Door.tsx内のLoopSettleパターン)。
アーティファクト契約
ai-shorts/<name>/
script.md · beats.json — /make-short と同じ契約(実際のワードタイムを持つ vo[])
character.json — 固定された参照(+ video_model ブロック = 選択されたモデル)
shots/NN-slug.png — ショットごとの開始フレーム(gen_image.py --ref character.png [--ref scene])
shots/NN-slug.json — gen_clip サイドカー(モデル、ペイロード、リクエストID)
shots/upload-urls.json — fal ストレージURL(アップロードは再ロール間で再利用可能)
voice/ · sfx-plan.json · output/ [voice/output は gitignore]
remotion/src/shots/ai-N/ — 合成 + vo.gen.ts
media/projects/<name>/ — staticFile() 用のクリップコピー(コミット対象 — ピクセルは再現不能;
shots/*.mp4 ワーキングコピーは gitignore)
実行手順(ピクセルより音声が先)
- スクリプト — script.md + beats.json。フックはフレーム0で構成。テーゼはループを欲するものに(ブルーマン #1:「到着は神話である」→ 終わり=同じドアに向かって歩く)。約80~100ワード、2.5~2.7 wpsのウィンドウ。エンゲージメントCTAのアウトロは一切禁止 — 見返りで終わり、ループがエンディングです。
- 音声が先 —
python tools/gen_voice.py --beats ... --emit-ts remotion/src/shots/ai-N/vo.gen.ts(音声とモデルは.envから、またはシリーズがナレーター(例:eleven_v3タグ付きの GeorgeJBFqnCBsd6RMkjVDRZzb)を必要とする場合、beats.json の"voice"ブロックから。クリップが生成される前に、make-short ステージ4と同様に LINE CHECK フラグに従って処理)。実際のワードタイムがショットウィンドウを決定。Seedanceは整数の4〜12秒の長さを取るため、ショットの長さはウィンドウから切り上げます。ダイアログ(2人以上のキャラクター): beats.json の"cast"は各話者をそのcharacter.json("voice"ブロックを持つ)にマッピングし、各VOラインは"who"で話者を指定します。--pack 0.3は、推定ウィンドウに詰め込む代わりに、実際の長さからラインを端から端まで配置します。各キャラクターの音声はその character.json に1つだけ存在し、他にはありません。ダイアログ中の口の動き: ビデオモデルは誰が話しているかを認識しません。聞き手に話させ、話し手を凍らせ、次の文にまたがって笑い声を引き延ばします。クリップの後、python tools/mouth_sync.py ai-shorts/<name>を実行します(beats.json の"shots"は各クリップにグローバルな開始時間と長さを与えます。各 character.json の"mouth"ブロックは、サイド、半径、ゾーン、スタイル、keep_aboveを与えます)。モデルの口を消去し、各キャラクター独自の口を音声トラックのラウドネスから描画します。同様にQAします:両方の顔を0.5秒ごとに、誰が話しているかと照合します。1つのライン内でのアクションの変更(笑ってから話す)は2つのクリップで、2つ目は最初のクリップのカット時点のフレームから開始します。次に、口を動かした各クリップをフレームごとにスキャンします:クリップの中央値の4倍を超えるスパイクはカット、高速なジェスチャー、またはゴーストフィーチャーを伴う2フレームのモーフです。モーフフレームをそのショットの"hold"にリストし、再実行して、ジャンプがクリーンなカットとして再生されるようにします。 - 開始フレーム — ショットごとに1つ、
python tools/gen_image.py --model pro --aspect 9:16 --ref character.png(+ ロケーションや小道具がショット間で一致する必要がある場合、ショット1のシーン静止画を2番目の参照として追加)。すべてのPNGをビデオ呼び出しの前にQA — ここでのドリフト修正は安価です。- 手、服の詳細、プロポーションを確認 — 再ロール時にはプロンプトで明示的に名前を付けてください。
- ジオメトリは歩行可能でなければなりません(ショット6の教訓、コスト再ロール2回):終端フレームピンがある場合、開始フレームはモーションが歩行前進によって終了状態に到達できるようにする必要があります。終了フレームのキャラクターが開始時よりもカメラに近い場合、モデルは代わりにクローンを生成します。開始時は終了状態よりもキャラクターを大きく/近く配置し、前方への動きでキャラクターが縮小して終了状態に入るようにします。
- クリップ — 導出された総コストを明示し、次に各ショットに対して
python tools/gen_clip.pyを並行して実行(--set image_url=<fal storage url> --set 'duration="N"' --set resolution=1080p --set generate_audio=false、最終ショットは--set end_image_url=<frame0 url>)。参照はbakeoff_clip.upload_refを使用して一度アップロードし、URLを shots/upload-urls.json に保持します。上記のfalフォームはこのチェックアウトのデフォルトです(CLIP_PROVIDER=fal、FAL_VIDEO_MODELは .env)。代わりに kie.ai の場合(--provider kie): フィールド名は fal ではなく kie モデルに従います。bytedance/seedance-2の場合:--aspect none --set first_frame_url=@ai-shorts/<name>/shots/NN-slug.png --set duration=N --set resolution=1080p --set generate_audio=false、最終ショットは--set last_frame_url=@<frame0.png>。@pathはローカルファイルをアップロードします(kieは24時間保持するため、upload-urls.json の再利用は1日以内のみ有効)。コスト:kie.ai/pricing、falのテーブルではありません。実行時に使用クレジットが表示されます。bakeoff_clip.pyは依然として fal 専用です。すべてのクリップを以下のプロンプトで生成:正確なキャラクターのフレーズ + 「ショット全体を通してシーン内に正確に1人のキャラクター」(クローンが#1の障害) + カメラ移動 + 「モーフィングなし」 + スタイルライン。 - クリップQA、フレームごと — クリップごとに開始/中間/終了のストリップ + 最終クリップの最後のフレームとループターゲットの並べ替え。失敗したものは再ロール(再ロールコストを明示)。5秒の1080p再ロールは約$0.29 — クローンを出荷するより安価です。
- 合成 — 1つのタイムライン上の
OffthreadVideoのシーケンス、約8フレームのクロスフェードアンダーラップ(クリップはウィンドウより約1秒長い — これが余裕)、ルートにCaptions+ProgressBar、末尾にLoopSettleディゾルブ。最終クリップをmedia/projects/<name>/にコピー。npm run gen、frames.mjs を境界とハイライトで実行、すべてのPNGを読み取り、その後 render-all --scale=1 でレンダリングし voice.wav をマルチプレクサ。 - SFX — /suggest-sfx フロー。ギャップVOキャリブレーション(ブルーマン #1): VOギャップ内のキューはマスキングもダッキングも不要 — 密なナレーションテーブルより約7~8 dB低くする必要があります(ギャップのウィッシュ音:-9~-13、-3~-5ではありません)。volumedetect RMSを音声平均参照と比較して検証。ヒーローレイヤーは一度だけ音声レベルに触れる可能性があります。ミックス試聴はユーザーの耳を待ちます(ハードゲート)。
- 保存して戻す — catalog used_in、beats.json の実際のタイミング(gen_voice が書き込み)、サイドカーをコミット、コスト元帳を最終レポートに記録。
完了条件 =
すべてのクリップのすべてのフレームでキャラクターが一貫している(フレームごとにQA済み) · ループが検証されている(最終フレーム vs フレーム0の並べ替え) · 実際のワードタイムでキャプション · 各支出前にコストを明示し、合計を後で表示 · SFX試聴レンダリング済み、ユーザーの耳を待つ · CTAアウトロなし。· tools/check_text.py が話し言葉と画面上のテキストでクリーン · tools/export_phone.py で電話用コピー作成 · LESSONS.md に日付入りのエントリ(教訓が動作を変更する場合、一般的な用語でスキルも更新)。