🔬 不器用パパの休日

声を3つ試して気づいたこと

3つのTTSエンジンを試して、ひとつの真実にぶつかった。

「正確に読ませる」と棒読みになる。「自然に読ませる」と漢字を間違える。

OCRでテキストを取り出し、LLMで校正した次のステップが「声」だった。テキストを音声に変換するTTS(Text-to-Speech)。クラウドサービスなら選択肢は山ほどあるが、自分のプロジェクトは「ローカルで完結」が大前提。手持ちの小説を外部サーバーに送るわけにはいかない。

ローカルで動く3つに絞って、同じ文章を読ませて聴き比べた。結果は1枚の図にまとまる。

TTS3エンジン比較 — 正確さと自然さのトレードオフ

同じ「吾輩は猫である」を読ませてみた

夏目漱石の冒頭をサンプルにした。3つのエンジンに同じテキストを渡し、peak-normalizedして揃えた音声がこちら。

VOICEVOX(ずんだもん / ノーマル)

Style-Bert-VITS2(default / Neutral)

Qwen3-TTS(Ono_Anna)

聴き比べると、最初の段落で違いがはっきり出る。VOICEVOXは1音1音が正確だが平坦。Qwen3-TTSは「人間が読んでいる」感じだが、漢字を時々取り違える。SBV2はその中間に座る。

やったこと

3エンジンをDockerで構築

エンジン動作ポートセットアップ難度
VOICEVOX EngineCPU50021低(公式イメージ)
Style-Bert-VITS2CPU8000中(依存固定地獄)
Qwen3-TTS 1.7BGPU必須8001高(修正4回)

VOICEVOXは公式Dockerイメージでほぼ即動いた。SBV2は numpy 1.26.4setuptools <76transformers <5 の三重バージョン縛りに苦戦。

問題はQwen3-TTSだった。

Qwen3-TTSのDockerfile沼

NGC PyTorchコンテナ(25.03-py3)の上に構築したが、Dockerfileの修正が4回必要だった。

  1. torchaudioのバージョン解決不能 → --no-deps でバイパス
  2. soxモジュール未インストール → 依存追加
  3. CUDA 12.6/12.8バージョン不一致 → cu128ビルド取得
  4. torchaudioの .so がABIミスマッチ → .so ファイルを削除してPython fallbackを強制

特に4番目は衝撃だった。NGC PyTorchの独自ビルドが aoti_torch_abi_version シンボルを公開していないため、torchaudioのC++拡張が起動できない。解決策は「.soファイルを消す」という力技。

「ローカルで動かす」と言うのは簡単だが、新しめのGPU(RTX 5090 / sm_120)と新しめのモデルを組み合わせると、ビルドの整合性で詰むことが多い。

結果

3エンジンの特性まとめ

項目VOICEVOXSBV2Qwen3-TTS
読みの正確さ◎(辞書ベース)△(AI推測)
自然さ△(機械的)◎(人間に近い)
速度(374字)9.1秒18.5秒138.4秒
504頁換算約40分約80分約10時間
GPU必要性不要不要必須
テキスト上限制限なし100文字500文字

Qwen3-TTSはVOICEVOXの15倍遅い。504ページの小説を通しで処理すると10時間。品質は高いが、実用面のハンデは大きい。

結局、漢字の読み間違いが核心だった

聴き比べてわかった品質問題の正体は、ほぼ「漢字の読み間違い」に集約される。

SF小説には「宙賊」「魔導具」「転移門」のような造語が頻出する。VOICEVOXは辞書ベースなので、登録すれば確実に正しく読む。Qwen3-TTSはAI推論で読みを推測するが、推測が外れる。

対策として tts_yomi_dict.json(読み仮名辞書)を構築した。OCRのルビ情報から自動抽出し、手動で作品固有の用語を追加。tts_preprocess.py で漢字をひらがなに変換してからTTSに渡す。

ただし副作用がある。「とうだ」が「倒打」なのか「問うた」なのか、ひらがなだけでは区別できない。同音異義語が多い日本語では、ひらがな化は万能薬にならない。

エンジン選択の指針

「正確さ」と「自然さ」のトレードオフを受け入れたうえで、こう使い分けることにした。

  • 正確に読ませたい → VOICEVOX + 辞書
  • 自然に聴かせたい → Qwen3-TTS(読み間違いは許容)
  • バランスを取りたい → SBV2

パイプラインは runtime.allowed.json で22のボイスプリセットを管理し、ジョブごとにエンジンを切り替えられる設計にした。1冊の本で1つに固定する必要はない。

うまくいった点

  • マルチエンジン対応の設計tts.sh がエンジンを動的に切り替える仕組みは正解だった。allowlistでボイス→エンジンの対応を定義すれば、将来「台詞はQwen3-TTS、地の文はVOICEVOX」のような使い分けもできる
  • tts_yomi_dict.jsonの自動構築 — OCRのルビ情報を辞書に自動登録する仕組みは、作品固有の造語対策として効果的。YomiTokuがルビを読み取る精度がそのままTTSの読み正確さに繋がる
  • チャンク分割合成 — SBV2のテキスト上限100字をチャンク分割→個別合成→ffmpegで結合して回避
  • pre-flightチェック — 各エンジンの起動確認を tts.sh 冒頭で実施。エンジン未起動のまま処理を走らせて時間を無駄にする事故を防いでいる

失敗・課題

  • Qwen3-TTSのDockerfile地獄.so 削除はNGC更新やtorchaudioのメジャーアップで壊れる前提のハック。保守コストが高い
  • SBV2の聴感評価が不完全 — 比較が完了したのはVOICEVOXとQwen3-TTSのみで、SBV2は評価が中途半端なまま先に進んでしまった
  • 読み仮名辞書の限界 — 同音異義語の区別ができない。辞書登録もどうしても手作業が残る。完全自動化はここでも壁に当たった
  • GPUの排他問題 — Qwen3-TTSとOllama(qwen3:32b、20GB)の同時実行はVRAM 32GBでも厳しい。順番実行になるので並列化できない

次にやること

3つのエンジンを試して、どれも一長一短だった。Qwen3-TTSの「自然だが遅い」も、全エンジン共通の「漢字読み間違い」も、根の深い問題として残っている。

次回は、この音質問題にどうアプローチしたか——ComfyUIによるTTS統合と、Whisperによる音声品質の自動検証について書く。

音声素材クレジット


この実験で使った機材 【PR】

ZOTAC GAMING GeForce RTX 5090 SOLID — Qwen3-TTS 1.7BのGPU推論に使用。VOICEVOX・SBV2はCPU動作なのでGPUなしでも試せる