#!/usr/bin/env python3
import json, re, subprocess, wave
from pathlib import Path

PROJECT=Path('/data/video-pipeline/Youtube-Video-Maker/Project/08062026-003-Le-Hoan')
SEG_MANIFEST=PROJECT/'audio/voice_segments/voice_segments_manifest.json'
MASTER=PROJECT/'audio/master_narration.wav'
TIMELINE=PROJECT/'audio/master_narration_timeline.json'
LOG_MANIFEST=PROJECT/'logs/step8_compose_tts_manifest.json'
TMP=PROJECT/'audio/_compose_tmp'
PRE_HEADING_PAUSE=1.0
POST_HEADING_PAUSE=1.0
SENTENCE_POST_PAUSE=0.6
STRONG_POST_PAUSE=0.8
SAMPLE_RATE=24000
CHANNELS=1
SAMPLE_WIDTH=2


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


def convert(src, dst):
    dst.parent.mkdir(parents=True, exist_ok=True)
    subprocess.check_call(['ffmpeg','-y','-v','error','-i',str(src),'-ac',str(CHANNELS),'-ar',str(SAMPLE_RATE),'-sample_fmt','s16',str(dst)], timeout=60)


def silence_frames(sec):
    frames=int(round(sec*SAMPLE_RATE))
    return b'\x00' * frames * CHANNELS * SAMPLE_WIDTH


def post_pause_for(text, scene_type, is_last_in_scene):
    if scene_type == 'heading' and is_last_in_scene:
        return POST_HEADING_PAUSE
    t=text.strip().rstrip('”"')
    if t.endswith(('!', '?', '…', ':')):
        return STRONG_POST_PAUSE
    if t.endswith('.'):
        return SENTENCE_POST_PAUSE
    return 0.0


def main():
    TMP.mkdir(parents=True, exist_ok=True); MASTER.parent.mkdir(parents=True, exist_ok=True)
    doc=json.loads(SEG_MANIFEST.read_text(encoding='utf-8'))
    items=doc['items']
    scenes=json.loads((PROJECT/'script/scenes.json').read_text(encoding='utf-8'))['scenes']
    scene_lookup={s['scene_id']:s for s in scenes}
    voice_timeline=[]; scene_timeline=[]; cursor=0.0
    current_scene_id=None; scene_acc=None
    with wave.open(str(MASTER),'wb') as out:
        out.setnchannels(CHANNELS); out.setsampwidth(SAMPLE_WIDTH); out.setframerate(SAMPLE_RATE)
        for item in items:
            sid=item['scene_id']; scene_type=item.get('scene_type') or scene_lookup.get(sid,{}).get('type','')
            if current_scene_id != sid:
                if scene_acc:
                    scene_timeline.append(scene_acc)
                current_scene_id=sid
                pre=PRE_HEADING_PAUSE if scene_type=='heading' else 0.0
                if pre:
                    out.writeframes(silence_frames(pre)); cursor += pre
                scene_acc={'scene_id':sid,'scene_type':scene_type,'type':scene_type,'narration':scene_lookup.get(sid,{}).get('narration',item['source_narration']),'source_text':scene_lookup.get(sid,{}).get('narration',item['source_narration']),'image_path':str(PROJECT/'images/scenes'/f'{sid}.png'),'start':round(cursor,3),'end':None,'duration':None,'pre_pause':pre,'post_pause':0.0,'voice_segment_ids':[]}
            src=Path(item['audio_path'])
            tmp=TMP/f"{item['voice_segment_id']}.wav"
            convert(src,tmp)
            dur=duration(tmp)
            seg_start=cursor
            with wave.open(str(tmp),'rb') as w:
                out.writeframes(w.readframes(w.getnframes()))
            cursor += dur
            is_last = item['segment_index_in_scene'] == item['segments_in_scene']
            post=post_pause_for(item['text'], scene_type, is_last)
            voice_timeline.append({'scene_id':sid,'voice_segment_id':item['voice_segment_id'],'segment_index_in_scene':item['segment_index_in_scene'],'segments_in_scene':item['segments_in_scene'],'audio_path':item['audio_path'],'image_path':item['image_path'],'text':item['text'],'start':round(seg_start,3),'end':round(seg_start+dur,3),'duration':round(dur,3),'pre_pause':0.0,'post_pause':post})
            scene_acc['voice_segment_ids'].append(item['voice_segment_id'])
            scene_acc['end']=round(cursor,3)
            scene_acc['duration']=round(scene_acc['end']-scene_acc['start'],3)
            scene_acc['post_pause']=post if is_last else 0.0
            if post:
                out.writeframes(silence_frames(post)); cursor += post
        if scene_acc:
            scene_timeline.append(scene_acc)
    payload={'project_id':PROJECT.name,'step':8,'version':'voice_segments_scene_grouped','master_audio':str(MASTER),'total_duration_sec':round(cursor,3),'timeline':scene_timeline,'voice_segment_timeline':voice_timeline,'pause_rules':{'pre_heading_seconds':PRE_HEADING_PAUSE,'post_heading_seconds':POST_HEADING_PAUSE,'sentence_post_pause_seconds':SENTENCE_POST_PAUSE,'strong_post_pause_seconds':STRONG_POST_PAUSE,'split_punctuation':['.','!','?','…',':']}}
    TIMELINE.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8')
    LOG_MANIFEST.write_text(json.dumps({'project_id':PROJECT.name,'step':8,'status':'completed','master_audio':str(MASTER),'scene_items':len(scene_timeline),'voice_segments':len(voice_timeline),'duration_sec':round(cursor,3)}, ensure_ascii=False, indent=2), encoding='utf-8')
    print(json.dumps({'status':'completed','scene_items':len(scene_timeline),'voice_segments':len(voice_timeline),'duration_sec':round(cursor,3)}, ensure_ascii=False))

if __name__=='__main__': main()
