03-oct: superguardado automático
This commit is contained in:
108
scripts/voz_mensaje.py
Normal file
108
scripts/voz_mensaje.py
Normal file
@@ -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())
|
||||
Reference in New Issue
Block a user