Files
beige/backend/scripts/real_image_check.py
yangqianqian 6a2632da70 baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

104 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""真实出图验证正式留存版不带_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)