[{"data":1,"prerenderedAt":309},["ShallowReactive",2],{"content-/quiz-narration-pipeline":3,"all-pages-for-dir":307,"og-image-/quiz-narration-pipeline":308},{"id":4,"title":5,"body":6,"category":289,"description":290,"extension":291,"meta":292,"navigation":255,"ogImage":293,"path":294,"project_name":295,"published":296,"publishedAt":297,"seo":298,"stem":299,"tags":300,"todo":293,"unpublished":296,"updatedAt":293,"__hash__":306},"pages/2026-05/2026-05-16/quiz-narration-pipeline.md","投資判断クイズ75問をVOICEVOXで音声化：WAV合成→MP3→Cloudflare R2まで一気通貫の自動化ログ",{"type":7,"value":8,"toc":276},"minimark",[9,13,17,25,29,32,51,54,70,73,77,80,87,90,94,105,112,119,123,130,153,160,164,171,192,195,198,201,208,211,214,234,241,244],[10,11,12],"h2",{"id":12},"このセッションでやったこと",[14,15,16],"p",{},"Part 2 投資判断クイズ全75問（25問+18問+32問）に音声を載せた。問題文・選択肢A〜C・解説・正解の読み上げに加え、誤答パターンD（不正解選択時の補足音声）まで含めて、計150問分のWAVを合成し、MP3に変換してCloudflare R2へアップロードし、Nuxtページから再生できるところまで一気通貫で通した。",[14,18,19,20,24],{},"ただし手を動かしたのはほぼClaude Code側で、人間側は「音声化やっていきましょう」「OK」「y」とだけ打っていた。",[21,22,23],"strong",{},"Claude Codeに音声化パイプラインを派遣して、自分は承認スタンプを押す係に徹した","、というのが今日の構図に近い。",[10,26,28],{"id":27},"着手前の状態abc用jsonベースの既存スクリプト","着手前の状態：A/B/C用JSONベースの既存スクリプト",[14,30,31],{},"朝、昨日の積み残しを確認したら「Part 2 投資判断クイズの音声化が未着手」と書いてあった。",[14,33,34,35,39,40,39,43,46,47,50],{},"既存のVOICEVOX音声化スクリプトはA/B/C 3択用のJSONを読み込んで合成する作りになっていた。今回扱う投資判断クイズは TS ファイル（",[36,37,38],"code",{},"investment-1.ts","、",[36,41,42],{},"investment-2.ts",[36,44,45],{},"investment-3.ts","）で定義されていて、しかも誤答パターンD（不正解を選んだときに再生する補足音声）の概念が新規追加されていた。",[21,48,49],{},"スクリプトを拡張する必要がある","、と判断した時点でClaude Codeに作業を投げた。",[14,52,53],{},"依頼内容は「既存のVOICEVOX合成スクリプトをTSファイル対応にして、Dパターンも合成対象に追加して、investment-1/2/3を順次合成して」だけ。Claude Codeが以下を順次こなした:",[55,56,57,61,64,67],"ul",{},[58,59,60],"li",{},"TSファイルから問題データを抽出するパーサーを追加",[58,62,63],{},"Dパターン（誤答時補足）の合成ロジックをスクリプトに組み込み",[58,65,66],{},"investment-1 → investment-2 → investment-3 の順でWAV合成",[58,68,69],{},"investment-3 は問題数が32問と多かったのでバックグラウンド実行に切り替え、並行してinvestment-1/2のMP3変換に着手",[14,71,72],{},"人間はターミナルを眺めながら「進んでるな」と確認するだけ。WAV合成が終わったらMP3変換、終わったらアップロード、と次の工程に勝手に進んでいった。",[10,74,76],{"id":75},"_150問のwavmp3変換151ファイルがr2へ流れる","150問のWAV→MP3変換、151ファイルがR2へ流れる",[14,78,79],{},"WAV合成が全て完了した時点で、各問題ごとに「問題文・選択肢3つ・解説・正解・誤答補足」のWAVが並んでいた。これをMP3に変換したら151ファイルになっていた（共通音声1ファイル + 75問 × 2バリエーション）。",[14,81,82,83,86],{},"Cloudflare R2へのアップロードは並列実行した。",[36,84,85],{},"_common"," ディレクトリ（共通音声）を先に流し込んでから、investment-1/2/3 のディレクトリを3並列でアップロード。150問分のMP3が数分で R2 に乗った。",[14,88,89],{},"ここまでで「音声ファイルは全部用意できた」状態。あとはNuxt側で再生できるようにつなぎ込むだけ、のはずだった。",[10,91,93],{"id":92},"落とし穴1narrationconfig-がページに渡されていなかった","落とし穴1：narrationConfig がページに渡されていなかった",[14,95,96,97,100,101,104],{},"投資判断クイズの18ページ（各トピックのページとサブページ）を順に開いてみたら、",[21,98,99],{},"音声再生ボタンが反応しない","。Claude Codeに調べさせたら、各ページのコンポーネント呼び出しで ",[36,102,103],{},"narrationConfig"," propsが渡されていなかった。R2のURLパターン・問題IDのprefix・音声有効化フラグ等を渡す設計だったのに、ページ側が完全に無設定状態で動いていた。",[14,106,107,108,111],{},"18ページに同じパターンを書き込む必要がある。最初、Claude CodeはPowerShellのhere-stringで一括置換を試みたが、Windowsの引用符エスケープに引っかかって動かなかった。",[21,109,110],{},"ここで方針を切り替え、Python スクリプトで一括置換","。テンプレート文字列を直接読み込んで、各ファイルのコンポーネント呼び出し箇所に narrationConfig ブロックを挿入する、という単純な処理にしたら一発で通った。",[14,113,114,115,118],{},"PowerShellのhere-stringは、長文テンプレートを変数に入れて置換、というユースケースとは相性が悪い。今後同じことをやるなら",[21,116,117],{},"最初からPython","でいい、というのが今日の学び。",[10,120,122],{"id":121},"落とし穴2inv-nest-prefixのtypo","落とし穴2：INV-NEST prefixのtypo",[14,124,125,126,129],{},"ページ修正後、再度ブラウザで再生確認すると、investment-3 の冒頭問題で 404 が出た。コンソールを見ると ",[36,127,128],{},"/audio/INV-NEST-Q01.mp3"," を取りに行っているのにR2にはそのキーが存在しない、というエラー。",[14,131,132,133,136,137,144,145,148,149,152],{},"R2側のキーを確認したらprefixは ",[36,134,135],{},"NESTEGG"," だった。",[21,138,139,140,143],{},"ページに渡したprefixが ",[36,141,142],{},"INV-NEST"," でtypoしていた","。investment-1（",[36,146,147],{},"INV-PORT","）とinvestment-2（",[36,150,151],{},"INV-RISK","）は通っていたので、investment-3 だけ取り違えていた形。",[14,154,155,156,159],{},"Claude Codeが該当箇所を3秒で修正、再度ロードしたら通った。Claude Codeが書いたコードだから人間側が気づかない、ということではなく、人間が「音が出ないぞ」と耳で気づいた違和感をClaude Codeが原因特定・修正まで走る、という分担。",[21,157,158],{},"人間は耳と目で違和感を拾う係、AIが原因と修正を引き受ける係","。",[10,161,163],{"id":162},"ローカル-本番-で音声再生をフル確認","ローカル → 本番 で音声再生をフル確認",[14,165,166,167,170],{},"ローカル（",[36,168,169],{},"localhost:3200","）で auto モードに切り替えて開始ボタンを押したら、想定どおりの流れが回った:",[172,173,174,177,180,183,186,189],"ol",{},[58,175,176],{},"問題文MP3が再生",[58,178,179],{},"選択肢A〜C の読み上げが順に再生",[58,181,182],{},"「Bを選択」のクリック",[58,184,185],{},"「正解です」の音声が再生",[58,187,188],{},"解説MP3が再生",[58,190,191],{},"次の問題へ自動遷移",[14,193,194],{},"75問を全部聞いたわけではないが、investment-1/2/3 から数問ずつサンプリングして再生確認した。",[14,196,197],{},"その後、コミット→push→デプロイ（5.4分）→本番サイトで再度確認。本番ドメインから R2 にアクセスしている経路もエラーなく動作した。",[10,199,200],{"id":200},"セッション総括",[14,202,203,204,207],{},"セッション最後に、Claude Codeに ",[36,205,206],{},"memo/2026-05-16/00-session-summary.md"," としてセッション総括を書かせた。「何をやったか・何が躓いたか・次に再現するときの注意点」を書いておくと、明日以降に似たパイプラインを別コンテンツで走らせるときに参照できる。",[10,209,210],{"id":210},"このパイプラインを業務に応用するなら",[14,212,213],{},"会計士・税理士の業務アナロジーで考えると、以下のようなパイプラインに転用できる:",[55,215,216,222,228],{},[58,217,218,221],{},[21,219,220],{},"税務通達の要点を音声化"," → 移動中に通達アップデートを耳で追う",[58,223,224,227],{},[21,225,226],{},"クライアント月次報告のサマリ音声化"," → 報告書PDFと一緒に音声ファイルを納品",[58,229,230,233],{},[21,231,232],{},"会計監査チェックリストの読み上げ"," → 現場でハンズフリーで確認",[14,235,236,237,240],{},"要するに「テキストの一次情報 → 音声 → クラウドストレージ → 再生エンドポイント」という構造そのものは汎用で、コンテンツが投資判断クイズか税務文書かが変わるだけ。",[21,238,239],{},"人間は再生して耳で違和感を拾う係、AIが合成・変換・配置を担う係","、という分担は同じ。",[10,242,243],{"id":243},"明日以降の改善メモ",[55,245,248,258,264,270],{"className":246},[247],"contains-task-list",[58,249,252,257],{"className":250},[251],"task-list-item",[253,254],"input",{"disabled":255,"type":256},true,"checkbox"," narrationConfigのデフォルト値を親コンポーネント側に寄せ、ページ側で書かなくても動くようにする",[58,259,261,263],{"className":260},[251],[253,262],{"disabled":255,"type":256}," prefix のtypoを早期検出するため、R2に存在しないキーを叩いた瞬間にコンソールへ警告を出すフォールバックを入れる",[58,265,267,269],{"className":266},[251],[253,268],{"disabled":255,"type":256}," PowerShellでの一括置換はもう試さない。最初からPythonで書く",[58,271,273,275],{"className":272},[251],[253,274],{"disabled":255,"type":256}," WAV→MP3変換のビットレートを再検討（現在は標準設定、ファイルサイズと音質のバランスを見直す余地あり）",{"title":277,"searchDepth":278,"depth":278,"links":279},"",2,[280,281,282,283,284,285,286,287,288],{"id":12,"depth":278,"text":12},{"id":27,"depth":278,"text":28},{"id":75,"depth":278,"text":76},{"id":92,"depth":278,"text":93},{"id":121,"depth":278,"text":122},{"id":162,"depth":278,"text":163},{"id":200,"depth":278,"text":200},{"id":210,"depth":278,"text":210},{"id":243,"depth":278,"text":243},"dev","Part 2 投資判断クイズ75問の問題文・選択肢・解説・誤答パターンをVOICEVOXで音声合成し、MP3変換からCloudflare R2へのアップロード、Nuxtページへの組み込み、本番デプロイまでをClaude Codeに順次実行させた。PowerShellの罠やprefix typo、narrationConfig未渡しといった躓きも含めて記録","md",{},null,"/quiz-narration-pipeline","eurekapu-nuxt4",false,"2026-05-16T00:00:00.000Z",{"title":5,"description":290},"2026-05/2026-05-16/quiz-narration-pipeline",[301,302,303,304,305],"VOICEVOX","音声合成","Cloudflare R2","Nuxt 3","音声化パイプライン","1MdgYjHZTvDAGOTqMeDI3j-F-MSHx87rBXM7YkLXYjs",[],"https://log.eurekapu.com/og/blog/quiz-narration-pipeline.png?v=2026-05-16T00%3A00%3A00.000Z&title=%E6%8A%95%E8%B3%87%E5%88%A4%E6%96%AD%E3%82%AF%E3%82%A4%E3%82%BA75%E5%95%8F%E3%82%92VOICEVOX%E3%81%A7%E9%9F%B3%E5%A3%B0%E5%8C%96%EF%BC%9AWAV%E5%90%88%E6%88%90%E2%86%92MP3%E2%86%92Cloudflare%20R2%E3%81%BE%E3%81%A7%E4%B8%80%E6%B0%97%E9%80%9A%E8%B2%AB%E3%81%AE%E8%87%AA%E5%8B%95%E5%8C%96%E3%83%AD%E3%82%B0&author=Kei%20Komatsu&sig=b4888c6778d3c3b1",1782528837349]