個人開発でアプリを作っていると、ある日ふと「ここに声があったら伝わるのに」と思う瞬間があります。私の場合は、ガイド音声を読み上げる画面でした。文章はあるのに、それを淡々と読み上げるだけの既存 TTS では、どうにも事務的で温度が乗りません。
Gemini の TTS(音声合成)に切り替えてみようと思ったのは、この「温度が乗らない」という不満からでした。ここでは、実際に組み込む過程で固まった呼び出し方と、ドキュメントには太字で書かれていないつまずきどころを共有します。
従来の読み上げと何が違うのか
これまでの TTS は、テキストを音に変換するツールでした。Gemini の TTS は、テキストの意図をくみ取って読み上げる方向に寄っています。
具体的には、同じ一文でも「落ち着いて」「弾んだ調子で」とプロンプトで指示するだけでトーンが変わります。SSML タグを一つずつ書いて抑揚を作っていた頃と比べると、調整の入口がぐっと手前に来た感覚があります。
ニュース原稿のように淡々と読ませたい場面と、物語のナレーションのように間を効かせたい場面。この振り分けが、タグではなく自然言語でできるのは実装の負担を確かに軽くします。
まず一本、音を出すまでの最短経路
最新の Gemini は google-genai パッケージ(from google import genai)を使います。古い google.generativeai 系のサンプルが今もネットに多く残っていますが、こちらは順次非推奨になっているため、新規実装では新 SDK に寄せておくのが安全です。
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_API_KEY")
response = client.models.generate_content(
model="gemini-2.5-flash-preview-tts",
contents="次の文を落ち着いた声で読み上げてください: 本日は晴天なり。散歩には絶好の天気です。",
config=types.GenerateContentConfig(
response_modalities=["AUDIO"],
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name="Kore"
)
)
),
),
)
pcm = response.candidates[0].content.parts[0].inline_data.dataモデル名はプレビュー期に何度か変わってきました。品質重視の場面では Pro 系(gemini-2.5-pro-preview-tts)、速度とコスト重視なら Flash 系という棲み分けが基本ですが、正確な文字列はその時々で動きます。実装前に必ず公式の changelog で現行名を確認してください。プレビュー版のモデルはある日付で停止することがあり、固定名のべた書きは将来の障害の種になります。
最初のつまずき — 出てくるのは WAV ではなく生 PCM
ここが、私が最初に小一時間ハマった箇所です。
上のコードで取り出した inline_data.data を、そのまま output.wav という名前で書き出しても、多くのプレイヤーで再生できません。Gemini の TTS が返すのは WAV ファイルではなく、ヘッダのない生の PCM データ(24kHz・16bit・モノラル)だからです。拡張子だけ wav にしても中身は裸の波形なので、再生側が困ります。
正しくは、PCM にきちんと WAV ヘッダを付けてから保存します。
import wave
def save_as_wav(pcm_bytes, path, rate=24000, channels=1, width=2):
# Gemini TTS の出力は 24kHz・16bit・モノラルの生 PCM
with wave.open(path, "wb") as wf:
wf.setnchannels(channels)
wf.setsampwidth(width) # 16bit = 2 バイト
wf.setframerate(rate)
wf.writeframes(pcm_bytes)
save_as_wav(pcm, "output.wav")このひと手間を入れた瞬間に、無音だった出力がちゃんと声になりました。公式サンプルでも触れられてはいるのですが、エラーが出ず「無音の wav が生成される」という形で失敗するので、原因にたどり着くまでが地味に長いのです。
声とペーシングは「プロンプト+声色」の二段で決める
読み上げの印象は、声色(voice_name)とプロンプト指示の組み合わせで決まります。私は最初、声色だけをいくつも試して「どれもしっくり来ない」と悩みましたが、原因の半分はプロンプト側にありました。
声色は土台のキャラクター、プロンプトはその場の演技、と分けて考えると調整が早くなります。
contents = """次のガイド文を、聞き手をやさしく案内するトーンで読み上げてください。
句点では少し長めに間を取り、専門用語の前で一拍置いてください:
それでは、最初の設定を一緒に進めていきましょう。
画面の右上にある歯車のアイコンを押してください。"""「一拍置いて」「やさしく」のような演技指示を文章で添えるだけで、同じ声色でも印象が変わります。複数話者の対話を読み分けたい場合は multi_speaker_voice_config で話者ごとに声を割り当てられますが、まずは一人の声で温度を出すところから始めると、調整の勘所がつかみやすいと感じています。
運用コストは「文字数」より「秒数」で効いてくる
音声は、テキスト生成と費用感が違います。長い原稿をまるごと毎回合成していると、文字あたりは小さくても累積でじわじわ効いてきます。
私が実務で取った対策は、生成済みの音声をキャッシュすることでした。ガイド音声のように内容が固定の文言は、一度合成したら保存して使い回す。動的に変わる部分だけを都度合成する。この線引きをするだけで、月の API 費用は体感で大きく下がりました。広告(AdMob)収益で回している個人開発のアプリでは、この差がそのまま運営の余裕につながります。
リアルタイム性が必要ない読み上げは、ユーザーがアクセスするたびに合成する必要はありません。「いつ合成するか」を設計に組み込むことが、TTS をアプリに載せ続けられるかどうかの分かれ目になります。
TTS を入れないほうがよい場面
最後に、入れなかった判断も書いておきます。
ごく短い通知音や、一文字単位で頻繁に変わるテキストには、Gemini TTS は向きませんでした。合成のレイテンシがそのまま体感の遅さになりますし、コストも見合いません。こうした場面では、端末側の OS 標準 TTS で十分でした。
表現力が要るナレーション・ガイド・物語の読み上げにこそ Gemini TTS の良さが出ます。何でも置き換えるのではなく、「温度が要る場所」に絞って使うのが、私なりの現実的な使いどころです。
次に試すなら
まずは手元で一文を gemini-2.5-flash-preview-tts に渡し、生 PCM を WAV に整えて再生するところまでを通してみてください。音が出たら、声色を二つ三つ替えて、同じ文がどう変わるかを耳で確かめる。この最小ループが回り始めれば、あとはプロンプトの演技指示を足しながら、自分のアプリに合う声を詰めていくだけです。
同じように「ここに声があれば」と感じている方の、最初の一歩の参考になれば幸いです。