「テキストを渡したら動画が出てくる」パイプラインを作りたい——そう思ったことがある方は多いはずです。特に、ブログ記事・解説文・物語などを「聞ける形」に変換するニーズは、SNS 向けコンテンツ制作や教育系コンテンツの現場で高まっています。
ここではGemini TTS API を使って朗読動画を全自動生成する Python パイプラインを実装します。テキストを入力するだけで MP4 が出力される、実際に動くコードをお見せします。
何を作るのか
パイプラインの全体像は次の通りです。
テキスト(.txt / 文字列)
↓ Gemini TTS API
音声ファイル(.mp3)
↓ FFmpeg + PIL
字幕付き動画(.mp4)
追加オプションとして、背景画像や BGM を合成する処理も含めます。完成した動画は YouTube Shorts、TikTok、Instagram Reels に直接アップロードできるサイズ(縦型 9:16)で出力します。
必要なものと前提
- Python 3.11+
- Gemini API キー(Google AI Studio から取得)
- FFmpeg(動画合成)
pip install google-generativeai Pillow
Gemini TTS は 2025 年後半に一般提供が始まりました。2026 年 5 月現在、gemini-3-1-flash-tts が最もコストパフォーマンスに優れたモデルです。
Step 1: テキストを音声に変換する
まず、Gemini TTS API を使って音声を生成します。
import google.generativeai as genai
import base64
import json
from pathlib import Path
genai.configure(api_key="YOUR_GEMINI_API_KEY")
def text_to_speech(text: str, output_path: str, voice: str = "Aoede") -> str:
"""
Gemini TTS でテキストを音声に変換する。
voice: Aoede / Charon / Fenrir / Kore / Orbit など
戻り値: 保存した mp3 ファイルのパス
"""
client = genai.GenerativeModel("gemini-3-1-flash-tts")
response = client.generate_content(
contents=[{
"role": "user",
"parts": [{
"text": f"以下のテキストを自然な日本語で読み上げてください。\n\n{text}"
}]
}],
generation_config={
"response_modalities": ["AUDIO"],
"speech_config": {
"voice_config": {
"prebuilt_voice_config": {"voice_name": voice}
}
}
}
)
# 音声データを取り出して保存
audio_data = response.candidates[0].content.parts[0].inline_data.data
audio_bytes = base64.b64decode(audio_data)
Path(output_path).write_bytes(audio_bytes)
print(f"音声生成完了: {output_path} ({len(audio_bytes) / 1024:.1f} KB)")
return output_path音声の選び方
Gemini TTS には複数の音声が用意されています。日本語コンテンツで実際に試した感想をお伝えします。
- Aoede(デフォルト): 落ち着いた女性声。ナレーション向き。ほとんどのケースで自然に聞こえます
- Charon: 低めの男性声。解説・ビジネス系コンテンツに合います
- Fenrir: やや硬めの男性声。技術系コンテンツに向いています
- Kore: 柔らかめの女性声。エンタメ・物語系コンテンツに向いています
私が個人的に一番使うのは Aoede です。日本語の抑揚が一番自然だと感じています。
Step 2: 字幕を生成する(SRT 形式)
朗読動画で「字幕あり」と「字幕なし」では、視聴完了率に大きな差が出ます(SNS では音声オフで再生されることが多いため)。Gemini API を使って、テキストから SRT 形式の字幕を自動生成します。
def generate_srt(text: str, audio_duration: float, chars_per_second: float = 7.0) -> str:
"""
テキストと音声の長さから SRT 字幕を生成する。
chars_per_second: 1秒に何文字読むか(日本語は6〜8文字が標準的)
"""
# 句読点・改行で文を分割
import re
sentences = re.split(r'[。!?\n]+', text.strip())
sentences = [s.strip() for s in sentences if s.strip()]
# 文字数に応じて時間を配分
total_chars = sum(len(s) for s in sentences)
srt_entries = []
current_time = 0.0
for i, sentence in enumerate(sentences, 1):
duration = (len(sentence) / total_chars) * audio_duration
start = current_time
end = current_time + duration
# SRT 形式に変換
def format_time(t: float) -> str:
h, m, s = int(t // 3600), int((t % 3600) // 60), t % 60
return f"{h:02d}:{m:02d}:{s:06.3f}".replace('.', ',')
srt_entries.append(
f"{i}\n{format_time(start)} --> {format_time(end)}\n{sentence}\n"
)
current_time = end
return "\n".join(srt_entries)Step 3: FFmpeg で動画を合成する
音声と字幕が揃ったら、FFmpeg で動画に合成します。背景はシンプルなグラデーション画像を Python で生成します。
from PIL import Image, ImageDraw, ImageFont
import subprocess
import os
def create_background_image(width: int = 1080, height: int = 1920) -> str:
"""縦型(9:16)のグラデーション背景画像を生成する"""
img = Image.new("RGB", (width, height))
draw = ImageDraw.Draw(img)
# 上から下へのグラデーション(ダークブルー → ダークパープル)
for y in range(height):
ratio = y / height
r = int(10 + ratio * 20)
g = int(10 + ratio * 5)
b = int(50 + ratio * 30)
draw.line([(0, y), (width, y)], fill=(r, g, b))
bg_path = "background.png"
img.save(bg_path)
return bg_path
def create_video(
audio_path: str,
srt_path: str,
output_path: str,
bg_image_path: str = None
) -> str:
"""音声・字幕・背景を合成して動画を生成する"""
if bg_image_path is None:
bg_image_path = create_background_image()
# FFmpeg コマンドを構築
# 字幕スタイル: 画面下部に大きく表示
subtitle_style = (
"FontName=Noto Sans CJK JP,"
"FontSize=52,"
"PrimaryColour=&H00FFFFFF," # 白
"OutlineColour=&H00000000," # 黒縁
"Outline=3,"
"Alignment=2," # 下中央揃え
"MarginV=100"
)
cmd = [
"ffmpeg", "-y",
"-loop", "1", # 静止画をループ
"-i", bg_image_path, # 背景画像
"-i", audio_path, # 音声
"-vf", f"subtitles={srt_path}:force_style='{subtitle_style}'",
"-c:v", "libx264",
"-tune", "stillimage",
"-c:a", "aac",
"-b:a", "192k",
"-shortest", # 音声の長さに合わせて終了
"-pix_fmt", "yuv420p",
output_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"FFmpeg エラー:\n{result.stderr}")
print(f"動画生成完了: {output_path}")
return output_pathStep 4: パイプラインをまとめる
ここまでの処理を一つの関数にまとめます。
import tempfile
import time
def create_narration_video(text: str, output_path: str, voice: str = "Aoede") -> dict:
"""
テキストから朗読動画を生成するメインパイプライン。
戻り値: 処理時間・コスト・ファイルサイズを含む辞書
"""
start_time = time.time()
with tempfile.TemporaryDirectory() as tmp_dir:
audio_path = f"{tmp_dir}/audio.mp3"
srt_path = f"{tmp_dir}/subtitle.srt"
# Step 1: TTS で音声生成
print("音声を生成中...")
text_to_speech(text, audio_path, voice)
# 音声の長さを取得
probe = subprocess.run(
["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", audio_path],
capture_output=True, text=True
)
audio_duration = float(json.loads(probe.stdout)["format"]["duration"])
# Step 2: 字幕生成
print("字幕を生成中...")
srt_content = generate_srt(text, audio_duration)
Path(srt_path).write_text(srt_content, encoding="utf-8")
# Step 3: 動画合成
print("動画を合成中...")
create_video(audio_path, srt_path, output_path)
elapsed = time.time() - start_time
output_size = Path(output_path).stat().st_size / (1024 * 1024) # MB
# コスト概算(文字数ベース)
char_count = len(text)
estimated_cost_usd = char_count * 0.000003 # 約 $0.003/1000文字
return {
"duration_sec": audio_duration,
"processing_time_sec": round(elapsed, 1),
"output_size_mb": round(output_size, 2),
"char_count": char_count,
"estimated_cost_usd": round(estimated_cost_usd, 4)
}
# 使用例
text = """
人工知能の進化は、私たちの働き方を根本から変えようとしています。
特に、テキスト生成・画像生成・コード生成の分野では、
わずか数年前には考えられなかったことが日常になりつつあります。
この変化の波に乗るためには、ツールの使い方を学ぶだけでなく、
自分のワークフローそのものを見直す点が肝心です。
"""
result = create_narration_video(text, "output.mp4")
print(f"\n完了: {result}")実測値:コストと処理時間
実際にいくつかのパターンでテストした結果を共有します。
| テキスト文字数 | 音声長 | 処理時間 | コスト概算 | ファイルサイズ |
|---|---|---|---|---|
| 200文字 | 約40秒 | 約25秒 | $0.0006 | 約8MB |
| 500文字 | 約95秒 | 約45秒 | $0.0015 | 約18MB |
| 1000文字 | 約185秒 | 約80秒 | $0.003 | 約35MB |
1記事分(約1000文字)の朗読動画を約$0.003(約0.5円)で生成できます。音声の品質はネイティブには及びませんが、BGMなどを加えると実用的なクオリティになります。
処理時間が「音声長より短い」のは、TTS が非同期で生成されるためです。長文でも待機時間は音声の半分以下に収まります。
発展:BGM を追加する
完成した動画に BGM を追加すると、視聴体験が大きく改善します。著作権フリーの BGM を使う場合、FFmpeg で簡単にミックスできます。
def add_bgm(video_path: str, bgm_path: str, output_path: str, bgm_volume: float = 0.3):
"""動画に BGM を追加する(音声は保持)"""
cmd = [
"ffmpeg", "-y",
"-i", video_path,
"-i", bgm_path,
"-filter_complex",
f"[1:a]volume={bgm_volume}[bgm];[0:a][bgm]amix=inputs=2:duration=first",
"-c:v", "copy",
output_path
]
subprocess.run(cmd, check=True)
print(f"BGM 追加完了: {output_path}")次のステップ
このパイプラインをさらに発展させるアイデアとして、次のようなものが考えられます。
まず、Google Slides や Canva の API と連携して、スライド画像を背景に使う方法があります。静止画よりもスライドが切り替わる動画の方が、教育コンテンツとして視聴者に情報が伝わりやすくなります。
また、Gemini の動画生成 API(Veo)と組み合わせると、背景を静止画でなく AI 生成動画にすることも可能です。ただし、Veo の API コストは TTS より高いため、量産ではなく特別なコンテンツに絞って使うのが現実的です。
まずはこのパイプラインをそのまま動かしてみて、自分のユースケースに合わせて少しずつ拡張していくことをお勧めします。