#!/usr/bin/env python3 """Calienta la cache de voz: coge las frases que cayeron a Piper (misses.log, que escribe jarvis-voz.sh) y las regenera con la voz CLONADA (XTTS) en cache_voz/. La proxima vez que JARVIS diga esa frase, sonara con tu voz al instante. Cuanto mas usas JARVIS, mas habla con tu voz. python calienta_cache.py [cuda|cpu] [--min N] --min N: solo frases vistas al menos N veces (por defecto 1 = todas las nuevas). No reproduce nada. Pensado para correr de noche (timer de systemd). """ import os, sys, re, json, time, hashlib, collections os.environ.setdefault("COQUI_TOS_AGREED", "1") DISP = "cuda" MIN = 1 for i, a in enumerate(sys.argv[1:]): if a in ("cuda", "cpu"): DISP = a elif a == "--min" and i + 2 <= len(sys.argv) - 1: MIN = int(sys.argv[i + 2]) BASE = os.path.dirname(os.path.abspath(__file__)) MISSES = os.path.join(BASE, "misses.log") CACHE = os.path.join(BASE, "cache_voz") sys.path.insert(0, BASE) from clonador import Clonador, clave # la voz, definida en un solo sitio os.makedirs(CACHE, exist_ok=True) def cacheable(f): # ni muy corta ni muy larga; nada con rutas (frases unicas que no repiten); # ni mayoria de digitos (numeros sueltos de telemetria, casi unicos) if not (3 <= len(f) <= 180): return False if "/" in f or "\\" in f: return False letras = sum(c.isalpha() for c in f) return letras >= max(3, len(f) * 0.5) def main(): if not os.path.exists(MISSES): print("no hay misses.log todavia; nada que calentar."); return 0 with open(MISSES, encoding="utf-8") as f: frases = [ln.rstrip("\n") for ln in f if ln.strip()] cuenta = collections.Counter(frases) pendientes = [] for frase, n in cuenta.items(): if n < MIN or not cacheable(frase): continue dst = os.path.join(CACHE, clave(frase) + ".wav") if not os.path.exists(dst): pendientes.append((frase, n)) pendientes.sort(key=lambda x: -x[1]) # las mas repetidas primero print(f"{len(cuenta)} frases distintas en el log ยท " f"{len(pendientes)} nuevas a generar (min={MIN})", flush=True) if not pendientes: return 0 t0 = time.time() voz = Clonador(DISP) print(f"modelo cargado en {time.time()-t0:.1f}s", flush=True) hechas = 0 for i, (frase, n) in enumerate(pendientes, 1): dst = os.path.join(CACHE, clave(frase) + ".wav") try: t0 = time.time() voz.genera(frase, dst) hechas += 1 print(f" [{i}/{len(pendientes)}] x{n} {time.time()-t0:.1f}s " f"{frase[:50]}", flush=True) except Exception as e: print(f" fallo '{frase[:40]}': {e}", flush=True) print(f"\nlisto: {hechas} frases nuevas en la voz clonada. " f"cache -> {CACHE}", flush=True) return 0 if __name__ == "__main__": sys.exit(main())