name: loop-video description: "Use when コレクションのサムネイル画像からループ動画背景を生成したいとき。Veo 3.1 API で main.png/jpg を元に微細アニメーション付きの 8秒シームレスループ動画を生成。ループ動画、背景動画、loop.mp4、アニメーション背景、動画背景など、静止画を動画化する場面で必ず使用すること"
Overview
Veo 3.1 API を使い、コレクションの main.png/jpg から 8秒のシームレスループ動画を生成します。
生成された loop.mp4 は generate_videos.sh が自動検出し、静止画の代わりに動画背景として使用します。
前提
config/channel/ が存在すること(load_config() でロード可能)。
存在しない場合、ユーザーに確認:
- 新規チャンネル →
/channel-newを案内 - 既存チャンネル(YouTube で既に運営中)→
/channel-importを案内
ループ動画化の有効/無効: skill-config の enabled(default true)で制御する。config/skills/loop-video.yaml::enabled: false のチャンネルでは本スキルは実行不可で、yt-generate-loop-video は fail-loud で停止する(Veo 課金を防ぐ)。サムネが完成済みでループ動画のリターンが Veo コストに見合わないチャンネルは false にする。
Script
| スクリプト | 役割 | 場所 |
|---|---|---|
generate_loop_video.py |
main.png/jpg → 8秒ループ動画 (Veo 3.1) | entry point: uv run yt-generate-loop-video |
Quick Reference
| コマンド | 説明 |
|---|---|
uv run yt-generate-loop-video <collection-path> -y |
指定コレクションでループ動画生成(既存 loop.mp4 は loop-v{n}.mp4 に退避して Veo 再課金) |
uv run yt-generate-loop-video -y |
CWD のコレクションでループ動画生成 |
uv run yt-generate-loop-video <path> --skip-existing -y |
既存 loop.mp4 があれば Veo を叩かず skip して exit 0(冪等再実行・再課金回避) |
uv run yt-generate-loop-video <path> --smooth -y |
post-process 専用: 既存 loop.mp4 に FFmpeg クロスフェード補正のみ適用(Veo を叩かない) |
uv run yt-generate-loop-video <path> --motion-targets "leaves,steam" --static-targets "character,two animals (count remains 2)" -y |
構造化プロンプトで生成(推奨) |
uv run yt-generate-loop-video <path> --prompt "..." -y |
カスタムプロンプトで全文上書き |
再実行時のコスト警告
Veo 3.1 は課金 API(モデルにより 1 本あたり数十円〜数百円)。デフォルト経路は 既存 loop.mp4 を skip せず、loop-v{n}.mp4 に退避してから Veo を再度叩く(=フル再課金)。同じコレクションで yt-generate-loop-video <path> を素で繰り返すと、その回数だけ Veo クレジットが消費される点に注意。
既存 loop.mp4 がある状態での挙動マトリクス
| コマンド | 既存 loop.mp4 の扱い |
Veo 呼び出し | FFmpeg post-process | 課金 |
|---|---|---|---|---|
yt-generate-loop-video <path> -y (素) |
loop-v{n}.mp4 に退避 |
あり(新規生成) | あり | フル再課金 |
yt-generate-loop-video <path> --skip-existing -y |
温存(退避なし) | なし(早期 exit 0) | なし | 0 円 |
yt-generate-loop-video <path> --smooth -y |
クロスフェード補正で in-place 更新 | なし(post-process 専用) | あり | 0 円 |
yt-generate-loop-video <path> --skip-existing --smooth -y |
--smooth が優先(明示アクション > no-op) |
なし | あり | 0 円 |
既存ファイル不在で --smooth を指定した場合は [ERROR] --smooth は既存 loop.mp4 を必要としますが見つかりません で exit 1(fail-loud)。Veo を叩かずに失敗する。
運用ガイドライン
- 冪等再実行のデフォルト: バッチや CI から繰り返し叩く経路では
--skip-existingを必ず付ける。既存があれば 0 円で no-op、無ければ通常生成にフォールバック(ではなく early exit のため事前に生成済みの前提を明示)。 - 継ぎ目だけ直したいとき:
--smoothを 単独 mode として使う。/loop-videoで 1 度生成 → 品質確認 → 継ぎ目が気になる →--smooth再実行、という二段運用にすれば Veo は 1 回しか叩かない。 - 本気で作り直したいとき: 素の
yt-generate-loop-video <path> -yを再実行。既存はloop-v{n}.mp4に自動退避されるため上書き事故はない。ただし 必ず Veo 再課金が発生するので意図的に行うこと。
Instructions
対象コレクション
$ARGUMENTS
引数が指定されている場合、そのコレクションを対象とします。
未指定の場合、collections/planning/ から thumbnail.approved = true かつ loop.mp4 が未生成のコレクションを自動検出します。
前提条件
10-assets/main.pngまたはmain.jpgが存在すること(サムネイル生成済み)- Vertex AI ADC が初期化されていること (
gcloud auth application-default login+set-quota-project)。project_id は ADC quota project から自動解決される(GOOGLE_CLOUD_PROJECTは任意で上書き可)。region はgenerate_loop_video.py側でus-central1を明示固定するためGOOGLE_CLOUD_LOCATIONは不要 gcloud auth application-default loginで ADC が取得済みであること
ステップ
- 対象確認:
10-assets/にmain.pngormain.jpgがあることを確認 - プロンプト検討: シーンに応じた自然な動きを指定
- デフォルトプロンプトは skill-config (
config/skills/loop-video.yamlまたは.claude/skills/loop-video/config.default.yaml) のveo.default_promptを使用 - シーンに合わない場合は
--promptでカスタマイズ
- デフォルトプロンプトは skill-config (
- 生成実行:
uv run yt-generate-loop-video <collection-path> -y- 冪等再実行が必要な場面(CI / バッチ / 「もう生成済みか分からない」ケース)では
--skip-existingを付けて Veo 再課金を避ける
- 冪等再実行が必要な場面(CI / バッチ / 「もう生成済みか分からない」ケース)では
- 品質確認: ユーザーに
loop.mp4を確認してもらう - ループ確認: 継ぎ目が気になる場合は
--smooth単独 mode で再実行(uv run yt-generate-loop-video <path> --smooth -y)。Veo は叩かず FFmpeg クロスフェード補正のみ適用するため 追加課金は発生しない。--smoothは post-process 専用 mode で、生成と post-process は別コマンドとして 2 段に分かれている — 「再生成 + post-process」を 1 コマンドでまとめる API は 意図的に提供していない(再生成は明示的なフル再課金イベントとして扱うため)
構造化プロンプト(推奨)
「動かす対象」と「固定対象」をリストで明示する方式。Veo 3.1 公式推奨のポジティブ表現テンプレートに展開される。default_prompt の単一文字列より事故率が低い(動物の数が変わる / 肩から鳥が湧く等の現象を抑制)。
プロンプト解決の優先順位:
| 順位 | 入力 | 用途 |
|---|---|---|
| 1 | --prompt "..." |
全文上書き(最強、structured 系は無視される) |
| 2 | --motion-targets / --static-targets |
コレクション単位の都度上書き |
| 3 | skill-config の motion_targets / static_targets |
チャンネル単位デフォルト |
| 4 | skill-config の default_prompt |
freeform 上書き(後方互換) |
| 5 | ハードコード DEFAULT_PROMPT |
最終フォールバック |
Veo 3.1 公式推奨:
- ポジティブ表現で書く("do not animate X" より "only Y moves, the rest remains exactly as in the source image")
- count / shape を肯定文で固定:
"two animals (count remains 2)","the character (same posture)"のように数や形を明示 - 微動語彙:
subtle,gentle,slight,barely perceptible,slow sway,living painting - I2V は 50〜100 words が目安(テンプレが既に圧縮されている)
動かす対象(motion_targets)の例:
slow leaves swaying,gentle leaves fluttering(屋外)subtle steam rising from coffee,steam from mug(カフェ/書斎)gentle candle flame flicker,slow candle flicker(室内)slight character breathing(人物のみ動かしたいとき)soft light shifts on surfaces(光のみ)rain streaks on window(雨シーン)slow subtle ripples on water surface(水辺)subtle monitor glow shifts(書斎・モニター)
固定対象(static_targets)の例:
the character (same posture, same expression)— 人物の姿勢/表情固定two animals (count remains 2)— 動物の数固定keyboard,monitor frame,books,desk objects— オブジェクト固定interior structure,background elements— 背景固定dessert plates,cups and saucers— 小物固定
チャンネル別 structured 設定例
config/skills/loop-video.yaml の上書き例:
rjn(lo-fi jazz × rainy night):
veo:
motion_targets:
- "rain streaks on window"
- "subtle steam rising from coffee"
- "soft light shifts on surfaces"
static_targets:
- "the character (same posture, same expression)"
- "dessert plates and cups"
- "interior structure"
deepfocus365(deep focus × deep house):
veo:
motion_targets:
- "subtle monitor screen glow shifts"
- "slow steam from coffee mug"
- "slight character breathing"
static_targets:
- "the character (same posture)"
- "keyboard and monitor frame"
- "books and desk objects"
bobble(cafe ambience):
veo:
motion_targets:
- "subtle steam from coffee"
- "soft light shifts"
static_targets:
- "the character"
- "cups, plates, and counter items"
freeform プロンプトガイドライン(後方互換)
default_prompt を直接書く旧方式。structured が使えない場合のみ。
基本テンプレート:
Static scene with only natural subtle movements: [シーン固有の動き].
No smoke, no magical effects, no particles, no falling objects.
Keep the scene calm and grounded, like a living painting.
避けるべき表現:
- smoke, particles, falling leaves(不自然な追加要素が生成される)
- magical effects(原画にない要素が追加される)
- butterflies, insects(描画品質が低く不自然になる)
- dramatic movement(BGM チャンネルには過度な動き)
サードパーティ IP ガードレール
Veo 3.1 は画像内容からディズニー等の IP を認識すると 生成をブロック する。 PD(パブリックドメイン)の童話キャラでも、ディズニー版に似た見た目だと弾かれる。
| 画像タイプ | 結果 |
|---|---|
| オリジナルファンタジーキャラ | OK |
| PD 騎士・魔法使い(ジャンヌ・ダルク等) | OK |
| ラプンツェル(金髪ロングヘア+塔) | NG(Tangled 誤認) |
| 白雪姫風・人魚姫風 | 要テスト(NG の可能性あり) |
ブロックされた場合の対策:
- プロンプト変更では回避不可(画像自体が判定される)
- 背景のみの画像を別途生成して Veo に渡す
- または静止画背景のまま運用
設定
skill-config (.claude/skills/loop-video/config.default.yaml) で管理。チャンネル側上書きは config/skills/loop-video.yaml:
ループ動画化を停止したいチャンネルは 1 行で無効化できる:
# config/skills/loop-video.yaml
enabled: false # default: true。/loop-video は fail-loud 停止、/videoup は静止背景を使用
veo:
model: "veo-3.1-fast-generate-001"
# structured prompt(推奨)
motion_targets:
- "slow leaves swaying"
- "subtle steam rising from coffee"
static_targets:
- "the character"
- "two animals (count remains 2)"
# freeform プロンプト(後方互換、structured 未指定時のみ使用)
default_prompt: |
Static scene with only natural subtle movements...
duration_seconds: 8
crossfade_sec: 0.5
| 項目 | 既定 | 説明 |
|---|---|---|
enabled |
true |
このチャンネルでループ動画化を行うか。false で CLI は fail-loud 停止(Veo 課金防止)、/videoup は静止画背景にフォールバック |
veo.model |
veo-3.1-fast-generate-001 | Veo API モデル(選択肢: veo-3.1-fast-generate-001 / veo-3.1-generate-001 / veo-3.1-lite-generate-preview) |
veo.motion_targets |
[] |
動かす対象のリスト。structured prompt に展開される |
veo.static_targets |
[] |
固定対象のリスト。count や shape を肯定文で書く |
veo.prompt_template |
公式 5 要素テンプレ | {motion_clause} / {static_clause} / {base_rules} をプレースホルダに持つ |
veo.base_rules |
アンビエンス固定文 | {base_rules} に差し込まれる共通追加ルール |
veo.default_prompt |
汎用微動プロンプト | structured 未使用時の freeform プロンプト |
veo.duration_seconds |
8 | 生成尺(Veo API 制約で 8 秒固定) |
veo.crossfade_sec |
0.5 | FFmpeg ループ補正のクロスフェード秒数 |
compression.enabled |
true |
Veo 出力直後に libx264 で再エンコードして容量削減(Issue #175)。false で完全 skip |
compression.crf |
22 | H.264 CRF。22 で約 40% 削減、24 なら約 55% 削減(攻める設定) |
compression.preset |
slow | libx264 preset |
Integration
loop.mp4 が 10-assets/ に存在すると、generate_videos.sh v11.0 が自動検出し、
静止画の代わりにループ動画を背景として使用します(24fps、CRF 20)。
パイプラインは Veo 生成 → strip_audio → CRF 圧縮(compression.enabled=true のとき)。
--smooth 経路でも同じ crf/preset が適用されるため、/loop-video → --smooth のいずれの順でも
最終的な loop.mp4 ビットレートは設定値(既定 CRF 22 ≒ 3〜4 Mbps)に揃う。
中断 (Ctrl+C) 時の挙動
yt-generate-loop-video の Veo 生成中に Ctrl+C を送ったときの挙動は、運用上以下の通り厳密に決まっている。「中断 = キャンセル = 無料」ではないことに注意。
現状のコード挙動 (cancel API 未利用)
| フェーズ | Ctrl+C の効き | API 側 operation | クレジット消費 | 再開可否 |
|---|---|---|---|---|
client.models.generate_videos(...) 送信中(submit 中) |
ローカルプロセスは即停止 | submit が成立していれば API 側で開始済み | submit 後ならフル課金 | 不可(operation_name を持たない) |
| polling 中(submit 済み、生成待ち) | ローカルプロセスは即停止 | 継続実行される(cancel されない) | フル課金(中断しても止まらない) | 可(<CHANNEL_DIR>/tmp/veo-operations/ に保存された operation_name を次回実行で resume) |
polling 中の operations.get 一時障害 |
例外捕捉、state を保持 | API 側継続 | フル課金 | 可 |
polling 中の operations.get で失効(404) |
state を削除 | (失効済み) | 課金済み bytes は取りこぼし | 不可 |
ポイント:
- Veo API には
operations.cancel相当が現状未実装 (client.operations.cancelは未提供)。本スキルの実装 (utils/veo_generator.py) もKeyboardInterruptを捕捉してメッセージ表示と state 保存だけを行い、cancel API を呼んでいない。Ctrl+C はあくまでローカルプロセスを止めるだけで、API 側のジョブとクレジット消費は止められない。 - submit が成功した時点で課金は確定する想定で運用する。中断は「節約」にはならず、せいぜい「次回の二重課金を resume で防ぐ」効果しかない。
- 中断 → 即再実行すれば、保存済み operation_name から resume して既課金分を回収できる(loop.mp4 を取り出せる)。再課金は発生しない。
- 完全に捨てる場合でも、state ファイル (
<CHANNEL_DIR>/tmp/veo-operations/<output-hash>.json) を手動削除しない限り、次回yt-generate-loop-video実行時に resume を試みる点に注意(不要なら削除)。
運用ガイドライン
- submit 成功後の Ctrl+C は「無料化」ではなく「次回 resume の予約」と思え。クレジット節約目的で中断してはいけない。
- 真に止めたい(誤プロンプトでの submit など)場合でも、submit が通った後は API 側のジョブを止める手段が(現状コードからは)ない。submit 前に prompt を確認すること。
- ループ動画化そのものを停止したいチャンネルは
config/skills/loop-video.yaml::enabled: falseで CLI ごと無効化する(yt-generate-loop-videoが fail-loud で停止し、submit 自体が走らない)。 - 将来 Veo API が
operations.cancelを公開し、本スキルの実装が対応した場合は、Ctrl+C で API 側 operation も cancel して以降のクレジット消費を停止する挙動に変わる予定。現状はその段階に到達していないため、上記の「中断してもクレジットは消費される」前提で運用する。
state ファイルの場所
- パス:
<CHANNEL_DIR>/tmp/veo-operations/<output-hash>.json - 中身:
{ "operation_name": "operations/...", "model": "veo-3.1-fast-generate-001", "output_path": "..." } - 再開不要なら手動削除可(次回実行は新規 submit になる)
長時間処理の取り扱い
yt-generate-loop-video は Veo 3.1 API を同期ポーリングするため 30〜90 秒 程度(モデルとリージョン次第)かかる。必ず Bash ツールを run_in_background=true で起動する。これによりユーザーは処理中も同じセッションで質問できる(Claude Code は完了時に自動でメッセージ通知するため、sleep ループや until での自前ポーリングは禁止)。
spawn 例:
uv run yt-generate-loop-video <collection-path> -y > /tmp/loop-video-$(date +%s).log 2>&1
これを Bash run_in_background=true で投げ、spawn 直後に次のメッセージを返す:
⏳ Veo 3.1 でループ動画を生成中(推定 30〜90 秒)。完了まで他の質問にもお答えできます。 ログ: /tmp/loop-video-*.log
cmux 環境下($CMUX_WORKSPACE_ID あり)であれば補助で cmux set-status "loop-video" "running" --icon "hourglass" --color "#f59e0b"、完了で cmux clear-status "loop-video" + cmux notify --title "loop-video 完了" を呼ぶ(非 cmux 環境では skip)。
完了通知が届いたらログ末尾から結果サマリー(10-assets/loop.mp4 のパス)をユーザーへ返す。--smooth 再実行時も同じパターンで起動する。IP ガードレールでブロックされた場合のエラーメッセージはログから抜き出して報告する。
障害時ガイダンス
| 状況 | 兆候 | 対処 |
|---|---|---|
| GCP ADC 未取得/失効 | ConfigError / ADC 認証エラー |
gcloud auth application-default login(必要なら set-quota-project)を再実行 |
| Vertex AI rate | HTTP 429 | 時間を置いて再実行。並列実行を避け順次処理する |
| API 障害 / サービス停止 | HTTP 503 / タイムアウト | Google Cloud(Vertex AI)のステータスを確認し、時間を置いて再実行 |
| 生成の途中失敗(課金済み) | プロセス中断・IP ガードレールでブロック | コストは発生済み。10-assets/loop.mp4 の生成有無を確認し、未生成ならコマンドを再実行 |
Next Step
ループ動画生成後:
→ /videoup <collection-path> でマスター動画を生成