#!/usr/bin/env python3 """Mide el ajuste ACTUAL contra el propuesto, con las mismas frases. Las dos vueltas anteriores compararon cosas distintas entre si; esto compara lo que hay hoy en la cache con lo que se quiere poner, para poder decir una cifra honesta de cuanto mejora. De paso deja creada la referencia buena: ref/jarvis_sesion.wav, las tomas de una misma sesion pegadas. Mezclar sesiones distintas puntuaba peor —el promediado de XTTS diluye el timbre cuando los clips no suenan igual—, que es justo lo contrario de lo que se supuso al montarlo. python antes_despues.py [cuda|cpu] """ import glob import os import subprocess import sys import tempfile import torch DISPOSITIVO = sys.argv[1] if len(sys.argv) > 1 else "cuda" BASE = os.path.dirname(os.path.abspath(__file__)) REF = os.path.join(BASE, "ref") SALIDA = os.path.join(BASE, "comparativa") DESCARGAS = os.path.expanduser("~/Downloads") SESION = os.path.join(REF, "jarvis_sesion.wav") os.environ.setdefault("COQUI_TOS_AGREED", "1") FRASES = [ "Buenas noches, señor. Soy su asistente personal.", "Un momento, lo miro.", "He encontrado catorce ficheros, señor. Dígame cuál abro.", ] # lo que hay hoy en generar_cache_voz.py ANTES = dict( refs=[os.path.join(REF, n) for n in ("jarvis_lento_b.wav", "jarvis_lento_a.wav", "jarvis_ref.wav")], cond=dict(gpt_cond_len=6, gpt_cond_chunk_len=6, max_ref_length=30), gen=dict(temperature=0.65, repetition_penalty=2.0, speed=1.0), ) # lo propuesto DESPUES = dict( refs=[SESION], cond=dict(gpt_cond_len=30, gpt_cond_chunk_len=6, max_ref_length=30), gen=dict(temperature=0.65, repetition_penalty=2.0, speed=1.0), ) def parecido(a, b): return float(torch.nn.functional.cosine_similarity( a.squeeze().float(), b.squeeze().float(), dim=0)) def construye_sesion(): """Pega las tomas de la misma sesion en un unico wav de referencia.""" if os.path.exists(SESION): return SESION mp3s = sorted(glob.glob(os.path.join(DESCARGAS, "Jarvis-*Buenas-noches*.mp3"))) if not mp3s: raise SystemExit("no encuentro los mp3 de la sesion en ~/Downloads") with tempfile.TemporaryDirectory() as tmp: trozos = [] for i, m in enumerate(mp3s): d = os.path.join(tmp, f"{i}.wav") subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-i", m, "-ar", "24000", "-ac", "1", d], check=True) trozos.append(d) lista = os.path.join(tmp, "lista.txt") with open(lista, "w") as f: for t in trozos: f.write(f"file '{t}'\n") subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i", lista, "-ar", "24000", "-ac", "1", SESION], check=True) print(f" referencia nueva: {SESION}") return SESION def main(): os.makedirs(SALIDA, exist_ok=True) construye_sesion() from TTS.api import TTS import soundfile as sf print(f"cargando XTTS en {DISPOSITIVO}...", flush=True) api = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(DISPOSITIVO) modelo = api.synthesizer.tts_model largo = dict(gpt_cond_len=30, gpt_cond_chunk_len=6, max_ref_length=30) def emb(rutas): _, e = modelo.get_conditioning_latents(audio_path=list(rutas), **largo) return e patron = emb([os.path.join(REF, "jarvis_lento_b.wav")]) print() for etiqueta, cfg in (("ANTES (lo que suena hoy)", ANTES), ("DESPUES(lo propuesto) ", DESPUES)): gpt, hablante = modelo.get_conditioning_latents( audio_path=cfg["refs"], **cfg["cond"]) puntos = [] for j, frase in enumerate(FRASES): out = modelo.inference(text=frase, language="es", gpt_cond_latent=gpt, speaker_embedding=hablante, enable_text_splitting=True, **cfg["gen"]) nombre = ("antes" if cfg is ANTES else "despues") + f"_{j}.wav" sf.write(os.path.join(SALIDA, nombre), out["wav"], 24000) puntos.append(parecido(patron, emb([os.path.join(SALIDA, nombre)]))) media = sum(puntos) / len(puntos) detalle = " ".join(f"{p:.3f}" for p in puntos) print(f" {etiqueta} {media:.4f} [{detalle}]", flush=True) print(f"\n wavs para escuchar en {SALIDA} (antes_*.wav / despues_*.wav)") if __name__ == "__main__": main()