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.
This commit is contained in:
commit
8e4bc8ad94
125 changed files with 25033 additions and 0 deletions
80
voz/analiza_voz.py
Normal file
80
voz/analiza_voz.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Compara el original y el clon en lo que el oido nota, no solo en timbre.
|
||||
|
||||
La puntuacion de parecido que se venia usando es el coseno entre embeddings de
|
||||
hablante: mide TIMBRE. Es ciega al tono y al ritmo, y una voz puede tener el
|
||||
timbre correcto y aun asi "no parecerse nada" porque habla mas rapido, mas
|
||||
agudo o mas plano que el original. Esto mide eso.
|
||||
|
||||
- tono medio y rango (F0): si el clon habla medio tono por encima, se nota
|
||||
muchisimo mas que una diferencia de timbre
|
||||
- variacion del tono: un original expresivo contra un clon monotono suena a
|
||||
otra persona aunque el timbre coincida
|
||||
- velocidad: silabas por segundo, aproximada por picos de energia
|
||||
- brillo: centroide espectral, delata si el clon suena mas apagado
|
||||
|
||||
python analiza_voz.py fichero.wav [fichero2.wav ...]
|
||||
"""
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def carga(ruta, sr=22050):
|
||||
import librosa
|
||||
y, _ = librosa.load(ruta, sr=sr, mono=True)
|
||||
return y, sr
|
||||
|
||||
|
||||
def analiza(ruta):
|
||||
import librosa
|
||||
y, sr = carga(ruta)
|
||||
|
||||
# F0 con pyin, que es lento pero fiable en voz
|
||||
f0, sonoro, _ = librosa.pyin(
|
||||
y, sr=sr, fmin=50, fmax=400, frame_length=2048)
|
||||
f0v = f0[~np.isnan(f0)]
|
||||
|
||||
# velocidad: picos del envelope de energia ~ silabas
|
||||
env = librosa.onset.onset_strength(y=y, sr=sr)
|
||||
picos = librosa.util.peak_pick(env, pre_max=3, post_max=3, pre_avg=3,
|
||||
post_avg=5, delta=0.2, wait=4)
|
||||
dur = len(y) / sr
|
||||
silabas_s = len(picos) / dur if dur else 0
|
||||
|
||||
# brillo
|
||||
cent = float(np.mean(librosa.feature.spectral_centroid(y=y, sr=sr)))
|
||||
|
||||
# proporcion de voz sonora: cuanto del audio es habla con tono
|
||||
prop = float(np.mean(sonoro)) if sonoro is not None else 0.0
|
||||
|
||||
return {
|
||||
"dur": dur,
|
||||
"f0_medio": float(np.median(f0v)) if len(f0v) else 0.0,
|
||||
"f0_p10": float(np.percentile(f0v, 10)) if len(f0v) else 0.0,
|
||||
"f0_p90": float(np.percentile(f0v, 90)) if len(f0v) else 0.0,
|
||||
"f0_desv": float(np.std(f0v)) if len(f0v) else 0.0,
|
||||
"silabas_s": silabas_s,
|
||||
"brillo": cent,
|
||||
"sonoro": prop,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
print(f"{'fichero':34s} {'dur':>6s} {'tono':>7s} {'rango':>11s} "
|
||||
f"{'varia':>7s} {'sil/s':>6s} {'brillo':>7s}")
|
||||
print("-" * 84)
|
||||
for ruta in sys.argv[1:]:
|
||||
try:
|
||||
a = analiza(ruta)
|
||||
except Exception as e:
|
||||
print(f"{ruta[:34]:34s} error: {e}")
|
||||
continue
|
||||
nombre = ruta.split("/")[-1][:34]
|
||||
print(f"{nombre:34s} {a['dur']:5.1f}s {a['f0_medio']:6.1f}Hz "
|
||||
f"{a['f0_p10']:4.0f}-{a['f0_p90']:<4.0f}Hz {a['f0_desv']:6.1f} "
|
||||
f"{a['silabas_s']:6.2f} {a['brillo']:6.0f}Hz")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue