• #VOICEVOX
  • #Google Cloud TTS
  • #Cloudflare R2
  • #Vue
  • #音声合成
  • #ナレーション
  • #eurekapu
開発eurekapu-nuxt4メモ

会計学習コンテンツのナレーション機能構築

スライドとSVG図解で構成していた学習コンテンツに、音声ナレーションを載せた。テキストを目で追うだけだった教材が、声に合わせてスライドが切り替わる「見て聴く」体験に変わった。朝にVOICEVOXで最初の音声を生成し、夜にはCloudflare R2から配信される状態まで持っていった。

VOICEVOXで音声WAV生成

最初の音声合成にはVOICEVOXのローカルAPIを使った。プロローグから第3話までの138台詞 + 第4話の43台詞、合計181台詞分のWAVファイルを生成した。再生時間は約27分。

台詞テキストをAPIに投げてWAVを受け取るスクリプトを回すだけで音声ファイルが揃う。手軽さは申し分ないが、一部の漢字で読み間違いが発生した。「石」を「こく」と読んでしまう問題は、台詞テキスト側で「いし」とひらがなに置換して回避した。

NarrationViewer.vueの作成

音声に合わせてスライドが自動で進行するビューアコンポーネントを作った。

主な機能:

  • 音声自動再生: 台詞ごとにWAVを順次再生し、終わったら次のスライドへ遷移
  • 倍速再生: 1x / 1.25x / 1.5x / 1.8x / 2x の5段階。AudioのplaybackRateを切り替える
  • シーン一覧ドロワー: ハンバーガーメニューから開く。チャプター番号を表示し、本の目次のように構造化した
  • シーン切替トランジション: 映画のフェードイン・フェードアウトでシーン間をつなぐ

SVG画像と台詞のマッピングも調整した。シーン7で3つの表が登場するが、台詞との紐づけがずれていたので修正。どの台詞でどの表を見せるかを1つずつ確認して合わせた。

ランディングページの3カード構成

コンテンツへの入口となるランディングページを作成した。同じ教材を3つの視聴モードで提供する構成にした:

  1. カラム版 -- Miller Columns形式で自由にスライドを行き来する
  2. フォーカス版 -- ナビゲーションを消して全幅でスライドだけ見せる
  3. ナレーション版 -- 音声付きで自動進行する

カード型のUIで3つを並べ、それぞれの特徴が一目で伝わるようにした。

MillerViewerのカラムヘッダー名変更

Miller Columnsの各カラムに表示するヘッダー名を見直した。「カテゴリー」を「セクション」、「中カテ」を「チャプター」に変更し、トピック名も追加。学習コンテンツの階層構造と用語を揃えた。

Google Cloud TTS(Chirp 3 HD)への移行

VOICEVOXの音声は素早く量産できたが、キャラクターの声質が教材のトーンと合わない箇所があった。Google Cloud TTSのChirp 3 HDモデルに切り替えて、より自然な読み上げを目指した。

ボイスの選定に時間をかけた。複数の声を試聴して、役割ごとに割り当てた:

役割ボイス名選定理由
ナレーターLeda落ち着いたトーンで説明に向く
生徒役Zephyr明るく、質問する場面に合う
先生役Algieba穏やかで信頼感がある

第1話の全55行をGCP音声に差し替えた。VOICEVOXとGCPの音声が混在する過渡期を経て、第1話はGCPに統一できた。

GCP ADC認証方式への移行

当初はサービスアカウントのキーファイル(JSONファイル)でGCP認証していたが、ADC(Application Default Credentials)方式に切り替えた。gcloud auth application-default login でローカル環境の認証を済ませ、キーファイルを削除した。秘密鍵ファイルがリポジトリ周辺から消えて、管理がすっきりした。

Cloudflare R2への音声ファイル移行

210ファイルの音声データをローカルからCloudflare R2に移した。

手順:

  1. R2バケットを作成
  2. wranglerで210ファイルをアップロード
  3. カスタムドメインを設定してCDN配信を有効化
  4. NarrationViewer側の音声URLをR2ドメインに書き換え
  5. CSP(Content Security Policy)の media-srcconnect-src にR2ドメインを追加

ローカルR2とリモートR2の転送で詰まった。wrangler r2 object put がデフォルトでローカルのR2エミュレータに書き込んでいた。--remote フラグを付け忘れていたのが原因で、フラグを追加したら本番のR2バケットにファイルが入った。

リファクタリング

一通り動くようになった段階で、3つのレビューエージェントを並列に起動してコードレビューを回した。コンポーネントの責務分離、composableの切り出し、型定義の整理など、指摘を受けた箇所を修正した。

振り返り

朝の時点では「スライドに音声を付ける」という1つのタスクだったが、音声合成エンジンの選定、ファイル配信のインフラ、認証方式の見直しと、レイヤーをまたいで手を動かす一日になった。VOICEVOXで素早くプロトタイプを作り、GCP TTSで品質を上げ、R2で配信基盤を整える。この順番で進めたことで、各段階で動くものを確認しながら前に進めた。

181台詞・約27分の音声付き教材が、ブラウザで再生ボタンを押すだけで動く。テキストだけだった教材が、声と映像の教材に変わった手応えがある。