Arreglado el boton PARAR y repaso de portabilidad

PARAR no pausaba de verdad: el HUD estaba entero para el estado "parado"
pero el backend nunca mandaba el campo "parar" en el evento controles, asi
que el boton no alternaba ni pausaba la escucha. Ahora pausa (el bucle de
escucha descarta el audio en pausa) y alterna a "reanudar".

Portabilidad (para que encaje en otros portatiles y torres, no solo en el
equipo del autor):
- microfono: pw-record / parec / arecord (PipeWire, PulseAudio o ALSA)
- whisper: binario del build local o del PATH; modelos por
  JARVIS_WHISPER_MODELOS o busqueda en varios sitios; CUDA en
  LD_LIBRARY_PATH solo si existe
- README: la tabla de portabilidad refleja audio y whisper
This commit is contained in:
sito 2026-08-16 16:06:43 +02:00
parent 55e967b940
commit a0b9afe2f9
5 changed files with 102 additions and 23 deletions

View file

@ -26,6 +26,7 @@ con tres agujeros, y aqui se cierran los tres:
"""
import collections
import os
import shutil
import signal
import struct
import subprocess
@ -50,13 +51,31 @@ MAXIMO_S = 25.0
def _grabador(fuente=None):
"""La orden para capturar crudo a la salida estandar."""
if fuente:
pw = ["pw-record", "--target", fuente]
else:
pw = ["pw-record"]
return pw + ["--rate", str(FRECUENCIA), "--channels", str(CANALES),
"--format", "s16", "-"]
"""La orden para capturar crudo (s16 mono) a la salida estandar.
Se prueba pw-record (PipeWire), parec (PulseAudio) y arecord (ALSA), en ese
orden, la primera que exista en el sistema. Asi el microfono funciona en
cualquier Linux una torre con PulseAudio, un equipo con solo ALSA y no
solo donde hay PipeWire. Los tres sacan el mismo crudo (s16 little-endian,
mono, 16 kHz) por la salida estandar, que es lo que lee el detector."""
r, c = str(FRECUENCIA), str(CANALES)
if shutil.which("pw-record"):
cmd = ["pw-record", "--rate", r, "--channels", c, "--format", "s16"]
if fuente:
cmd += ["--target", fuente]
return cmd + ["-"]
if shutil.which("parec"):
cmd = ["parec", "--rate=" + r, "--channels=" + c, "--format=s16le"]
if fuente:
cmd += ["-d", fuente]
return cmd
if shutil.which("arecord"):
cmd = ["arecord", "-q", "-r", r, "-c", c, "-f", "S16_LE", "-t", "raw"]
if fuente:
cmd += ["-D", fuente]
return cmd
# ninguno instalado: se devuelve pw-record para que el fallo sea claro
return ["pw-record", "--rate", r, "--channels", c, "--format", "s16", "-"]
class Captura:

View file

@ -56,6 +56,7 @@ import atexit
import json
import os
import re
import shutil
import signal
import socket
import subprocess
@ -64,13 +65,40 @@ import urllib.error
import urllib.request
RAIZ = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
BINARIO = os.path.join(RAIZ, "voz", "whisper-cuda", "whisper.cpp", "build", "bin",
"whisper-server")
MODELOS = os.path.expanduser(
"~/.var/app/io.github.qwersyk.Newelle/config/models/whisper/whisper.cpp/models")
# Las libs de ggml estan al lado del binario y CUDA en /usr/local. Fuera del
# flatpak esto es una linea; dentro era media pagina de flatpak-spawn.
# El binario de whisper-server: el build local del repo si esta compilado, y si
# no, el que haya en el PATH (asi vale un whisper.cpp instalado del sistema, en
# cualquier distro). Cada quien lo compila para su plataforma —CUDA, ROCm,
# Metal o CPU—; aqui solo se busca donde esta.
_LOCAL = os.path.join(RAIZ, "voz", "whisper-cuda", "whisper.cpp", "build", "bin",
"whisper-server")
BINARIO = _LOCAL if os.path.exists(_LOCAL) else (shutil.which("whisper-server") or _LOCAL)
def _dir_modelos():
"""Donde estan los ggml-*.bin. Se puede fijar con JARVIS_WHISPER_MODELOS;
si no, se prueba junto al build, el sitio estandar del usuario, y por
compatibilidad la ruta del antiguo flatpak. Gana el primero que TENGA un
modelo de verdad el dir del source de whisper.cpp existe pero suele estar
vacio, asi que mirar solo si el dir existe elegiria mal."""
import glob
env = os.environ.get("JARVIS_WHISPER_MODELOS")
if env:
return os.path.expanduser(env)
for c in (os.path.join(os.path.dirname(_LOCAL), "..", "..", "models"),
"~/.local/share/jarvis/whisper/models",
"~/.var/app/io.github.qwersyk.Newelle/config/models/whisper/whisper.cpp/models"):
ruta = os.path.normpath(os.path.expanduser(c))
if glob.glob(os.path.join(ruta, "ggml-*.bin")):
return ruta
return os.path.expanduser("~/.local/share/jarvis/whisper/models")
MODELOS = _dir_modelos()
# Las libs de CUDA, solo si estan: en un equipo AMD o CPU no hay /usr/local/cuda,
# y meterlo en LD_LIBRARY_PATH a la fuerza no aporta nada. Se añade mas abajo
# solo si el directorio existe.
LIBS = "/usr/local/cuda/lib64"
# Un prompt largo no ayuda: Whisper empieza a "oir" esas palabras donde no las
@ -223,8 +251,10 @@ class Oido:
if not self.gpu:
orden.append("--no-gpu")
entorno = {**os.environ,
"LD_LIBRARY_PATH": LIBS + ":" + os.environ.get("LD_LIBRARY_PATH", "")}
# CUDA en LD_LIBRARY_PATH solo si existe; en AMD/CPU no estorba.
entorno = dict(os.environ)
if os.path.isdir(LIBS):
entorno["LD_LIBRARY_PATH"] = LIBS + ":" + os.environ.get("LD_LIBRARY_PATH", "")
self._proc = subprocess.Popen(
orden, env=entorno, cwd=os.path.dirname(BINARIO),
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,