"""真实出图验证(正式留存版,不带_tmp,勿当临时文件清理) 用法: cd Clover/backend && python3 scripts/real_image_check.py key 只从 ../.env 读,不硬编码不打印明文。 """ from __future__ import annotations import asyncio, base64, io, logging, os, sys, time from pathlib import Path import httpx ROOT = Path(__file__).resolve().parents[2] _env = ROOT / ".env" if _env.exists(): for _l in _env.read_text().splitlines(): _l = _l.strip() if _l and not _l.startswith("#") and "=" in _l: k, _, v = _l.partition("=") os.environ.setdefault(k.strip(), v.strip()) logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger("img") TEST_PROMPT = "\n".join([ "生成一张小红书可上传的独立竖版3:4图文海报,目标1024x1536,不是App截图。", "产品:倍分子素颜霜(美妆护肤·素颜霜)。", "画面角色:封面-钩子。画面主体:自然光生活场景,手持素颜霜或产品在桌面前景,像iPhone实拍封面。", "图上文字:主标题「黄黑皮逆袭|伪素颜天花板」,点位「黄黑皮也能拥有的妈生好皮」。", "核心卖点:黄黑皮提亮、伪素颜自然、不假白不卡纹、洗面奶就能卸、懒人必备。", "成分:烟酰胺(改善暗沉)、水解珍珠(锁水保湿)、角鲨烷(不卡纹)。", "视觉风格:小红书种草风,明亮干净,暖色调,模拟iPhone主摄浅景深。", "重要限制:禁止生成App截图/界面元素;禁止肤色变白/功效前后对比;" "避免美白/祛斑/速效/医用等违规词;必须保持产品包装与参考图一致。", ]) _REF = ROOT / "test/output/01_连通测试.png" async def _apiports(key, url, ref, model, size): payload = {"model": model, "prompt": TEST_PROMPT, "n": 1, "size": size, "image": base64.b64encode(ref).decode()} async with httpx.AsyncClient(timeout=300.0) as c: r = await c.post(url, json=payload, headers={"Authorization": f"Bearer {key}"}) if r.status_code != 200: raise RuntimeError(f"HTTP {r.status_code}: {r.text[:200]}") return _parse(r.json()) async def _codeproxy(key, base, ref, model, size): files = [("model", (None, model)), ("prompt", (None, TEST_PROMPT)), ("size", (None, size)), ("n", (None, "1")), ("image[]", ("ref.png", io.BytesIO(ref), "image/png"))] async with httpx.AsyncClient(timeout=300.0) as c: r = await c.post(f"{base}/images/edits", files=files, headers={"Authorization": f"Bearer {key}"}) if r.status_code != 200: raise RuntimeError(f"HTTP {r.status_code}: {r.text[:200]}") return _parse(r.json()) def _parse(j): item = (j.get("data") or [{}])[0] if "b64_json" in item: return base64.b64decode(item["b64_json"]) if "url" in item: rr = httpx.get(item["url"], timeout=30.0); rr.raise_for_status() return rr.content raise ValueError(f"无法解析: {list(item.keys())}") async def run(): model = os.environ.get("MODEL_IMAGE", "gpt-image-2") size = os.environ.get("IMAGE_SIZE", "1024x1536") primary = os.environ.get("IMAGE_PROVIDER_PRIMARY", "apiports").lower() fallback = os.environ.get("IMAGE_PROVIDER_FALLBACK", "codeproxy").lower() out_dir = Path(__file__).parent.parent / "verify_output" out_dir.mkdir(parents=True, exist_ok=True) ref = _REF.read_bytes() t0 = time.time(); img = None; used = None for prov in [primary, fallback]: key = os.environ.get(f"{prov.upper()}_KEY", "") url = os.environ.get(f"{prov.upper()}_BASE_URL", "") if not key or not url: continue logger.info("[%s] 出图中 model=%s size=%s", prov, model, size) try: img = await (_apiports(key, url, ref, model, size) if prov == "apiports" else _codeproxy(key, url, ref, model, size)) used = prov; break except Exception as e: logger.error("[%s] 失败: %s", prov, e) dt = time.time() - t0 print("\n===== 出图结果 =====") if img: out = out_dir / "素颜霜_封面hook.png" out.write_bytes(img) print(f" provider: {used}\n 耗时: {dt:.1f}s\n 大小: {len(img)//1024}KB\n 路径: {out}") else: print(f" 耗时: {dt:.1f}s\n 状态: 全失败") print("====================\n") return img is not None if __name__ == "__main__": sys.exit(0 if asyncio.run(run()) else 1)