#!/usr/bin/env python3
from pathlib import Path
import datetime as dt,hashlib,json,time,urllib.request
ROOT=Path('/data/video-pipeline/HaTramAudio/project/021-Ngay-Toi-Bi-Gach-Ten-Khoi-Gia-Pha');RUN='run-20260721T131911Z-ac5d64d1';CANON='bb80e97d9adcb0427ea3828449858445323db228cca38f37cdf0446fffefdd6f';TTS='f39e3db2b844dd4740e2509e46787a3aafb42110df26b1e9c0bceac675e3ad08';LAYOUT='415639e1d60ad353b7af2618c14a73a1c949ec18e40a5771425a6d491d770e76';BASE='http://192.168.1.104:8022';INTENT=ROOT/'log/footage-intent.json';RECEIPT=ROOT/'log/footage-render.json';OUT=ROOT/'output/footage/footage.mp4'
def now():return dt.datetime.now(dt.timezone.utc).isoformat().replace('+00:00','Z')
def sha(p):
 h=hashlib.sha256()
 with open(p,'rb') as f:
  while b:=f.read(8*1024*1024):h.update(b)
 return h.hexdigest()
def atomic(p,o):
 p.parent.mkdir(parents=True,exist_ok=True);t=p.with_suffix(p.suffix+'.tmp');t.write_text(json.dumps(o,ensure_ascii=False,indent=2)+'\n');t.replace(p)
def get(path):
 with urllib.request.urlopen(BASE+path,timeout=30) as r:return json.loads(r.read())
def guard():
 l=json.loads((ROOT/'.ownership-lock.json').read_text());assert l['run_id']==RUN and l['owner']=='zoro' and l['status']=='main_session_pipeline';assert sha(ROOT/'story/story-canon.txt')==CANON and sha(ROOT/'audio/story-full.wav')==TTS and sha(ROOT/'image/normalized/layout-1920x1080.png')==LAYOUT;assert json.loads((ROOT/'script/tts-manifest.json').read_text())['terminal_verified'];assert json.loads((ROOT/'image/layout-manifest.json').read_text())['verified']
def main():
 guard();health=get('/health');assert health['status']=='ok' and health['h264_nvenc'] is True and health['default_footage_dir']=='/data/video-pipeline/GacMaiAudio/footage' and health['output_policy']=='request_path_under_video_root'
 if RECEIPT.exists():
  r=json.loads(RECEIPT.read_text());
  if r.get('status')=='completed' and r.get('verified') and OUT.exists() and r.get('output_sha256')==sha(OUT):print(json.dumps({'status':'already_completed','job_id':r['job_id'],'sha256':r['output_sha256']}));return
 if INTENT.exists():
  intent=json.loads(INTENT.read_text());jid=intent.get('job_id');assert jid,'ambiguous intent without job id';job=get('/v1/footage-render-huyenan/jobs/'+jid)
 else:
  deadline=time.time()+14400
  while True:
   jobs_response=get('/v1/footage-render-huyenan/jobs')
   jobs=jobs_response.get('jobs',[]) if isinstance(jobs_response,dict) else jobs_response
   assert isinstance(jobs,list)
   running=[x for x in jobs if isinstance(x,dict) and x.get('status') in ('queued','running')]
   if not running:break
   if time.time()>deadline:raise TimeoutError('capacity wait exceeded before intent/POST')
   time.sleep(30)
  guard();payload={'layout':str(ROOT/'image/normalized/layout-1920x1080.png'),'footage_dir':'/data/video-pipeline/GacMaiAudio/footage','pattern':'*.mp4','recursive':True,'audio':str(ROOT/'audio/story-full.wav'),'output':str(OUT),'seed':20260721,'overwrite':False,'width':1920,'height':1080,'fps':30,'center_x':656,'center_y':0,'center_width':608,'center_height':1080,'video_encoder':'h264_nvenc','preset':'p4','cq':21};intent={'schema_version':1,'project_id':ROOT.name,'run_id':RUN,'status':'creating','source_canon_sha256':CANON,'audio_sha256':TTS,'layout_sha256':LAYOUT,'payload':payload,'created_at':now()};atomic(INTENT,intent);req=urllib.request.Request(BASE+'/v1/footage-render-huyenan/jobs',data=json.dumps(payload).encode(),headers={'Content-Type':'application/json'},method='POST')
  with urllib.request.urlopen(req,timeout=60) as r:job=json.loads(r.read())
  jid=job['job_id'];intent.update({'status':'submitted','job_id':jid,'submitted_at':now()});atomic(INTENT,intent)
 while job.get('status') in ('queued','running'):
  time.sleep(15);job=get('/v1/footage-render-huyenan/jobs/'+jid)
 if job.get('status')!='completed':atomic(RECEIPT,{'schema_version':1,'project_id':ROOT.name,'run_id':RUN,'status':'failed','verified':False,'job_id':jid,'response':job,'failed_at':now()});raise RuntimeError('footage job terminal '+str(job.get('status')))
 assert job['verification']['verified'] and job['output']==str(OUT) and OUT.exists();h=sha(OUT);receipt={'schema_version':1,'project_id':ROOT.name,'run_id':RUN,'status':'endpoint_completed_pending_independent_qa','verified':False,'source_canon_sha256':CANON,'audio_sha256':TTS,'layout_sha256':LAYOUT,'job_id':jid,'output_path':str(OUT),'output_sha256':h,'output_size_bytes':OUT.stat().st_size,'hash_method':'sha256_stream_8MiB','horizontal_mirror':'hflip','endpoint_response':job,'completed_at':now()};atomic(RECEIPT,receipt);print(json.dumps({'status':'endpoint_completed_pending_independent_qa','job_id':jid,'bytes':OUT.stat().st_size,'sha256':h},ensure_ascii=False))
if __name__=='__main__':main()
