#!/usr/bin/env python3 """CHAR-004 R3 · 反乞丐滑坡版 · 纯文生图""" import sys, os, json, time, subprocess from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) from secrets_loader import secret, endpoint API_KEY = secret("SC-001") BASE_URL = endpoint("EPT-001") IMAGE_MODEL = "doubao-seedream-4-0-250828" REPO_ROOT = Path(__file__).resolve().parents[1] JZAO_BASE = Path("/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/图片/zai-fu-fei-xiu-xian/ep01") CANDIDATES_DIR = REPO_ROOT / "assets/candidates/D157-VA-05-001-R3" / "CHAR-004-ZhugeFeng" POSITIVE = """3D动漫卡通渲染,国风玄幻动态漫质感,单人男性角色全身立绘,纯白背景。 角色为16岁少年,清瘦挺拔,身高约175cm,肩宽略窄,身形单薄但精神利落,不佝偻,不颓废。小麦色肤色,面部轮廓棱角分明,剑眉星目,眼神锐利有韧劲,鼻梁挺直,嘴唇偏薄,下颌线清晰,少年气强,气质冷硬。黑色短发,高束半马尾,发尾有少量碎发,用普通深棕色木簪固定,额前散落几缕碎发,整体整洁。 服装为朴素修仙学徒服,灰蓝色棉麻粗布交领上衣,右衽,袖口收窄,内搭浅色中衣;衣服整体干净整洁,只在领口、袖口边缘有非常轻微的磨毛质感,没有破洞,没有补丁,没有污渍。腰间系深棕色粗麻绳腰带,系法简单。下装为深灰蓝色同色系棉麻长裤,裤脚收进黑色短靴,膝盖和裤脚仅有轻微磨白,没有破损,没有泥污。黑色粗布短靴,厚鞋底,干净整洁。 腰间麻绳上挂一个拳头大小的深灰色旧布包,布包完整平整,布料略有年代感,但没有破口,没有补丁,没有脏污。 整体气质是清贫但整洁、有骨气、一心求仙的少年,不是乞丐,不是流民,不是流浪汉。 人物完整居中露出,比例协调,线条清晰,色彩柔和,卡通渲染,3D动漫角色质感。""" NEGATIVE = """乞丐,流民,流浪汉,破损服装,破洞,补丁,污渍,泥污,脏鞋,破裤腿,烂袖口,脏布包,佝偻,颓废,萎靡,肮脏,凌乱,衣衫褴褛,夸张破旧,CHAR-003同款脸,CHAR-003同款五官,CHAR-003同款发型,与苏白长相相似,真人照片,写实,畸形肢体,五官崩坏,模糊,水印。""" VARIATIONS = [ "正面直视镜头,表情沉稳有韧劲", "微微侧头,目光坚毅看向前方", "略微仰视,下颌抬起,眼神向上有向往", "3/4侧脸,侧视左前方,少年英气", ] def call_api(prompt_text, neg_text, seed): url = f"{BASE_URL}/images/generations" payload = json.dumps({ "model": IMAGE_MODEL, "prompt": prompt_text, "negative_prompt": neg_text, "size": "1440x2560", "n": 1, "seed": seed, }) proc = subprocess.run([ "curl", "-s", "-m", "120", "--noproxy", "*", "-X", "POST", url, "-H", f"Authorization: Bearer {API_KEY}", "-H", "Content-Type: application/json", "-d", payload ], capture_output=True, text=True, timeout=130) try: data = json.loads(proc.stdout) if "data" in data and len(data["data"]) > 0: return data["data"][0].get("url") except: pass return None def download(url, dst): Path(dst).parent.mkdir(parents=True, exist_ok=True) subprocess.run(["curl", "-s", "-m", "60", "-L", "--noproxy", "*", "-o", dst, url], capture_output=True, timeout=65) return os.path.exists(dst) and os.path.getsize(dst) > 1024 def scale(src, dst): subprocess.run(["sips", "-z", "1920", "1080", str(src), "--out", str(dst)], capture_output=True) return os.path.exists(dst) def main(): print("CHAR-004 R3 · 反乞丐滑坡") print(f"策略: 整洁优先+负面词锁死+{len(VARIATIONS)}角度变体") if not API_KEY: print("✗ 无API Key"); sys.exit(1) ts = time.strftime("%Y%m%d-%H%M%S") run_dir = JZAO_BASE / "D157-R3" / ts run_dir.mkdir(parents=True, exist_ok=True) CANDIDATES_DIR.mkdir(parents=True, exist_ok=True) results = [] seeds = [42, 137, 256, 399] for i, (seed, var) in enumerate(zip(seeds, VARIATIONS)): full = f"{POSITIVE} {var}" print(f"\n[{i+1}/4] seed={seed} 角度={var[:10]}...") url = call_api(full, NEGATIVE, seed) if not url: print(" ✗ API失败"); results.append({"c": i+1, "ok": False, "err": "api"}) continue raw = run_dir / f"CHAR-004-R3-{i+1:02d}-1440x2560.jpg" if not download(url, raw): print(" ✗ 下载失败"); results.append({"c": i+1, "ok": False, "err": "download"}) continue print(f" ✓ raw {os.path.getsize(raw)/1024:.0f}KB") scaled = CANDIDATES_DIR / f"CHAR-004-R3-candidate-{i+1:02d}-1080x1920.jpg" if not scale(raw, scaled): print(" ✗ 缩放失败"); results.append({"c": i+1, "ok": False, "err": "scale"}) continue print(f" ✓ scaled {os.path.getsize(scaled)/1024:.0f}KB") results.append({"c": i+1, "ok": True, "raw": str(raw), "scaled": str(scaled), "seed": seed}) time.sleep(2) ok = sum(1 for r in results if r["ok"]) print(f"\n{'='*40}\n完成: {ok}/4") meta = {"spec": "VA-05-001-R3", "asset": "CHAR-004", "ts": ts, "ok": ok, "total": 4, "results": results} with open(CANDIDATES_DIR / "generation-meta.json", "w") as f: json.dump(meta, f, ensure_ascii=False, indent=2) return 0 if ok >= 4 else 1 if __name__ == "__main__": sys.exit(main())