6b61250b0b
Este repo llevaba desde el 28-jun sin actualizarse (salvo sync_carta_from_prod.py).
Se pone al dia con 3 semanas de trabajo que solo vivian en el checkout local
completo del proyecto:
- Fix del buscador (issue #178): AND obligatorio en FULLTEXT (antes devolvia
practicamente todo el sitio con cualquier busqueda), exclusion de paginas
indice residuales de la migracion K2, soporte de frase exacta entre comillas.
- Nuevos mu-plugins ya desplegados a prod: fea-carta-id-api, fea-cloudflare-realip,
fea-crear-autor-api, fea-gsc-verification, fea-legacy-redirect (cutover
Joomla->WP, ver issue #162).
- TTS multi-voz por autor, scripts de traduccion, sync de audio a prod,
homenajes Mardones/Galarreta.
Se mantiene la estructura plana del repo (mu-plugins/ + scripts/, sin el
wordpress/wp-content/ del checkout completo) tal y como documenta el README:
es la convencion establecida para este repo, mas limpia para quien solo
necesita leer el codigo. Anadido .gitignore (cache de Python que no debia
commitearse).
Fuente: checkout local completo del proyecto, rama main (commit 69e849d).
123 lines
4.6 KiB
Python
123 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Orquestador nocturno: locuta cartas ES del gap con MiniMax, una a una,
|
|
repartido en el tiempo. Voz por defecto Nico; los artículos de autores con voz
|
|
clonada (AUTHOR_VOICES en minimax_tts.py, ej. Fray Marcos) usan la suya propia.
|
|
Reanudable (meta fea_audio_done) y con freno ante la cuota (para tras N fallos
|
|
seguidos). NO toca el front; solo genera el mp3 y asocia la URL al post (meta
|
|
fea_audio_url).
|
|
|
|
Lanzar: nohup ~/tts-local/xtts-venv/bin/python scripts/tts_produce.py > /tmp/feadulta-tts-prod.out 2>&1 &
|
|
Log: /tmp/feadulta-tts-prod.log
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import minimax_tts as mm # get_post_text, add_pauses, t2a, OUT
|
|
import translate_post as tp # carta_article_ids
|
|
|
|
VOICE = "NicoFeadulta2026"
|
|
MODEL = "speech-2.8-hd"
|
|
CONTAINER = "wordpress-web"
|
|
PROD = Path(__file__).resolve().parent.parent / "wordpress/wp-content/uploads/tts"
|
|
LOG = Path("/tmp/feadulta-tts-prod.log")
|
|
INTERVAL = 180 # s entre cartas exitosas (reparte el ritmo)
|
|
BACKOFF = 1800 # s de espera ante fallo de cuota antes de reintentar
|
|
MAX_CONSEC_FAIL = 3 # fallos seguidos → parar (cuota probablemente agotada)
|
|
MIN_CHARS = 200 # por debajo, se considera sin contenido locutable
|
|
|
|
# Cola de cartas a locutar. Override por entorno (FEA_TTS_CARTAS) para priorizar
|
|
# la carta nueva de la semana; si no, cae al orden del gap histórico.
|
|
_DEFAULT_CARTAS = "45018 44997 44975 44230 44229 44228 44090 44089 44088 44087 44086 44085 44084 44083 42590"
|
|
CARTAS = os.environ.get("FEA_TTS_CARTAS", _DEFAULT_CARTAS).replace(",", " ").split()
|
|
|
|
|
|
def log(msg):
|
|
line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
|
|
print(line, flush=True)
|
|
with LOG.open("a") as f:
|
|
f.write(line + "\n")
|
|
|
|
|
|
def php(*args):
|
|
return subprocess.run(["docker", "exec", CONTAINER, "php", "/tmp/fea_post_io.php", *args],
|
|
capture_output=True, text=True)
|
|
|
|
|
|
def meta(pid, key):
|
|
return php("getmeta", str(pid), key).stdout.strip()
|
|
|
|
|
|
def build_queue():
|
|
# Cola literal de IDs (ya filtrada/ordenada) para priorizar la carta nueva.
|
|
ids_override = os.environ.get("FEA_TTS_IDS", "").replace(",", " ").split()
|
|
if ids_override:
|
|
return [int(x) for x in ids_override if x.strip().isdigit()]
|
|
q = []
|
|
for c in CARTAS:
|
|
cid = int(c)
|
|
for pid in tp.carta_article_ids(cid):
|
|
if pid not in q:
|
|
q.append(pid)
|
|
return q
|
|
|
|
|
|
def main():
|
|
PROD.mkdir(parents=True, exist_ok=True)
|
|
subprocess.run(["docker", "cp", "scripts/fea_post_io.php", f"{CONTAINER}:/tmp/fea_post_io.php"],
|
|
capture_output=True)
|
|
queue = build_queue()
|
|
log(f"=== INICIO orquestador TTS. Cola: {len(queue)} posts ES del gap ===")
|
|
|
|
i = consec = ok = 0
|
|
while i < len(queue):
|
|
pid = queue[i]
|
|
if meta(pid, "fea_audio_done") == "1" or meta(pid, "fea_audio_skip") == "1":
|
|
i += 1
|
|
continue
|
|
try:
|
|
title, text, author = mm.get_post_text(pid)
|
|
except Exception as e: # noqa: BLE001
|
|
log(f"#{pid}: error leyendo ({e}); skip")
|
|
php("setflag", str(pid), "fea_audio_skip", "1")
|
|
i += 1
|
|
continue
|
|
if len(text) < MIN_CHARS:
|
|
log(f"#{pid}: sin contenido ({len(text)} car); skip")
|
|
php("setflag", str(pid), "fea_audio_skip", "1")
|
|
i += 1
|
|
continue
|
|
|
|
voice = mm.voice_for_author(author, VOICE)
|
|
rc = mm.t2a(mm.add_pauses(text), voice, MODEL, f"prod-{pid}")
|
|
if rc == 0:
|
|
src = mm.OUT / f"prod-{pid}.mp3"
|
|
dst = PROD / f"{pid}.mp3"
|
|
shutil.move(str(src), str(dst))
|
|
php("setaudio", str(pid), f"/wp-content/uploads/tts/{pid}.mp3", voice)
|
|
ok += 1
|
|
consec = 0
|
|
voice_tag = f" [{voice}]" if voice != VOICE else ""
|
|
log(f"#{pid} OK «{title[:45]}»{voice_tag} → tts/{pid}.mp3 (total {ok})")
|
|
i += 1
|
|
time.sleep(INTERVAL)
|
|
else:
|
|
consec += 1
|
|
log(f"#{pid} FALLO rc={rc} (fallo seguido {consec}/{MAX_CONSEC_FAIL})")
|
|
php("setflag", str(pid), "fea_audio_error", str(rc))
|
|
if consec >= MAX_CONSEC_FAIL:
|
|
log("Demasiados fallos seguidos → cuota agotada probablemente. PARO. "
|
|
"Reanudable: relanzar el script más tarde (salta lo ya hecho).")
|
|
break
|
|
time.sleep(BACKOFF) # reintenta el mismo post tras esperar
|
|
|
|
log(f"=== FIN tanda. {ok} audios generados esta ejecución. ===")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|