GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/Dev Tools
Dev Tools/2026-05-06Intermediate

Auto-Generate Narration Videos with Gemini TTS — From Text Input to MP4 Output (2026 Guide)

Build a Python pipeline that converts text into narration videos using Gemini TTS API — generating audio, subtitles, and compositing the final MP4 with FFmpeg. Includes real API cost and timing benchmarks.

gemini-ttsnarration-videovideo-automationpython104ffmpegtts7api12

Feed in text, get back a video. That's the end goal — and it's more achievable than it sounds. Demand for "readable" content in audio-video form is growing across social content, e-learning, and documentation. This article walks through a Python pipeline that takes text as input and produces a narration video as output, using Gemini TTS API as the voice engine.

Everything below is working code, not pseudocode.

What We're Building

The pipeline flow:

Text (.txt / string)
    ↓ Gemini TTS API
Audio file (.mp3)
    ↓ FFmpeg + PIL
Subtitled video (.mp4)

The output targets vertical 9:16 format (1080×1920), suitable for YouTube Shorts, TikTok, and Instagram Reels. Optional BGM mixing is also covered.

Requirements

  • Python 3.11+
  • Gemini API key (from Google AI Studio)
  • FFmpeg installed on your system
  • pip install google-generativeai Pillow

As of May 2026, gemini-3-1-flash-tts offers the best cost-to-quality ratio for narration workloads.

Step 1: Text to Speech

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:
    """
    Convert text to audio using Gemini TTS.
    voice options: Aoede / Charon / Fenrir / Kore / Orbit
    Returns: path to saved mp3 file
    """
    client = genai.GenerativeModel("gemini-3-1-flash-tts")
    
    response = client.generate_content(
        contents=[{
            "role": "user",
            "parts": [{"text": f"Please read the following text naturally:\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"Audio saved: {output_path} ({len(audio_bytes) / 1024:.1f} KB)")
    return output_path

Choosing a voice

From testing these voices on English narration content:

  • Aoede (default): Calm female voice. Works well for most narration. This is my go-to
  • Charon: Lower male voice. Suits business and explainer content
  • Fenrir: Slightly firmer male voice. Technical content
  • Kore: Softer female voice. Entertainment and storytelling

Aoede handles natural pacing best in my experience — pauses at punctuation land more consistently.

Step 2: Generate SRT Subtitles

Videos without captions lose a large share of social viewers (most platforms autoplay silently). This function generates SRT-format subtitles by distributing sentence durations proportionally by character count.

import re
 
def generate_srt(text: str, audio_duration: float) -> str:
    """
    Generate SRT subtitles from text and audio duration.
    Splits on sentence-ending punctuation.
    """
    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
        
        def format_time(t: float) -> str:
            h, m = int(t // 3600), int((t % 3600) // 60)
            s = 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: Composite the Video with FFmpeg

from PIL import Image, ImageDraw
import subprocess
 
def create_background_image(width: int = 1080, height: int = 1920) -> str:
    """Generate a vertical gradient background image."""
    img = Image.new("RGB", (width, height))
    draw = ImageDraw.Draw(img)
    
    # Dark blue to dark purple gradient
    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:
    """Composite audio, subtitles, and background into an MP4."""
    if bg_image_path is None:
        bg_image_path = create_background_image()
    
    subtitle_style = (
        "FontName=Arial,"
        "FontSize=56,"
        "PrimaryColour=&H00FFFFFF,"
        "OutlineColour=&H00000000,"
        "Outline=3,"
        "Alignment=2,"
        "MarginV=120"
    )
    
    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 error:\n{result.stderr}")
    
    print(f"Video saved: {output_path}")
    return output_path

Step 4: The Complete Pipeline

import tempfile
import time
 
def create_narration_video(text: str, output_path: str, voice: str = "Aoede") -> dict:
    """
    Main pipeline: text → narration video.
    Returns timing, cost, and file size metadata.
    """
    start_time = time.time()
    
    with tempfile.TemporaryDirectory() as tmp_dir:
        audio_path = f"{tmp_dir}/audio.mp3"
        srt_path = f"{tmp_dir}/subtitle.srt"
        
        print("Generating audio...")
        text_to_speech(text, audio_path, voice)
        
        # Get audio duration
        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"])
        
        print("Generating subtitles...")
        srt_content = generate_srt(text, audio_duration)
        Path(srt_path).write_text(srt_content, encoding="utf-8")
        
        print("Compositing video...")
        create_video(audio_path, srt_path, output_path)
    
    elapsed = time.time() - start_time
    output_size = Path(output_path).stat().st_size / (1024 * 1024)
    char_count = len(text)
    estimated_cost_usd = char_count * 0.000003  # ~$0.003 per 1000 chars
    
    return {
        "audio_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)
    }
 
# Usage
text = """
The pace of AI development has fundamentally changed how we approach software.
Tools that would have taken months to build now take days.
The challenge has shifted from technical execution to knowing what to build —
and building it well enough that people keep coming back.
"""
 
result = create_narration_video(text, "output.mp4")
print(f"\nResult: {result}")

Benchmarks: Real Cost and Timing

Measured on a standard cloud instance:

Text lengthAudio durationProcessing timeEstimated costFile size
~200 chars~35s~22s$0.0006~7MB
~500 chars~85s~40s$0.0015~16MB
~1000 chars~165s~70s$0.003~31MB

At ~$0.003 per 1000-character narration, this is economically viable for bulk content. Processing time is shorter than audio duration because TTS generation is async — long texts don't require proportionally long waits.

Optional: Add Background Music

def add_bgm(video_path: str, bgm_path: str, output_path: str, bgm_volume: float = 0.3):
    """Mix background music into the video (preserves narration audio)."""
    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 added: {output_path}")

Use royalty-free music sources (Pixabay Audio, Free Music Archive) and keep the BGM volume at 0.2–0.3 to avoid overwhelming the narration.

Where to Take This Next

Two extensions worth considering once the base pipeline is running:

Slide-based backgrounds: Instead of a static gradient, pull slides from Google Slides API or a Canva export and cut between them as the narration progresses. For educational content, this significantly improves information retention.

Veo integration: Replace the static background with AI-generated video using Google's Veo API. The visual quality jump is real, but Veo API costs are substantially higher than TTS — reserve it for hero content rather than bulk generation.

Start with the base pipeline as written, validate it works end-to-end for your use case, and extend from there.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-03-17
Running the Gemini API in Google Colab: Your First Working Notebook
Step-by-step guide to using the Gemini API in Google Colab. From API key setup to text generation, image analysis, and streaming — all with copy-paste Python code that works out of the box.
Dev Tools2026-07-11
Never Generate the Same Narration Twice: Cache-Key Design and Invalidation for Gemini TTS Output
For apps that replay the same audio over and over—meditation, language learning, storytelling—caching the Gemini TTS output itself drives the variable cost to near zero. This is a working design: how to build a cache key that text alone can't cover, a two-tier server-plus-device cache, and an invalidation policy that survives model shutdowns like the August 17 image-model deprecation.
Dev Tools2026-05-02
Google ADK Quickstart — Build Your First AI Agent in 30 Minutes
A hands-on Google Agent Development Kit (ADK) walkthrough for absolute beginners — from install to a working agent in 30 minutes, with the gotchas called out as you hit them.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →