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.
49 lines
2.2 KiB
Python
49 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
# Afinado de XTTS: combina varias referencias (promedia el timbre) y prueba
|
|
# distintos ajustes de velocidad/temperatura para acercarse al Jarvis original.
|
|
# Genera variantes en ref/afinar/ para comparar de oido y elegir la buena.
|
|
import os, sys, time
|
|
os.environ.setdefault("COQUI_TOS_AGREED", "1")
|
|
DISP = sys.argv[1] if len(sys.argv) > 1 else "cuda"
|
|
BASE = os.path.dirname(os.path.abspath(__file__))
|
|
OUT = os.path.join(BASE, "ref", "afinar")
|
|
os.makedirs(OUT, exist_ok=True)
|
|
|
|
# varias referencias: XTTS calcula el latent del hablante promediandolas
|
|
REFS = [os.path.join(BASE, "ref", n) for n in
|
|
("jarvis_lento_b.wav", "jarvis_lento_a.wav", "jarvis_ref.wav")]
|
|
REFS = [r for r in REFS if os.path.exists(r)]
|
|
|
|
FRASES = {
|
|
"saludo": "Buenas noches, señor. Todo está listo.",
|
|
"pregunta": "He encontrado tres ficheros que coinciden. ¿Cuál abro?",
|
|
}
|
|
|
|
# (etiqueta, kwargs de inferencia)
|
|
VARIANTES = [
|
|
("v1_natural", dict(speed=1.0, temperature=0.65, repetition_penalty=2.0)),
|
|
("v2_sereno", dict(speed=0.92, temperature=0.60, repetition_penalty=3.0)),
|
|
("v3_grave", dict(speed=0.88, temperature=0.55, repetition_penalty=5.0)),
|
|
]
|
|
|
|
print(f"referencias ({len(REFS)}): {[os.path.basename(r) for r in REFS]}", flush=True)
|
|
t0 = time.time()
|
|
from TTS.api import TTS
|
|
tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(DISP)
|
|
print(f"modelo cargado en {time.time()-t0:.1f}s", flush=True)
|
|
|
|
for fid, frase in FRASES.items():
|
|
for etiqueta, kw in VARIANTES:
|
|
dst = os.path.join(OUT, f"{fid}_{etiqueta}.wav")
|
|
t0 = time.time()
|
|
try:
|
|
tts.tts_to_file(text=frase, speaker_wav=REFS, language="es",
|
|
file_path=dst, **kw)
|
|
print(f" {fid}/{etiqueta}: {time.time()-t0:4.1f}s {kw}", flush=True)
|
|
except TypeError as e:
|
|
# algun kwarg no soportado: reintenta solo con speed+temperature
|
|
kw2 = {k: kw[k] for k in ("speed", "temperature") if k in kw}
|
|
tts.tts_to_file(text=frase, speaker_wav=REFS, language="es",
|
|
file_path=dst, **kw2)
|
|
print(f" {fid}/{etiqueta}: (sin rep_penalty) {kw2}", flush=True)
|
|
print("listo.", flush=True)
|