guanghulab/video-ai-system/tools/char004_gen_r3.py

122 lines
5.6 KiB
Python
Raw Normal View History

#!/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())