import os,sys,json,hashlib,subprocess
from pathlib import Path

def load_env(path):
 for line in Path(path).read_text(errors='ignore').splitlines():
  line=line.strip()
  if line and not line.startswith('#') and '=' in line:
   k,v=line.split('=',1);os.environ.setdefault(k.strip(),v.strip().strip('"\''))
load_env('/opt/OpenMontage/.env');sys.path.insert(0,'/opt/OpenMontage')
from tools.graphics.cx_image import CXImage
P='gacmai_20260718_091744';R=Path('/data/video-pipeline/GacMaiAudio/project')/P;A=R/'artifacts';out=R/'assets/images';out.mkdir(parents=True,exist_ok=True)
contract=A/'visual_campaign_contract_rev1.json';csha=hashlib.sha256(contract.read_bytes()).hexdigest();assert csha=='3d345b87df3fa3d0c67458a6930b2cbf41a82f80ac7de3c4e7b9e5e920a16f9b'
base='Premium cinematic semi-realistic HAND-PAINTED GOUACHE-AND-OIL theatrical poster campaign, unmistakable expressive brush strokes on faces, clothes and props, simplified graphic tonal planes, hand-painted edges, subtle paper tooth, tactile but intentionally non-photographic materials, Vietnamese faces and hands, muted dark teal table-tennis hall with old tungsten amber accents, cream paper details, premium print grain. CLEARLY ILLUSTRATED POSTER ART. NOT photography, NOT film still, NOT photoreal, NOT CGI concept render, NOT plastic 3D, NOT flat vector, NOT anime. No readable text, no numbers, no logo, no watermark, no brand, no courtroom symbols, no accusation, no villain, no luxury sports arena. '
prompts={
'intro_source_rev5.png':('1920x1080',base+'NATIVE LANDSCAPE 16:9 hand-painted gouache/oil key art. EXACTLY one identifiable person: Nguyễn Hoài Lan, Vietnamese woman 42, low-tied black hair, blue-gray rolled-sleeve shirt, charcoal trousers, worn ivory canvas shoes. She waits behind a painted floor line holding a completely BLANK cream ticket. In her other hand she carries a faded olive-brown OPEN-TOP canvas TABLE-TENNIS RACKET BAG together with a COMPLETE OLD WOODEN TABLE-TENNIS PADDLE held visibly OUTSIDE the bag: the small oval paddle blade is fully visible above the bag opening, with a narrow neck and 20-centimeter oval wood grip, worn varnish and visible wood grain. The complete paddle silhouette is unmistakable and physically separated by clear empty space from every bag strap and trim. No cylindrical stick, no hidden blade, no ambiguity. Empty scratched dark-teal table and cloth net lead to an enamel board containing ONLY solid abstract color rectangles and an analog wall clock containing ONLY twelve plain tick marks and two hands. ABSOLUTELY ZERO letters, numerals, pseudo-writing, glyphs, labels, brands or scribbles on any surface; table apron plain solid dark teal. One face only, no other person. Restrained tension, no accusation or triumph. Visible expressive brush strokes, simplified graphic tonal planes, premium theatrical illustrated poster, clearly not photograph/film still/CGI. Safe inset for Lan, ticket, complete bag and exposed handle, board and clock.'),
}
provider=CXImage();results=[]
for name,(size,prompt) in prompts.items():
 path=out/name
 r=provider.execute({'prompt':prompt,'model':'cx/gpt-5.5-image','size':size,'quality':'high','background':'auto','image_detail':'high','output_format':'png','n':1,'output_path':str(path)})
 if not r.success:raise SystemExit(f'{name}: {r.error}')
 probe=subprocess.check_output(['ffprobe','-v','error','-select_streams','v:0','-show_entries','stream=width,height','-of','json',str(path)],text=True)
 stream=json.loads(probe)['streams'][0];w=int(stream['width']);h=int(stream['height'])
 results.append({'file':str(path),'sha256':hashlib.sha256(path.read_bytes()).hexdigest(),'requested_size':size,'measured_width':w,'measured_height':h,'measured_aspect':w/h,'provider':r.data.get('provider'),'model':r.model})
(A/'visual_generation_manifest_rev0.json').write_text(json.dumps({'status':'generated_pending_qa','contract_sha256':csha,'results':results},ensure_ascii=False,indent=2)+'\n')
print(json.dumps({'status':'generated_pending_qa','results':results},ensure_ascii=False))
