#!/usr/bin/env python3
import json, re, subprocess, time
from pathlib import Path
PROJECT=Path('/data/video-pipeline/Channel-Loi-Co-Nhan/project/11062026-001-Biet-Du-Thi-Khong-Nhuc')
LOGO=Path('/data/video-pipeline/Channel-Loi-Co-Nhan/data/logo/logoloiconhan.png')
BGM=Path('/data/video-pipeline/Channel-Loi-Co-Nhan/data/bgm/paulyudin-sentimental-story-182512.mp3')
LOG=PROJECT/'logs/fix_timing_sub_v3.log'
SCENE_PAUSE=0.5
INTRO_PAUSE=3.0
SUB_FONT_SIZE=10
SUB_MARGIN_V=55
MAX_CHARS=34

def log(s):
    line=time.strftime('%F %T ')+s
    print(line, flush=True)
    LOG.parent.mkdir(parents=True, exist_ok=True)
    with LOG.open('a', encoding='utf-8') as f: f.write(line+'\n')

def run(cmd):
    log('RUN '+' '.join(map(str, cmd)))
    subprocess.check_call(list(map(str, cmd)))

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

def ts(sec):
    ms=int(round(sec*1000)); h=ms//3600000; ms%=3600000; m=ms//60000; ms%=60000; s=ms//1000; ms%=1000
    return f'{h:02}:{m:02}:{s:02},{ms:03}'

def chunks(text):
    text=' '.join(text.replace('\n',' ').split())
    out=[]
    for piece in re.split(r'(?<=[,.;:!?…])\s+', text):
        cur=[]
        for w in piece.split():
            cand=' '.join(cur+[w])
            if cur and len(cand)>MAX_CHARS:
                out.append(' '.join(cur)); cur=[w]
            else:
                cur.append(w)
        if cur: out.append(' '.join(cur))
    return [x for x in out if x]

def main():
    LOG.write_text('', encoding='utf-8')
    data=json.loads((PROJECT/'script/scenes.json').read_text(encoding='utf-8'))
    # Rebuild master narration with only 0.5s between scenes.
    silence=PROJECT/'audio/silence_scene_pause_0_5.wav'
    run(['ffmpeg','-y','-v','error','-f','lavfi','-i','anullsrc=r=44100:cl=mono','-t',SCENE_PAUSE,'-c:a','pcm_s16le',silence])
    concat=PROJECT/'audio/concat_0_5.txt'
    lines=[]; timeline=[]; t=0.0
    for sc in data['scenes']:
        sid=sc['scene_id']; ap=PROJECT/'audio/voice_segments'/sid/'seg_001.wav'
        d=dur(ap); visual=d+SCENE_PAUSE
        lines.append(f"file '{ap}'\nfile '{silence}'\n")
        timeline.append({'scene_id':sid,'start':t,'end':t+visual,'speech_end':t+d,'duration':visual,'audio_duration':d,'pause_sec':SCENE_PAUSE,'audio_path':str(ap),'image_path':str(PROJECT/'images/scenes'/f'{sid}.png')})
        t += visual
    concat.write_text(''.join(lines), encoding='utf-8')
    master=PROJECT/'audio/master_narration.wav'
    run(['ffmpeg','-y','-v','error','-f','concat','-safe','0','-i',concat,'-ar','44100','-ac','1','-c:a','pcm_s16le',master])
    (PROJECT/'audio/master_narration_timeline.json').write_text(json.dumps({'duration_sec':dur(master),'items':timeline},ensure_ascii=False,indent=2),encoding='utf-8')
    # Subtitle cues cover voice continuously; short natural blanks only happen in the 0.5s scene pause.
    sub=PROJECT/'subtitles/master_subtitles_one_line_bottom_split.srt'
    blocks=[]; idx=1
    for it in timeline:
        text=next(sc['narration'] for sc in data['scenes'] if sc['scene_id']==it['scene_id'])
        cs=chunks(text); start=it['start']; end=it['speech_end']; total=max(0.1,end-start)
        weights=[max(1,len(c)) for c in cs]; wsum=sum(weights); cur=start
        for c,w in zip(cs,weights):
            e=min(end, cur + total*w/wsum)
            blocks.append(f"{idx}\n{ts(cur)} --> {ts(e)}\n{c}\n")
            idx += 1; cur=e
    sub.write_text('\n'.join(blocks), encoding='utf-8')
    # Rebuild render segment durations from new timeline.
    seglist=PROJECT/'render_onepass/segments.txt'
    segtxt=[]
    for it in timeline:
        segtxt.append(f"file '{PROJECT/'images/scenes'/(it['scene_id']+'.png')}'\n")
        segtxt.append(f"duration {it['duration']:.3f}\n")
    segtxt.append(f"file '{PROJECT/'images/scenes'/(timeline[-1]['scene_id']+'.png')}'\n")
    seglist.write_text(''.join(segtxt), encoding='utf-8')
    mainv=PROJECT/'output/main_story.mp4'
    fc=(f"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,subtitles='{sub}':force_style='FontName=DejaVu Sans,FontSize={SUB_FONT_SIZE},Outline=1,Alignment=2,MarginV={SUB_MARGIN_V},WrapStyle=2'[base];"
        f"movie='{LOGO}',scale=160:-1,format=rgba,colorchannelmixer=aa=0.55[wm];[base][wm]overlay=W-w-35:35[v]")
    run(['ffmpeg','-y','-v','error','-f','concat','-safe','0','-i',seglist,'-i',master,'-filter_complex',fc,'-map','[v]','-map','1:a','-r','30','-c:v','libx264','-pix_fmt','yuv420p','-c:a','aac','-b:a','192k','-ar','44100','-ac','2','-shortest',mainv])
    # Rebuild 3s intro pause.
    thumb=PROJECT/'output/thumbnails/thumb_9x16_final.png'
    pause=PROJECT/'output/intro/intro_pause_3s.mp4'
    run(['ffmpeg','-y','-v','error','-loop','1','-i',thumb,'-f','lavfi','-i','anullsrc=r=44100:cl=stereo','-t',INTRO_PAUSE,'-vf','scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920','-r','30','-c:v','libx264','-pix_fmt','yuv420p','-c:a','aac','-shortest',pause])
    intro=PROJECT/'output/intro/intro.mp4'; final=PROJECT/'output/final/final_video.mp4'
    filter_complex=('[0:v]setpts=PTS-STARTPTS[v0];[0:a]aresample=44100:async=1:first_pts=0[a0];'
        '[1:v]setpts=PTS-STARTPTS[v1];[1:a]aresample=44100:async=1:first_pts=0[a1];'
        '[2:v]setpts=PTS-STARTPTS[v2];[2:a]aresample=44100:async=1:first_pts=0[a2];'
        '[v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[vcat][voice];'
        '[3:a]aresample=44100,volume=0.10[bgm];[voice]volume=1.8[voiceboost];'
        '[voiceboost][bgm]amix=inputs=2:duration=first:dropout_transition=2,alimiter=limit=0.95[a]')
    run(['ffmpeg','-y','-v','error','-i',intro,'-i',pause,'-i',mainv,'-stream_loop','-1','-i',BGM,'-filter_complex',filter_complex,'-map','[vcat]','-map','[a]','-r','30','-c:v','libx264','-pix_fmt','yuv420p','-c:a','aac','-b:a','192k','-ar','44100','-ac','2','-shortest',final])
    run(['ffmpeg','-v','error','-i',final,'-f','null','-'])
    info=json.loads((PROJECT/'output/info.json').read_text(encoding='utf-8'))
    info.update({'duration_sec':dur(final),'scene_pause_sec':SCENE_PAUSE,'intro_pause_sec':INTRO_PAUSE,'subtitle_mode':'one_line_bottom_split_continuous','audio_fix':'concat_filter_voice_boost_bgm_low_v3'})
    (PROJECT/'output/info.json').write_text(json.dumps(info,ensure_ascii=False,indent=2),encoding='utf-8')
    log('COMPLETE '+str(final))
if __name__=='__main__': main()
