#!/usr/bin/env python3 # Genera, con la voz CLONADA (XTTS-v2), las frases fijas que JARVIS repite: # los acuses de las acciones y las respuestas de plantilla del panel. Cada WAV # se guarda en cache_voz/ con nombre = sha1(texto normalizado), que es la misma # clave que usa el wrapper jarvis-voz.sh para servirlas al instante. # # Corre en el host (venv clonador-venv), en GPU, sin prisa: es trabajo de una # sola vez. En uso, el wrapper solo copia el WAV -> latencia cero, GPU libre. # # python generar_cache_voz.py [cuda|cpu] import os, sys, re, json, glob, hashlib, time DISPOSITIVO = sys.argv[1] if len(sys.argv) > 1 else "cuda" BASE = os.path.dirname(os.path.abspath(__file__)) # La voz (referencia, condicionado y parametros) vive en clonador.py: tenerla # aqui y en calienta_cache.py significaba dos copias que se desincronizan. sys.path.insert(0, BASE) from clonador import Clonador, clave CACHE = os.path.join(BASE, "cache_voz") CATALOGO = os.path.expanduser( "~/COFRE/CODERS/JARVIS/newelle/data/acciones/acciones.json") os.makedirs(CACHE, exist_ok=True) os.environ.setdefault("COQUI_TOS_AGREED", "1") def frases_fijas(): """Acuses del catalogo + respuestas de plantilla del panel.""" fijas = set() try: with open(CATALOGO, encoding="utf-8") as f: cat = json.load(f) for a in cat.get("acciones", cat if isinstance(cat, list) else []): ac = a.get("acuse") if ac: fijas.add(ac) except Exception as e: print(f"aviso: no pude leer el catalogo ({e}); sigo con las fijas") # las de plantilla del panel que no dependen de argumento fijas.update({ "Buenas noches, señor. Todo está listo.", "Lo busco dentro, señor.", "A la orden, señor.", "Hecho, señor.", "No he encontrado nada, señor.", "¿Cuál de estos, señor?", }) return sorted(fijas) def main(): frases = frases_fijas() print(f"{len(frases)} frases fijas a generar en {DISPOSITIVO}", flush=True) t0 = time.time() voz = Clonador(DISPOSITIVO) print(f"modelo cargado en {time.time()-t0:.1f} s " f"(referencia: {os.path.basename(voz.referencia)})", flush=True) hechas = saltadas = 0 for i, frase in enumerate(frases, 1): dst = os.path.join(CACHE, clave(frase) + ".wav") if os.path.exists(dst): saltadas += 1 continue t0 = time.time() voz.genera(frase, dst) print(f" [{i}/{len(frases)}] {time.time()-t0:4.1f}s {frase[:50]}", flush=True) hechas += 1 print(f"listo: {hechas} generadas, {saltadas} ya estaban. cache -> {CACHE}", flush=True) if __name__ == "__main__": main()