movie/ 実装レビュー(final-plan.md 照合)
movie/final-plan.md に基づく実装(movie_pipeline/ + remotion/ + config/ + prompts/)のレビュー結果。sleep-01 の実ビルド成果物(movie/build/sleep-01/)で各指摘を検証済み。
報告されていた症状「テキストの表示時間が短すぎて読む前に消える 」は、単なる調整不足ではなく下記 #1 + #2 の複合バグ が根本原因。
🔴 Critical
1. onScreen スキーマ契約の不一致 — quiz 以外のシーンで常設テキストが一切描画されていない(報告症状の主因)
prompts/scenario.md の出力例は quiz シーンの question/options のみで、他のシーン型の onScreen フィールド名を定義していない。
その結果、Claude 生成の sleep-01 script.json は onScreen.title / subtitle / note / points を出力した。
一方 remotion/src/Root.tsx が読むのは headline / subhead / mark / items / sources / badge のみ。
実測 : sleep-01 の timeline.json では 11 シーン中 quiz 以外の 10 シーンすべてで headline が undefined。takeaway の箇条書き(items)も出典リスト(sources)も未描画。画面に出る大テキストは emphasis のフラッシュ(下記 #2、0.28〜2.4 秒)だけだった。
ヒューリスティック生成の exercise-01 は headline/subhead を出すため正常。--method claude の経路だけが壊れる 。
movie_pipeline/validate.py は onScreen を一切検証しないため素通りした。final-plan §4.2 は timeline.json を「TTS バックエンドとの契約」と定義しており、契約のスキーマ検証が欠けているのが構造的な原因。
対処案 : (a) prompts/scenario.md にシーン型ごとの onScreen フィールドを明記、(b) validate.py にシーン型別の必須フィールド検証を追加(title/hook/explain→headline、answer→headline+mark、takeaway→items、sources→sources)、(c) 移行措置として Root.tsx 側で title→headline, subtitle/note→subhead, points→items のエイリアス吸収も可。
2. emphasis 演出が「headline を置換して発話中だけ表示」— サブ秒のフラッシュになる
Root.tsx の DefaultScene: activeEmphasis(...) ?? scene.onScreen.headline。強調語が headline を置き換え 、モーラ窓(実測 0.28〜2.4 秒、中央値 ~1 秒)だけ表示されて消える。
final-plan §6 の設計は「画面中央に巨大な強調語1つを常設表示 し、今喋っている語がカラオケ式に光る 」。置換+瞬間表示は設計と逆で、headline があっても画面がチラつく。movie/ 実装レビュー: テキストが読む前に消える問題の根本原因と final-plan.md との乖離 #1 で headline が undefined のため、症状が「無地の画面に単語が一瞬出て消える」として顕在化した。
最低表示時間(例: 1.5〜2 秒)のフロアもどこにも無い。
対処案 : headline は常設し、emphasis はハイライト(色・スケール)の切替のみにする。切替に最低表示時間のフロアを入れる。
🟠 High
3. 強調語のモーラ対応付けが「文字数=モーラ数」近似で系統的にずれる
movie_pipeline/timeline.py の _emphasis_timing() は表層文字列の文字数をそのままモーラ index に使う。漢字は 1 文字複数モーラ(例:「睡眠」=2文字4モーラ)なので、窓が常に早く・短く なる。実測でも「俗説」0.28 秒、「請求書」0.42 秒など読解不能な窓が多数。
final-plan §3③ は AudioQuery の kana を使った表層→モーラ対応付けを指定し、照合失敗時は「キーワード先頭一括表示 」へフォールバックする設計。実装のフォールバックは 0.35〜0.9 秒のフラッシュ(fallback: True)で、これも設計と異なる。
4. 尺ガードが未実装 — sleep-01 は 6分13秒(373 秒)で許容 4:40〜5:20 を大幅超過、無警告
final-plan §3②/§4.4 の「合成前尺チェック → pause 圧縮 → 台本自動圧縮リトライ → 人間フラグ」が丸ごと未実装。
validate.py は文字数(1000〜2100 字)しか見ておらず、sleep-01 は文字数チェックはパスしたまま実尺が 24% 超過した。timeline 生成後に totalSec を許容レンジと照合する検証だけでも先に入れる価値がある。
5. Claude 経路の検証リトライループ(最大2回)が未実装
final-plan §3② の「生成 → スキーマ検証 → 不合格ならエラー内容を添えて再生成(最大2回)」に対し、cli.py cmd_script は検証エラーで exit 2 して終わるだけ。
script_generator.py generate_script_with_claude() は json.loads(raw["result"]) 一発で、コードフェンス付き応答や前置き文で即クラッシュする。subprocess.run(check=True) なので失敗時に stderr も表示されない。今回 movie/ 実装レビュー: テキストが読む前に消える問題の根本原因と final-plan.md との乖離 #1 の不正スキーマがそのまま下流に流れたのは、このループが無いことも一因。
🟡 Medium
6. シーン境界のフレーム丸めで 1 フレームの黒画面/重なりが出る(実データで再現)
Root.tsx はシーンの from と durationInFrames を 独立に round() している。連続する秒値でも丸めが食い違う。
実測(sleep-01, fps=30) : explain-04 の直前に 1 フレームの隙間 (背景のみの黒フレーム)、myth-01 の直前に 1 フレームの重なり。また最終シーンの終端がコンポジション長を 7 フレーム超えて尻切れ。
対処: durationInFrames = round(endSec*fps) - round(startSec*fps) に統一する。
7. fitText 未使用で長文がはみ出しうる
final-plan §3⑥ は @remotion/layout-utils の fitText を指定し、依存にも入っているが、Root.tsx は全て固定 fontSize(headline 112px 等)。quiz の選択肢 6 個(sleep-01 で実際に 6 個)や長い headline で溢れる。validate.py の headline 警告閾値 34 字も plan の「主テキスト 28 字以内」と不整合。
8. Noto Sans JP がロードされていない(環境依存レンダ)
スタイルの fontFamily に名前があるだけで、フォントのバンドル/明示ロード(plan §6「Noto Sans JP バンドル+<Font> 明示ロード」)が無い。レンダ結果がマシンのインストール状況に依存し、10 本のブランド統一が保証されない。
9. VOICEVOX キャラクターのクレジット表記が無い
final-plan §7 は「規約に従いエンドロールにクレジット表記(キャラごとに義務あり)」。speakers.json は style_id 2/3(四国めたん・ずんだもん)を使うが、sources シーンにもエンドロールにも VOICEVOX:キャラ名 表記が無い。公開すると規約違反になる。
🟢 Minor
シーン id 重複の未検証 : timeline.py の scene_map は id をキーに統合するため、LLM が id を重複させると黙って utterance がマージされ、同一シーンが二重に出力される。validate.py に一意性チェックを。
サンプリングレート固定 : concat_wavs() は 24000Hz 決め打ちで、query に outputSamplingRate を明示設定していない。エンジン設定・スタイル次第で ValueError 停止。query 側で 24000 を明示するのが安全。
テスト無し : 尺計算・pause 規則・emphasis 対応付けは純関数でユニットテスト可能。2本目以降の横展開(plan §10 Step 6)前に最低限欲しい。
✅ 良かった点
実測検証ゲート(±50ms)が設計どおり動作し、warnings に計算尺と実測尺の乖離が記録されている(sleep-01 で 7 件検出・実測採用)。
無音挿入と timeline 書き戻しが同一ループ(plan §5 の「片側更新を構造的に不可能に」を遵守)。
dry-run モードにより VOICEVOX 無しでもパイプライン全体が通る。中間成果物が全てファイルで再実行可能。
寛容パーサ・pacing.json・pronunciation.json・timeline 契約の分離は plan に忠実。
修正の優先順位(提案)
movie/ 実装レビュー: テキストが読む前に消える問題の根本原因と final-plan.md との乖離 #1 + #2(症状の直接原因): プロンプトのスキーマ明記 + validate のシーン型別検証 + emphasis を常設ハイライト化
#4 + #5: 尺ゲートと検証リトライループ(パイプラインの品質ゲートとして本丸)
#3, #6, #9: 同期精度・黒フレーム・ライセンス
#7, #8, Minor 群
🤖 Generated with Claude Code
movie/ 実装レビュー(final-plan.md 照合)
movie/final-plan.mdに基づく実装(movie_pipeline/+remotion/+config/+prompts/)のレビュー結果。sleep-01 の実ビルド成果物(movie/build/sleep-01/)で各指摘を検証済み。報告されていた症状「テキストの表示時間が短すぎて読む前に消える」は、単なる調整不足ではなく下記 #1 + #2 の複合バグが根本原因。
🔴 Critical
1. onScreen スキーマ契約の不一致 — quiz 以外のシーンで常設テキストが一切描画されていない(報告症状の主因)
prompts/scenario.mdの出力例は quiz シーンのquestion/optionsのみで、他のシーン型のonScreenフィールド名を定義していない。script.jsonはonScreen.title/subtitle/note/pointsを出力した。remotion/src/Root.tsxが読むのはheadline/subhead/mark/items/sources/badgeのみ。timeline.jsonでは 11 シーン中 quiz 以外の 10 シーンすべてでheadlineが undefined。takeaway の箇条書き(items)も出典リスト(sources)も未描画。画面に出る大テキストは emphasis のフラッシュ(下記 #2、0.28〜2.4 秒)だけだった。headline/subheadを出すため正常。--method claudeの経路だけが壊れる。movie_pipeline/validate.pyはonScreenを一切検証しないため素通りした。final-plan §4.2 は timeline.json を「TTS バックエンドとの契約」と定義しており、契約のスキーマ検証が欠けているのが構造的な原因。対処案: (a)
prompts/scenario.mdにシーン型ごとのonScreenフィールドを明記、(b)validate.pyにシーン型別の必須フィールド検証を追加(title/hook/explain→headline、answer→headline+mark、takeaway→items、sources→sources)、(c) 移行措置として Root.tsx 側でtitle→headline,subtitle/note→subhead,points→itemsのエイリアス吸収も可。2. emphasis 演出が「headline を置換して発話中だけ表示」— サブ秒のフラッシュになる
Root.tsxのDefaultScene:activeEmphasis(...) ?? scene.onScreen.headline。強調語が headline を置き換え、モーラ窓(実測 0.28〜2.4 秒、中央値 ~1 秒)だけ表示されて消える。対処案: headline は常設し、emphasis はハイライト(色・スケール)の切替のみにする。切替に最低表示時間のフロアを入れる。
🟠 High
3. 強調語のモーラ対応付けが「文字数=モーラ数」近似で系統的にずれる
movie_pipeline/timeline.pyの_emphasis_timing()は表層文字列の文字数をそのままモーラ index に使う。漢字は 1 文字複数モーラ(例:「睡眠」=2文字4モーラ)なので、窓が常に早く・短くなる。実測でも「俗説」0.28 秒、「請求書」0.42 秒など読解不能な窓が多数。kanaを使った表層→モーラ対応付けを指定し、照合失敗時は「キーワード先頭一括表示」へフォールバックする設計。実装のフォールバックは 0.35〜0.9 秒のフラッシュ(fallback: True)で、これも設計と異なる。4. 尺ガードが未実装 — sleep-01 は 6分13秒(373 秒)で許容 4:40〜5:20 を大幅超過、無警告
validate.pyは文字数(1000〜2100 字)しか見ておらず、sleep-01 は文字数チェックはパスしたまま実尺が 24% 超過した。timeline 生成後にtotalSecを許容レンジと照合する検証だけでも先に入れる価値がある。5. Claude 経路の検証リトライループ(最大2回)が未実装
cli.py cmd_scriptは検証エラーで exit 2 して終わるだけ。script_generator.py generate_script_with_claude()はjson.loads(raw["result"])一発で、コードフェンス付き応答や前置き文で即クラッシュする。subprocess.run(check=True)なので失敗時に stderr も表示されない。今回 movie/ 実装レビュー: テキストが読む前に消える問題の根本原因と final-plan.md との乖離 #1 の不正スキーマがそのまま下流に流れたのは、このループが無いことも一因。🟡 Medium
6. シーン境界のフレーム丸めで 1 フレームの黒画面/重なりが出る(実データで再現)
Root.tsxはシーンのfromとdurationInFramesを 独立にround()している。連続する秒値でも丸めが食い違う。durationInFrames = round(endSec*fps) - round(startSec*fps)に統一する。7.
fitText未使用で長文がはみ出しうる@remotion/layout-utilsのfitTextを指定し、依存にも入っているが、Root.tsxは全て固定 fontSize(headline 112px 等)。quiz の選択肢 6 個(sleep-01 で実際に 6 個)や長い headline で溢れる。validate.pyの headline 警告閾値 34 字も plan の「主テキスト 28 字以内」と不整合。8. Noto Sans JP がロードされていない(環境依存レンダ)
<Font>明示ロード」)が無い。レンダ結果がマシンのインストール状況に依存し、10 本のブランド統一が保証されない。9. VOICEVOX キャラクターのクレジット表記が無い
speakers.jsonは style_id 2/3(四国めたん・ずんだもん)を使うが、sources シーンにもエンドロールにもVOICEVOX:キャラ名表記が無い。公開すると規約違反になる。🟢 Minor
timeline.pyのscene_mapは id をキーに統合するため、LLM が id を重複させると黙って utterance がマージされ、同一シーンが二重に出力される。validate.pyに一意性チェックを。concat_wavs()は 24000Hz 決め打ちで、query にoutputSamplingRateを明示設定していない。エンジン設定・スタイル次第で ValueError 停止。query 側で 24000 を明示するのが安全。✅ 良かった点
修正の優先順位(提案)
🤖 Generated with Claude Code