MMD ダンスビデオ字幕:エンドツーエンドワークフロー
0. ワンフレーズ原則
書き終わった/レンダリング終わったら必ずフレーム抜き出して確認すること:ffprobe の長さが合っていても字幕が実際に表示されるとは限らない;ASS のタイムスタンプやスタイルの色形式が間違っていると「レンダリングは成功したが文字がない」状態になる。
このプロジェクトでは「直したつもりが、実際には焼き込まれていなかった」という失敗が何度もあった。承認はすべてフレーム抜き出しのスクリーンショットに基づく。
1. アセットとパス(このワークスペース)
| もの | パス |
|---|---|
| 字幕プロジェクト | D:\yingyong2\論项目\字幕\ |
| LRC 歌詞 | 字幕\[by维尔日记].txt — KISS OF LIFE《Who is she (Japanese Ver.)》,36 行 |
| 旧 MMD 完成ビデオ | 字幕\0001-4956 - 副本.mkv(2560×1080 / 30fps;ファイル不完全、公称 165s だが実際にデコードできるのは約 ~137s まで) |
| 新 MMD 完成ビデオ | D:\mmd.mp4\0002-4956.mkv(2560×1080 / 30fps / 165.166s、完全) |
| フォント | 字幕\build\fonts\(Anton, ArchivoBlack, BebasNeue, Bungee, Monoton, PermanentMarker, RubikGlitch, Silkscreen, PirataOne, UnifrakturCook)+ システム Georgia / Microsoft YaHei |
| 生成スクリプト | 字幕\build\*.py |
| 完成ビデオ出力 | 字幕\出力\<タスク名>\(タスクごとにフォルダ、納品名は日本語で読みやすい名前を付ける) |
| ffmpeg | C:\Users\35530\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0-full_build\bin\ffmpeg.exe |
| python | C:\Users\35530\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe(PIL 12.3 / numpy 2.3、cv2 なし) |
2. 7ステップのメインワークフロー
① 素材をプローブする ffprobe 解像度/フレームレート/長さ/色彩;デコードテストでファイルが切り詰められているか判断
② セグメントを選ぶ 歌の部分 vs ダンスの動き;サンプル 5s / 30s / 全編
③ 歌詞を合わせる LRC の絶対時間 → セグメント開始点を引く = ASS の相対時間
④ スタイルを決める S1–S6(style-frames-compare 参照)を参考に;最初に静止フレーム、次に動的フレーム
⑤ ASS を書く Python で生成;ass-render-playbook の形式のハードルールに従う
⑥ レンダリング ffmpeg で焼き込み(または透明オーバーレイ);最初に 5–10s 検証してから全セグメント
⑦ 承認 フレーム抜き出し(各行につき最低 1 フレーム)+ キーフレーム表の作成;構図、中国語がないこと、顔を隠さないこと、最後のフェードアウトを確認
① 素材のプローブ
$ff='...\ffmpeg.exe'; $fp='...\ffprobe.exe'
& $fp -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,nb_frames,pix_fmt,color_space -show_entries format=duration -of default=nw=1 $v
& $ff -v error -i $v -map 0:v:0 -c copy -f null NUL # "File ended prematurely" と出たら = 切り詰められている
- このプロジェクトの素材はすべて 2560×1080 / 30fps / h264 yuv420p / bt709、超ワイド 64:27。
- 165s のビデオ ≈ 4956 フレーム;30s の完成ビデオ = 900 フレーム。
② セグメントを選ぶ
- LRC サビの最初の部分:
36.892 / 40.366 / 44.003 / 47.577 / 51.179 / 54.698 / 58.309 / 61.926、〜66s まで。 - 5s 比較サンプルは
0:58.2–1:03.2が一般的;30s サンプルは0:36.5–1:06.5が一般的;全編は 0–154.6s(古いファイルは 137s までしか使えない)。 - 切り抜き:
-ss 36.5 -t 30 -i src -vf "eq=brightness=-0.05:contrast=1.04:saturation=1.02" -c:v libx264 -crf 16 -pix_fmt yuv420p(若干暗くすることで白/クリーム色の文字にコントラストを持たせる;完全なカラーグレーディングは subject-color-grade を参照)
③ 歌詞を合わせる
LRC は絶対時間;ASS はセグメント開始点からの相対時間を使う。例:セグメントが 36.5s から始まる場合、36.892 → 0.392s。
一行につき一つのキュー;行内でさらに単語ごとに複数のイベントに分割する(下記参照)。終了イベントは 0.14–0.4s 早く終了させ、行をまたいでくっつかないようにする。
④ スタイルを決める
- 最初に
style-frames-compareスキルを読み、ターゲットの雰囲気に合わせて S1–S6 を選び、まず静止フレームをユーザーに見せる。 - 重要な制約:ユーザーが明示的に「中国語は使わない」と言った場合、元の歌詞の英語/日本語だけを残す;人物は中央に、大きな文字は左右のセーフエリアと上下の角に配置する。
- 既に再利用可能な生成器がある:
styles_anim.py(S2/S4/S6 の 5s アニメーション)、flare_wide.py(炸裂エフェクト)、neon_wide.py(ネオン)、avoid_side2.py(完全回避)、kpop_styles.py(静止稿)、art_frames.py(アート文字静止稿)。
⑤ ASS を書く
- Python で生成する(手書きより安定):
Assクラス +word()/rect()ヘルパーを定義し、最後にwrite()する。 - ハードルール、テンプレート、落とし穴については
ass-render-playbookを参照(時間形式H:MM:SS.cc、スタイル色値に末尾&なし、fontsdir は相対パスなど)。
⑥ レンダリング
- 作業ディレクトリ = 出力ディレクトリ;ASS は ASCII ファイル名を使う;フォントディレクトリは相対パス:
cd "字幕\出力\S4S6_30s"
& $ff -v error -y -i _base_30s.mp4 -vf "ass=_s4.ass:fontsdir=fonts" `
-c:v libx264 -preset medium -crf 16 -pix_fmt yuv420p -movflags +faststart S4_30s.mp4
- 透明オーバーレイ(後処理用):
-f lavfi -i "[email protected]:s=2560x1080:r=30,format=rgba" -vf "ass=x.ass:alpha=1"+ VP9yuva420p(詳細は ass-render-playbook を参照)。
⑦ 承認チェックリスト
-
ffprobeduration が設計と一致しているか(5.000 / 30.000 / 154.6) - フレーム抜き出し:各行につき最低 1 フレーム、文字が実際に表示されているか、位置が合っているか、文字化けがないかを確認
- ユーザーの要求が中国語禁止 → 2–3 フレーム抜き出して英語/日本語のみであることを確認
- 人物の顔/体が隠されていないか(クローズアップ/接写フレームは特にチェック)
- 最終行が終了前にフェードアウトしているか(例:29.86s までに)、文字が残っていないか
-
キーフレーム.jpg(2×2 または 2×3、各セル 1280×540、ラベル付き)を作成し、ユーザーが見やすいようにする - ファイル名は読みやすく、説明 md も一緒に納品する
3. タスクタイプ → 納品物
| タスク | 納品物 |
|---|---|
| 静止フレーム案 | N 枚の 2560×1080 jpg + 概要.jpg + 説明 md |
| 5s サンプル | 1–3 本の 5s mp4 + 比較ビデオ + キーフレーム表 |
| 30s サンプル | 1–2 本の 30s mp4(900 フレーム)+ 比較版 + キーフレーム表 + 説明 md |
| ペア納品(一般的) | 各スタイルにつき 2 本:X_背景あり_30s.mp4(H.264 焼き込み)+ X_字幕のみ_透明_30s.webm(VP9 alpha);N スタイル × 2 本のビデオ、字幕のみ_透明プレビュー.jpg(チェッカーボード合成プレビュー)付き |
| 全編 | 字幕の ASS + 焼き込み mp4 / 透明 webm + 説明 |
| カラーグレーディング | グレーディング済み mp4 + 比較フレーム + パラメータ説明 / LUT |
透明版のレンダリング(背景あり版と同じ ASS、フレーム対フレームで一致):
& $ff -v error -y -f lavfi -i "[email protected]:s=2560x1080:r=30,format=rgba" ` -vf "ass=_s4.ass:alpha=1:fontsdir=fonts" -t 30 -c:v libvpx-vp9 -pix_fmt yuva420p ` -b:v 0 -crf 30 -row-mt 1 -auto-alt-ref 0 -metadata:s:v:0 alpha_mode=1 OUT.webmalpha の検証:
ffmpeg -c:v libvpx-vp9 -ss 12.4 -i OUT.webm -frames:v 1 -pix_fmt rgba f.png、PIL で alpha min=0 / max=255、カバレージ>0 を確認;フレーム抜き出し時は明示的に-c:v libvpx-vp9を指定すること、さもないと alpha が失われてすべて 255 になる。
4. 並列処理(サブエージェント)の約束事
ユーザーが「サブエージェントで別々にやって」と言った場合の標準的なやり方:
- メインエージェントは先に共有ベースビデオ(切り抜き済み、一律暗くしたもの)と
fonts/のコピー、cue sheet(相対時間表)を準備する; - 各スタイルに対してサブエージェントを一つ用意し、プロンプトで固定する:入力パス、出力ファイル名(
S4_30s.mp4など)、ASS のハードルール、タイムライン表、承認要件; - サブエージェントはそれぞれ自分の接頭辞ファイルだけを書き、互いに上書きしない;
- メインエージェントはすべてを収集し、比較ビデオ/キーフレーム表/説明を作成し、統一してフレーム抜き出しによる承認を行う。 実績:単一の 30s スタイル(2560×1080、8 行、約 40–60 の Dialogue)はデザインから完成まで約 10–20 分;2 つのサブエージェントの並列処理では、最も遅い方の時間が全体の時間となる。