#!/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_sub_v4.log'
SUB_FONT_SIZE=9
SUB_MARGIN_V=55
MAX_CHARS=42
MIN_CHARS=18
MIN_DUR=1.15

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())
    raw=[]
    for piece in re.split(r'(?<=[,.;:!?…])\s+', text):
        words=piece.split(); cur=[]
        for w in words:
            cand=' '.join(cur+[w])
            if cur and len(cand)>MAX_CHARS:
                raw.append(' '.join(cur)); cur=[w]
            else:
                cur.append(w)
        if cur: raw.append(' '.join(cur))
    # Merge tiny fragments so they don't flash too quickly and feel "missing".
    out=[]
    for part in raw:
        if out and (len(part)<MIN_CHARS or part in {'mình.','thiếu.'}) and len(out[-1]+' '+part)<=MAX_CHARS:
            out[-1]=out[-1]+' '+part
        else:
            out.append(part)
    # Second pass: merge a short previous chunk into the next when it fits.
    merged=[]; i=0
    while i < len(out):
        if i+1 < len(out) and len(out[i])<MIN_CHARS and len(out[i]+' '+out[i+1])<=MAX_CHARS:
            merged.append(out[i]+' '+out[i+1]); i+=2
        else:
            merged.append(out[i]); i+=1
    return [x for x in merged if x]

def write_srt():
    data=json.loads((PROJECT/'script/scenes.json').read_text(encoding='utf-8'))
    timeline=json.loads((PROJECT/'audio/master_narration_timeline.json').read_text(encoding='utf-8'))['items']
    scenes={s['scene_id']:s for s in data['scenes']}
    blocks=[]; idx=1
    for it in timeline:
        cs=chunks(scenes[it['scene_id']]['narration'])
        start=float(it['start']); end=float(it['speech_end']); total=max(0.1,end-start)
        # Allocate by text length, then enforce a soft minimum by reducing cue count if needed.
        while cs and total/len(cs) < MIN_DUR and len(cs)>1:
            # merge the shortest adjacent pair that fits best
            best=None
            for j in range(len(cs)-1):
                combo=cs[j]+' '+cs[j+1]
                score=len(combo) if len(combo)<=MAX_CHARS+10 else 999
                if best is None or score<best[0]: best=(score,j,combo)
            _,j,combo=best
            cs=cs[:j]+[combo]+cs[j+2:]
        weights=[max(1,len(c)) for c in cs]; wsum=sum(weights); cur=start
        for n,(c,w) in enumerate(zip(cs,weights)):
            e=end if n==len(cs)-1 else min(end, cur + total*w/wsum)
            blocks.append(f"{idx}\n{ts(cur)} --> {ts(e)}\n{c}\n")
            idx+=1; cur=e
    sub=PROJECT/'subtitles/master_subtitles_one_line_bottom_split.srt'
    sub.write_text('\n'.join(blocks), encoding='utf-8')
    return sub

def main():
    LOG.write_text('', encoding='utf-8')
    sub=write_srt()
    seglist=PROJECT/'render_onepass/segments.txt'
    master=PROJECT/'audio/master_narration.wav'
    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])
    intro=PROJECT/'output/intro/intro.mp4'; pause=PROJECT/'output/intro/intro_pause_3s.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),'subtitle_mode':'one_line_bottom_split_v4_min_duration','subtitle_font_size':SUB_FONT_SIZE,'subtitle_max_chars':MAX_CHARS,'subtitle_min_duration_sec':MIN_DUR})
    (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()
