from pathlib import Path
import subprocess, json, glob
BASE=Path('/home/kevin/shared/video-packs/ia-tomo-control-V1-production')
V2=Path('/home/kevin/shared/video-packs/ia-tomo-control-V2-premium')
OUT=V2/'video'; TMP=V2/'tmp_build'; QA=V2/'qa'; MAN=V2/'manifests'; SHORT=V2/'short'
for d in [OUT,TMP,QA,MAN,SHORT]: d.mkdir(parents=True, exist_ok=True)

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

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

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

scenes=[]
for i in range(1,10):
    sid=f's{i:02d}'
    audio=first(BASE/f'assets/runway-visual-voice/{sid}-voiceover-*.mp3') or first(BASE/f'assets/runway-gateway/{sid}-voiceover-*.mp3')
    visual=first(BASE/f'assets/runway-visual-voice/{sid}-visual-*.mp4') or first(BASE/f'assets/runway-gateway/{sid}-visual-*.mp4')
    image=first(BASE/f'assets/image2-scenes/scene-{i:02d}-*.png')
    style=BASE/f'assets/styleframes-v2/styleframe-{i:02d}-measured-text-v2.png'
    avatar_map={1:'s01-hook-avatar-v2.mp4',2:'s02-mobile-avatar-v2.mp4',4:'s04-defense-avatar-v2.mp4',6:'s06-cyber-avatar-v2.mp4',9:'s09-close-avatar-v2.mp4'}
    avatar=V2/'assets/avatar-v2'/avatar_map[i] if i in avatar_map else None
    if avatar and not avatar.exists(): avatar=None
    scenes.append(dict(i=i,sid=sid,audio=audio,visual=visual,image=image,style=style if style.exists() else None,avatar=avatar))

concat_list=TMP/'concat-scenes.txt'
manifest=[]
with open(concat_list,'w') as cf:
    for s in scenes:
        if not s['audio']:
            raise SystemExit(f'missing audio {s["sid"]}')
        adur=dur(s['audio'])
        intro=min(5.5, max(3, adur*0.08)) if s['style'] else 0
        rem=max(0.1, adur-intro)
        seg_intro=TMP/f'{s["sid"]}_intro.mp4'
        seg_main=TMP/f'{s["sid"]}_main.mp4'
        seg_video=TMP/f'{s["sid"]}_video.mp4'
        scene_out=TMP/f'{s["sid"]}_scene.mp4'
        if intro>0:
            vf='scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1,format=yuv420p'
            run(['ffmpeg','-y','-loop','1','-framerate','25','-t',f'{intro:.3f}','-i',str(s['style']),'-vf',vf,'-an','-c:v','libx264','-preset','veryfast','-crf','18','-r','25',str(seg_intro)], TMP/f'{s["sid"]}_intro.log')
        if s['visual']:
            vf='scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1,fps=25,format=yuv420p'
            run(['ffmpeg','-y','-stream_loop','-1','-t',f'{rem:.3f}','-i',str(s['visual']),'-vf',vf,'-an','-c:v','libx264','-preset','veryfast','-crf','20','-r','25',str(seg_main)], TMP/f'{s["sid"]}_main.log')
        else:
            vf="scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1,zoompan=z='min(zoom+0.00035,1.08)':d=1:s=1920x1080:fps=25,format=yuv420p"
            run(['ffmpeg','-y','-loop','1','-framerate','25','-t',f'{rem:.3f}','-i',str(s['image']),'-vf',vf,'-an','-c:v','libx264','-preset','veryfast','-crf','20','-r','25',str(seg_main)], TMP/f'{s["sid"]}_main.log')
        if intro>0:
            l=TMP/f'{s["sid"]}_parts.txt'; l.write_text(f"file '{seg_intro}'\nfile '{seg_main}'\n")
            run(['ffmpeg','-y','-f','concat','-safe','0','-i',str(l),'-c','copy',str(seg_video)], TMP/f'{s["sid"]}_concat.log')
        else:
            seg_video=seg_main
        if s['avatar']:
            avdur=dur(s['avatar']); start=min(7.0, max(0.0, adur-avdur-1)); end=min(adur, start+avdur)
            fc=("[0:v]format=yuv420p[bg];"
                "[1:v]scale=520:-2:force_original_aspect_ratio=decrease,setsar=1[av];"
                f"[bg][av]overlay=x=W-w-90:y=H-h-90:enable='between(t,{start:.3f},{end:.3f})':format=auto,format=yuv420p[v]")
            run(['ffmpeg','-y','-i',str(seg_video),'-i',str(s['avatar']),'-i',str(s['audio']),'-filter_complex',fc,'-map','[v]','-map','2:a','-t',f'{adur:.3f}','-c:v','libx264','-preset','veryfast','-crf','18','-c:a','aac','-b:a','192k','-shortest',str(scene_out)], TMP/f'{s["sid"]}_mux.log')
        else:
            run(['ffmpeg','-y','-i',str(seg_video),'-i',str(s['audio']),'-map','0:v','-map','1:a','-t',f'{adur:.3f}','-c:v','libx264','-preset','veryfast','-crf','18','-c:a','aac','-b:a','192k','-shortest',str(scene_out)], TMP/f'{s["sid"]}_mux.log')
        cf.write(f"file '{scene_out}'\n")
        manifest.append({k:(str(v) if isinstance(v,Path) else v) for k,v in s.items()} | {'duration':adur,'sceneFile':str(scene_out)})
long=OUT/'la-ia-ya-tomo-el-control-V2-assembly-avatar-image2-runway-motionarray-pending.mp4'
run(['ffmpeg','-y','-f','concat','-safe','0','-i',str(concat_list),'-c','copy',str(long)], TMP/'concat_long.log')
short=SHORT/'la-ia-ya-tomo-el-control-V2-assembly-short-9x16-motionarray-pending.mp4'
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'
run(['ffmpeg','-y','-i',str(long),'-t','59','-vf',vf,'-c:v','libx264','-preset','veryfast','-crf','19','-c:a','aac','-b:a','192k',str(short)], TMP/'short.log')
(MAN/'v2-assembly-manifest.json').write_text(json.dumps({'status':'assembly_created_motionarray_pending','long':str(long),'short':str(short),'scenes':manifest},indent=2,ensure_ascii=False))
print(long)
print(short)
