JARVIS/voz/clonar_afinar.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

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)