◉GEMINI LABEN
●TTS — Gemini 3.8 Flash TTS と 3.8 Flash-Lite TTS が9月22日に一般提供になりました。Flash-Lite TTS は 3.1 Flash TTS preview の置き換えです●GEMINI2.5 — 2.5 系モデルは過去に使っていた利用者に限られるようになりました。非推奨ではなく、新しい開発には 3.5 Flash-Lite か 3.8 Flash が案内されています●9/30 — gemini-omni-flash-preview は9月30日に非推奨になります。残り5日です●SANDBOX — サンドボックスで動かす Gemini CLI が v0.60.0 以降は毎回ログインを求め、過去のセッションも再開できない、という報告が出ています●NEW — Gemini の下書きを送る前に、オレンジの文だけ見直す●CONFIG — Antigravity 2.17.0 はリポジトリ設定を .gemini/config.json から読むようになり、Gemini CLI と置き場所を共有する形になりました●TTS — Gemini 3.8 Flash TTS と 3.8 Flash-Lite TTS が9月22日に一般提供になりました。Flash-Lite TTS は 3.1 Flash TTS preview の置き換えです●GEMINI2.5 — 2.5 系モデルは過去に使っていた利用者に限られるようになりました。非推奨ではなく、新しい開発には 3.5 Flash-Lite か 3.8 Flash が案内されています●9/30 — gemini-omni-flash-preview は9月30日に非推奨になります。残り5日です●SANDBOX — サンドボックスで動かす Gemini CLI が v0.60.0 以降は毎回ログインを求め、過去のセッションも再開できない、という報告が出ています●NEW — Gemini の下書きを送る前に、オレンジの文だけ見直す●CONFIG — Antigravity 2.17.0 はリポジトリ設定を .gemini/config.json から読むようになり、Gemini CLI と置き場所を共有する形になりました
記事一覧/最新情報
◎ 最新情報/2026-09-26中級

3.8 Flash-Lite TTS への置き換えで、案内音声の声を 150 を超える候補から選び直すまで

9 月 22 日に GA になった gemini-3.8-flash-lite-tts は、3.1 Flash TTS プレビューの置き換えです。ヒーリング音源アプリの案内音声で使っていたプレビュー名を差し替え、新しい /v1beta/voices から声を選び直した一週間の記録を書き残します。

Gemini API242TTS4Gemini 3.8個人開発120運用14

9 月 22 日の夜、Gemini API の changelog を読み返していて、手が止まりました。gemini-3.8-flash-tts と gemini-3.8-flash-lite-tts が GA になり、Flash-Lite TTS は gemini-3.1-flash-tts-preview の置き換えだと明記されていたのです。

その preview の名前を、私は覚えがありました。個人開発で運用しているヒーリング音源アプリの、再生前に流れる短い案内音声——「画面を伏せて、呼吸に合わせてください」といった数十秒の一言——を生成する小さなスクリプトの中です。半年前に一度声を決めて、それきり触っていませんでした。

プレビューのモデルは、いつか名前ごと消えます。置き換え先が公式の言葉で示された週は、動いているものを動かす理由としては十分でした。ここから先は、その一週間で私が実際にやったことと、途中で引き返したことの記録です。

置き換え先は「同じ名前の GA 版」ではありませんでした

最初にお伝えしたいのは、置き換えの対応関係です。3.1 Flash TTS プレビューの後継は 3.8 Flash TTS ではなく、Flash-Lite TTS のほうだと changelog は書いています。上位の 3.8 Flash TTS には、音声デザインや、同意確認つきの声の複製といった新しい機能が乗っています。

案内音声に求めているのは、落ち着いた声で、決まった文を、毎回同じ調子で読むことだけです。声を似せたい誰かもいませんし、感情を揺らす必要もありません。要するに私の用途は Lite で足りるのです——というより、機能が増えたモデルを選ぶと、増えた分の判断が自分に返ってきます。

ここで一度、机の上で線を引きました。短く固定された案内文は Flash-Lite TTS、長い読み物や表現が要るものは 3.8 Flash TTS。この線は今後も動かさないつもりです。

用途私が選んだモデル理由
アプリ内の案内音声(数十秒・定型文)gemini-3.8-flash-lite-tts3.1 Flash TTS プレビューの正式な置き換え先で、必要な機能が揃っている
記事の朗読・複数話者・声の演出gemini-3.8-flash-tts音声デザインや声の複製は、こちらにしかない

モデル名を書き換える前に、呼び出し箇所を数えました

書き換え自体は一行です。けれど一行だと分かっているときほど、私は先に数えるようにしております。半年前の自分が、どこに何を書いたか信用できないからです。

# 案内音声を生成するリポジトリの中で、preview の名前が残っている場所を洗い出す
grep -rn "flash-tts-preview\|flash-preview-tts" --include="*.py" --include="*.json" --include="*.env*" .

出てきたのは 2 箇所でした。生成スクリプトの定数と、.env.example の見本です。見本のほうは見落としやすく、次に環境を作り直す自分が古い名前をそのまま写す罠になります。両方を gemini-3.8-flash-lite-tts へ揃え、モデル名は環境変数から読む形に寄せました。

import os
 
# モデル名はコードに埋めず、環境変数で差し替えられるようにしておく
TTS_MODEL = os.environ.get("TTS_MODEL", "gemini-3.8-flash-lite-tts")

なぜ環境変数に出すのかというと、次に置き換えが来たとき、grep から始めずに済むからです。今回の grep は、そのための最後の grep にしたいと思っております。

声は /v1beta/voices を、まず生のまま眺めました

モデルを替えるだけなら、声の名前はそのままでも動いたはずです。ところが今回は、既製音声が 150 以上になり、/v1beta/voices というエンドポイントから一覧を引けるようになっていました。半年前は片手で数えられる候補から選んだ声です。選び直す機会として、これ以上のものはないと感じました。

私はドキュメントの表を読む前に、まず一覧を JSON のまま保存しました。フィールド名を推測して書いたコードは、たいてい最初の実行で KeyError を返すのです。

import json
import os
import urllib.request
 
# 一覧をそのまま保存し、フィールド名を自分の目で確かめてから絞り込みを書く
API_KEY = os.environ["GEMINI_API_KEY"]  # 実際の値は環境変数から。コードに書かない
URL = "https://generativelanguage.googleapis.com/v1beta/voices"
 
req = urllib.request.Request(URL, headers={"x-goog-api-key": API_KEY})
with urllib.request.urlopen(req, timeout=30) as res:
    raw = json.load(res)
 
with open("voices_raw.json", "w", encoding="utf-8") as f:
    json.dump(raw, f, ensure_ascii=False, indent=2)
 
voices = raw.get("voices", [])
print(f"{len(voices)} voices")
for v in voices:
    # フィールドを決め打ちしない。どんな項目があるかを一行で眺める
    print(json.dumps(v, ensure_ascii=False)[:160])

一覧が手に入ると、次は絞り込みたくなります。ここで一度、失敗しました。説明文に「calm」や「warm」が入っている声だけを機械的に抜いたところ、候補はそれなりに減ったものの、聴いてみると日本語の語尾が硬いものが混ざっていたのです。言葉のタグは、耳の代わりにはなりませんでした。そう気づいたのは、候補を半分ほど聴き進めたあとでした。

同じ一文を、候補の声で順に聴き比べました

そこで絞り込みの基準を変えました。タグで落とすのは最初の一段だけにして、残った候補には全部、同じ案内文を読ませて WAV に書き出し、自分の耳で聴くことにしたのです。読ませる文は、アプリで実際に流している一文にしました。

import base64
import os
import struct
import wave
from google import genai
from google.genai import types
 
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
TTS_MODEL = os.environ.get("TTS_MODEL", "gemini-3.8-flash-lite-tts")
 
# アプリで実際に流している案内文。候補の声すべてに同じ文を読ませる
GUIDE_TEXT = "画面を伏せて、ゆっくりと呼吸に合わせてください。"
CANDIDATES = ["Kore", "Charon", "Aoede", "Leda"]  # 一段目の絞り込みで残した声を並べる
 
 
def rate_from_mime(mime: str, default: int = 24000) -> int:
    # 例: "audio/L16;codec=pcm;rate=24000" — サンプルレートは応答から読む
    for part in mime.split(";"):
        part = part.strip()
        if part.startswith("rate="):
            return int(part.split("=", 1)[1])
    return default
 
 
def synth_to_wav(voice: str, text: str, path: str) -> None:
    res = client.models.generate_content(
        model=TTS_MODEL,
        contents=text,
        config=types.GenerateContentConfig(
            response_modalities=["AUDIO"],
            speech_config=types.SpeechConfig(
                voice_config=types.VoiceConfig(
                    prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=voice)
                )
            ),
        ),
    )
    part = res.candidates[0].content.parts[0]
    pcm = part.inline_data.data
    if isinstance(pcm, str):  # SDK の版によって bytes ではなく base64 文字列で届く
        pcm = base64.b64decode(pcm)
    rate = rate_from_mime(part.inline_data.mime_type or "")
    with wave.open(path, "wb") as w:
        w.setnchannels(1)
        w.setsampwidth(2)  # 16bit
        w.setframerate(rate)
        w.writeframes(pcm)
 
 
for name in CANDIDATES:
    out = f"guide_{name}.wav"
    synth_to_wav(name, GUIDE_TEXT, out)
    print("wrote", out)

サンプルレートを応答の mime_type から読んでいるのは、以前の自分への戒めです。TTS の出力は 24kHz・16bit・モノラルの PCM で、それを 44.1kHz と決め打ちしてヘッダを書いた夜に、声が早口の別人になった経験があります。ストリーミングで受け取るときの連結とヘッダの順序は、Gemini TTS を streamGenerateContent で受け取り、先頭のチャンクから鳴らすに書き残したとおりです。

聴き比べは、深夜にヘッドホンで、同じ一文を続けて流す形で行いました。数十秒の案内文だからこそ、語尾の落ち方ひとつで印象が変わります。最後に残ったのは、以前の声とは別の一本でした。

引き返した二つのこと

一つ目は、声の複製です。3.8 Flash TTS 側には同意確認つきで声を複製する機能が入りましたが、案内音声に誰かの声を借りる理由が私にはありませんでした。既製の声で足りるところに新機能を持ち込むと、説明しなければならない事柄が一つ増えるだけなのです。

二つ目は、声を替えるタイミングです。新しい声のほうが好みだと分かっても、既にアプリで流れている音声をその日のうちに差し替えるのはやめました。声はアプリの一部として覚えられていますので、アプリの次回更新に合わせ、更新内容に一行添えて替えることにしています。モデルの置き換えは今日、声の置き換えは次の更新日。 二つを分けたことで、肩の荷がひとつ下りました。

次の一歩

もし手元のコードに gemini-3.1-flash-tts-preview が残っているなら、まず grep で数えるところから始めていただければと思います。置き換え先は Flash-Lite TTS で、声は /v1beta/voices から選び直せます——半年前より、選ぶ自由は増えています。

長い記事を割って繋ぎ、一本のナレーションにする側の話は、プレビュー時代に書いたGemini 3.1 Flash TTS プレビューで記事を音声化するに、コストの見積もりまで含めて残しております。モデル名を読み替えていただけば、そのまま今の構成に載せられるはずです。

私自身は、数十秒の案内文という小さな用途から手を付けました。小さいからこそ、聴き比べを最後までやり切れたのだと、いまは感じています。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

◎ 最新情報2026-09-10
9月14日の iOS 27 で、Gemini API の呼び出しは1行も変わりません — 変わるのは選択肢のほうです
iOS 27 の配信は9月14日です。新しい Siri が Gemini をベースにしていても、自分のアプリが叩いている Gemini API は変わりません。混ざりやすい三つの層を呼び出し口で分ける方法と、配信日までに済ませておく手順をまとめます。
◎ 最新情報2026-07-04
8月17日の Gemini 画像生成モデル停止に備える — 移行先を選ぶ前に『どこから呼んでいるか』を洗い出す
8月17日に提供終了となる Gemini 画像生成モデルへの備えとして、移行先を選ぶ前の棚卸し手順をまとめました。grep では漏れる3つの盲点、実行ログから実際に呼ばれているモデルを集計する方法、移行優先度の付け方、モデル ID の一元管理までを扱います。
◎ 最新情報2026-09-03
Wear OS と Android Auto では、失敗も声で返す必要がありました
9月4日から Google アシスタントが Gemini へ置き換わります。手の塞がる Wear OS と Android Auto では、失敗をトーストで伝えられません。メディアセッションのエラー状態として返す実装と、切り替え前に決めておくことを書き残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます