Apunta los scripts de sync prod al Hetzner nuevo y suma trabajo pendiente

- sync_translations_to_prod.py / sync_audio_to_prod.py / sync_carta_from_prod.py:
  migran de FEA_PROD_HOST/PASS (CDMON, password) a FEA_PROD_SSH_HOST/PASS +
  FEA_PROD_DOCKER_CONTAINER (Hetzner/Coolify, auth por clave), con wrapping
  docker exec y el fix del bug de redirecciones (wc -c/cat) resuelto en el host
  en vez de dentro del contenedor.
- fea_translate_helper.php: subcomando clone_new para clonar en ID local nuevo
  cuando el ID de prod ya está ocupado localmente.
- translate_post.py: --dry-run.
- tts_produce.py: --allow-default-voice (voz Nico solo si se permite
  explícitamente para autores sin voz clonada) + fix voice_for_author.
- minimax_tts.py: parámetro speed en t2a/_synth_chunk.
- mirror-antiguo/deploy/nginx-mirror.conf: redirects de URLs históricas de
  catálogo y vista imprimible EFFA.
- Importaciones humanas de Pagola (carta 738) + scripts/manifest de la
  fase A Enrique, y release_issue181_prod.sh / cdmon-retire-fewp1.sh.
- .gitignore: excluye docs/backups/ (dumps SQL, mismo motivo que backups/*).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 07:09:29 -04:00
parent 159080f0c8
commit df7bf98ef1
21 changed files with 980 additions and 110 deletions
+8 -6
View File
@@ -361,12 +361,12 @@ def _split_for_tts(text, limit=CHAR_LIMIT):
return chunks
def _synth_chunk(text, voice_id, model):
def _synth_chunk(text, voice_id, model, speed=1.0):
"""Una petición t2a. Devuelve (audio_bytes|None, rc, usage_chars)."""
body = {
"model": model,
"text": text,
"voice_setting": {"voice_id": voice_id, "speed": 1.0, "vol": 1.0, "pitch": 0},
"voice_setting": {"voice_id": voice_id, "speed": speed, "vol": 1.0, "pitch": 0},
"audio_setting": {"sample_rate": 32000, "bitrate": 128000, "format": "mp3", "channel": 1},
"language_boost": "Spanish",
}
@@ -381,13 +381,15 @@ def _synth_chunk(text, voice_id, model):
return bytes.fromhex(audio_hex), 0, usage
def t2a(text, voice_id, model, name):
def t2a(text, voice_id, model, name, speed=1.0):
if not 0.5 <= float(speed) <= 2.0:
raise ValueError(f"speed fuera de rango: {speed} (permitido 0.52.0)")
chunks = _split_for_tts(text)
print(f"Sintetizando {len(text)} car con {model} / {voice_id} "
print(f"Sintetizando {len(text)} car con {model} / {voice_id} a {float(speed):.2f}× "
f"({len(chunks)} petición/es)…", flush=True)
raw = OUT / f"{name}.raw.mp3"
if len(chunks) == 1:
audio, rc, _ = _synth_chunk(chunks[0], voice_id, model)
audio, rc, _ = _synth_chunk(chunks[0], voice_id, model, speed)
if audio is None:
return rc
raw.write_bytes(audio)
@@ -398,7 +400,7 @@ def t2a(text, voice_id, model, name):
import os as _os, time as _t
_t.sleep(int(_os.environ.get("FEA_CHUNK_PAUSE", "35"))) # respetar TPM de MiniMax
print(f" trozo {k + 1}/{len(chunks)} ({len(ch)} car)…", flush=True)
audio, rc, _ = _synth_chunk(ch, voice_id, model)
audio, rc, _ = _synth_chunk(ch, voice_id, model, speed)
if audio is None:
for p in parts:
p.unlink(missing_ok=True)