• #NarrationViewer
  • #VOICEVOX
  • #Playwright
  • #ffmpeg
  • #Vitest
  • #Vue
  • #ナレーション
  • #eurekapu
開発eurekapu-nuxt4メモ

ナレーションコンテンツ改善とNarrationViewer拡張

昨日R2配信まで通したナレーション機能を、今日は中身の精度とビューアの表現力の両面から詰めた。196行のセリフを頭から再生して耳で追いながら、図とセリフのずれやscene.idの重複を8件つぶした。夜にはPlaywrightで各セリフのスクリーンショットを撮って動画に組み立てるパイプラインが動き始めた。

Chapter 01 + 04 通しレビューと8件の修正

Chapter 01(プロローグ〜第3話)とChapter 04を合わせた196行のナレーションを、先頭から再生して1行ずつ確認した。

見つかった問題:

  • 未解説の専門用語: セリフ中に突然登場する用語が、前段で導入されていなかった
  • スライドとメッセージラインの図のずれ: セリフが指す図と実際に表示される図が一致していない箇所
  • scene.idの重複: 2つのシーンに同じIDが振られていて、ジャンプ機能が壊れていた
  • 旧プロジェクトからの移行漏れ: cockpit-nuxt-vuetifyからの移行時にメッセージラインのテキストが書き換わっていた箇所を、原文に忠実に復元

8件すべてをデータファイル上で修正し、再度通しで確認して問題が消えたことを耳と目で確かめた。

Chapter 04の書き換え -- 3/14版プロットに準拠

3/14に作成した詳細版プロットをベースに、narrationChapter04.ts を全面的に書き換えた。

特に力を入れたのが「羊の3回の移動」の解説部分。ストーリー上、以下の3つの場面で羊が動く:

  1. 自分の羊が増える -- 収益の発生
  2. 他人の羊が増える -- 費用の発生
  3. 他人の羊が減る -- 費用の戻し

これらを1つずつ丁寧に追えるようセリフを組み直した。勘定の概念(ホームポジションの左右が逆転する意味、なぜ複式で記録するのか)もストーリーの流れに沿って自然に出てくるよう配置した。

NarrationViewerの機能追加

messageLine表示(左右分割レイアウト)

スライド画像の横に、現在のセリフに対応するメッセージライン(T字フォームの図)を表示する機能を追加した。左にスライド、右にメッセージラインの2カラム構成。セリフが進むと右側の図も連動して切り替わる。

ステレオパン

キャラクターごとに音声の定位を分けた:

キャラクターパン値定位
ユイ(生徒)-0.7左寄り
カイ先生+0.7右寄り

Web Audio APIのStereoPannerNodeで実装した。ヘッドホンで聴くと2人が左右に立って会話している感覚が出る。

デバッグパネル

Dキーで表示を切り替えるデバッグパネルを追加した。現在のscene.id、セリフインデックス、再生状態、messageLineのマッピング情報などを表示する。コンテンツ確認時にブラウザのDevToolsを開かなくても状態が追える。

Playwright + ffmpegによる動画レンダリング試行

ナレーションの内容をそのまま動画として書き出すパイプラインを組んだ。

処理の流れ:

  1. Playwrightでナレーションページを開く
  2. 各セリフごとにスクリーンショットを撮影(PNG)
  3. 対応する音声WAVの再生時間を取得
  4. ffmpegで画像+音声を結合して動画に

音声の長さの取得にffprobeを使っていたが、外部ツールへの依存を減らすためWAVヘッダーから直接計算する方式に切り替えた。WAVのRIFFヘッダーにはデータサイズとサンプリングレートが入っているので、割り算するだけで再生秒数が出る。

TTS生成スクリプトのSource of Truth一本化

音声合成用のPythonスクリプトに、セリフのテキストがハードコードされていた。TypeScript側のナレーションデータとPython側のテキストが二重管理になっていて、片方を直すともう片方との整合性が崩れる状態だった。

TypeScriptのナレーションデータからJSONを書き出し、Python側はそのJSONを読む方式に変更した。Source of Truthがtsファイル1箇所に定まり、セリフを修正したらJSONを再生成するだけで音声合成に反映される。

ロジック抽出とVitest 49テスト作成

NarrationViewerのコンポーネント内に埋まっていた計算ロジックを純粋関数として抽出した。シーン検索、インデックス計算、再生状態の判定、messageLineのマッピングなど。

抽出した関数群に対してVitestで49テストを新規作成した。プロジェクト全体で292テストが走り、全て合格。コンポーネントのリファクタリング前にテストを固めたことで、この後の変更を安心して進められる。

命名規則移行計画(Section > Chapter > Topic)

コンテンツの階層構造を表す名前がコード内で揺れていた。「category / section / chapter」が場所によって異なる意味で使われている状態だった。

Section > Chapter > Topic の3階層に統一する移行計画をマークダウンにまとめた。影響範囲のファイル一覧、移行ステップ、後方互換性の扱いを記載し、Codexでレビューを通した。

振り返り

196行のセリフを耳で通して図のずれを8件見つけた作業が、今日の土台になった。コンテンツの中身が正確になって初めて、messageLine表示やステレオパンといった表現の拡張に意味が出る。

TTS生成のSource of Truth問題を片付けたのも地味に効いている。セリフを直す→JSON生成→音声再生成、の流れが1本道になったことで、今後のセリフ修正のたびに「あっちのファイルも直さなきゃ」と探し回る時間が消えた。

純粋関数への抽出とテスト49本は、明日以降の命名規則移行を安全に進めるための布石。292テストが緑のまま移行を完了させるのが次の目標になる。