#!/usr/bin/env python3
from __future__ import annotations
import datetime as dt,hashlib,json,os,subprocess,time,urllib.request
from pathlib import Path
ROOT=Path('/data/video-pipeline/HaTramAudio/project/018-Hon-Uoc-Khong-Duoc-Phep-Yeu');PROJECT=ROOT.name;RUN='run-20260721T040505Z-cd9f944a';OWNER='zoro';CANON='d12e1733f81bf173b5db789b0028a0be478d0d2e565c05a91e236c95cd33bd78';BASE='http://192.168.1.104:8024';INTENT=ROOT/'log/transcode-intent.json';RECEIPT=ROOT/'log/transcode-upload.json';OUT=ROOT/'output/final-upload.mp4'
def now():return dt.datetime.now(dt.timezone.utc).isoformat().replace('+00:00','Z')
def sha(p):
 h=hashlib.sha256()
 with p.open('rb') as f:
  for b in iter(lambda:f.read(8*1024*1024),b''):h.update(b)
 return h.hexdigest()
def atomic(p,v):p.parent.mkdir(parents=True,exist_ok=True);q=p.with_suffix(p.suffix+'.tmp');q.write_text(json.dumps(v,ensure_ascii=False,indent=2)+'\n');os.replace(q,p)
def get(path):
 with urllib.request.urlopen(BASE+path,timeout=30) as r:return json.load(r)
def probe(p):return json.loads(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration:stream=codec_type,codec_name,width,height,sample_rate,channels,r_frame_rate','-of','json',str(p)],text=True))
def guard():
 x=json.loads((ROOT/'.ownership-lock.json').read_text());assert (x['project_id'],x['run_id'],x['owner'],x['status'])==(PROJECT,RUN,OWNER,'main_session_pipeline');assert sha(ROOT/'story/story-canon.txt')==CANON

def main():
 guard();f=json.loads((ROOT/'log/final-render.json').read_text());assert f['status']=='completed' and f['verified'] and f['technical_verified'] and f['visual_qa']['verified'];src=Path(f['output_path']);assert src.resolve()==(ROOT/'output/final.mp4').resolve() and sha(src)==f['output_sha256'];assert not INTENT.exists() and not RECEIPT.exists() and not OUT.exists();h=get('/health');assert h['status']=='ok' and h['h264_nvenc'] is True
 payload={'input_video_path':str(src),'output_video_path':str(OUT),'target_output_bytes':800000000,'max_output_bytes':1000000000,'width':1920,'height':1080,'fps':30,'audio_bitrate_kbps':96,'overwrite':False}
 intent={'schema_version':1,'project_id':PROJECT,'run_id':RUN,'status':'submitting','source_canon_sha256':CANON,'source_final_sha256':f['output_sha256'],'request':payload,'created_at':now()};atomic(INTENT,intent)
 req=urllib.request.Request(BASE+'/v1/huyenan-transcode-upload/jobs',data=json.dumps(payload).encode(),headers={'Content-Type':'application/json'},method='POST')
 with urllib.request.urlopen(req,timeout=60) as r:created=json.load(r)
 jid=created.get('job_id') or created.get('id');assert jid;intent.update({'status':'submitted','job_id':jid,'submitted_at':now()});atomic(INTENT,intent)
 while True:
  job=get('/v1/huyenan-transcode-upload/jobs/'+jid);st=job.get('status')
  if st in ('completed','failed','cancelled'):break
  time.sleep(5)
 if st!='completed':atomic(RECEIPT,{'schema_version':1,'project_id':PROJECT,'run_id':RUN,'status':st,'verified':False,'job_id':jid,'job':job,'failed_at':now()});raise RuntimeError('transcode job '+str(st))
 ver=job.get('verification',{});assert ver.get('verified') is True and ver.get('under_one_gb') is True and Path(job['output']).resolve()==OUT.resolve() and OUT.exists() and OUT.stat().st_size<1_000_000_000;pr=probe(OUT);v=[x for x in pr['streams'] if x['codec_type']=='video'];a=[x for x in pr['streams'] if x['codec_type']=='audio'];d=float(pr['format']['duration']);assert len(v)==len(a)==1 and v[0]['codec_name']=='h264' and v[0]['width']==1920 and v[0]['height']==1080 and v[0]['r_frame_rate']=='30/1' and a[0]['codec_name']=='aac' and a[0]['sample_rate']=='48000' and abs(d-f['output_duration'])<=0.08
 receipt={'schema_version':1,'project_id':PROJECT,'run_id':RUN,'status':'awaiting_visual_qa','verified':False,'technical_verified':True,'job_id':jid,'source_canon_sha256':CANON,'source_final_sha256':f['output_sha256'],'output_path':str(OUT),'output_sha256':sha(OUT),'output_bytes':OUT.stat().st_size,'output_duration':d,'duration_delta':abs(d-f['output_duration']),'under_one_gb':True,'probe':pr,'job':job,'created_at':now()};atomic(RECEIPT,receipt);print(json.dumps({'status':'awaiting_visual_qa','job_id':jid,'bytes':receipt['output_bytes'],'sha256':receipt['output_sha256']}))
if __name__=='__main__':main()
