#!/usr/bin/env python3
"""冒烟臂提交器：H3无损融合单采 × validation case02 (5s 短臂)
diff 纪律: case 素材/prompt/时长/seed/输出前缀，其余零改动。
"""
import json, sys, copy, urllib.request, urllib.error

BASE = json.load(open('/home/kai/shared/2026-10-17/h3_lossless_fusion_api.json'))

def build(case: str, dur_s: float, prompt: str, seed: int, out_prefix: str,
          megapixels: float = None, width: int = None, height: int = None,
          ref_files: dict = None):
    api = copy.deepcopy(BASE)
    # 时长: PrimitiveFloat#227 (秒) + length 由 224 数学吸附
    api['227']['inputs']['value'] = dur_s
    # FIX(冒烟2教训): API 图里 224 是孤儿节点(零消费者), 136.length 是字面量。
    # 直接在 JSON 层算吸附值: round(秒*24) 吸附到 17n+5 网格
    _L = max(5, round(dur_s * 24))
    _L += (5 - (_L % 17)) % 17
    api['136']['inputs']['length'] = _L
    # prompt
    api['238']['inputs']['value'] = prompt
    # seed 固定
    api['129']['inputs']['noise_seed'] = seed
    # 输出前缀
    api['148']['inputs']['filename_prefix'] = out_prefix
    # 分辨率: ResolutionSelector widget / 或显式 width/height (改用 h3#136 直填)
    if megapixels is not None:
        api['228']['inputs']['megapixels'] = megapixels
    if width and height:
        # H3#136 widgets 原为 [_,1344,1792,1008,'match'] — width/height 真值在 136
        api['136']['inputs']['width'] = width
        api['136']['inputs']['height'] = height
    # ref images: h3lossless/caseNN/refN.jpg / scene.png
    # 原映射: ref0<-199(角色A), ref1..5<-188,189,190,191,192
    if ref_files:
        mapping = [('199','ref_images.ref_image_0'), ('188','ref_images.ref_image_1'),
                   ('189','ref_images.ref_image_2'), ('190','ref_images.ref_image_3'),
                   ('191','ref_images.ref_image_4'), ('192','ref_images.ref_image_5')]
        for (nid, key), fname in zip(mapping, ref_files):
            if fname:
                api[nid]['inputs'] = {'image': fname}
            else:
                # 断链该 ref
                api['136']['inputs'].pop(key, None)
                del api[nid]
    return api

def submit(api, port=8190):
    body = json.dumps({'prompt': api, 'client_id': 'h3lossless'}).encode()
    req = urllib.request.Request(f'http://127.0.0.1:{port}/prompt', data=body,
                                 headers={'Content-Type': 'application/json'})
    try:
        r = urllib.request.urlopen(req, timeout=120)
        resp = json.loads(r.read().decode())
        return resp
    except urllib.error.HTTPError as e:
        return {'ERROR': json.loads(e.read().decode())}

if __name__ == '__main__':
    case = 'case02'
    # 5s 冒烟: 分辨率 0.5MP@16:9 ≈ 960x544 (笔记表 L0.5 行), seed 固定
    prompt = open('/home/kai/文档/LTX/validition_v1/02/prompt.txt').read().replace('\r\n',' ').strip()
    # FIX(冒烟3教训): 服色跑偏根因=砍掉外观段。原作者设计=完整integrated prompt
    # (外观+动态一体), ref板只锚身份/脸, 服装颜色语义靠prompt文字。喂全文。
    prompt_short = prompt.replace('\r\n', ' ').strip()
    api = build(case, dur_s=5.0, prompt=prompt_short, seed=20261017,
                out_prefix='h3lossless/smoke4_case02_5s',
                megapixels=0.5, width=960, height=544,
                ref_files=['h3lossless/case02/ref0.jpg','h3lossless/case02/ref1.jpg',
                           'h3lossless/case02/scene.png', None, None, None])
    resp = submit(api)
    print(json.dumps(resp, ensure_ascii=False, indent=1)[:2000])
    if 'prompt_id' in resp:
        json.dump(api, open(f'/home/kai/shared/2026-10-17/h3lossless/api_{case}_5s.json','w'),
                  ensure_ascii=False, indent=1)
