Text to speech
Type a line and hear it. The model returns audio, rendered in a player. OpenAI
Showcase — Text to speech
Type a line and hear it. The model returns audio, rendered in a player. OpenAI returns MP3 directly; Gemini returns raw PCM samples that the backend wraps in a WAV container (standard library) before sending a data URI to the page.
Run
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Open http://localhost:3000. Gemini: PROVIDER=gemini docker compose up --build.
What's where
backend/ai_openai.py—audio.speech.create, MP3 → data URI.backend/ai_gemini.py— Gemini TTS PCM → WAV (wave) → data URI.frontend/app/page.tsx— text box; renders the returned audio in an<audio>.
Stop
docker compose down
Run locally
Download the project as a ZIP and run it with Docker. Brings up a FastAPI backend + Next.js frontend on localhost:3000.
unzip text-to-speech.zip
cd text-to-speech
bash bootstrap-secrets.sh # one-time: pulls API keys into ./secrets
docker compose up --build # default provider: openai
# or: PROVIDER=gemini docker compose up --build
Type some input, pick a provider, and run the same code shown in Source against the live API. Sign-in required.
The same modules the Run button hits. The whole project (frontend, Dockerfile, compose) is in the ZIP under README.
backend/ai_openai.py
"""Showcase 2 (OpenAI): text to speech.
Type a line, hear it. The model returns MP3 bytes, which we base64 into a data
URI the page plays in an <audio> element.
"""
import base64
from openai import OpenAI
_client = OpenAI()
_MODEL = "gpt-4o-mini-tts"
def run(text: str) -> str:
t = text.strip()
if not t:
return "Enter some text to speak."
response = _client.audio.speech.create(model=_MODEL, voice="alloy", input=t)
b64 = base64.b64encode(response.content).decode()
return f"data:audio/mpeg;base64,{b64}"
backend/ai_gemini.py
"""Showcase 2 (Gemini): text to speech.
Gemini TTS returns raw PCM samples, so we wrap them in a WAV container (standard
library) before base64-ing into a data URI the page can play.
"""
import base64
import io
import os
import wave
from google import genai
from google.genai import types
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-2.5-flash-preview-tts"
def _pcm_to_wav(pcm: bytes, rate: int = 24000) -> bytes:
buf = io.BytesIO()
with wave.open(buf, "wb") as w:
w.setnchannels(1)
w.setsampwidth(2) # 16-bit
w.setframerate(rate)
w.writeframes(pcm)
return buf.getvalue()
def run(text: str) -> str:
t = text.strip()
if not t:
return "Enter some text to speak."
response = _client.models.generate_content(
model=_MODEL,
contents=t,
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
b64 = base64.b64encode(_pcm_to_wav(pcm)).decode()
return f"data:audio/wav;base64,{b64}"
Project files
.gitignoreREADME.es.mdREADME.mdbackend/Dockerfilebackend/ai_gemini.pybackend/ai_openai.pybackend/main.pybackend/requirements.txtbootstrap-secrets.shdocker-compose.ymlfrontend/Dockerfilefrontend/app/layout.tsxfrontend/app/page.tsxfrontend/next.config.tsfrontend/package.jsonfrontend/tsconfig.json