#!/usr/bin/env bash # Cron del backlog de TTS por autor (issue rafa/feadulta#188). # Corre cada 5 h los lunes, viernes, sábados y domingos, aprovechando la cuota # ociosa de MiniMax para locutar artículos antiguos. Por ventana: # 1) Gate de cuota MiniMax: si la semana va alta, o si la ventana de 5 h ya la # está usando alguien (la carta semanal, el backfill de summaraise...), se # salta. El trabajo de fondo nunca le come la cuota al trabajo con dueño. # 2) tts_produce.py --autor ... --max N: la cola sale de la BD, así que esto es # idempotente por construcción — lo ya locutado no vuelve a salir. # SOLO LOCAL: no toca producción. Publicar en prod es sync_audio_to_prod.py, que # está bloqueado hasta después del cutover a Hetzner (#180). # flock evita solapes si una ventana se alargara. Log por día. set -uo pipefail export PATH="/home/rafa/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" export HOME="/home/rafa" REPO="/home/rafa/joomla-migration" PY="/home/rafa/tts-local/xtts-venv/bin/python" QUOTA="/home/rafa/ytsummaries/scripts/quota.py" WORK="/tmp/fea-tts-backlog" LOG="$WORK/cron-$(date +%F).log" LOCK="$WORK/cron.lock" # Cola: cambiar aquí (o por entorno) para pasar de un autor a otro. # 382 Fray Marcos · 383 Pagola · 774 Sicre · 386 Arregi AUTOR="${FEA_TTS_AUTOR:-382}" DESDE="${FEA_TTS_DESDE:-2025}" HASTA="${FEA_TTS_HASTA:-2026}" BATCH="${FEA_TTS_BATCH:-10}" # Gates de cuota (%). El semanal es el techo real del backlog. El de 5 h es # "¿cabe esta tanda en lo que queda de ventana?": medido, cada audio gasta ~3-4 % # de la ventana de 5 h, así que un batch de 10 pide ~40 %. Gate en 55 % deja sitio # y frena solo cuando la ventana ya la está usando otro (la carta semanal son ~28 # audios de golpe) o cuando la está llenando este mismo cron. Si se sube BATCH, # subir también este número o se saltarán ventanas. MAX_SEMANA="${FEA_TTS_MAX_SEMANA:-85}" MAX_5H="${FEA_TTS_MAX_5H:-55}" mkdir -p "$WORK" cd "$REPO" || exit 1 ts() { date +'%F %T'; } exec 9>"$LOCK" if ! flock -n 9; then echo "[$(ts)] otra corrida en curso, salto." >> "$LOG"; exit 0 fi echo "[$(ts)] === cron TTS backlog start (autor=$AUTOR $DESDE-$HASTA batch=$BATCH) ===" >> "$LOG" # 1) Gate de cuota MiniMax (ventana de 5 h y semanal) read -r PCT5 PCTW <<< "$(python3 "$QUOTA" --json --no-local 2>/dev/null | python3 -c ' import json, sys try: d = json.load(sys.stdin) m = next(p for p in d["providers"] if p["provider"] == "minimax" and p.get("ok")) print(int(m.get("five_h_pct") or 100), int(m.get("week_pct") or 100)) except Exception: print(100, 100) # sin lectura fiable de cuota, no se gasta ' 2>/dev/null || echo "100 100")" echo "[$(ts)] MiniMax 5h=${PCT5}% semana=${PCTW}%" >> "$LOG" if [ "${PCTW:-100}" -ge "$MAX_SEMANA" ]; then echo "[$(ts)] ABORT: cuota semanal ${PCTW}% >= ${MAX_SEMANA}%, salto esta ventana." >> "$LOG" exit 0 fi if [ "${PCT5:-100}" -ge "$MAX_5H" ]; then echo "[$(ts)] ABORT: ventana de 5h al ${PCT5}% >= ${MAX_5H}%, no cabe la tanda; salto." >> "$LOG" exit 0 fi # 2) Tanda. tts_produce.py ya para solo ante rc 2056/1039 (cuota/rate limit). echo "[$(ts)] tts_produce --autor $AUTOR --desde $DESDE --hasta $HASTA --max $BATCH ..." >> "$LOG" "$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \ --max "$BATCH" >> "$LOG" 2>&1 # 3) Cuánto queda tras la tanda (recuento fresco de la BD, barato y sin cuota). QUEDAN="$("$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \ --dry-run 2>/dev/null | sed -n 's/.*Cola: \([0-9]*\) posts.*/\1/p' | tail -1)" echo "[$(ts)] === cron TTS backlog done. Pendientes autor $AUTOR $DESDE-$HASTA: ${QUEDAN:-?} ===" >> "$LOG"