#!/usr/bin/env python3
"""对照臂: GLM-4.6V 云端评判同样任务, 输出 results/judge_glm.jsonl
与 judge.py (本地 Qwen3.8-27B) 完全同 prompt 同帧, 用于 A/B 判别力对比。
只跑 pos + neg (跳过 rep, 省时)。
"""
import json, base64, time, sys, re
from pathlib import Path
import urllib.request

EP = Path("/home/kai/runs/ep-cat-worker-001")
FR = Path("/home/kai/shared/qwen-eye-exp/frames")
OUT = Path("/home/kai/shared/qwen-eye-exp/results")
API = "https://open.bigmodel.cn/api/anthropic/v1/messages"
KEY = "8c860aa821db4700a1e39051f71143b1.gPRie2pQqpbJN44M"

shots = json.load(open(EP / '.pipeline-assets/shot-list.json'))['value']
spec_by_id = {s['shot_id']: s for s in shots}

# 与 judge.py 相同的 prompt (复制, 保持逐字一致)
PROMPT = """你是影视级AI视频质检员。给你【镜头规格 SPEC】和该镜头视频的5帧均匀采样(按时间顺序 image1 最早 → image5 最晚)。逐维判断视频是否符合规格。

判定标准:
- framing(景别): 远景WS=环境为主人物很小 / 中景FS/MS=膝~腰上 / 近景MCU=胸部以上 / 过肩OTS=越肩看对方 / 特写CU/BCU=脸部或局部充满画面 / INSERT=插入的局部或物件
- camera(运镜): 固定=画面框基本不动 / 跟随=主体保持画面中背景持续流动 / 缓慢推进=视野收窄主体渐大 / 缓慢拉远=视野扩大主体渐小 / 下俯=俯视角
- composition(构图): 三分法=主体位于三分线或交点附近 / 引导线=画面线条(建筑/道路/站台)引导视线指向主体
- axis(轴线): 左→右=主体运动或朝向从左向右 / 右→左=反向 / 中立=无明显横向方向
- 多段景别如 "MCU→WS" 取首尾帧变化判断; "MCU/CU montage" 取主导类型

严格输出以下 JSON（不要 markdown 代码块, 不要额外文字）:
{"shot_id":"...","framing_ok":bool,"camera_ok":bool,"composition_ok":bool,"axis_ok":bool,"quality_issues":["肢体畸变/多肢/面部崩坏/服装异常/背景漂移等,无则空"],"confidence":0.0-1.0,"verdict":"pass|fail","reasons":"一句话核心依据"}

SPEC: """

def b64(p):
    return "data:image/jpeg;base64," + base64.b64encode(Path(p).read_bytes()).decode()

def call(shot_id, spec, tag):
    d = FR / shot_id
    content = [{"type": "text", "text": PROMPT + json.dumps({k: spec.get(k) for k in
        ['shot_id','shot_type','framing','camera_movement','composition','axis_line','intent']},
        ensure_ascii=False)}]
    for f in ['f0.jpg', 'f1.jpg', 'f2.jpg', 'f3.jpg', 'f4.jpg']:
        content.append({"type": "image", "source": {"type": "base64",
            "media_type": "image/jpeg", "data": b64(d / f).split(",", 1)[1]}})
    body = json.dumps({
        "model": "glm-4.6v", "max_tokens": 600,
        "messages": [{"role": "user", "content": content}]
    }).encode()
    t0 = time.time()
    req = urllib.request.Request(API, data=body, headers={
        "Content-Type": "application/json", "x-api-key": KEY, "anthropic-version": "2023-06-01"})
    for attempt in range(3):
        try:
            with urllib.request.urlopen(req, timeout=300) as r:
                resp = json.load(r)
            break
        except Exception as e:
            if attempt == 2: raise
            time.sleep(5 * (attempt + 1))
    dt = time.time() - t0
    txt = resp['content'][0]['text'].strip()
    m = re.search(r'\{.*\}', txt, re.S)
    try:
        parsed = json.loads(m.group(0)) if m else None
    except Exception:
        parsed = None
    return {"tag": tag, "video": shot_id, "spec_of": spec.get('shot_id'), "elapsed_s": round(dt, 1),
            "raw": txt, "parsed": parsed}

CONTROLS = [
    ("S1-01", "S4-01", "fail", "强反差:WS远景跟随 vs CU特写推进"),
    ("S5-03", "S8-01", "fail", "强反差:CU特写固定 vs 远景拉远"),
    ("S7-03", "S6-02", "fail", "强反差:WS跟随 vs CU推进"),
    ("S4-02", "S2-02", "fail", "强反差:MCU固定 vs WS跟随"),
    ("S8-01", "S1-03", "fail", "强反差:远景拉远 vs INSERT特写"),
    ("S3-01", "S1-02", "fail", "中反差:FS中景 vs MCU过肩"),
    ("S1-02", "S2-01", "fail", "仅运镜:固定 vs 缓慢拉远"),
    ("S7-02", "S5-03", "fail", "仅运镜:缓慢推进 vs 固定"),
]

def main():
    log = open(OUT / 'judge_glm.jsonl', 'a')
    tasks = []
    for s in shots: tasks.append(("pos", s['shot_id'], spec_by_id[s['shot_id']]))
    for vid, sid, _, _ in CONTROLS: tasks.append(("neg", vid, spec_by_id[sid]))
    for i, (tag, vid, spec) in enumerate(tasks):
        rec = call(vid, spec, tag)
        log.write(json.dumps(rec, ensure_ascii=False) + "\n"); log.flush()
        p = rec['parsed']
        verdict = p.get('verdict') if p else "PARSE_FAIL"
        print(f"[{i+1}/{len(tasks)}] {tag:3} video={vid} spec={rec['spec_of']} -> {verdict} ({rec['elapsed_s']}s)", flush=True)
    log.close()
    print("DONE")

if __name__ == '__main__':
    main()
