aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/gen_audio.py
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-26 20:50:41 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-26 20:50:41 -0300
commitfafaebb051907a848a9406f9da19669c81a83a3b (patch)
treec30ea26e6b549e5523af2bae5c39569e9a946b12 /scripts/gen_audio.py
parent59355909f2de9236af8168a26c70bcf6caa3b285 (diff)
download100cia-videos-main.tar.gz
100cia-videos-main.zip
Translate code, comments and logs to English; English README; configurable paths and env varsHEADmain
Identifiers, docstrings, comments and console messages are now in English. Narration, subtitles and on-screen text stay in Spanish (they are the video content). The Blender <-> Godot physics protocol uses English keys and body prefixes chosen to keep the original creation order, so cached simulations and renders stay bit-identical. The old Spanish environment variable names are still accepted.
Diffstat (limited to 'scripts/gen_audio.py')
-rw-r--r--scripts/gen_audio.py39
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)):