from pathlib import Path
import subprocess, json, glob, math, os, shutil
from PIL import Image, ImageDraw, ImageFont

BASE=Path('/home/kevin/shared/video-packs/ia-tomo-control-V1-production')
WORK=Path('/home/kevin/shared/video-packs/ia-tomo-control-V6-do-it-right')
NCROOT=Path('/mnt/nexus-drive-22tb/nextcloud-data/silvio@silviocosta.net/files/Maria/canal-ia-video-packs/2026-05-19-ia-tomo-control')
MA=NCROOT/'assets/motionarray'
for d in ['segments','chunks','overlays','video','short','qa','manifests','logs']:
    (WORK/d).mkdir(parents=True, exist_ok=True)

FONT_B='/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf'
FONT_R='/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'

def font(sz,bold=False): return ImageFont.truetype(FONT_B if bold else FONT_R, sz)

def run(cmd, log):
    with open(log,'w') as f:
        p=subprocess.run(cmd, stdout=f, stderr=f)
    if p.returncode:
        raise RuntimeError(f'cmd failed {log}: {cmd}')

def first(pat):
    xs=sorted(glob.glob(str(pat)))
    return Path(xs[0]) if xs else None

def duration(p):
    return float(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration','-of','csv=p=0',str(p)], text=True).strip())

def asset(name):
    p=MA/name
    if not p.exists(): raise FileNotFoundError(p)
    return p

scenes=[
 dict(id='s01',title='LA IA YA TOMÓ EL CONTROL',chips=['Decide','Vende','Vigila','Actúa'],voice=first(BASE/'assets/runway-visual-voice/s01-voiceover-*.mp3'),avatar=Path('/home/kevin/shared/video-packs/ia-tomo-control-V2-premium/assets/avatar-v2/s01-hook-avatar-v2.mp4'),pip=False,full_avatar=True,assets=[asset('3108585-artificial-intelligence-network.mp4'), first(BASE/'assets/runway-visual-voice/s01-visual-*.mp4'), asset('3430075-ai-datasets-data-center-server-racks.mp4')]),
 dict(id='s02',title='EL MÓVIL PASA A SER AGENTE',chips=['Contexto','Permisos','Frecuencia'],voice=first(BASE/'assets/runway-visual-voice/s02-voiceover-*.mp3'),avatar=Path('/home/kevin/shared/video-packs/ia-tomo-control-V2-premium/assets/avatar-v2/s02-mobile-avatar-v2.mp4'),pip=True,assets=[asset('2849636-smart-virtual-assistant-smartphone.mp4'), first(BASE/'assets/runway-visual-voice/s02-visual-*.mp4'), asset('3979675-ai-brain-microchip-data-streams.mp4')]),
 dict(id='s03',title='ANUNCIOS DENTRO DE LA CONFIANZA',chips=['Orgánico','Patrocinado','Persuasión'],voice=first(BASE/'assets/runway-visual-voice/s03-voiceover-*.mp3'),avatar=None,pip=False,assets=[asset('1853685-ai-in-marketing.mp4'), asset('3565482-business-ai-automation-tools.mp4'), first(BASE/'assets/runway-gateway/s03-visual-*.mp4') or first(BASE/'assets/runway-visual-voice/s03-visual-*.mp4')]),
 dict(id='s04',title='IA CIVIL EN DEFENSA',chips=['Chips','Nube','Modelos','Decisión'],voice=first(BASE/'assets/runway-visual-voice/s04-voiceover-*.mp3'),avatar=Path('/home/kevin/shared/video-packs/ia-tomo-control-V2-premium/assets/avatar-v2/s04-defense-avatar-v2.mp4'),pip=True,assets=[asset('2676690-military-command-center-satellite-feeds.mp4'), first(BASE/'assets/runway-visual-voice/s04-visual-*.mp4'), asset('3430075-ai-datasets-data-center-server-racks.mp4')]),
 dict(id='s05',title='PROTEGER SIN CONVERTIRLO TODO EN VIGILANCIA',chips=['Protección','Privacidad','Orden judicial'],voice=first(BASE/'assets/runway-visual-voice/s05-voiceover-*.mp3'),avatar=None,pip=False,assets=[asset('3610580-cybernetics-expert-ai-interface.mp4'), first(BASE/'assets/runway-gateway/s05-visual-*.mp4') or first(BASE/'assets/runway-visual-voice/s05-visual-*.mp4'), asset('2594359-cyber-defense-team-soc.mp4')]),
 dict(id='s06',title='LA IA BAJA EL PRECIO DEL CIBERCRIMEN',chips=['Identidad fuerte','Backups','Mínimos privilegios'],voice=first(BASE/'assets/runway-visual-voice/s06-voiceover-*.mp3'),avatar=Path('/home/kevin/shared/video-packs/ia-tomo-control-V2-premium/assets/avatar-v2/s06-cyber-avatar-v2.mp4'),pip=True,assets=[asset('2594359-cyber-defense-team-soc.mp4'), first(BASE/'assets/runway-visual-voice/s06-visual-*.mp4'), asset('3610580-cybernetics-expert-ai-interface.mp4')]),
 dict(id='s07',title='EL NUEVO PETRÓLEO SON LOS VATIOS',chips=['Chips','Energía','Modelos / datos'],voice=first(BASE/'assets/runway-visual-voice/s07-voiceover-*.mp3') or first(BASE/'assets/runway-gateway/s07-voiceover-*.mp3'),avatar=None,pip=False,assets=[asset('3430075-ai-datasets-data-center-server-racks.mp4'), first(BASE/'assets/runway-gateway/s07-visual-*.mp4'), asset('3979675-ai-brain-microchip-data-streams.mp4')]),
 dict(id='s08',title='LA IA SALE DE LA PANTALLA',chips=['Diseña','Simula','Coordina','Mueve'],voice=first(BASE/'assets/runway-visual-voice/s08-voiceover-*.mp3') or first(BASE/'assets/runway-gateway/s08-voiceover-*.mp3'),avatar=None,pip=False,assets=[asset('4128523-humanoid-robots-futuristic-industry.mp4'), first(BASE/'assets/runway-gateway/s08-visual-*.mp4'), asset('3108585-artificial-intelligence-network.mp4')]),
 dict(id='s09',title='QUIÉN DISEÑA LAS REGLAS',chips=['Control humano','Transparencia','Auditoría','Soberanía'],voice=first(BASE/'assets/runway-visual-voice/s09-voiceover-*.mp3'),avatar=Path('/home/kevin/shared/video-packs/ia-tomo-control-V2-premium/assets/avatar-v2/s09-close-avatar-v2.mp4'),pip=False,full_avatar=True,assets=[first(BASE/'assets/runway-visual-voice/s09-visual-*.mp4'), asset('3108585-artificial-intelligence-network.mp4'), asset('3430075-ai-datasets-data-center-server-racks.mp4')]),
]

def overlay_png(scene, idx):
    W,H=1920,1080
    im=Image.new('RGBA',(W,H),(0,0,0,0)); d=ImageDraw.Draw(im)
    # subtle left gradient panel
    for x in range(0,760):
        a=int(150*(1-x/760))
        d.line([(x,0),(x,H)],fill=(2,8,20,a))
    title=scene['title']
    ftitle=font(58, True); fchip=font(34, True); fsmall=font(24, False)
    # measured title box, split if needed
    words=title.split(); lines=[]; cur=''
    for w in words:
        test=(cur+' '+w).strip()
        if d.textbbox((0,0),test,font=ftitle)[2] > 980 and cur:
            lines.append(cur); cur=w
        else: cur=test
    lines.append(cur)
    x,y=80,74
    for line in lines[:2]:
        bb=d.textbbox((0,0),line,font=ftitle); tw,th=bb[2]-bb[0],bb[3]-bb[1]
        padX,padY=34,20
        d.rounded_rectangle([x-24,y-16,x+tw+padX,y+th+padY],radius=22,fill=(2,10,24,210),outline=(59,210,255,200),width=2)
        d.text((x,y),line,font=ftitle,fill=(245,250,255,255))
        y += th+48
    d.text((84, y+4), 'Punto clave', font=fsmall, fill=(160,225,255,230))
    y += 54
    # chips
    cx=80; cy=y
    for chip in scene['chips'][:4]:
        bb=d.textbbox((0,0),chip,font=fchip); tw,th=bb[2]-bb[0],bb[3]-bb[1]
        box=[cx,cy,cx+tw+58,cy+th+34]
        d.rounded_rectangle(box,radius=18,fill=(6,22,42,220),outline=(47,190,255,190),width=2)
        d.text((cx+29,cy+16),chip,font=fchip,fill=(255,255,255,255))
        cy += th+52
    # right-side editorial progress nodes (small, not microtext)
    nx,ny=1410,86
    d.rounded_rectangle([nx-24,ny-24,1850,ny+74],radius=18,fill=(2,10,20,145),outline=(255,255,255,55),width=1)
    d.text((nx,ny),'CANAL IA',font=font(28,True),fill=(245,250,255,235))
    # bottom energetic rule
    d.line([(80,1000),(1840,1000)],fill=(0,210,255,150),width=3)
    out=WORK/'overlays'/f"{scene['id']}_{idx:02d}.png"
    im.save(out)
    return out

def prep_bg(src, dur, out, scene, idx):
    ov=overlay_png(scene,idx)
    ext=src.suffix.lower()
    if ext in ['.png','.jpg','.jpeg']:
        vf=f"[0:v]scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,zoompan=z='min(zoom+0.0011,1.12)':d=1:s=1920x1080:fps=25,format=rgba[bg];[bg][1:v]overlay=0:0,format=yuv420p"
        cmd=['ffmpeg','-y','-loop','1','-framerate','25','-t',f'{dur:.3f}','-i',str(src),'-i',str(ov),'-filter_complex',vf,'-f','lavfi','-t',f'{dur:.3f}','-i','anullsrc=channel_layout=stereo:sample_rate=48000','-map','[v]' if False else '0:v']
        # use simpler below due mapping
        cmd=['ffmpeg','-y','-loop','1','-framerate','25','-t',f'{dur:.3f}','-i',str(src),'-i',str(ov),'-filter_complex',vf,'-t',f'{dur:.3f}','-c:v','libx264','-preset','veryfast','-crf','19','-r','25','-an',str(out)]
    else:
        vf=f"[0:v]scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,fps=25,eq=contrast=1.04:saturation=1.08,format=rgba[bg];[bg][1:v]overlay=0:0,format=yuv420p"
        cmd=['ffmpeg','-y','-stream_loop','-1','-t',f'{dur:.3f}','-i',str(src),'-i',str(ov),'-filter_complex',vf,'-t',f'{dur:.3f}','-c:v','libx264','-preset','veryfast','-crf','19','-r','25','-an',str(out)]
    run(cmd, WORK/'logs'/f'{out.stem}.log')

def build_scene(scene):
    ad=duration(scene['voice'])
    # Create 7-9 sec visual beats
    beat=7.0
    chunks=[]; t=0; idx=0
    # Full avatar for first 10-14s where available, then broll
    while t < ad-0.05:
        cd=min(beat, ad-t)
        srcs=scene['assets']
        if scene.get('full_avatar') and idx < 2 and scene['avatar'].exists():
            src=scene['avatar']
        else:
            src=srcs[idx % len(srcs)]
        if not src or not Path(src).exists():
            src=first(BASE/f'assets/styleframes-v2/styleframe-{int(scene["id"][1:]):02d}-measured-text-v2.png')
        out=WORK/'chunks'/f"{scene['id']}_{idx:02d}.mp4"
        prep_bg(Path(src), cd, out, scene, idx)
        chunks.append(out); t += cd; idx += 1
    lst=WORK/'chunks'/f"{scene['id']}_list.txt"
    lst.write_text(''.join(f"file '{c}'\n" for c in chunks))
    bg=WORK/'chunks'/f"{scene['id']}_bg.mp4"
    run(['ffmpeg','-y','-f','concat','-safe','0','-i',str(lst),'-c','copy',str(bg)], WORK/'logs'/f'{scene["id"]}_concat_bg.log')
    out=WORK/'segments'/f"{scene['id']}_v6.mp4"
    if scene.get('pip') and scene.get('avatar') and scene['avatar'].exists():
        # topmost PIP: starts after 1s, lasts 26s or full avatar duration if shorter; muted, scene voice stays master
        vf="[0:v]setpts=PTS-STARTPTS[bg];[2:v]setpts=PTS-STARTPTS,scale=500:282:force_original_aspect_ratio=increase,crop=500:282,format=rgba[pip0];color=c=0x07101b:s=524x306:d=999,format=rgba[frame];[frame][pip0]overlay=12:12[pipf];[bg][pipf]overlay=W-w-56:H-h-56:enable='between(t,1,28)',format=yuv420p[v]"
        cmd=['ffmpeg','-y','-i',str(bg),'-i',str(scene['voice']),'-stream_loop','-1','-i',str(scene['avatar']),'-filter_complex',vf,'-map','[v]','-map','1:a','-t',f'{ad:.3f}','-c:v','libx264','-preset','veryfast','-crf','18','-r','25','-c:a','aac','-b:a','192k','-ar','48000','-ac','2','-shortest',str(out)]
    else:
        cmd=['ffmpeg','-y','-i',str(bg),'-i',str(scene['voice']),'-map','0:v','-map','1:a','-t',f'{ad:.3f}','-c:v','libx264','-preset','veryfast','-crf','18','-r','25','-c:a','aac','-b:a','192k','-ar','48000','-ac','2','-shortest',str(out)]
    run(cmd, WORK/'logs'/f'{scene["id"]}_final.log')
    return out, ad

manifest=[]; segs=[]
for sc in scenes:
    seg, ad=build_scene(sc); segs.append(seg)
    manifest.append({k:(str(v) if isinstance(v,Path) else ([str(x) for x in v] if isinstance(v,list) else v)) for k,v in sc.items() if k!='voice'} | {'voice':str(sc['voice']),'duration':ad,'segment':str(seg)})
concat=WORK/'segments/concat_v6.txt'; concat.write_text(''.join(f"file '{s}'\n" for s in segs))
long=WORK/'video/la-ia-ya-tomo-el-control-V6-do-it-right-motionarray-pip-graphics.mp4'
run(['ffmpeg','-y','-f','concat','-safe','0','-i',str(concat),'-c','copy',str(long)], WORK/'logs/render_long_v6.log')
# normalize timestamps
fixed=WORK/'video/la-ia-ya-tomo-el-control-V6-do-it-right-motionarray-pip-graphics-fixed.mp4'
run(['ffmpeg','-y','-fflags','+genpts','-i',str(long),'-vf','fps=25,format=yuv420p','-af','aresample=async=1:first_pts=0','-c:v','libx264','-preset','veryfast','-crf','19','-c:a','aac','-b:a','192k','-ar','48000','-ac','2',str(fixed)], WORK/'logs/fix_long_v6.log')
short=WORK/'short/la-ia-ya-tomo-el-control-V6-do-it-right-short-9x16.mp4'
run(['ffmpeg','-y','-i',str(fixed),'-t','59','-vf','scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,format=yuv420p','-c:v','libx264','-preset','veryfast','-crf','19','-c:a','aac','-b:a','192k','-ar','48000','-ac','2',str(short)], WORK/'logs/render_short_v6.log')
(WORK/'manifests/v6-do-it-right-manifest.json').write_text(json.dumps({'version':'V6 do it right','invalidates':'V5','long':str(fixed),'short':str(short),'requirements':['MotionArray real assets visible across scenes','Runway animated visuals retained','avatar PIP topmost where real scene avatar exists','editorial graphics/text overlays measured with PIL boxes','images/videos animated or cut every ~7 seconds'], 'scenes':manifest}, indent=2, ensure_ascii=False))
print(fixed); print(short)
