Explorar el Código

Enforce transparent asset prompts

bang hace 2 semanas
padre
commit
2067ece449
Se han modificado 2 ficheros con 42 adiciones y 30 borrados
  1. 30 21
      pipeline.py
  2. 12 9
      providers.py

+ 30 - 21
pipeline.py

@@ -43,12 +43,7 @@ def run(manifest, out_root, creds=None, log=print):
     def transparent_prompt(extra):
         return ", ".join([
             extra,
-            "no shadow",
-            "no background",
-            "transparent background",
-            "isolated subject",
-            "clean PNG asset",
-            "输出 PNG,背景必须是真实 Alpha 透明通道,不是白底,不是棋盘格。主体居中,边缘干净,适合用于 App、网页和海报叠加",
+            "生成纯透明背景 PNG,真实 Alpha 通道,不要棋盘格,不要白底,不要阴影。",
         ])
 
     def alpha_report(img):
@@ -59,6 +54,9 @@ def run(manifest, out_root, creds=None, log=print):
         ratio = transparent / max(1, img.width * img.height)
         return mn, mx, ratio
 
+    def has_alpha(img):
+        return alpha_report(img)[0] == 0
+
     def log_alpha(label, img, required):
         if not required:
             return
@@ -68,6 +66,27 @@ def run(manifest, out_root, creds=None, log=print):
         else:
             log(f"⚠️ [{label}] 模型返回 PNG 但没有透明 Alpha:alpha={mn}-{mx},请重新生成或换支持透明输出的图像模型")
 
+    def generate_checked(label, prompt, size, require_alpha):
+        retry_suffixes = [
+            "",
+            "这不是真透明背景。请重新生成:\n背景必须是 Alpha 透明通道,不是白色、灰色或棋盘格。\n去掉所有背景、阴影、光晕和底板,只保留主体,输出 PNG。",
+            "这不是真透明背景。请重新生成:\n背景必须是 Alpha 透明通道,不是白色、灰色或棋盘格。\n去掉所有背景、阴影、光晕和底板,只保留主体,输出 PNG。",
+        ]
+        last = None
+        for attempt, suffix in enumerate(retry_suffixes, start=1):
+            attempt_prompt = ", ".join(x for x in [prompt, suffix] if x)
+            if attempt > 1:
+                log(f"🔁 [{label}] Alpha 不合格,重新生成透明 PNG(第 {attempt}/{len(retry_suffixes)} 次)…")
+            img = providers.generate(creds["provider"], attempt_prompt, creds["api_key"],
+                                     creds.get("base_url", "https://api.openai.com/v1"),
+                                     creds.get("model", "gpt-image-2"),
+                                     size)
+            last = img
+            log_alpha(label, img, require_alpha)
+            if not require_alpha or has_alpha(img):
+                return img
+        raise RuntimeError(f"模型连续 {len(retry_suffixes)} 次没有返回真实 Alpha 透明通道;请换支持透明输出的图像模型或稍后重试")
+
     # ---- A. 角色(Spine)----
     for i, c in enumerate(manifest.get("characters", [])):
         cid = c.get("id", f"char_{i}")
@@ -88,11 +107,9 @@ def run(manifest, out_root, creds=None, log=print):
                         transparent_prompt("single separated rigging part only, centered, no text, no other body parts")
                     ] if x)
                     log(f"🎨 [{cid}/{part_id}] 生成 Boss 拆件…")
-                    pimg = providers.generate(creds["provider"], part_prompt, creds["api_key"],
-                                             creds.get("base_url", "https://api.openai.com/v1"),
-                                             creds.get("model", "gpt-image-2"),
-                                             part.get("size", c.get("size", creds.get("size", "1024x1024"))))
-                    log_alpha(f"{cid}/{part_id}", pimg, True)
+                    pimg = generate_checked(f"{cid}/{part_id}", part_prompt,
+                                            part.get("size", c.get("size", creds.get("size", "1024x1024"))),
+                                            True)
                     part_images[part_id] = pimg
                 spine_builder.build_parts_character(cid, part_images, chars_out, anims, parts)
                 w, h = 1000, 1000
@@ -103,11 +120,7 @@ def run(manifest, out_root, creds=None, log=print):
                     transparent_prompt("single game icon character or slot symbol, centered, full body in frame, no text, not a boss, not a demon lord, not dark armor"),
                 ] if x)
                 log(f"🎨 [{cid}] 生成角色图…")
-                img = providers.generate(creds["provider"], full_prompt, creds["api_key"],
-                                         creds.get("base_url", "https://api.openai.com/v1"),
-                                         creds.get("model", "gpt-image-2"),
-                                         c.get("size", creds.get("size", "1024x1024")))
-                log_alpha(cid, img, True)
+                img = generate_checked(cid, full_prompt, c.get("size", creds.get("size", "1024x1024")), True)
                 spine_builder.build_character(cid, img, chars_out, anims)
                 w, h = spine_builder.trim_to_content(img).size
                 files = [f"characters/{cid}.json", f"characters/{cid}.atlas", f"characters/{cid}.png"]
@@ -140,11 +153,7 @@ def run(manifest, out_root, creds=None, log=print):
                      else "full-bleed illustration, no text, no UI elements")
             full_prompt = ", ".join(x for x in [a.get("prompt", ""), style if a.get("use_style") else "", extra] if x)
             log(f"🖼  [{aid}] 生成 UI 美术…")
-            img = providers.generate(creds["provider"], full_prompt, creds["api_key"],
-                                     creds.get("base_url", "https://api.openai.com/v1"),
-                                     creds.get("model", "gpt-image-2"),
-                                     a.get("size", creds.get("size", "1024x1024")))
-            log_alpha(aid, img, transparent)
+            img = generate_checked(aid, full_prompt, a.get("size", creds.get("size", "1024x1024")), transparent)
             os.makedirs(ui_art_out, exist_ok=True)
             img.save(os.path.join(ui_art_out, f"{aid}.png"))
             library["ui_art"].append({"id": aid, "file": f"ui_art/{aid}.png",

+ 12 - 9
providers.py

@@ -104,7 +104,7 @@ def _read_http_error(error):
 
 
 def _should_retry_without_url_fields(status, body):
-    if status not in (400, 422):
+    if status not in (400, 422, 500, 502, 503):
         return False
     text = body.lower()
     retry_markers = [
@@ -117,6 +117,7 @@ def _should_retry_without_url_fields(status, body):
         "unrecognized",
         "extra inputs",
         "not permitted",
+        "upstream request failed",
     ]
     return any(marker in text for marker in retry_markers)
 
@@ -163,14 +164,16 @@ def gen_image_openai(prompt, api_key, base_url="https://api.openai.com/v1",
     headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
     model_name = model.lower()
     payload = {"model": model, "prompt": prompt, "size": size, "n": 1}
-    wants_transparent = "transparent background" in prompt.lower() or "no background" in prompt.lower()
-    if model_name == "gpt-image-2":
-        # x.long.bid exposes gpt-image-2, but its upstream only accepts the
-        # minimal image payload. Adding background/output_format causes 502.
-        pass
-    elif "gpt-image" in model_name:
-        # gpt-image-1 official-compatible endpoints can use these fields.
-        # If a gateway rejects them with 400/422, the retry path strips them.
+    prompt_l = prompt.lower()
+    wants_transparent = (
+        "transparent background" in prompt_l
+        or "no background" in prompt_l
+        or "alpha" in prompt_l
+        or "透明" in prompt
+    )
+    if "gpt-image" in model_name:
+        # Official-compatible endpoints can use these fields. Some gateways
+        # still reject them; the retry path strips them and falls back minimal.
         if wants_transparent:
             payload["background"] = "transparent"
         payload["output_format"] = "png"