From 34806538d565d086671c993686d340968c658afa Mon Sep 17 00:00:00 2001 From: minguezsanzjuanjose Date: Sat, 3 Oct 2026 19:56:53 +0200 Subject: [PATCH] =?UTF-8?q?03-oct:=20superguardado=20autom=C3=A1tico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/voz_mensaje.py | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 scripts/voz_mensaje.py diff --git a/scripts/voz_mensaje.py b/scripts/voz_mensaje.py new file mode 100644 index 0000000..148a35a --- /dev/null +++ b/scripts/voz_mensaje.py @@ -0,0 +1,108 @@ +#!/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())