
子ども向けサイトの読み上げに 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には speakingRate や pitch のような数値パラメータがありません。代わりに、プロンプトの指示文で読み方を制御できます。
「ゆっくりと優しく読んでください:きりん」
「元気よく明るく:ライトニング・マックィーン」
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 API | Cloud TTS (Chirp 3: HD) | Gemini TTS | |
|---|---|---|---|
| 音質 | 環境依存 | 高品質・安定 | 非常に自然 |
| コスト | 無料 | 月100万文字まで無料 | トークン従量課金 |
| 安定性 | デバイスごとに異なる | 一定 | 一定 |
| レートリミット | なし | 緩め | 厳しい(プレビュー) |
| 認証 | 不要 | サービスアカウント/APIキー | APIキー |
| 出力形式 | ブラウザ直接再生 | MP3/WAV | PCM(WAV変換必要) |
| 速度・ピッチ調整 | 数値パラメータ | 数値パラメータ | 自然言語プロンプト |
| おすすめ用途 | プロトタイプ・個人利用 | 安定運用したいサービス | 音質重視のサービス |
使い分けの指針
Web Speech API を選ぶべき場面
- プロトタイプや個人利用
- コストをかけたくない場合
- 音質にこだわらない場合
Google Cloud TTS を選ぶべき場面
- 安定した品質で多くのユーザーに提供したい場合
- SSML(読み方のマークアップ)を細かく制御したい場合
- 無料枠の範囲で運用したい場合
Gemini TTS を選ぶべき場面
- ナチュラルな音声が最優先の場合
- 読み上げるテキストの種類が限定的で、キャッシュ戦略が有効な場合
- 今後のGA(一般提供)を見据えた先行投資として
今後の展望
Gemini TTSは現在プレビュー段階ですが、GAになればレートリミットの緩和と料金の最適化が期待できます。現時点のベストプラクティスは以下の通りです。
- Gemini TTSでキャッシュを事前生成(最高品質の音声を確保)
- キャッシュミス時はGoogle Cloud TTSにフォールバック(安定性を担保)
- 開発中・ローカル環境ではWeb Speech APIで手軽に確認
「APIを使うことで環境依存をなくし、安定した品質を提供できる」という基本と、「Gemini TTSの自然さは従来のTTSとは別次元」という発見が今回の大きな収穫でした。