#!/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()