TTS(音声合成)で作った音声を聴いて、「機械っぽいな」と感じて使うのをやめた経験は、多くの開発者にあるのではないでしょうか。
2026年4月にリリースされた Gemini 3.1 Flash TTS は、その感覚を根本から覆してくれました。200種類以上の「感情タグ」を文中に埋め込むだけで、驚くほど自然な感情表現が生まれます。[excitement] や [whispers] といったタグを使うと、まるでプロのナレーターが録音したかのような音声が生成されるのです。
私がこのモデルを初めて試したのは、Artificial Analysis の TTS リーダーボードでスコア 1,211 という数字を見たときでした。実際に動かしてみると、その数字が単なる指標ではないことがすぐに分かりました。ここではその具体的な使い方と、ポッドキャスト量産パイプラインの構築方法をすべて公開します。
Gemini 3.1 Flash TTS が変えたこと — 感情タグという発明
従来のTTSとの決定的な違い
これまでのTTSモデルは「声の種類(ボイス)」と「速度・ピッチ」くらいしか制御できませんでした。つまり、感情的なニュアンスはほぼ放棄して使う前提だったのです。
Gemini 3.1 Flash TTS が持ち込んだのは、「自然言語で感情を指定する」という発想です。タグを文中に入れると、そのタグの感情に合わせて音声の質感が変わります。
よく使われるタグの例を挙げると:
[excitement]— 興奮、高揚感[whispers]— ささやき声[determination]— 決意、力強さ[amusement]— 楽しさ、くすくす笑い[nervousness]— 不安、緊張[curiosity]— 好奇心、探求心[sadness]— 悲しみ[enthusiasm]— 熱意、積極性
さらに [laughs] のような非言語的な表現も入れられます。70言語以上をサポートしており、日本語でも問題なく動作します。
音声リーダーボードのスコアが意味するもの
Artificial Analysis のスコア 1,211 は、2026年5月時点で TTS カテゴリの最上位クラスに位置します。このスコアは「人間が聴いて、どちらが自然に聞こえるか」のペアワイズ比較で決まるため、数字の差が実際のユーザー体験の差に直結しています。
重要なのは、このモデルが「高品質だが遅い」という従来のトレードオフを崩したことです。Flash という名の通り、レスポンスが速い。しかも価格は Vertex AI でも実用的な水準に設定されています。
感情タグの実践的な使い方 — コピペで動くコード集
まず環境を準備します。
pip install google-genai基本的な単一話者音声生成
import os
import base64
from google import genai
from google.genai import types
# クライアント初期化
client = genai.Client(api_key=os.environ["GOOGLE_AI_API_KEY"])
def generate_speech(text: str, voice_name: str = "Kore", output_path: str = "output.wav") -> None:
"""
テキストから音声を生成し、WAVファイルとして保存する。
感情タグを含むテキストを渡すと、タグに応じた感情表現が生成される。
Args:
text: 音声化するテキスト(感情タグ含む)
voice_name: ボイス名(Kore, Puck, Zephyr など)
output_path: 出力ファイルパス
"""
try:
response = client.models.generate_content(
model="gemini-3.1-flash-tts-preview",
contents=text,
config=types.GenerateContentConfig(
response_modalities=["AUDIO"],
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name=voice_name
)
)
)
)
)
# 音声データを取得してファイルに保存
audio_data = response.candidates[0].content.parts[0].inline_data.data
audio_bytes = base64.b64decode(audio_data)
with open(output_path, "wb") as f:
f.write(audio_bytes)
print(f"✅ 音声生成完了: {output_path} ({len(audio_bytes):,} bytes)")
except Exception as e:
print(f"❌ エラー: {e}")
raise
# 感情タグを使った実例
sample_texts = [
"[excitement] 今日は特別なお知らせがあります!信じられないかもしれませんが、本当のことです!",
"[determination] 困難な道のりですが、必ずやり遂げます。諦めることはしません。",
"[whispers] これは内緒の話ですが、実は先週からずっと準備していたんです。",
"[curiosity] 面白いですね。いったいどういう仕組みになっているのでしょうか?",
"[amusement] [laughs] それは予想外でした!まさかそんな展開になるとは。",
]
for i, text in enumerate(sample_texts):
generate_speech(text, output_path=f"sample_{i+1}.wav")このコードで最も重要なのは、感情タグが文章の「流れ」に影響する点です。[excitement] を文頭に置くと、その後の文全体が興奮気味のトーンになります。途中でタグを切り替えると、感情が変化するナチュラルな音声になります。
複数の感情を組み合わせる
# 複数感情を組み合わせた原稿の例
podcast_intro = """
[enthusiasm] Gemini Lab ポッドキャストへようこそ。
今週も AI の最前線から、使えるニュースをお届けします。
[curiosity] 今回のメイントピックは、Google が発表した最新の TTS モデル。
[excitement] これが本当に素晴らしくて、ナレーションの仕事が変わりそうです。
[neutral] まず、このモデルの基本的な仕組みから説明しましょう。
"""
generate_speech(podcast_intro, voice_name="Kore", output_path="podcast_intro.wav")実際に試してみると、感情の切り替えポイントが自然に聞こえることに驚かれるはずです。人間のアナウンサーが原稿を読んでいるときのような、文脈に応じた表情の変化が出ます。
多話者ポッドキャストの実装 — 対談形式コンテンツの自動化
ここからが本記事の核心です。1人の語りより、2人の対話の方がコンテンツとして明確に聴き応えがあります。Gemini 3.1 Flash TTS の多話者機能を使えば、対談形式のポッドキャストを完全自動生成できます。
2話者対談の実装
import os
import base64
from google import genai
from google.genai import types
client = genai.Client(api_key=os.environ["GOOGLE_AI_API_KEY"])
def generate_multi_speaker_podcast(
script: str,
host_voice: str = "Kore",
guest_voice: str = "Puck",
output_path: str = "podcast.wav"
) -> None:
"""
2話者の対談音声を生成する。
スクリプトフォーマット:
Speaker1: [emotion_tag] セリフ
Speaker2: [emotion_tag] セリフ
Args:
script: 対談スクリプト(Speaker1/Speaker2 形式)
host_voice: ホストの音声名
guest_voice: ゲストの音声名
output_path: 出力ファイルパス
"""
try:
response = client.models.generate_content(
model="gemini-3.1-flash-tts-preview",
contents=script,
config=types.GenerateContentConfig(
response_modalities=["AUDIO"],
speech_config=types.SpeechConfig(
multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
speaker_voice_configs=[
types.SpeakerVoiceConfig(
speaker="Speaker1",
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name=host_voice
)
)
),
types.SpeakerVoiceConfig(
speaker="Speaker2",
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name=guest_voice
)
)
)
]
)
)
)
)
audio_data = response.candidates[0].content.parts[0].inline_data.data
audio_bytes = base64.b64decode(audio_data)
with open(output_path, "wb") as f:
f.write(audio_bytes)
print(f"✅ 対談音声生成完了: {output_path}")
except Exception as e:
print(f"❌ エラー: {e}")
raise
# 対談スクリプトの例
podcast_script = """
Speaker1: [enthusiasm] Gemini Lab ポッドキャスト、今日もよろしくお願いします。今日は特別ゲストをお招きしています。
Speaker2: [amusement] こんにちは!呼んでいただいて嬉しいです。いつも聴いていますよ。
Speaker1: [curiosity] ありがとうございます。早速ですが、Gemini 3.1 Flash TTS を実際に使ってみた感想を聞かせてください。
Speaker2: [excitement] もう、感動しました。[determination] これまでの TTS との差が歴然で、正直、自分の声で録音する必要があるのかと思い始めています。
Speaker1: [amusement] [laughs] それは言い過ぎじゃないですか?
Speaker2: [nervousness] 言い過ぎかもしれません。でも、感情タグの自然さは本当にすごいんです。[curiosity] 実際に聴いてみると分かるんですが、[whispers] 特にウィスパーの品質が他のモデルとは別格です。
Speaker1: [enthusiasm] なるほど。開発者的な視点だと、API の使いやすさはどうでしたか?
Speaker2: [determination] 思ったよりシンプルでした。Python の google-genai ライブラリを使えば、10行くらいで動きます。
"""
generate_multi_speaker_podcast(
script=podcast_script,
host_voice="Kore",
guest_voice="Puck",
output_path="discussion_podcast.wav"
)このコードで注意すべき点が一つあります。Speaker1 / Speaker2 というラベルは、スクリプト内のラベルと speaker_voice_configs の speaker フィールドで一致させる必要があります。大文字小文字も区別されます。
長尺コンテンツのチャンク分割
20〜30分のポッドキャストを一括で生成しようとすると、トークン上限やタイムアウトの問題が発生します。実用的な解決策はチャンク分割です。
import re
import os
from pathlib import Path
from pydub import AudioSegment # pip install pydub
def split_script_into_chunks(script: str, max_exchanges: int = 10) -> list[str]:
"""
スクリプトを話者の発言単位で分割し、max_exchanges 発言ごとにグループ化する。
Args:
script: 全体スクリプト
max_exchanges: 1チャンクあたりの最大発言数
Returns:
チャンクごとのスクリプトリスト
"""
# "Speaker1:" または "Speaker2:" で始まる行を分割点として認識
lines = [line.strip() for line in script.strip().split("\n") if line.strip()]
speaker_lines = [l for l in lines if re.match(r'^Speaker\d+:', l)]
chunks = []
for i in range(0, len(speaker_lines), max_exchanges):
chunk = "\n\n".join(speaker_lines[i:i + max_exchanges])
chunks.append(chunk)
return chunks
def generate_long_podcast(full_script: str, output_path: str = "full_podcast.mp3") -> None:
"""
長尺スクリプトをチャンク分割して生成し、最終的に1つのMP3に結合する。
Args:
full_script: 完全なポッドキャストスクリプト
output_path: 最終出力ファイルパス(.mp3)
"""
chunks = split_script_into_chunks(full_script, max_exchanges=8)
temp_files = []
print(f"📦 {len(chunks)} チャンクに分割して処理します")
for i, chunk in enumerate(chunks):
temp_path = f"/tmp/podcast_chunk_{i:03d}.wav"
try:
generate_multi_speaker_podcast(
script=chunk,
output_path=temp_path
)
temp_files.append(temp_path)
print(f" ✅ チャンク {i+1}/{len(chunks)} 完了")
except Exception as e:
print(f" ❌ チャンク {i+1} 失敗(スキップ): {e}")
continue
if not temp_files:
raise RuntimeError("全チャンクの生成に失敗しました")
# pydub で音声ファイルを結合
combined = AudioSegment.empty()
silence = AudioSegment.silent(duration=500) # 500ms の無音をつなぎ目に挿入
for path in temp_files:
segment = AudioSegment.from_wav(path)
combined += segment + silence
# MP3 として出力(ビットレート指定)
combined.export(output_path, format="mp3", bitrate="192k")
print(f"🎙️ 完成: {output_path} ({combined.duration_seconds:.1f} 秒)")
# 一時ファイルのクリーンアップ
for path in temp_files:
Path(path).unlink(missing_ok=True)台本自動生成 → 音声合成の完全パイプライン
手動でスクリプトを書いていたのでは量産できません。台本の自動生成まで組み込んでこそ、真の自動化パイプラインになります。
from google import genai
from google.genai import types
import json
client = genai.Client(api_key=os.environ["GOOGLE_AI_API_KEY"])
def generate_podcast_script(topic: str, duration_minutes: int = 10) -> str:
"""
Gemini 2.5 Pro を使って対談形式のポッドキャスト台本を生成する。
Args:
topic: ポッドキャストのテーマ
duration_minutes: 目標尺(分)
Returns:
Speaker1/Speaker2 形式のスクリプト文字列
"""
word_count = duration_minutes * 150 # 日本語は1分あたり約150文字ペース
prompt = f"""以下のトピックで、{duration_minutes}分尺(約{word_count}文字)の対談形式ポッドキャスト台本を生成してください。
トピック: {topic}
フォーマット要件:
- Speaker1(ホスト)とSpeaker2(ゲスト)の2名
- 各発言の冒頭に感情タグを1〜2個付ける(例: [excitement], [curiosity], [determination]等)
- 自然な会話の流れで、一方的な説明にならないようにする
- ホストは質問役、ゲストは専門家として回答する形式
- 専門用語は使いすぎず、リスナーに分かりやすく説明する
使用できる感情タグ: [excitement], [enthusiasm], [curiosity], [determination], [amusement], [whispers], [nervousness], [interest], [awe], [hope], [laughs], [neutral], [positive]
台本のみを出力してください(説明文は不要)。"""
response = client.models.generate_content(
model="gemini-2.5-pro-preview-05-06",
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.8,
max_output_tokens=4096,
)
)
return response.text
def run_full_pipeline(topic: str, output_dir: str = "./podcasts") -> dict:
"""
台本生成 → 音声合成の完全パイプラインを実行する。
Returns:
処理結果のサマリー辞書
"""
import os
from datetime import datetime
os.makedirs(output_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
slug = topic[:20].replace(" ", "_").replace(" ", "_")
print(f"🎙️ ポッドキャスト生成開始: {topic}")
# Step 1: 台本生成
print("📝 Step 1: 台本を生成中...")
script = generate_podcast_script(topic, duration_minutes=10)
script_path = f"{output_dir}/{timestamp}_{slug}_script.txt"
with open(script_path, "w", encoding="utf-8") as f:
f.write(script)
print(f" ✅ 台本保存: {script_path}")
# Step 2: 音声合成
audio_path = f"{output_dir}/{timestamp}_{slug}.mp3"
print("🔊 Step 2: 音声を合成中...")
generate_long_podcast(script, output_path=audio_path)
# 結果サマリー
result = {
"topic": topic,
"script_path": script_path,
"audio_path": audio_path,
"timestamp": timestamp,
}
print(f"✅ パイプライン完了: {result}")
return result
# 実行例
if __name__ == "__main__":
topics = [
"Gemini 3.1 Flash TTS の感情制御技術と音声コンテンツの未来",
"個人開発者が AI を使って副業で月10万円を稼ぐ現実的な戦略",
"Google I/O 2026 で発表されたAI技術の使いどころを実例で整理",
]
for topic in topics:
try:
result = run_full_pipeline(topic)
print(f" → {result['audio_path']}\n")
except Exception as e:
print(f" ❌ 失敗 ({topic}): {e}\n")よくある失敗パターンと解決策
実際に本番運用を始めると、必ずぶつかるポイントがあります。3週間ほどこのパイプラインを回した経験から、よくある失敗を3つ挙げます。
1. 感情タグが英語、本文が日本語で効果が薄れる
症状: [excitement] を付けているのに、生成音声が平坦な読み上げになります。
原因: 感情タグは有効ですが、日本語の場合は文末の表現(「〜です!」vs「〜でございます」)や文の長さが音声のトーンに強く影響します。タグだけで感情を全部カバーしようとするのは無理があります。
解決策: タグと文体の両方を意識します。興奮を表現したいなら [excitement] と短い文+感嘆符の組み合わせが効果的です。
# ❌ タグだけ付けて文体が硬い
bad_example = "[excitement] 本日のアップデートにつきましてご説明申し上げます。"
# ✅ タグと文体が一致している
good_example = "[excitement] ついに来ました!今日のアップデート、本当にすごいんです!"2. 長尺生成でクオリティが後半で落ちる
症状: チャンク8以降の音声が、前半に比べて感情表現が薄くなります。
原因: APIは各チャンクを独立して処理するため、物語の文脈や感情の流れをまたいで引き継いでくれません。後半チャンクが「冒頭に戻った」状態から始まってしまいます。
解決策: 各チャンクの冒頭に「文脈ヒント」を加える。前のチャンクの最後の1〜2発言をコンテキストとして先頭に付与し、APIに渡す直前に除去します。
def add_context_prefix(current_chunk: str, previous_end: str | None) -> str:
"""前チャンクのコンテキストを先頭に追加して連続性を保つ"""
if previous_end is None:
return current_chunk
# コンテキストラベルを付けてから本文を連結
return f"# コンテキスト(この部分は音声化しない)\n{previous_end}\n\n# 本文\n{current_chunk}"3. レート制限(429エラー)でパイプラインが途中停止する
大量生成を試みると、必ず429エラーに遭遇します。単純なリトライでは解決しません。指数バックオフ付きのリトライ設計が必要です。
詳細な実装パターンについては、Gemini API レート制限と本番エラーハンドリングを参照してください。
import time
import random
from typing import Callable, TypeVar
T = TypeVar("T")
def retry_with_backoff(
func: Callable[[], T],
max_retries: int = 5,
base_delay: float = 2.0,
max_delay: float = 60.0,
) -> T:
"""
指数バックオフ + ジッターでリトライする。
429 (Rate Limit) と 503 (Service Unavailable) に対応。
"""
for attempt in range(max_retries):
try:
return func()
except Exception as e:
error_str = str(e).lower()
is_retryable = "429" in error_str or "503" in error_str or "rate limit" in error_str
if not is_retryable or attempt == max_retries - 1:
raise
# 指数バックオフ + ジッター
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
print(f" ⏳ レート制限: {delay:.1f}秒待機 (試行 {attempt + 1}/{max_retries})")
time.sleep(delay)
raise RuntimeError("最大リトライ数に達しました")コスト計算とビジネスモデル設計
このパイプラインが商用サービスとして成立するかどうか、数字で確認しましょう。
1エピソードあたりのコスト試算
Gemini 3.1 Flash TTS の料金は入力テキストのトークン数と出力音声の長さで決まります。Gemini API の料金体系についてはGemini API 料金完全ガイドで詳しく解説していますが、TTS の場合は音声出力が主なコストです。
10分尺のポッドキャスト(約1,500文字のスクリプト)を生成する場合の目安:
- 台本生成(Gemini 2.5 Pro): 約 ¥3〜5 / エピソード
- TTS 音声合成: 約 ¥8〜15 / エピソード(入出力合計)
- 合計: 約 ¥11〜20 / エピソード
月50エピソード生成した場合、コストは ¥550〜1,000 程度です。
月収10万円のビジネスモデル
このコスト構造で収益化を考えると、以下のモデルが現実的です。
モデルA: ニッチ特化ポッドキャストのサブスクリプション
特定分野(投資、語学、ビジネス英語など)に特化し、週3〜5本配信。Spotify や Apple Podcasts で聴衆を獲得してから、独自アプリやメンバーシップサイトへ誘導します。
- 月額 ¥980 × 150人 = ¥147,000 / 月
- コスト(エピソード60本/月): ¥660〜1,200
- 粗利率: 99%超
モデルB: B2B 向け音声コンテンツ制作代行
企業のブログ記事やプレスリリースをポッドキャスト化する代行サービスです。
- 1本 ¥8,000〜15,000 × 10社 × 月4本 = ¥320,000〜600,000 / 月
- 制作コスト(40本/月): ¥440〜800
- 人件費(編集・納品対応)を差し引いても十分な利益
どちらのモデルでも、音声の品質がサービス価値の核になります。Gemini 3.1 Flash TTS の品質は、その核として十分に機能します。
次のステップ — まず1本作ってみる
この記事で紹介したパイプラインを試す最初のステップは、Google AI Studio で感情タグを体験することです。Gemini API キーさえあれば、今日の夜のうちにサンプル音声を1本生成できます。
コードを全部そろえる前に、まずは感情タグ数種類を試して、「あ、これは本当に使える」という感触を自分の耳で確かめてみてください。その感触が、商用サービスを作るモチベーションになるはずです。
TTS の基本的な使い方についてはGemini TTS API 完全入門ガイドも参考にしてみてください。実装の出発点として役立ちます。
音声コンテンツ市場は、まだ個人が参入できる余白が大きく残っています。AI が音声品質の壁を壊した今、あとはどんなコンテンツを作るかというアイデア次第です。