JARVIS/voz/generar_cache_voz.py
sito 8e4bc8ad94 JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla
con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin
cuentas ni claves.

Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper
comparten tarjeta y solo caben porque estan dimensionados para ello. El
RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve
de una cache de frases.

Incluye instalador (install.sh), requisitos, y documentacion del stack,
del manejo de root y de las acciones. Los apuntes indexados y el diario NO
se incluyen: son privados y el .gitignore los bloquea.
2026-08-16 15:34:37 +02:00

76 lines
2.7 KiB
Python

#!/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()