Course EN
← back to chapter

Texto a voz

Escribe una línea y escúchala. El modelo regresa audio, renderizado en un

Showcase — Texto a voz

Escribe una línea y escúchala. El modelo regresa audio, renderizado en un reproductor. OpenAI regresa MP3 directo; Gemini regresa muestras PCM crudas que el backend envuelve en un contenedor WAV (biblioteca estándar) antes de mandar un data URI a la página.

Córrelo

bash bootstrap-secrets.sh              # reads ../../../../.env, writes secrets/
docker compose up --build              # default: PROVIDER=openai

Abre http://localhost:3000. Gemini: PROVIDER=gemini docker compose up --build.

Qué hay aquí

  • backend/ai_openai.pyaudio.speech.create, MP3 → data URI.
  • backend/ai_gemini.py — TTS de Gemini PCM → WAV (wave) → data URI.
  • frontend/app/page.tsx — caja de texto; renderiza el audio devuelto en un <audio>.

Detenlo

docker compose down

Ejecútalo en tu máquina

Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en localhost:3000.

Descargar text-to-speech.zip

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

Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.


  

Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña 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}"

Archivos del proyecto

  • .gitignore
  • README.es.md
  • README.md
  • backend/Dockerfile
  • backend/ai_gemini.py
  • backend/ai_openai.py
  • backend/main.py
  • backend/requirements.txt
  • bootstrap-secrets.sh
  • docker-compose.yml
  • frontend/Dockerfile
  • frontend/app/layout.tsx
  • frontend/app/page.tsx
  • frontend/next.config.ts
  • frontend/package.json
  • frontend/tsconfig.json