#!/usr/bin/env python3
from pathlib import Path
import hashlib,json,os
ROOT=Path('/data/video-pipeline/HaTramAudio/project/025-Nguoi-Giu-Cho-Cuoi-Cung');PROJECT_ID=ROOT.name;RUN_ID='run-20260721T232244Z-a0837ee4';OWNER='Levy';WORK=ROOT/'work/artwork'/RUN_ID
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 guard():
 d=json.loads((ROOT/'.ownership-lock.json').read_text());assert d['project_id']==PROJECT_ID and d['run_id']==RUN_ID and d['owner']==OWNER
def main():
 guard();manifest=json.loads((ROOT/'script/project-manifest.json').read_text());canon=manifest.get('canon_sha256')
 story=manifest.get('gate_events',{}).get('story',{})
 if manifest.get('steps',{}).get('story')!='completed' or story.get('verified') is not True or story.get('canon_sha256')!=canon:raise RuntimeError('Story Gate is not committed/current')
 authority={k:ROOT/k for k in ['story/story-canon.txt','script/story-brief.json','script/ledger.json','script/outline.json','script/promotion-report.json']}
 if not all(p.is_file() for p in authority.values()):raise RuntimeError('committed Story Gate authority files are missing')
 promo=json.loads(authority['script/promotion-report.json'].read_text());assert promo['verified'] and promo['canon_sha256']==canon and sha(authority['story/story-canon.txt'])==canon
 brief=json.loads(authority['script/story-brief.json'].read_text());TITLE=brief['canonical_title']
 shared=f'''Thông tin nhân vật nội bộ, chỉ để giữ nhất quán, tuyệt đối không render thành chữ: Lâm Vãn Ý là nữ tổ chức dịch vụ giữ chỗ hai mươi tám tuổi, người châu Á hư cấu, tóc đen buộc thấp, áo sơ mi xanh nhạt, túi vải có các thẻ số màu cam, ánh mắt nhanh nhạy và kiên định. Chu Cảnh Thâm là nam kỹ sư thiết kế dịch vụ công ba mươi mốt tuổi, người châu Á hư cấu, tóc đen gọn, kính gọng mảnh, sơ mi trắng xắn tay, cầm sổ lưới xanh, vẻ điềm tĩnh và biết lắng nghe. Tên chính thức: "{TITLE}". Thể loại: ngôn tình đô thị, oan gia nghề nghiệp, chữa lành đời thường, hài nhẹ. Bối cảnh công khai: sảnh Trung tâm Hành chính Minh Châu vào sáng sớm, hàng ghế xám, màn hình xếp hàng trừu tượng không số đọc được, quầy hỗ trợ và ánh nắng vàng qua cửa kính. Motif công khai: thẻ số màu cam, sổ lưới xanh, các hàng ghế và đường dẫn luồng phục vụ. Giới hạn tiết lộ: không thể hiện cảnh xé danh sách ở lễ nghiệm thu, tắt dashboard, mất hợp đồng hoặc coda Vãn Ý tự lấy số.
Phong cách key art phim điện ảnh cao cấp, digital illustration bán hiện thực, tối giản, một focal point rõ, low-key lighting, tương phản mạnh có kiểm soát, depth foreground-midground-background, không collage, không card, không slide, không mockup, không mô phỏng người thật.
Mọi phiếu số, màn hình, biểu mẫu, bảng chỉ dẫn và nhãn trong cảnh phải trơn hoặc chỉ có đường nét không tạo ký tự đọc được. Không logo, watermark, số, pseudo-text hoặc chữ môi trường ngoài các khối chữ bắt buộc. Provider tạo hoàn chỉnh hình và typography trong cùng raster; cấm background no-text, text layer riêng hoặc chừa chỗ hậu kỳ.
'''
 poster=shared+f'''Tạo poster ngang tỷ lệ 16:9, master 1920x1080 cho intro và thumbnail. Lâm Vãn Ý là focal point đứng cạnh hàng ghế xám, cầm một thẻ số màu cam; Chu Cảnh Thâm đứng lớp sau cạnh quầy hỗ trợ với sổ lưới xanh. Giữa họ là lối xếp hàng chia hai luồng, nhiều ghế trống và ánh sáng bình minh vàng nhạt, tạo cảm giác đối đầu nghề nghiệp nhưng ấm áp, đời thường. Không lộ kết thúc. Provider tự quyết font, vị trí, kích thước, xuống dòng, khoảng cách và phân cấp.
RENDER CHÍNH XÁC BA KHỐI CHỮ TIẾNG VIỆT SAU TRỰC TIẾP TRONG CÙNG ẢNH, KHÔNG THÊM CHỮ KHÁC:
"Hạ Trâm Audio"
"{TITLE}"
"Truyện được phát độc quyền tại Hạ Trâm Audio, nghiêm cấm sao chép dưới mọi hình thức"
Giữ nguyên từng chữ, dấu tiếng Việt và dấu phẩy; chữ dễ đọc, không cắt hoặc che.
'''
 right=shared+f'''Tạo key art dọc tỷ lệ chính xác 656:1080, master 1080x1778 dùng nguyên raster làm panel phải. Lâm Vãn Ý là focal point ở tiền cảnh cầm thẻ số màu cam và túi vải; Chu Cảnh Thâm đứng lớp sau với sổ lưới xanh cạnh quầy hỗ trợ. Hàng ghế xám và các dải phân luồng là motif; màn hình chỉ có hình khối trừu tượng không chữ hoặc số đọc được. Bố cục dọc tối giản, không crop chủ thể quan trọng. Provider tự quyết toàn bộ typography.
RENDER CHÍNH XÁC BA KHỐI CHỮ TIẾNG VIỆT SAU TRỰC TIẾP TRONG CÙNG ẢNH, KHÔNG THÊM CHỮ KHÁC:
"Hạ Trâm Audio"
"{TITLE}"
"Truyện được phát độc quyền tại Hạ Trâm Audio, nghiêm cấm sao chép dưới mọi hình thức"
Giữ nguyên từng chữ, dấu tiếng Việt và dấu phẩy; chữ dễ đọc, không cắt hoặc che.
'''
 left=shared+'''Tạo branding panel điện ảnh dọc với KÍCH THƯỚC PIXEL BẮT BUỘC CHÍNH XÁC 1080x1778, tỷ lệ 656:1080; không được xuất tỷ lệ dọc khác, không được làm ảnh hẹp hơn hoặc cao hơn. Cùng color grade với poster: hàng ghế xám trừu tượng, dải phân luồng màu cam, cửa kính sáng sớm và các ô hình học gợi giao diện dịch vụ công nhưng không tạo chữ hoặc số; không nhân vật, không tên truyện, không dòng độc quyền. Cạnh phải có transition texture nhẹ, không aperture, khung màn hình hoặc layout giả. Provider tự quyết typography.
RENDER CHÍNH XÁC HAI KHỐI CHỮ TIẾNG VIỆT SAU TRỰC TIẾP TRONG CÙNG ẢNH, KHÔNG THÊM CHỮ KHÁC:
"Hạ Trâm Audio"
"Like • Chia sẻ • Đăng ký"
Giữ nguyên chính tả và dấu tiếng Việt; chữ dễ đọc, không cắt hoặc che.
'''
 WORK.mkdir(parents=True,exist_ok=True);items={}
 for key,prompt in {'intro_poster':poster,'right_panel':right,'left_panel':left}.items():
  p=WORK/f'{key}.prompt.txt';p.write_text(prompt);items[key]={'prompt_path':str(p),'prompt_sha256':sha(p)}
 manifest={'schema_version':1,'project_id':PROJECT_ID,'run_id':RUN_ID,'status':'locked','verified':True,'source_canon_sha256':canon,'authority_sha256':{k:sha(p) for k,p in authority.items()},'canonical_title':TITLE,'provider_skill':'tao-anh','provider_model':'cx/gpt-5.5-image','provider_only_typography':True,'text_postprocessing_allowed':False,'items':items}
 p=ROOT/'script/image-prompts.json';tmp=p.with_name('.image-prompts.json.tmp');tmp.write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n');os.replace(tmp,p);print(json.dumps({'status':'locked','verified':True,'source_canon_sha256':canon,'items':items},ensure_ascii=False))
if __name__=='__main__':main()
