diff options
Diffstat (limited to 'scripts/gen_audio.py')
| -rw-r--r-- | scripts/gen_audio.py | 39 |
1 files changed, 22 insertions, 17 deletions
diff --git a/scripts/gen_audio.py b/scripts/gen_audio.py index 9dbbb47..4b2cb29 100644 --- a/scripts/gen_audio.py +++ b/scripts/gen_audio.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""Genera un WAV por segmento con qwen-tts (voz clonada rioplatense), -verifica cada uno transcribiendo con whisper y reintenta los que salen mal.""" +"""Generates one WAV per segment with qwen-tts (cloned Rioplatense voice), +checks each one by transcribing it with whisper and retries the bad ones.""" import json, os, re, shutil, subprocess, sys, unicodedata, wave from difflib import SequenceMatcher @@ -9,13 +9,18 @@ from narration import SCRIPTS from numspell import expand ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -TTS = os.path.expanduser("~/GIT-MIRRO/qwentts.cpp/build/qwen-tts") -MODEL = os.path.expanduser("~/GIT-MIRRO/qwentts.cpp/models/qwen-talker-1.7b-base-Q8_0.gguf") -CODEC = os.path.expanduser("~/GIT-MIRRO/qwentts.cpp/models/qwen-tokenizer-12hz-Q8_0.gguf") -REF = dict(spk="/tmp/Firefox/output.spk", rvq="/tmp/Firefox/output.rvq", txt="/tmp/Firefox/output.txt") +# qwentts.cpp checkout and the cloned reference voice. The voice is not part of +# the repository: VOICE_REF is the path prefix of its .spk/.rvq/.txt files +# (produced by `qwen-codec --talker` from a recording and its transcript). +QWENTTS = os.path.expanduser(os.environ.get("QWENTTS_DIR", "~/GIT-MIRRO/qwentts.cpp")) +TTS = os.path.join(QWENTTS, "build", "qwen-tts") +MODEL = os.path.join(QWENTTS, "models", "qwen-talker-1.7b-base-Q8_0.gguf") +CODEC = os.path.join(QWENTTS, "models", "qwen-tokenizer-12hz-Q8_0.gguf") +VOICE_REF = os.path.expanduser(os.environ.get("VOICE_REF", "/tmp/Firefox/output")) +REF = dict(spk=VOICE_REF + ".spk", rvq=VOICE_REF + ".rvq", txt=VOICE_REF + ".txt") -MIN_RATIO = 0.90 # similitud minima aceptada contra la transcripcion -TIMEOUT = 150 # segundos por segmento; mas que eso es generacion desbocada +MIN_RATIO = 0.90 # minimum accepted similarity against the transcription +TIMEOUT = 150 # seconds per segment; more than that is runaway generation MAX_ROUNDS = 4 def norm(s): @@ -38,18 +43,18 @@ def synth(text, out, seed, timeout=TIMEOUT): stderr=subprocess.DEVNULL, check=True, timeout=timeout) -def synth_seguro(text, out, seed, etiqueta=""): - """A veces el modelo no emite fin y genera hasta el infinito: corto y cambio semilla.""" +def synth_safe(text, out, seed, label=""): + """Sometimes the model never emits an end and generates forever: cut and change the seed.""" for k in range(3): try: synth(text, out, seed + 7919 * k) return seed + 7919 * k except (subprocess.TimeoutExpired, subprocess.CalledProcessError): - print(f" {etiqueta} se colgo con semilla {seed + 7919 * k}, reintento", flush=True) - raise RuntimeError(f"{etiqueta}: tres intentos colgados") + print(f" {label} hung with seed {seed + 7919 * k}, retrying", flush=True) + raise RuntimeError(f"{label}: three attempts hung") def transcribe(files, workdir): - """Una sola invocacion de whisper para todos los archivos (carga el modelo una vez).""" + """A single whisper invocation for all files (the model loads once).""" if not files: return {} subprocess.run(["conda", "run", "-n", "stt", "--no-capture-output", @@ -73,7 +78,7 @@ def build(name): seeds = [1000 + i for i in range(len(segs))] pend = list(range(len(segs))) score = {} - best = {} # i -> (ratio, ruta del mejor intento) + best = {} # i -> (ratio, path of the best attempt) keep = os.path.join(work, "best") os.makedirs(keep, exist_ok=True) @@ -81,7 +86,7 @@ def build(name): if not pend: break for i in pend: - seeds[i] = synth_seguro(segs[i][0], paths[i], seeds[i], f"[{name}] {i:02d}") + seeds[i] = synth_safe(segs[i][0], paths[i], seeds[i], f"[{name}] {i:02d}") tr = transcribe([paths[i] for i in pend], work) nxt = [] for i in pend: @@ -100,7 +105,7 @@ def build(name): if pend: print(f" [{name}] ronda {rnd+2}: reintentando {pend}", flush=True) - for i, (r, b) in best.items(): # me quedo con el mejor intento, no el ultimo + for i, (r, b) in best.items(): # keep the best attempt, not the last one shutil.copy(b, paths[i]) score[i] = r @@ -112,7 +117,7 @@ def build(name): with open(os.path.join(outdir, "segments.json"), "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) bad = [s["i"] for s in data["segments"] if s["score"] < MIN_RATIO] - print(f"[{name}] listo. audio={data['total_audio']}s sin_verificar={bad}", flush=True) + print(f"[{name}] done. audio={data['total_audio']}s unverified={bad}", flush=True) if __name__ == "__main__": for n in (sys.argv[1:] or list(SCRIPTS)): |