#!/usr/bin/env python3
"""Compose DINÁMICO (prueba estilo @danielgalloq) — reconstruye el #4 con:
  1. Captions GRANDES palabra-por-palabra (pop, keyword en dorado)
  2. Push-in / reencuadre sawtooth en la cara (zoompan) -> nunca quieta
  3. Entrada de b-roll snappy (corte duro, sin fade blando)
Mantiene cartel superior + b-rolls full-screen + suppress de captions bajo b-roll."""
import subprocess, pathlib, sys
sys.path.insert(0, "/home/clawd/bin/agentsquad-shorts")
from lib import captions

BASE = "/home/clawd/playgrounds/ai4m-dirigir"
AV = f"{BASE}/avatar.mp4"
BROLL = f"{BASE}/brolls"
WORK = pathlib.Path("/tmp/ai4m-dir-dyn"); WORK.mkdir(exist_ok=True)
OUTF = f"{BASE}/final_dyn.mp4"
CARTEL = f"{BASE}/cartel.png"
BAND = 220

BEATS = [
    {"clip": f"{BROLL}/sora.mp4",        "start": 4.0,  "end": 8.0},
    {"clip": f"{BROLL}/caminos.mp4",     "start": 25.5, "end": 33.5},
    {"clip": f"{BROLL}/herramienta.mp4", "start": 48.5, "end": 56.5},
    {"clip": f"{BROLL}/senales.mp4",     "start": 64.5, "end": 75.5},
]
sup = [(b["start"], b["end"]) for b in BEATS]

# palabras que se resaltan en dorado
GOLD = {"dirige","diriges","dirigir","dirigirla","dirigiendo","dirección","mira","miras","mirando",
        "opcional","indispensable","herramienta","equipo","contexto","prioridades","criterio",
        "16","agentes","señales","tres","resultado","importar","liderar"}

def run(cmd):
    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode != 0:
        raise RuntimeError(f"FAILED: {' '.join(map(str,cmd))}\n{r.stderr[-1800:]}")

def t2cs(t):
    cs = int(round(t*100)); h=cs//360000; cs%=360000; m=cs//6000; cs%=6000; s=cs//100; cc=cs%100
    return f"{h:d}:{m:02d}:{s:02d}.{cc:02d}"

def in_sup(a,b):
    return any(not (b<=ws or a>=we) for ws,we in sup)

def big_word_ass(words, out):
    import re
    # intervalo de cada palabra = [start, next_start)
    evs=[]
    for i,w in enumerate(words):
        txt=str(w.get("word","")).strip()
        if not txt: continue
        s=float(w["start"]);
        e=float(words[i+1]["start"]) if i+1<len(words) else float(w.get("end",s+0.4))
        if e-s<0.18: e=s+0.18
        if in_sup(s,e): continue   # suprimir bajo b-roll
        evs.append((s,e,txt))
    head=("[Script Info]\nScriptType: v4.00+\nPlayResX: 1080\nPlayResY: 1920\nScaledBorderAndShadow: yes\nWrapStyle: 2\n\n"
          "[V4+ Styles]\n"
          "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"
          # White big + gold big. Outline 6, shadow 4 for readability over face/scene.
          "Style: Big,Arial Black,148,&H00FFFFFF,&H00FFFFFF,&H00000000,&H96000000,1,0,0,0,100,100,2,0,1,7,4,5,80,80,0,1\n"
          "Style: Gold,Arial Black,148,&H004EC2EC,&H004EC2EC,&H00120E08,&H96000000,1,0,0,0,100,100,2,0,1,7,4,5,80,80,0,1\n\n"
          "[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n")
    lines=[]
    YPOS=1290  # centro vertical de la palabra (sobre el pecho, bajo la cara, sobre las manos)
    for s,e,txt in evs:
        kw = re.sub(r"[^\wáéíóúñ]","",txt.lower()) in GOLD
        style="Gold" if kw else "Big"
        # pop: entra 72% -> overshoot 106% -> 100%
        eff=(r"{\an5\pos(540,%d)\fad(40,30)\fscx72\fscy72"
             r"\t(0,110,\fscx106\fscy106)\t(110,180,\fscx100\fscy100)}" % YPOS)
        lines.append(f"Dialogue: 0,{t2cs(s)},{t2cs(e)},{style},,0,0,0,,{eff}{txt}")
    open(out,"w").write(head+"\n".join(lines)+"\n")
    return len(evs)

print("transcribiendo + alineando...")
words = captions.transcribe_words(AV)
SCRIPT_TXT = open(f"{BASE}/script.txt").read().strip()
words = captions.align_to_script(words, SCRIPT_TXT)
ass = WORK/"big.ass"
n = big_word_ass(words, ass)
print(f"  big-word captions: {n} palabras")

print("audio...")
audio_norm = WORK/"audio.m4a"
run(["ffmpeg","-y","-i",AV,"-vn","-af","loudnorm=I=-16:TP=-1.5","-ar","44100","-c:a","aac","-b:a","192k",str(audio_norm)])

print("b-rolls...")
for i,b in enumerate(BEATS):
    run(["ffmpeg","-y","-i",b["clip"],"-vf","scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,fps=30,setsar=1","-an",str(WORK/f"fill{i}.mp4")])

print("componiendo (push-in sawtooth + cartel + big captions + b-rolls snappy)...")
# base: avatar -> push-in/reencuadre sawtooth (cada 12s: zoom 1.0->1.09, snap) centrado en la cara
# zoompan: z sawtooth via on (frame nº). foco y en el tercio superior (cara).
zp=("zoompan=z='1.0+0.09*(mod(on\\,360)/360)':d=1:"
    "x='iw/2-(iw/zoom/2)':y='ih*0.40-(ih/zoom*0.40)':s=1080x1920:fps=30")
inputs=["-i",AV]
for i in range(len(BEATS)): inputs+=["-i",str(WORK/f"fill{i}.mp4")]
inputs+=["-i",str(audio_norm),"-i",CARTEL]
cartel_idx=len(BEATS)+2
parts=[
    f"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,fps=30,setsar=1,{zp},"
    f"pad=1080:{1920+BAND}:0:{BAND}:color=0x0a0a0a,crop=1080:1920:0:0[shift]",
    f"[shift][{cartel_idx}:v]overlay=0:0[carteled]",
    f"[carteled]subtitles={ass}[base]",
]
# b-rolls: entrada SNAPPY = pequeño zoom-punch al inicio (1.10->1.0 en 0.18s) + corte duro (sin fade in); fade out corto
for i,b in enumerate(BEATS):
    dur=b["end"]-b["start"]
    parts.append(
        f"[{i+1}:v]scale=1188:2112,zoompan=z='if(lt(on,6),1.10-0.10*(on/6),1.0)':d=1:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1080x1920:fps=30,"
        f"fade=t=out:st={dur-0.15:.2f}:d=0.15:alpha=1,setpts=PTS+{b['start']:.2f}/TB[ov{i}]")
base="[base]"
for i,b in enumerate(BEATS):
    lbl="[v]" if i==len(BEATS)-1 else f"[c{i}]"
    parts.append(f"{base}[ov{i}]overlay=0:0:enable='between(t,{b['start']:.2f},{b['end']:.2f})'{lbl}")
    base=lbl
fc=";".join(parts)
run(["ffmpeg","-y",*inputs,"-filter_complex",fc,"-map","[v]","-map",f"{len(BEATS)+1}:a",
     "-c:v","libx264","-preset","medium","-crf","19","-pix_fmt","yuv420p","-c:a","copy","-movflags","+faststart",OUTF])
dur=subprocess.check_output(["ffprobe","-v","error","-show_entries","format=duration","-of","csv=p=0",OUTF]).decode().strip()
print(f"OK -> {OUTF} ({float(dur):.1f}s)")
