動画クリップ生成(静止画からクリップ、そしてリップシンク)
概要
完成した静止画は、画像から動画へのモデルによって4~9秒のクリップになります。その後、画面上のキャラクターが喋る場合、リップシンクモデルがその口元をあなたの台詞に合わせて再調整します。結果を直接見ることはできません。抽出されたフレーム(scripts/take_sheet.py と viewing-videos スキルを使用)からすべてのクリップを評価し、最終的な判断は人間に委ねてください。
必要条件: Python 3(標準ライブラリのみ)、ffmpeg/ffprobe。APIキーは環境変数で設定します: GEMINI_API_KEY(Google AI Studioキー。画像スコープのものでも動作し、フォールバックとして GEMINI_IMAGE_API_KEY も読み取られます)をOmni用に、FAL_KEY(falが表示する通りの完全なキー)をリップシンク用に。決して出力しないでください。
モデルを選ぶ
| モデル | アクセス方法 | 使用用途 | 備考 |
|---|---|---|---|
Gemini Omni Flash 1.1 (gemini-omni-1.1-flash) | Google Interactions API, AI Studioキー | デフォルト。イラスト、アニメ、漫画の静止画向け。写実的な静止画でも動作した(1テスト) | 画風と顔を保持。任意の入力アスペクト比(2:3の静止画でも9:16に綺麗に変換されるため、事前の外描きやクロップは不要)。720p、9:16または16:9。同期的、クリップあたり約25~35秒。独自のオーディオを追加するのでミュートすること |
| Veo 3.1 / Veo 3.1 Fast | Gemini API、画像を開始フレームとして | 写実的な静止画 | 当スキルの実行では未使用。使用前に最新ドキュメントを確認すること |
| Seedance 2.5 | ElevenLabs Flows、またはByteDance直接 | 代替の見た目 | 試用時はアカウント承認が必要だった |
| ElevenLabs Flows内の任意のモデル | ElevenLabs API | 1つのキーで全てをカバー | APIアクセスにはProプランが必要(402 paid_plan_required)、およびキーに image_video_generation 権限が必要(ない場合は401)。Omniはアプリ専用 |
リップシンクには、fal上のSync 3(fal-ai/sync-lipsync/v3)がデフォルトです。これはビデオツービデオで動作するため、クリップの動きを保持し、口元のみを再タイミングします。比較と価格: references/providers.md
静止画をアニメーション化する
python3 <this-skill-dir>/scripts/omni_generate.py jobs.json clips/ [--takes 3] [--only S05]
{"base": "この静止画をそのまま動かしてください。同じアートスタイル、パレット、照明、レイアウトで、すべてのキャラクターは静止画と同じ顔、髪、体型、服を保ちます。自然で滑らかな動き。画面上に文字は書かれず、吹き出しもなく、新しい人物は現れず、ワンカットの連続撮影です。",
"jobs": {"S05": {"image": "panels/p05.png",
"prompt": "[0-3s] 赤いジャケットの女性が電話を手に取り、興奮して話す。[3-6s] 緑のパーカーの友達がソファに寄りかかって笑う。ゆっくりカメラが押し込まれる。"}}}
プロンプトの書き方(完全なガイドと例は references/gemini-omni.md):
- 秒単位のタイムライン。各ブラケットが1ビート。クリップの長さはタイムラインとほぼ同じになります。
- 人物は服装で識別し、名前では決して呼ばないでください。モデルは「ダナ」が誰かを知りません。
- 「話す」と指定してください。リップシンク対象のキャラクターの口がすでに動くようにするためです。
- 変更してはいけないことを固定します。「カメラは静止」、「左壁に窓は1つだけ」、「フードの人物はちょうど1人」、「彼女の手はテーブルの上に平らに置いたまま」。各固定方法は
references/failure-modes.mdに実際の失敗例から記録されています。
使用前にすべてのテイクを確認する
python3 <this-skill-dir>/scripts/take_sheet.py sheet review/S09.jpg clips/S09_t1.mp4 clips/S09_t2.mp4 clips/S09_t3.mp4 --times 0.3,1,2,3,4,5,6
シートを読み取ります。行がテイク、列が時間です。プロップの重複、ドアや人物の出現、手が体を横切る、顔がずれる、ジェスチャーが2回行われるなどを探します。問題の正確な秒数を特定してください。修正方法はその位置に依存するためです。
ハードカットはサンプリングされたフレームの間で見逃しやすいです。直接リストアップします。出力がなければワンカット連続撮影です。
ffmpeg -i clips/S09_t1.mp4 -vf "select='gt(scene,0.3)',showinfo" -an -f null - 2>&1 | grep pts_time
クリップをショットに合わせる
編集では、クリップがそのショットを正確に満たし、編集のフレームレートに合っている必要があります。
| 問題 | 修正 |
|---|---|
| 開始が悪い(台詞の前にジェスチャーが行われる、最初の1秒でプロップが重複する) | 開始を遅らせる: --off 1.5 |
| トリミング後に短すぎる | --speed 1.22 で速度を落とす(最大約1.25まで問題なく読み取られる)。最終フレームが保持される |
| 中盤が悪い | ショットを分割する。問題のある部分をフレーム外に保つクロップで最初の台詞を再生し、その後カットバックする |
| 修正不可能(ドアが現れる、第二の人物が入ってくる) | 固定プロンプトで再生成し、2~3テイク、シートで選択する |
lipsync_parts.py normalize clip.mp4 shot.mp4 --frames N --off S --speed K は、オフセット、速度、fps、スケール(カバー&センタークロップ、ストレッチはしない)、最終フレーム保持を1ステップで行い、常に正確にNフレームを返します。
台詞にリップシンク
- クリップをショットに正規化する:
python3 <this-skill-dir>/scripts/lipsync_parts.py normalize clip.mp4 shot.mp4 --frames 132 --off 1.5 --speed 1.22 - ライン境界で分割し、各部分に1人の話者がいるようにする。
shot.jsonを書く: 各行にショット内の開始/終了(秒)と話者を含める。framesは正規化に使用した--framesと同じ値にする。後ろ向き、画面外、または群衆の叫び声の話者には"skip": trueを追加する:{"id": "S05", "video": "shot.mp4", "fps": 24, "frames": 132, "lines": [{"id": "05-1", "audio": "lines/05-1.mp3", "start": 0.72, "end": 2.31, "speaker": "maya"}, {"id": "05-2", "audio": "lines/05-2.mp3", "start": 2.43, "end": 4.10, "speaker": "ido"}]}lipsync_parts.py split shot.json parts/を実行する。パートに加えて、parts/S05_lipsync_jobs.jsonを書き出す。各 非スキップ パートに1エントリで、絶対パス、outはjoinが期待する名前(synced/S05_<k>.mp4)で既に命名され、face: nullが設定される。
- 各パートで話者を指定し、そのjobsファイルに
"face": "x,y@<mid>"として書き込む(midは各エントリにある)。- パートの中間フレームで、話者の顔のピクセルを1つ選ぶ。
- 確認する:
take_sheet.py point parts/S05_1.mp4 check.jpg --frame 30 --xy 410,560を実行し、画像を読む。 - 顔検出器は描かれた顔では信頼性が低いため、目視で選択・確認する。顔が1つだけ表示されている場合のみ
nullのままにする。
- 同期:
lipsync_fal.py --jobs parts/S05_lipsync_jobs.jsonを実行する。予想コストは1秒あたり約$0.13、1パートあたり約70~150秒。6つが並列実行される。既存の出力はスキップされる。顔を修正した後に1パートだけやり直すには、そのパート単独で実行する:lipsync_fal.py parts/S05_1.mp4 parts/S05_1.wav synced/S05_1.mp4 --face 410,560@30 --force(--jobsと--forceを一緒に使うとすべてのパートをやり直すことになるので注意)。 - 結合:
lipsync_parts.py join parts/S05_plan.json synced/ final/S05.mp4を実行する。各パートは正確なフレーム数に強制され、スキップされたパートは元のフレームを保持し、同期ファイルがないパートは報告される(--strictでエラーになる)。総フレーム数もチェックされる。
詳細、モデル選択、失敗事例: references/lipsync.md
コストと時間(2026年10月測定)
| ステップ | 計量単位 | コスト | 時間 |
|---|---|---|---|
| Omniクリップ | 出力動画1秒あたり(6秒クリップで約$0.60。リテイク込みでショットあたり約1.5回呼び出しを見込む) | 約$0.10 | 1呼び出し約30秒、4並列 |
| Sync 3 リップシンク | 出力1秒あたり(パートは通常3~6秒) | 約$0.13 | 1パート約70~150秒、6並列 |
よくある間違い
| 間違い | 結果 | 修正 |
|---|---|---|
| 動画プロンプトでキャラクターを名前で呼ぶ | 別の人物が動く | 服装で説明する |
| Omniのオーディオをそのまま使う | 台詞の下に作り物の声や音楽が流れる | ミュートする(-an) |
| シートを使わずにテイクを使用する | プロップの重複や新しいドアがそのまま使われる | すべてのテイクをシートで確認する |
| 最初の1秒が悪いだけでクリップ全体を再生成する | 別の場所に新しい問題が発生する | オフセットとスローダウンで対応する |
| 2人の話者がいるショット全体をリップシンクする | 1つの口が両方の台詞で動く | ライン境界で分割し、パートごとに1人の話者にする |
| モデルに話者を任せる | 間違った顔が話す | --face X,Y@FRAME で指定する |
| falキーを短縮する | 401エラー | falが表示する通りのキーをそのまま渡す |
| フレーム数を修正せずにパートを結合する | ショットが編集との同期からずれる | lipsync_parts.py join を使用する |
関連スキル
- image-generation: 静止画(キャラクターカード、パネル)。
- elevenlabs-tts: 口が同期する台詞。
- viewing-videos: クリップ確認用のフレーム抽出。
- producing-animated-episodes: これらのクリップを使用するエピソード全体のワークフロー。