#!/usr/bin/env python3 """voz_mensaje.py — Enrutador de voz para los mensajes del asistente (OpenCode). Genera y reproduce un mensaje hablado con VoiceStudio (http://127.0.0.1:3900) eligiendo el perfil de voz segun el TIPO de mensaje: default -> Carolina (10466680) Mensajes trascendentes de OpenCode (por defecto) biblioteca -> Juanjo (4f18d2df) Mensajes de la biblioteca Prolongo critico -> Abuelo (9f83c527) Mensajes importantes / criticos Uso: python voz_mensaje.py "texto a decir" [--tipo default|biblioteca|critico] [--idioma es] [--no-play] Proyecto: INFRAESTRUCTURA. Perfiles de voz locales de VoiceStudio. """ from __future__ import annotations import argparse import json import os import sys import urllib.error import urllib.parse import urllib.request BASE = os.environ.get("VOICESTUDIO_BASE", "http://127.0.0.1:3900") # tipo -> (profile_id, nombre) PROFILES = { "default": ("10466680", "Carolina"), "biblioteca": ("4f18d2df", "Juanjo"), "critico": ("9f83c527", "Abuelo"), } def _get_profile(pid: str) -> dict: """Metadatos del perfil (para reutilizar su ref_text al generar).""" try: with urllib.request.urlopen(f"{BASE}/profiles/{pid}", timeout=30) as r: return json.loads(r.read()) except Exception: return {} def generar(texto: str, tipo: str = "default", idioma: str = "es") -> str: """Sintetiza `texto` con el perfil del `tipo` y devuelve la ruta del WAV.""" pid, nombre = PROFILES.get(tipo, PROFILES["default"]) prof = _get_profile(pid) campos = { "text": texto, "language": idioma, "profile_id": pid, "stream": "false", } # Los perfiles clone necesitan su transcripcion de referencia para generar. if prof.get("ref_text"): campos["ref_text"] = prof["ref_text"] data = urllib.parse.urlencode(campos).encode() req = urllib.request.Request( f"{BASE}/generate", data=data, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) with urllib.request.urlopen(req, timeout=300) as r: wav = r.read() out_dir = os.path.join(os.path.expanduser("~"), "Documents", "voz_mensajes") os.makedirs(out_dir, exist_ok=True) out = os.path.join(out_dir, f"msg_{tipo}.wav") with open(out, "wb") as f: f.write(wav) print(f"[voz_mensaje] {nombre} ({tipo}) -> {out}") return out def reproducir(path: str) -> None: """Reproduce el WAV de forma sincrona (Windows).""" import winsound winsound.PlaySound(path, winsound.SND_FILENAME) def main() -> int: ap = argparse.ArgumentParser(description="Enrutador de voz para mensajes del asistente.") ap.add_argument("texto", help="Texto a decir") ap.add_argument("--tipo", choices=list(PROFILES), default="default") ap.add_argument("--idioma", default="es") ap.add_argument("--no-play", action="store_true", help="Solo generar, no reproducir") args = ap.parse_args() try: path = generar(args.texto, args.tipo, args.idioma) except Exception as e: # noqa: BLE001 print(f"[voz_mensaje] ERROR: {e}", file=sys.stderr) return 1 if not args.no_play: try: reproducir(path) except Exception as e: # noqa: BLE001 print(f"[voz_mensaje] ERROR reproduciendo: {e}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())