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

74 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Pone la voz de Piper: castellano de España, grave.
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
~/COFRE/CODERS/JARVIS/config/set_voz_piper.py
Por que no Kokoro: no tiene ni una voz de España. Sus dos masculinas en
español, em_alex y em_santa, suenan latinoamericanas. Piper si las tiene, es
libre (MIT, voces con licencias abiertas) y corre local.
Medido con la misma frase:
em_alex (Kokoro, latino) 129 Hz
es_ES-carlfm-x_low 118 Hz calidad baja, 16 kHz
es_ES-sharvard-medium 127 Hz
es_ES-davefx-medium 128 Hz
es_ES-sharvard-medium + tono 106 Hz <- elegida
106 Hz cae de lleno en el rango de voz masculina grave (85-110). Se llega ahi
con rubberband en el envoltorio, no con la cadena de la app: alli el descenso
usa asetrate, que depende de la frecuencia de muestreo y esta calculado para
los 24 kHz de Kokoro, no para los 22050 de Piper.
Se apaga por tanto la bajada de tono de la app, para no aplicarla dos veces.
El resto del tratamiento (paso alto, ecualizador, compresor, eco) se queda:
es independiente de la frecuencia y es lo que da el aire de "voz en la sala".
"""
import json
import os
import sys
from gi.repository import Gio
SCHEMA = "io.github.qwersyk.Newelle"
ENVOLTORIO = os.path.expanduser(
"~/COFRE/CODERS/JARVIS/config/jarvis-piper.sh")
def main():
if not os.path.exists(ENVOLTORIO):
print(f"FALLO: no esta {ENVOLTORIO}")
return 1
settings = Gio.Settings.new(SCHEMA)
try:
voces = json.loads(settings.get_string("tts-voice") or "{}")
except json.JSONDecodeError:
voces = {}
# {0} es el fichero y {1} el texto; el handler los entrecomilla, asi que
# aqui van sin comillas alrededor
voces["custom_command"] = {"command": f"{ENVOLTORIO} {{0}} {{1}}"}
settings.set_string("tts-voice", json.dumps(voces))
settings.set_string("tts", "custom_command")
Gio.Settings.sync()
motor = settings.get_string("tts")
orden = json.loads(settings.get_string("tts-voice")).get(
"custom_command", {}).get("command", "")
print(f"motor de voz: {motor}")
print(f"orden: {orden}")
ok = motor == "custom_command" and ENVOLTORIO in orden
print("verificado" if ok else "FALLO: no quedo como se pidio")
if ok:
print("\nrecuerda apagar la bajada de tono de la app, que ya la hace el envoltorio:")
print(" flatpak override --user --env=JARVIS_TTS_GRAVEDAD=1.0 io.github.qwersyk.Newelle")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())