子ども向けサイトの読み上げに Gemini TTS と Google Cloud TTS を試した話

子ども向けサイトの読み上げに Gemini TTS と Google Cloud TTS を試した話

作成日:
更新日:

子ども向けの教育Webサイトを2つ運営しています。ひとつはどうぶつ・たべもの・のりものなどを学べるインタラクティブ図鑑、もうひとつはピクサー映画カーズのキャラクター図鑑です。

どちらもカードをタッチすると名前を読み上げる機能があるのですが、その音声品質を改善するために Web Speech API → Google Cloud Text-to-Speech → Gemini TTS と3つの手段を試した記録です。


なぜ音声読み上げが必要なのか

対象ユーザーはまだ文字が読めない幼児〜小学校低学年。画像と音声がセットになることで、ひらがなの読みや語彙の獲得を自然に促せます。

  • 画像をタッチ → 名前が読み上げられる → 「これは"きりん"って言うんだ!」
  • カーズのキャラクターをタッチ → 名前と説明文が読まれる → 映画をもっと楽しめる

音声の安定した品質自然さは、子どもの集中力や学習意欲に直結します。


手段 1: Web Speech API(ブラウザ標準)

概要

APIを使わない場合、OSやブラウザに内蔵された音声合成エンジンを使うことになります。JavaScriptの SpeechSynthesisUtterance を使えば、サーバー不要・コスト0円で読み上げが実現できます。

const utterance = new SpeechSynthesisUtterance("きりん");
utterance.lang = "ja-JP";
utterance.rate = 0.9;  // ゆっくりめ
utterance.pitch = 1.1; // 少し高めの声
 
// 高品質なネットワーク音声があれば優先
const voices = window.speechSynthesis.getVoices();
const networkVoice = voices.find(
  (v) => v.lang.startsWith("ja") && !v.localService
);
if (networkVoice) utterance.voice = networkVoice;
 
window.speechSynthesis.speak(utterance);

メリットとデメリット

項目内容
コスト無料
実装コスト低(クライアントのみで完結)
音質OS/ブラウザ依存でばらつく
安定性デバイスごとに挙動が異なる

実際に使ってみた感想

macOS + Chrome では「Google 日本語(オンライン)」という比較的高品質な音声が利用できましたが、問題は環境依存です。

  • macOS Safari: Appleの機械的な音声のみ。品質にかなり差がある
  • iOS Safari: ユーザー操作なしに発話できない制限あり
  • Android: デバイスメーカーによって音声が全く異なる

つまり、「自分の環境ではOK」でもユーザーの環境では全然違う音質になる。子ども向けサイトで「端末によって声が変わる」のは体験として良くないため、API移行を決めました。


手段 2: Google Cloud Text-to-Speech(Chirp 3: HD)

概要

Googleが提供するクラウドTTSサービスです。REST APIにテキストを投げると、高品質な音声データが返ってきます。

POST https://texttospeech.googleapis.com/v1/text:synthesize?key={API_KEY}

APIの呼び出しコード

const VOICE = "ja-JP-Wavenet-B";
 
async function generateMp3(text: string, apiKey: string): Promise<Buffer> {
  const res = await fetch(
    `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        input: { text },
        voice: {
          languageCode: "ja-JP",
          name: VOICE,
        },
        audioConfig: {
          audioEncoding: "MP3",
          speakingRate: 0.9,
          pitch: 1.0,
        },
      }),
    }
  );
 
  if (!res.ok) {
    throw new Error(`API エラー ${res.status}: ${await res.text()}`);
  }
 
  const json = await res.json();
  return Buffer.from(json.audioContent, "base64");
}

レスポンスはBase64エンコードされたMP3データ。デコードすればそのまま再生可能です。

料金体系

音声タイプ無料枠超過後
Standard月400万文字$4/100万文字
WaveNet月100万文字$16/100万文字
Neural2月100万文字$16/100万文字
Chirp 3: HD月100万文字$30/100万文字

子ども向けサイトの読み上げ程度なら、無料枠内で十分収まります。

バッチ生成スクリプト

毎回APIを叩くのは非効率なので、全アイテムの音声を事前にMP3ファイルとして生成しておくスクリプトを作りました。

const items = getAllItems(); // 全211アイテム
const total = items.length;
 
for (let i = 0; i < items.length; i++) {
  const { text, filePath } = items[i];
 
  // 既存ファイルはスキップ
  if (existsSync(filePath)) {
    console.log(`[${i+1}/${total}] スキップ`);
    continue;
  }
 
  const mp3 = await generateMp3(text, apiKey);
  await writeFile(filePath, mp3);
 
  // レート制限対策
  await new Promise(r => setTimeout(r, 200));
}

一度生成すれば静的ファイルとして配信できるため、ランタイムでのAPI呼び出しは不要。コストも初回生成の1回分だけです。

実際の感想

WaveNet・Chirp 3: HD どちらも品質は十分。Web Speech APIとは雲泥の差で、環境に依存しない安定した品質を提供できます。ただし、「子どもに読んであげている」ような温かみのある声かというと、まだ少し機械的に感じる場面もありました。


手段 3: Gemini TTS(本命)

概要

Googleの生成AIモデル Gemini にTTS機能が搭載されました。従来のTTSとは異なり、LLMが音声を生成するため非常にナチュラルな発話が特徴です。

POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent

APIの呼び出しコード

async function generateWithGemini(
  text: string,
  apiKey: string,
  voice: string = "Kore"
): Promise<Buffer> {
  const res = await fetch(
    `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-goog-api-key": apiKey,
      },
      body: JSON.stringify({
        contents: [{
          parts: [{
            text: `次のテキストをそのまま読み上げてください:${text}`
          }]
        }],
        generationConfig: {
          responseModalities: ["AUDIO"],
          speechConfig: {
            voiceConfig: {
              prebuiltVoiceConfig: { voiceName: voice },
            },
          },
        },
      }),
    }
  );
 
  if (!res.ok) throw new Error(`Gemini API エラー: ${await res.text()}`);
 
  const json = await res.json();
  const b64 = json.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
  if (!b64) throw new Error("音声データが取得できませんでした");
 
  // Gemini TTSはPCM(24kHz, mono, 16bit)を返すのでWAVヘッダーを付与
  const pcm = Buffer.from(b64, "base64");
  return Buffer.concat([buildWavHeader(pcm.byteLength), pcm]);
}

Cloud TTSとの違い: WAVヘッダーの手動構築

Gemini TTSはMP3ではなく生のPCMデータをBase64で返します。ブラウザで再生するには自前でWAVヘッダーを付ける必要があります。

function buildWavHeader(pcmByteLength: number): Buffer {
  const sampleRate = 24000;
  const channels = 1;
  const bitsPerSample = 16;
  const byteRate = (sampleRate * channels * bitsPerSample) / 8;
  const blockAlign = (channels * bitsPerSample) / 8;
 
  const header = Buffer.alloc(44);
  header.write("RIFF", 0);
  header.writeUInt32LE(36 + pcmByteLength, 4);
  header.write("WAVE", 8);
  header.write("fmt ", 12);
  header.writeUInt32LE(16, 16);     // fmt チャンクサイズ
  header.writeUInt16LE(1, 20);      // PCM
  header.writeUInt16LE(channels, 22);
  header.writeUInt32LE(sampleRate, 24);
  header.writeUInt32LE(byteRate, 28);
  header.writeUInt16LE(blockAlign, 32);
  header.writeUInt16LE(bitsPerSample, 34);
  header.write("data", 36);
  header.writeUInt32LE(pcmByteLength, 40);
 
  return header;
}

この44バイトのヘッダー構築は外部ライブラリ不要で実装できます。

プリセット音声(8種類)

Gemini TTSには8つのプリセット音声が用意されています。

音声名性別日本語での印象
Aoede女性明るく聞き取りやすい
Kore女性落ち着いた標準的なトーン
Leda女性柔らかく優しい
Zephyr女性さわやかで軽快
Charon男性低めで落ち着き
Fenrir男性力強い
Orus男性深みのある声
Puck男性軽快で親しみやすい

子ども向けコンテンツには Kore(落ち着いた女性の声)や Leda(柔らかく優しい声)が適していました。

スタイル制御

Gemini TTSには speakingRatepitch のような数値パラメータがありません。代わりに、プロンプトの指示文で読み方を制御できます。

「ゆっくりと優しく読んでください:きりん」
「元気よく明るく:ライトニング・マックィーン」

LLMならではのアプローチです。数値で微調整するというよりも、自然言語で「こう読んでほしい」と伝える形です。

Geminiの読み上げはナチュラル!

実際に使い比べて一番驚いたのが、Gemini TTSの自然さです。Cloud TTSのWaveNetやChirp 3: HDと比べても、明らかに「人が読んでいる」ように聞こえます。

  • 抑揚が自然で、機械的な棒読み感がない
  • 日本語の固有名詞の読み方が正確(「マックィーン」を正しく発音する)
  • 文の区切りやイントネーションが自然

子どもに聞かせたとき、Cloud TTSでは「機械が喋ってる」と言われることがありましたが、Gemini TTSではそのような反応がなくなりました

ただし、課題もある

Gemini TTSは現時点でプレビュー段階のため、いくつかネックがあります。

1. レートリミット

HTTP 429: Resource has been exhausted

短時間に連続してリクエストすると、すぐにレートリミットに引っかかります。バッチ生成スクリプトでは 2秒のウェイト を入れて対処しましたが、それでもクォータ超過で中断されることがありました。

const DELAY_MS = 2000; // リクエスト間の待機時間(レートリミット対策)
 
// クォータ超過時は翌日に再実行
if (res.status === 429) {
  console.log("クォータ超過。明日以降に再実行してください");
  process.exit(0);
}

2. 従量課金

項目料金
テキスト入力$0.30/100万トークン
音声出力$2.50/100万トークン

Cloud TTSと比べると「文字数」ではなく「トークン数」での課金になるため、コスト比較が難しい面があります。音声出力のトークン料金は決して安くないため、キャッシュの活用が必須です。


実装アーキテクチャ: サーバーサイドキャッシュ

Gemini TTSのレートリミットとコスト問題に対処するため、Next.js API Route + ファイルキャッシュのアーキテクチャを採用しました。

ブラウザ(カードをクリック)
  ↓
POST /api/tts { text, provider: "gemini", voice: "Kore" }
  ↓
Next.js API Route
  ├── キャッシュあり → .cache/tts/{hash}.wav を即座に返却
  │
  └── キャッシュなし → Gemini API を呼び出し
                        ↓
                      PCMデータ(Base64)
                        ↓
                      WAVヘッダーを付与
                        ↓
                      .cache/tts/{hash}.wav に保存 → 返却

キャッシュキー

テキスト・プロバイダー・音声名の組み合わせをSHA-256でハッシュ化しています。

function cacheFilePath(provider: string, voice: string, text: string): string {
  const hash = createHash("sha256")
    .update(`${provider}:${voice}:${text}`)
    .digest("hex");
  const ext = provider === "gemini" ? "wav" : "mp3";
  return path.join(CACHE_DIR, `${hash}.${ext}`);
}

同じテキスト×声の組み合わせは常に同じ音声になるため、キャッシュに有効期限は不要です。一度生成すれば二度とAPIを叩く必要がありません。

クライアント側の再生

const res = await fetch("/api/tts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ text, provider: "gemini", voice: "Kore" }),
});
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.onended = () => URL.revokeObjectURL(url);
audio.play();

APIキーはサーバーサイドのみで保持し、クライアントには一切公開しません。


3つの手段の比較まとめ

Web Speech APICloud TTS (Chirp 3: HD)Gemini TTS
音質環境依存高品質・安定非常に自然
コスト無料月100万文字まで無料トークン従量課金
安定性デバイスごとに異なる一定一定
レートリミットなし緩め厳しい(プレビュー)
認証不要サービスアカウント/APIキーAPIキー
出力形式ブラウザ直接再生MP3/WAVPCM(WAV変換必要)
速度・ピッチ調整数値パラメータ数値パラメータ自然言語プロンプト
おすすめ用途プロトタイプ・個人利用安定運用したいサービス音質重視のサービス

使い分けの指針

Web Speech API を選ぶべき場面

  • プロトタイプや個人利用
  • コストをかけたくない場合
  • 音質にこだわらない場合

Google Cloud TTS を選ぶべき場面

  • 安定した品質で多くのユーザーに提供したい場合
  • SSML(読み方のマークアップ)を細かく制御したい場合
  • 無料枠の範囲で運用したい場合

Gemini TTS を選ぶべき場面

  • ナチュラルな音声が最優先の場合
  • 読み上げるテキストの種類が限定的で、キャッシュ戦略が有効な場合
  • 今後のGA(一般提供)を見据えた先行投資として

今後の展望

Gemini TTSは現在プレビュー段階ですが、GAになればレートリミットの緩和と料金の最適化が期待できます。現時点のベストプラクティスは以下の通りです。

  1. Gemini TTSでキャッシュを事前生成(最高品質の音声を確保)
  2. キャッシュミス時はGoogle Cloud TTSにフォールバック(安定性を担保)
  3. 開発中・ローカル環境ではWeb Speech APIで手軽に確認

「APIを使うことで環境依存をなくし、安定した品質を提供できる」という基本と、「Gemini TTSの自然さは従来のTTSとは別次元」という発見が今回の大きな収穫でした。


参考リンク