gTTS convierte texto en voz usando el servicio de Google Translate y guarda el resultado en MP3. En una Raspberry con un altavoz, eso es un aviso que habla. El ejemplo anterior tenía dos problemas: ya no arranca en Python 3.13 y su servidor no se podía abrir desde otro equipo.
También te puede interesar
Instalar
pip install fuera de un entorno virtual da el error externally-managed-environment. Es a propósito: protege el Python del sistema. Hay dos caminos, que son los que recomienda la documentación oficial: instalar con apt el paquete python3-… si existe, o crear un entorno virtual e instalar ahí con pip. La versión anterior de esta página usaba sudo pip3 install, que en las versiones actuales ya no funciona y en las antiguas podía estropear el sistema.python3 -m venv ~/voz ~/voz/bin/pip install gTTS sudo apt install vlc
Texto a voz
from gtts import gTTS
import subprocess
gTTS(text="Saludos, terrícola", lang="es").save("voz.mp3")
subprocess.run(["cvlc", "--play-and-exit", "voz.mp3"])salidavoz.mp3: 16512 bytes | empieza por: b'\\xff\\xf3\\x84'
Un MP3 de 16 KB. cvlc es VLC sin ventana; --play-and-exit hace que termine al acabar. subprocess.run con la orden en una lista es más seguro que el os.system de antes: si algún día el nombre del archivo viene de fuera, no se puede colar otra orden en él.
espeak-ng, con un sonido mucho más robótico.| Para… | Usa |
|---|---|
| Español de España | lang="es" |
| Otro acento | el parámetro tld, por ejemplo tld="com.mx" |
| Más despacio | slow=True |
| Ver los idiomas | gtts.lang.tts_langs(): son 69 |
Mandar mensajes desde el móvil
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs
from io import BytesIO
from gtts import gTTS
import subprocess, tempfile
PAGINA = """<!doctype html><meta charset="utf-8"><title>Manda un saludo</title>
<form method="post"><textarea name="texto" rows="4" cols="50"></textarea>
<button>Enviar</button></form>""".encode("utf-8")
class Manejador(BaseHTTPRequestHandler):
def do_GET(self):
self.responder(200, "text/html; charset=utf-8", PAGINA)
def do_POST(self):
largo = int(self.headers.get("Content-Length", 0))
datos = parse_qs(self.rfile.read(largo).decode("utf-8"))
texto = datos.get("texto", [""])[0].strip()[:300]
if not texto:
self.responder(400, "text/plain; charset=utf-8", "Escribe algo".encode())
return
with tempfile.NamedTemporaryFile(suffix=".mp3") as f:
gTTS(text=texto, lang="es").write_to_fp(f)
f.flush()
subprocess.run(["cvlc", "--play-and-exit", f.name])
self.responder(200, "text/html; charset=utf-8", PAGINA)
def responder(self, codigo, tipo, cuerpo):
self.send_response(codigo)
self.send_header("Content-Type", tipo)
self.send_header("Content-Length", str(len(cuerpo)))
self.end_headers()
self.wfile.write(cuerpo)
ThreadingHTTPServer(("0.0.0.0", 8080), Manejador).serve_forever()Se probó la misma lógica devolviendo el audio en lugar de reproducirlo:
salidaGET -> 200 text/html; charset=utf-8 POST -> 200 audio/mpeg 14016 bytes de audio POST vacío -> 400
Los dos fallos del ejemplo anterior
1. Usaba el módulo cgi, que ya no existe. Se eliminó en Python 3.13:
salidaModuleNotFoundError: No module named 'cgi'
Para leer un formulario basta urllib.parse.parse_qs, como en el código de arriba.
2. Escuchaba en "localhost". Así el servidor solo acepta conexiones de la propia Raspberry, y desde el móvil la página no carga. Con "0.0.0.0" escucha en todas sus direcciones; la IP para abrirla desde otro equipo la da hostname -I. Y como cualquiera de tu red podrá hacer hablar a la Raspberry, el texto se corta a 300 caracteres.

Revisada en octubre de 2026 para Raspberry Pi OS actual. Las salidas están copiadas de ejecuciones reales; lo que no se pudo ejecutar sin el hardware se indica en el propio texto.