制作ノート

MiniMax H3 でMVを量産する

アーキテクチャと運用の全手順

このサイトのアニメMVは、1曲あたり $0.29〜$1.00 のGPU課金で作っています。60秒・10〜23カットで、生成にかかる時間は23〜41分。この記事はその仕組みを、同じことをやりたい人が辿れる粒度で公開するものです。

ふんわりした概要ではなく、使っているモデル・pinしたコミット・実際のコマンド・実測した数字をそのまま書きます。ここに書いていないものは使っていません。

先に断っておきます。ここに出てくる mvpipe は自作の非公開パイプラインで、インストールして使えるパッケージではありません。コマンドをそのまま打っても動きません。載せている理由は、各ステップが何を入力に取り何を出すかを具体的に示すためです。既製品はComfyUI・rclone・ffmpeg・RunPod APIで、mvpipe がやっているのはそれらの呼び出しと、カット表の管理・フレーム格子への丸め・検証・アセンブルです。同じ構造を組めば同じ結果が出ます。何を作ればよいかはステップごとの説明で分かるように書きました。

00結論から — なぜ「1カットずつ」なのか

最初に、この設計を決定づけた事実を書きます。H3の生成時間は、クリップの長さに対して二乗以上で悪化します。

3秒のカット(73フレーム)は約3分で終わります。ところが8秒(192フレーム)は75分かけても終わりません。実際に3回連続でタイムアウト(per-jobの上限4500秒)し、14.4秒(345フレーム)でも同じでした。この検証で $3.18 を使い、使える映像は1秒も得られていません。参照画像の枚数を5枚から3枚に減らしても変わらなかったので、参照数が主因という仮説も棄却しています。

フレーム数に対する生成時間のグラフ。フレーム数が増えるほど時間が急激に伸び、192フレーム以降はタイムアウトに達する
生成時間 vs フレーム数(RTX 4090実測)。右端はタイムアウトで完走していない

したがって「1回の生成で複数カットをまとめて作る」方式は、現行スタックでは成立しません。短いカットを1本ずつ生成し、編集で繋ぐ——これが唯一の実用解です。以降の設計はすべてここから導かれています。

01登場人物と役割

人間ひとりとAI2種、それに使い捨てのGPUで回しています。誰が何を持つかを先に書きます。

担い手実体持ち場
micha人間(筆者)曲・歌詞・世界観の設計。GPT-Image2 でのキービジュアル制作とカット設計。すべての承認と検収
Claude CodeAnthropic Claude のCLI(WSL2上)michaとの対話でカット割りを設計。パイプライン本体の実装。ジョブ構築・投入・回収・アセンブルの実行
Codex CLIOpenAI Codex CLI 0.147.0(WSL2上)不足したキービジュアルの生成のみ。imagegen 組み込みツールを使う
RunPod PodRTX 4090 24GB(community / secure)動画生成だけ。終わったら自己削除する使い捨て

Codex CLI を画像生成に使っているのは、ChatGPTプランの範囲内で追加コストが $0 だからです。起動は以下の形で、設定既定のcodex系モデルはChatGPTアカウントで拒否されるためモデルを明示指定します。参照画像はカレントディレクトリに置きます。

codex exec --sandbox workspace-write -c model="gpt-5.4"

スループットは1枚あたり8〜10分・順次。24枚の生成と承認を1曲で回した実績があります。ただし現状の主力は micha が GPT-Image2 で作るキービジュアルで、パイプライン内の Codex CLI は不足分を埋める役です。

02アーキテクチャ

設計の原則は5つです。これは好みではなく、事故と課金から決まったものです。

  1. ローカルが正、GPU Podは使い捨て、状態はすべてGoogle Drive。Podは落ちても構わない前提で組む
  2. 音楽と映像の同期は編集レイヤーで取る。H3が出力する音声は捨て、Suno原音源(48kHz)を敷き直す
  3. 口パク(ref2va)は歌唱・タイトル回収カットに限定。他は高速な fl2va を使う
  4. 歌詞を焼き込んだキービジュアルは、その歌詞が実際に歌われる時だけ使う
  5. GPU課金の前に必ず人間が承認する。kvレビュー承認と preflight 承認の2つを越えて初めて launch(=課金開始)に進む

構成要素は4つです。ローカルのPythonパイプライン、Drive上のキュー、使い捨てPod、そしてCodex CLIのキービジュアル生成ライン。

mvpipeの三層構成図。ローカル(WSL2)がジョブをGoogle Driveへpushし、RunPodの使い捨てGPUがそれを取り込んで生成、結果をDriveへ書き出し、ローカルがpullで回収する。キービジュアルは別系統で、michaがGPT-Image2で作成し不足分をCodex CLIが生成、michaの承認を経てrefsへ入る。
クリックで全画面表示。三層構成。ローカルが指令と検証を持ち、Driveが状態を持ち、Podは計算だけを持つ。Podはいつ消えてもよい前提で組んでいる
場所中身
pipeline/mvpipe/自作の制御パイプライン(非公開)。Python 3.13 / uv / WSL2、テスト180本以上(pytest, ruff)。下の表が全モジュールと担当範囲
gdrive:H3/MiniMax-H3-Native-Colab/ジョブキューと成果物。mv/<song>/ 配下に jobs / refs / results / state / out
worker/Pod上で動くワーカー。起動時にDriveへアップロードされる
songs/<song>/曲ごとの作業場。音源・歌詞・解析結果・カット表・参照画像・完成品

mvpipe の中身は以下の単位に分かれています。自分で組む場合、必要な機能はこの14個です。

モジュール担当
config.py既定値(480×864・24fps・20steps・res_multistep/simple・Ref2VA参照上限)
analyze.pyビート/ダウンビート検出 + Whisper歌詞アライメント
framing.pyH3のフレーム制約 17k+5 への丸めとプロファイル検証
shotlist.pyカット表 shotlist.yaml の生成と検証
keyvisuals.py手動生成用のkvプロンプト集を出力
codexkv.pyCodex CLI を叩いてkv生成 + kv_review.html を出力
jobs.pyカット表とプロンプトからジョブJSON。高速化レーン選択・音声セグメント抽出・シード固定・参照検証
verify.pypreflight(起動前チェック表)と results(ffprobe全数照合)
sync.pyrclone で Drive キューを操作(push / pull / status)
runpod.pyPod の起動・停止・状態(GraphQL)。CUDA/RAM要件のピン留めと残高表示
retake.pytakeカウンタを進めて Drive 上で再キュー
review.pyクリップ格子のレビュー画面を出力
post.pyトリム→結合→Suno原音源→ASS歌詞→LUT/グレイン→出力
capcut_draft.pyCapCutドラフト書き出し(手編集する場合)

Drive をキューにしているのは、Podが消えても状態が残るからです。Podのテレメトリ(CPU/GPU使用率)は信用しません。community Podでは実態と合わないことがあるためで、健全性の判断は results/failed/ にファイルが出現したかどうかだけで行います。

03環境を固定する

再現するうえで最も重要なのがここです。すべてコミットハッシュでpinしています。H3周辺はツールの更新が速く、pinを外すと同じ入力から同じ出力が得られなくなります。

対象固定値
ComfyUI9a9fdb10
KJNodes195d312
Spectrum4b9a7d11(v0.2.5)
SageAttentionthu-ml/SageAttention @ d1a57a546c3d395b1ffcbeecc66d81db76f3b4b5(このコミットからビルドしたwheelをDriveにキャッシュして再利用)
PyTorch2.11.0+cu128
ベースイメージrunpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404
モデルComfy-Org/MiniMax-H3 @ 0543966fbdce5ba05709a8f2031c94bdba629b4a
Motion Context ノードf80e36bc1d7887a143b12e6645313fd6b9cd2aee(0.3.1)

重みは HuggingFace の Comfy-Org/MiniMax-H3 から、上のリビジョンを指定して hf_hub_download で取得します。ワーカーは取得後にサイズと sha256 を照合します。使うファイルは5つです。

ファイル用途sha256(先頭16桁)
diffusion_models/minimax_h3_fl2va_pruned_int8_convrot.safetensorsfl2va / t2va 本体e889202c41dafb67
diffusion_models/minimax_h3_ref2va_pruned_int8_convrot.safetensorsref2va 本体9255f52b6677845a
text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensorsテキストエンコーダ35a88d51044231fe
vae/minimax_h3_video_vae_fp16.safetensors映像VAE7c1f131492e7edda
vae/minimax_h3_audio_vae_fp32.safetensors音声VAE8e505d95dd1561d4

生成の既定値は 480×864(縦9:16)・24fps・20 steps・res_multistep/simple。Pod選定では CUDA>=12.8 かつ RAM>=45GB をピン留めしています。

H3には2つの厳しい制約があります。どちらも設計時に効いてきます。

  • フレーム数は 17k+5 の値しか取れません。73f=3.04秒、158f=6.58秒、192f=8.0秒といった飛び飛びの値です。学習レンジは124〜362フレーム。任意の秒数を指定できないので、カット尺は必ずこの格子へ丸めることになります
  • 解像度は32の倍数。参照キービジュアルは、キャンバス比へ事前にセンタークロップしておく必要があります。fl2va は非等比ストレッチをかけてくるので、比が合っていないと絵が歪みます

ほかに、Ref2VA へ渡す音声は15秒以下、ジョブ単位のタイムアウトは4500秒です。

04生成モードの使い分け

3つのモードがあり、カットごとに選びます。速度が3倍違うので、この選択がコストを決めます。

モード使う場面参照高速化速度(4090実測)
ref2va口パクが要るカット(歌唱・タイトル回収)画像≤9 + 動画≤3 + 音声1Sage + Spectrum 約2.55秒/フレーム
fl2va歌っていないカットfirst_frame = キービジュアルSage + EasyCache 約0.84秒/フレーム
t2vaテキストのみ(冒頭など)なしSage + EasyCache fl2va と同等

ref2va は口パクが合いますが fl2va の約3倍遅いので、歌っているカットとタイトルを回収するカットだけに使います。全編を ref2va で作ると $1.00 かかるところが、混成にすると $0.29〜$0.51 に下がります。これが冒頭の価格差の正体です。

高速化の組み合わせもモードで変えています。ref2va は Sage + Spectrum、fl2va と t2va は Sage + EasyCache。A/Bで測った結果、ref2va に EasyCache を使うと逆に遅くなりました(73フレームで185.8秒 → 195.5秒)。

051本作るまで — 10ステップ

ここからが本題です。実際のコマンドを添えて、順に追います。<s> は曲のスラッグ、作業ディレクトリは pipeline/ です。

ステップやること担い手
01素材受領Suno の mp3 と歌詞を songs/<song>/ へ置くmicha
02解析ビート・ダウンビート検出と歌詞アライメントローカル
03設計窓・カット割り・モード選択・kv対応表micha + Claude
04キービジュアルmicha が GPT-Image2 で作成 + 不足分を Codex CLI が生成 → 承認micha + Codex CLI
05ジョブ構築shotlist と prompts からジョブJSONを組むローカル
06投入Drive へ push → RunPod の Pod を起動ローカル
07生成Pod がキューをドレインするPod
08回収・検証pull して ffprobe で全数照合、必要ならリテイクローカル + micha
09アセンブルトリム・結合・Suno原音源・歌詞デザインローカル
10納品out/ へ出力ローカル

解析(ステップ2)

uv run python -m mvpipe.analyze ../songs/<s>/audio.mp3 \
  --lyrics ../songs/<s>/lyrics.txt --language ja --no-vad

ここが後工程すべての土台になります。やっていることは3つです。

1. ビート検出。librosa で音源を読み、beat.beat_track でBPMとビート位置を出します。ダウンビート(小節頭)は先頭ビートから4拍ごとに取ります。拍子解析まではしていません——4拍子前提の割り切りです。

2. 歌詞の書き起こし。faster-whisper の medium モデルを CPU・int8 で回し、単語単位のタイムスタンプを取ります。--no-vad が必須なのはここで、音声区間検出を有効にすると音圧の高いサビを「非音声」と誤判定して丸ごと落とします。伴奏が厚い曲ほど落ちるので、既定で切っています。

3. 歌詞ファイルとの突き合わせ。Whisperの書き起こしは誤字を含むので、そのままでは使えません。手元の正しい歌詞と書き起こしを文字単位で照合し(difflib.SequenceMatcher)、一致した文字の時刻を正しい歌詞側へ移します。一致しなかった文字は、その行の中で線形補間して埋めます。

この方式の利点は、表示する文字列が常に手元の歌詞であることです。Whisperが「未定稿」を「みていこう」と書き起こしても、時刻だけを借りて表記は正しいまま保てます。

出力: analysis.json

1曲ぶんの構造がこの1ファイルに入ります。実際の出力から抜粋します。

{
  "schema_version": 1,
  "duration_s": 248.36,
  "bpm": 130.81,
  "beats":     [0.0, 0.46, 0.92, ...],   // 537個
  "downbeats": [0.0, 1.86, 3.71, ...],   // 135個(4拍ごと)
  "sections": [
    { "label": "block1", "start": 0.0,   "end": 22.74, "vocal": true },
    { "label": "block2", "start": 22.74, "end": 36.96, "vocal": true },
    ...
  ],
  "lyrics": [
    {
      "line": "予定はまだ 鉛筆書き",
      "start": 0.0, "end": 3.94,
      "words": [
        { "w": "予定はまだ", "start": 0.0,  "end": 2.26 },
        { "w": "鉛筆書き",   "start": 2.26, "end": 3.94 }
      ],
      "matched_ratio": 1.0,
      "low_confidence": false
    },
    ...
  ]
}

押さえておきたいのが matched_ratiolow_confidence です。行ごとに「どれだけ照合できたか」が残るので、怪しい行だけを目視できます。上の曲では85行中73行にタイミングが付き、残りは [Intro] のようなセクション記号でした。

sections は歌詞ブロックから組み立てます。歌のかたまりを blockNvocal: true)とし、その間に一定以上の空きがあれば intro / interludeN / outro として器楽区間を差し込みます。区間は必ず時間軸を隙間なく覆います——カットは全区間に割り当てる必要があるので、穴が空くと設計が破綻するためです。

後工程はこのファイルだけを見ます。どの60秒を切るか(窓)は sectionsdownbeats から決め、カットの切れ目はダウンビートに合わせ、焼き込む歌詞と実際の歌唱時刻の一致は lyrics で保証します。音源そのものを再解析する工程は、以降どこにもありません。

設計(ステップ3)

ここが micha と Claude の対話です。analysis.json のビートと歌詞タイミングを見ながら、どこで切るか、どのカットを口パクにするか、どのキービジュアルを当てるかを決めます。micha が GPT で作った設計書(共通ヘッダー+カット表+口パク指定)があれば、それを最優先で移植します。自動生成に任せるより、設計が先にあるほうが仕上がりが良いためです。

決まった内容は曲別のビルダー make_<s>.py に書き下し、shotlist.yaml(カット表:id / 開始 / 終了 / モード / kv / タイポグラフィ)と prompts.yaml(カット毎プロンプト)を生成します。カット尺の 17k+5 への丸めは framing.py が行います。

キービジュアル(ステップ4)

uv run python -m mvpipe.codexkv ../songs/<s> --spec multicut_kvgen.yaml

不足分を Codex CLI に生成させます。仕様は multicut_kvgen.yaml(カット定義+style_tail)で、style_tail を上書きすると歌詞の焼き込みもできます。出力と同時に kv_review.html が生成され、マウスオーバーで原寸表示、クリックで別タブ、カット毎にコメントを書ける画面になります。

ここで micha が全枚チェックします。特に見るのは焼き込み文字の綴りです。直したいカットにコメントを書くと、再生成コマンドが自動で組み立てられます。

uv run python -m mvpipe.codexkv ../songs/<s> --spec <spec> \
  --force --cuts c01 --note c01="..."

ジョブ構築と起動前チェック(ステップ5)

uv run python ../songs/<s>/make_<s>.py
uv run python -m mvpipe.jobs build ../songs/<s>/
uv run python -m mvpipe.verify preflight ../songs/<s>/

jobs build が shotlist と prompts からジョブJSONを組みます。このときモード別の高速化レーンを自動選択し、ref2va用の音声セグメントを 32kHz wav で自動抽出し、シードを固定し、参照の存在を検証します。

preflight起動前チェック表を出します。向き(縦横)、参照の有無、尺、推定生成時間が一覧になります。これを micha が見て承認するまで、GPUには一切課金が発生しません。

投入と生成(ステップ6〜7)

uv run python -m mvpipe.sync push ../songs/<s>/ --shots s001 s002 ...
uv run python -m mvpipe.runpod launch ../songs/<s>/ [--cloud secure]

push は rclone で参照とジョブを Drive の jobs/pending/ へ送ります。done が空でない場合はガードがかかります。launch は community を先に試し、確保できなければ secure へフォールバックします。起動時に残高を表示します。

Pod側は start.sh が ffmpeg と rclone を入れ、torch 2.11.0+cu128 を確認してから h3_worker.py を起動します。ワーカーはモデルをHuggingFaceから落とし、ComfyUIを立ち上げ、pending を取り込んで生成し、results/ へ書き出して done へ移します。キューが空になると自分でPodを削除します(AUTO_TERMINATE)。

クラッシュしても running のジョブは pending へ戻されるので、取りこぼしません。失敗したジョブは failed/<shot>.error.json にステージ・エラー・経過秒が残ります。

実行中のPodがいる曲へ push してはいけません。ミラーがPod→Driveの一方向なので、書き戻しが衝突します。

回収と検証(ステップ8)

uv run python -m mvpipe.sync status ../songs/<s>
uv run python -m mvpipe.sync pull ../songs/<s>/
uv run python -m mvpipe.verify results ../songs/<s>/

verify resultsffprobe で全数照合します。尺・解像度・フレーム数が指定どおりか、meta.json のシードと一致するかを見ます。目視の前に機械で落とせるものを落とす、という順序です。

そのうえで review.html(クリップを格子状に並べたレビュー画面)で micha が見ます。直したいカットを選ぶと、リテイクコマンドが組み立てられます。

uv run python -m mvpipe.retake ../songs/<s> s0XX ...

リテイクは take カウンタを進めて Drive 上で再キューします。push は不要で、Podが動いていればそのまま拾います。

アセンブル(ステップ9〜10)

rm -rf ../songs/<s>/out/intermediate
uv run python -m mvpipe.post ../songs/<s>/

各クリップを所定のスロットへトリムし、結合し、Suno原音源(48kHz)を敷き直します。H3が生成した音声はここで完全に捨てます。歌と口の動きを合わせるのは生成側の仕事ですが、音そのものは原音源が正だからです。

歌詞を画面に出す場合は ASS 字幕でデザインします。位置を循環させ、長い行はフォントを自動縮小し、画面端でアンカーをクランプします。キービジュアルに歌詞を焼き込んだカット(typography=model)はスキップされます。最後にLUTとグレインをかけて出力します。

out/intermediate/ の削除は必須です。中間ファイルをキャッシュしているので、消さずに再実行すると前回の結果が混ざります。

06QC — どこで人間が見るか

自動化の目的は人間を外すことではなく、人間が見るべき場所を絞ることです。関門は5つ。うち4つが人間の目視、1つが機械の全数検証です。課金を止められるのは前の2つ(kvレビューと preflight)で、残りは作り直しの判断に使います。

関門種類見るもの止める理由
kvレビュー人間キービジュアル全枚(焼き込み文字の綴り)誤字のまま生成すると全カットが無駄になる
preflight人間向き・参照・尺・推定時間の一覧ここを越えると課金が始まる
verify results機械ffprobeで全クリップの尺・解像度・シード照合目視の前に機械で落とせるものを落とす
クリップレビュー人間各カットの絵と動きリテイク対象の判断
完成尺の確認人間繋ぎ目・音ズレ・歌詞の可読性カット単体では見えない問題が出る

運用上のルールとして決めていることが他に3つあります。RunPodの実行報告には毎回、消費額と残高を含めること。歌詞を焼き込んだキービジュアルはその歌詞が実際に歌われるカットだけに使うこと。そして post の再実行時は out/intermediate/ を必ず消すこと。

07実績

実運用した3曲の計測値です。数字はすべて実測で、推定は含みません。

日付カットGPU時間費用リテイク
Not December2026-08-1060.6秒 1940分$1.00少数あり
響け、夜2026-08-1159.4秒 2341分$0.51なし
Scarlet2026-08-1156.0秒 1023分$0.29なし
実運用3曲の生成時間と費用を比較したグラフ。Not Decemberは40分・$1.00でリテイク少数、響け、夜は41分・$0.51でリテイクなし、Scarletは23分・$0.29でリテイクなし
実運用3曲の生成時間と費用

Not December は全編 ref2va で作ったため $1.00 かかりました。以降の2曲で ref2va を歌唱カットに絞った結果、$0.51、$0.29 と下がっています。Scarlet はカット数が10と少なく、23分・$0.29 で完成しました。リテイクは後の2曲ではゼロです。

キービジュアル生成のコストは $0(ChatGPTプラン内)。micha が GPT-Image2 で作る分も ChatGPT プラン内なので、画像生成の変動費はゼロです。したがって1本あたりの実質的な変動費はGPU課金だけになります。

08捨てた選択肢

参考にする方のために、試して不採用にしたものも書いておきます。

  • マルチカット(1回の生成で複数カット) — 8秒窓・14.4秒窓ともタイムアウト。3回試して $3.18 を消費し、映像は0秒。実装は multicut.py として残していますが使っていません
  • ref2va への EasyCache — Spectrum より遅くなったため不採用(73フレームで 185.8秒 → 195.5秒)
  • Podのテレメトリ監視 — community Podの CPU/GPU 使用率は実態と合わないことがあるため、健全性の判断材料から外しました
  • H3の生成音声 — 使いません。音は Suno 原音源に統一します

09これから

H3はまだ研究開発から量産確立へ移る段階で、固定費の投資は方式の切り替え判断が済んでからにしています。キービジュアルは現時点では micha が GPT-Image2 で作る分が主力で、パイプライン内の自動生成をどこまで任せるかはこれからの検討です。

直近の課題は Drive ミラーの双方向化(現在はPod→Driveの一方向のため、実行中の曲へpushできない)と、マルチカットのカット秒数精度を測る cutcheck.py の稼働です。

個別の曲でどう作ったかはSuno × MiniMax H3 制作記制作日誌 2026-08-12流転 ─ Amor manet 制作記に書いています。特に「流転」は、この記事で触れたカット間の繋ぎを1日かけて検証した記録です。