JARVIS/config/comparar_voces.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

81 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Genera la misma frase con las cuatro voces britanicas de Kokoro.
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
~/COFRE/CODERS/JARVIS/config/comparar_voces.py
Todas salen ya con el tratamiento aplicado, que es como sonaran de verdad.
Elegir de oido y fijar la ganadora con set_voice.py.
"""
import os
import subprocess
import sys
sys.path.insert(0, "/app/share/newelle")
sys.path.insert(0, os.path.expanduser(
"~/.var/app/io.github.qwersyk.Newelle/config/pip"))
from gi.repository import Gio # noqa: E402
from newelle.handlers.tts.kokoro_handler import KokoroTTSHandler # noqa: E402
from newelle.handlers.tts.tts import jarvis_audio_filter # noqa: E402
VOCES = ["bm_george", "bm_daniel", "bm_lewis", "bm_fable"]
FRASE = ("Good evening, sir. All systems are running locally. "
"The GPU is idle, and you have 846 gigabytes free. "
"Shall I run the diagnostics?")
SALIDA = os.path.expanduser("~/COFRE/CODERS/JARVIS/muestras/voces")
CONFIG = os.path.expanduser("~/.var/app/io.github.qwersyk.Newelle/config")
def nivel(path):
"""mean/max en dB, para confirmar que ninguna sale floja o recortada."""
r = subprocess.run(
["ffmpeg", "-hide_banner", "-i", path, "-af", "volumedetect",
"-f", "null", "/dev/null"],
capture_output=True, text=True,
)
vals = [l.split(":")[-1].strip() for l in r.stderr.splitlines()
if "mean_volume:" in l or "max_volume:" in l]
return " / ".join(vals) if vals else "?"
def main():
os.makedirs(SALIDA, exist_ok=True)
settings = Gio.Settings.new("io.github.qwersyk.Newelle")
handler = KokoroTTSHandler(settings, os.path.join(CONFIG, "models"))
if not handler.is_installed():
print("instalando kokoro...")
handler.install()
filtro = jarvis_audio_filter()
print(f"{'voz':<12} {'fichero':<26} nivel (mean / max)")
for voz in VOCES:
handler.set_voice(voz)
crudo = os.path.join(SALIDA, f"_{voz}_seca.wav")
final = os.path.join(SALIDA, f"{voz}.wav")
try:
handler.save_audio(FRASE, crudo)
subprocess.run(
["ffmpeg", "-hide_banner", "-loglevel", "error",
"-i", crudo, "-af", filtro, "-y", final],
check=True,
)
os.remove(crudo)
print(f"{voz:<12} {os.path.basename(final):<26} {nivel(final)}")
except Exception as e:
print(f"{voz:<12} FALLO: {e}")
# dejar la voz como estaba, que este script solo compara
handler.set_voice(VOCES[0])
print(f"\nvoz restaurada a {VOCES[0]}")
print(f"escuchalas en: {SALIDA}")
return 0
if __name__ == "__main__":
sys.exit(main())