製品ビデオ AI ナレーション:「なんか微妙」からプロのナレーター品質へ
このスキルは、実際のプロジェクトでの修正経験に基づいています。2分43秒の英語ハッカソン製品ビデオ(11文のナレーション、映像に合わせたカット)で、ユーザーから「ナレーションがまだなんか変」というフィードバックがありました。9つのバージョン、10種類以上のTTSエンジンと音色、較正された評価、大規模サンプル統計を経て、最終的に MOSS-TTSによる一括連続生成+内容検証による文分割+音声を損なわないクラシックな後処理 を採用し、ユーザーに承認されました。
評価方法は references/evaluation.md を、音響処理の取捨選択は references/acoustic-polish.md を、再利用可能なスクリプトは scripts/ ディレクトリを参照してください。
1. まず「変」の原因を診断する:一般的には音質ではなく、韻律の問題
[!IMPORTANT] 最近のTTSは音質で通常の録音を凌駕しています。UTMOS / Distill-MOSのスコアはクリーンな人間の朗読(LibriSpeech)よりも高いため、「変」の原因はほぼ 韻律 にあります。パブリックドメインのプロフェッショナルなナレーション(NASAドキュメンタリー、Demucsでボーカル分離)を参照として、実測値は以下の通りです。
| 特徴 | プロのナレーション | 一文ごとのクローンTTS(初期バージョン) |
|---|---|---|
| ピッチの変動(F0標準偏差、半音) | 4.6 | 2.7(平坦) |
| 文末の下降(半音) | 0.7 | 2.3(文末が沈む) |
| 話速(音節/秒) | 4.2 | 4.55(やや速い) |
| 平均ポーズ(秒) | 0.39 | 0.54 |
scripts/prosody_compare.py、scripts/end_fall.py、scripts/take_features.py を使って、候補と参照を同じ特徴セット上で比較し、印象ではなくデータで判断します。
2. 生成:一文ごとに生成せず、全文を一括で読ませる
- 一括連続生成を優先。一文ごとに生成すると、各文の抑揚、強弱、息継ぎがそれぞれ独立して始まるため、つなぎ合わせると「変」になります。TTSにナレーション全文を一度に読ませ、文と文の間は空行で区切ります。モデルが文脈を認識できるため、前後の文がつながります。本例ではMOSS-TTS v1.5のクローンモードを使用し、参照音声は約15秒の適切な口調のナレーションとし、4つの異なるランダムシードでそれぞれ生成しました。各生成は約2分10秒、全文の文字誤り率は2〜6%でした。
- 候補比較の結果(同一評価基準):
- MOSS一括連続生成:プロのナレーションとの韻律距離 0.29(最小)、欠陥も最小。
- Qwen3-TTS VoiceDesignによる一文ごとの生成(演技指示付き、複数回抽出後、韻律で選択):距離 0.37、自然さはMOSSと同等。
- OpenAI gpt-audio:距離 0.57。
- 一文ごとのクローン:距離 0.89。
- 既知の問題:
- MOSS-VoiceGenerator(音色を文章で記述)は長いテキストで約33秒で生成を停止するため、全文生成には使用できません。
- Gemini TTSは「AUDIO PROFILE / DIRECTOR'S NOTES / TRANSCRIPT」のセクション形式を使用する必要があり、そうしないと指示文も読み上げてしまいます。無料枠では各モデル1日10回の呼び出し制限があります。
- OpenAIの音色は評価でピッチ変動が小さい傾向がありました。商用AI音声を使用する場合、ビデオの最後にその旨を明記する必要があります。
- シードの選択は韻律特徴で判断:文字誤り率が許容範囲内で、プロの参照との距離が最小のシードを選択します。特定の一文の出来が悪い場合(例:最後のキャッチフレーズが息継ぎ音で終わるなど)、その文だけを同じ参照音声の別シードバージョンに置き換えることも可能です。
3. 文分割:タイムスタンプだけに頼らず、内容で検証する
一括生成されたオーディオは、映像に合わせるために一文ごとに分割する必要があります。scripts/align_split.py の手順:
- SenseVoiceで単語レベルのタイムスタンプを取得し、編集距離で原稿とアライメントします。
- 各二文間の無音部分でカットします。
- さらに内容で検証:カットポイント前後1.5秒以内の全ての無音区間で試し切りし、左右のセグメントを再認識して、両方の文字誤り率の合計が最小になる位置を選択します。
タイムスタンプだけに頼ると誤りが発生します。TTSは文の内部でポーズを入れることがあり(例:「Just | sentences.」)、逆に二文間で連続して読むこともあります。また、単語レベルのタイムスタンプは実際の発声開始より遅れます。検証なしでは、「does | it」や「Just | sentences」が次の文の先頭に切り取られてしまいます。
4. 後処理:定量化可能な問題のみ修正し、ピッチは触らない
| 処理 | 結論 |
|---|---|
句読点に基づくポーズの整形(pause_fix_en.py:フレーズ内0.10秒、カンマ0.25秒、コロン0.33秒、文末0.45秒;短いキャッチフレーズは0.8秒の一拍を維持) | 採用:短くするのみで長くせず、プロのナレーションのポーズに近づける |
ラウドネスの統一(各文-20 LUFS)、ハイパスフィルター70 Hz、300 Hzで-2 dB、軽度のデエッサー、6%の小さな部屋の残響(vo_post.py) | 採用:音質スコアは低下しない |
| リミッター | 非採用:Distill-MOSが約0.14低下。代わりに線形ゲイン制御でピークを管理 |
| WORLDボコーダーやPraat PSOLAで文末を上げる、ピッチ変動を全体的に拡大 | 放棄:特徴はプロに近づくが、厳格な評価スコアがむしろ低下(WORLDは強く加工するほど悪化、最強設定で-0.30、信頼区間[-0.61, -0.03]) |
原則:文末やピッチ変動の問題は、複数回生成して選択することで解決し、事後的にピッチを変更しない。
5. ミキシング:音楽が文間で「ポンピング」しないようにする
scripts/duck_curve.py:文間のポーズが2.5秒未満の場合、音楽は低く抑えたまま維持(-11 dB)。ダッキングは0.25秒前から開始し、復帰は0.9秒の1次極スムージングを使用します。元の0.3秒移動平均では、音楽が各ポーズで盛り上がってしまい、リスナーは「息継ぎしている」ように感じます。最終的なビデオは-14 LUFS、ピークは-1 dBFS以下。ミックス内のナレーションのASR文字誤り率は0〜6.5%。
6. 映像の尺合わせ
- ナレーションが映像に収まらない場合、アニメーション部分は全体を約5%遅くするか、画面録画部分の表示時間を延長します。人の声を圧縮してはいけません。
- カンマが多いとTTSが不自然に読む傾向があるため、意味を変えない範囲で表現を調整します。例えば「Here it is, live, on real footage」を「running live on real footage」に変更します。
7. リリース基準
- 原稿の一文ごとの文字誤り率が0.08以下であり、ミックス内での再テストも合格。
- 各文を個別にGeminiで欠陥チェック(毎回1文のみ聴取)。カット音、文字の読み飛ばし、読み間違い、文末の息継ぎ音がないこと。
- プロのナレーションとの韻律距離、および厳格な自然さテスト(1文ずつ、大規模サンプル、bootstrap信頼区間)で参照を下回らないこと。詳細は評価ドキュメントを参照。
- 最終的にユーザーが試聴して確認。AIによる評価は「変に聞こえない」ことを証明できません。次のセクションを参照。
8. 「AI評価」の教訓
- Geminiによるペア比較は使用不可:86%の確率で先に再生された方を選択し、再生順序を逆にすると逆の結論になる。
- 複数サンプルの一斉ランキングは不安定:同じ素材の2回のランキング結果が矛盾する。
- 参照なしのスコアは信頼できない:アマチュアの実際の録音は約5点で、TTSの約6点よりも低い。
- 使用可能な方法:プロのナレーションを参照とした1サンプルごとの厳格なテストと、大規模サンプルおよび信頼区間の組み合わせ。それでも「人間と判定されるか」という項目はノイズが大きい(同じオーディオで0/8の場合も3/8の場合もあり、プロの俳優でもAIと判定されることがある)。
- AI評価の意見は検証が必要:例えば「ミキシングがポンピングしている」という意見は、実測のゲインカーブと矛盾するため、測定値を優先すべき。