#!/usr/bin/env python3
from pathlib import Path
import hashlib,json,re,urllib.parse,urllib.request,subprocess,datetime
R=Path('/data/video-pipeline/HaTramAudio/project/027-Nguoi-Thua-Ke-Khong-Nhan-Chiec-Ghe-Danh-San');W=R/'work/tts';O=R/'audio';END='http://192.168.40.32:7861/voice/ngoc-huyen-clone';CANON='4db101987ecb4d1447cb9c898eba5785db54d614f0c32b16a6b7bdb6cbfbd306'
def sha(p):
 h=hashlib.sha256()
 with p.open('rb') as f:
  for b in iter(lambda:f.read(1<<20),b''):h.update(b)
 return h.hexdigest()
def atomic(p,o):
 q=p.with_suffix(p.suffix+'.tmp');q.write_text(json.dumps(o,ensure_ascii=False,indent=2)+'\n');q.replace(p)
s=(R/'script/spoken-narration.txt').read_text();paras=[x.strip() for x in re.split(r'\n\s*\n',s) if x.strip()];chunks=[];cur=''
for p in paras:
 if len(cur)+len(p)+2<=2800:cur=(cur+'\n\n'+p).strip()
 else:
  if cur:chunks.append(cur)
  cur=p
if cur:chunks.append(cur)
text=chunks[7];th=hashlib.sha256(text.encode()).hexdigest();assert th=='fcf1fccf5659b1ee78655c813bb27dca26db28bd85b457908f5b43e05a5fe805'
ps=[x for x in text.split('\n\n') if x];best=min(range(1,len(ps)),key=lambda i:abs(len('\n\n'.join(ps[:i]))-len('\n\n'.join(ps[i:]))));subs=['\n\n'.join(ps[:best]),'\n\n'.join(ps[best:])];assert ''.join(subs).replace('\n','')==text.replace('\n','')
parts=[]
for j,sub in enumerate(subs,1):
 out=W/f'recovery-0008-{j}.wav';intent={'schema_version':1,'project_id':'027','run_id':'run-20260722T063859Z-aa8eca81','segment_index':8,'subpart':j,'status':'creating','source_canon_sha256':CANON,'parent_text_sha256':th,'text_sha256':hashlib.sha256(sub.encode()).hexdigest(),'created_at':datetime.datetime.now(datetime.timezone.utc).isoformat()};atomic(W/f'recovery-0008-{j}.json',intent)
 data=urllib.parse.urlencode({'text':sub,'style':'doc_truyen','speed':'1.0','denoise':'true'}).encode();req=urllib.request.Request(END,data=data,method='POST',headers={'Content-Type':'application/x-www-form-urlencoded'});tmp=out.with_suffix('.wav.tmp')
 with urllib.request.urlopen(req,timeout=600) as resp:tmp.write_bytes(resp.read())
 subprocess.run(['ffprobe','-v','error','-show_entries','format=duration','-of','json',str(tmp)],check=True,stdout=subprocess.DEVNULL);tmp.replace(out);intent.update({'status':'completed','verified':True,'artifact_sha256':sha(out),'bytes':out.stat().st_size,'completed_at':datetime.datetime.now(datetime.timezone.utc).isoformat()});atomic(W/f'recovery-0008-{j}.json',intent);parts.append(out)
lst=W/'recovery-0008-concat.txt';lst.write_text(''.join("file '%s'\n"%p for p in parts));target=O/'segment-0008.wav';tmp=target.with_suffix('.wav.tmp');subprocess.run(['ffmpeg','-y','-v','error','-f','concat','-safe','0','-i',str(lst),'-c','copy',str(tmp)],check=True);pr=json.loads(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration','-of','json',str(tmp)]));dur=float(pr['format']['duration']);assert dur>1;tmp.replace(target)
hot=W/'hot-manifest.json';m=json.loads(hot.read_text());assert len(m['segments'])==7 and m['segments'][-1]['index']==7;m['segments'].append({'index':8,'status':'completed','text_sha256':th,'artifact_path':'audio/segment-0008.wav','artifact_sha256':sha(target),'bytes':target.stat().st_size,'duration_seconds':dur,'recovery':'split_two_subparts_concat','completed_at':datetime.datetime.now(datetime.timezone.utc).isoformat()});atomic(hot,m);print(json.dumps({'status':'completed','index':8,'duration':dur,'sha256':sha(target)}))
