冰朔 c81307004d
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
铸渊自检 / health-check (push) Has been cancelled
D158 · 会话收尾 · 进度+脚本+QC报告入库(不含候选图)
- CURRENT-STATE 更新至D158
- 生成脚本: char004/char004_r3/prop002/prop002_r3/prop003 全量
- QC脚本: qc_char004_r2/r3 + qc_prop_r2
- QC报告: CHAR-004 R2~R5 + PROP全量
- generation-meta: 所有候选批次元数据
- 候选图(JPG)保留本地不入库
2026-07-02 01:07:43 +08:00

223 lines
8.3 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.

#!/usr/bin/env python3
"""
CHAR-004 诸葛风 · VA-05-001-R2 · 纯文生图执行脚本
分两步:先锁服装 → 再调面部
禁用任何图像参考全程纯文字prompt
"""
import os, sys, json, time, base64, subprocess
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from secrets_loader import secret, endpoint
# ── API 配置 ──
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-R2" / "CHAR-004-ZhugeFeng"
# ── R2规范 · 全文字prompt ──
CHAR004_PROMPT = """
3D动漫卡通渲染国风玄幻动态漫风格画面质感与光影水平统一。
男性16岁清瘦挺拔全身立绘正面站立姿势。身高约175cm肩宽偏窄身形单薄但有力量感无佝偻萎靡感。
面部:小麦色肤色,面部轮廓棱角分明,剑眉星目,眼神锐利有韧劲,鼻梁挺直,嘴唇偏薄,下颌线清晰。气质偏冷硬,少年气强,与温润型角色明确区分。
发型:纯黑色短发,高束半马尾,发尾有零散碎发。用一根普通深棕色木簪固定,无华丽发饰。额前散落几缕碎发,整体整洁不凌乱。
上衣:灰蓝色粗布交领短打,右衽,袖口收窄贴合手腕。衣服洗得微微褪色,领口、袖口边缘有轻微磨毛痕迹。无破洞、无污渍、无补丁,干净整洁。腰间系一根深棕色粗麻绳腰带,系法简单。
下装:深灰蓝色同色系粗布长裤,裤脚收紧塞进靴筒,膝盖位置有轻微磨白痕迹。无破损、无污渍。
鞋子:黑色粗布短靴,厚鞋底,鞋头有轻微磨损痕迹,干净无泥污。
配饰:无玉佩、香囊、首饰。仅腰间麻绳上挂一个拳头大的深灰色旧布包,布包平整无破损。
整体:清贫但不落魄,整洁有骨气。纯白背景,全身从头到鞋完整展示,画面干净。
负面:不要破洞,不要污渍,不要补丁,不要乞丐,不要乞丐服,不要麻绳缠身,不要短袖,不要破损衣服,不要无鞋,不要半身。
""".strip().replace("\n", " ")
def call_seedream(prompt_text, seed=None):
"""调用Seedream文生图API"""
url = f"{BASE_URL}/images/generations"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
payload = {
"model": IMAGE_MODEL,
"prompt": prompt_text,
"size": "1440x2560",
"n": 1,
}
if seed:
payload["seed"] = seed
print(f" → POST {url}")
print(f" → model: {IMAGE_MODEL}")
# 使用curl调用避免requests SSL问题
payload_json = json.dumps(payload)
cmd = [
"curl", "-s", "-m", "120", "--noproxy", "*",
"-X", "POST", url,
"-H", f"Authorization: Bearer {API_KEY}",
"-H", "Content-Type: application/json",
"-d", payload_json
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=130)
if result.returncode != 0:
print(f" ✗ curl error: {result.stderr}")
return None
try:
data = json.loads(result.stdout)
except json.JSONDecodeError:
print(f" ✗ JSON parse error: {result.stdout[:200]}")
return None
if "error" in data:
print(f" ✗ API error: {data['error']}")
return None
# Seedream4 响应结构data[0].url
if "data" in data and len(data["data"]) > 0:
img_url = data["data"][0].get("url", "")
print(f" ✓ 生成成功: {img_url[:80]}...")
return img_url
print(f" ✗ 未找到图片URL. 响应: {json.dumps(data)[:300]}")
return None
def download_image(url, filepath):
"""下载图片到指定路径"""
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
print(f" ↓ 下载到 {filepath}")
cmd = ["curl", "-s", "-m", "60", "-L", "--noproxy", "*", "-o", filepath, url]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=65)
if result.returncode != 0:
print(f" ✗ 下载失败: {result.stderr}")
return False
size = os.path.getsize(filepath) if os.path.exists(filepath) else 0
print(f" ✓ 已保存 {size/1024:.0f}KB")
return size > 1024 # 至少1KB
def scale_to_1080(src_path, dst_path):
"""1440x2560 → 1080x1920 等比缩放"""
print(f" ⇄ 缩放 {src_path}{dst_path}")
cmd = [
"sips", "-z", "1920", "1080",
str(src_path),
"--out", str(dst_path)
]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
def main():
print("=" * 60)
print("CHAR-004 诸葛风 · VA-05-001-R2 · 纯文生图")
print(f"模型: {IMAGE_MODEL}")
print(f"分辨率: 1440x2560 → 1080x1920")
print(f"备选数: 4")
print(f"策略: 全程纯文字 · 禁用图像参考")
print("=" * 60)
if not API_KEY:
print("✗ 未找到JIMENG_API_KEY退出")
sys.exit(1)
# ── 第一步生成4张锁服装身形 ──
print("\n── 第一步生成4张候选 ──")
timestamp = time.strftime("%Y%m%d-%H%M%S")
run_dir = JZAO_BASE / "D157-R2" / timestamp
run_dir.mkdir(parents=True, exist_ok=True)
candidates_dir = CANDIDATES_DIR
candidates_dir.mkdir(parents=True, exist_ok=True)
results = []
seeds = [42, 137, 256, 399] # 不同seed产生不同变体
for i, seed in enumerate(seeds):
print(f"\n[{i+1}/4] 生成候选 {i+1} (seed={seed})")
# 微调prompt增加面部变化
variations = [
"面部角度:正脸直视镜头",
"面部角度:微微侧头,目光坚毅",
"面部角度:略微仰视,眼神向上",
"面部角度3/4侧脸侧视左前方",
]
full_prompt = f"{CHAR004_PROMPT} {variations[i]}"
img_url = call_seedream(full_prompt, seed=seed)
if not img_url:
print(f" ✗ 候选 {i+1} 生成失败")
results.append({"candidate": i+1, "status": "failed", "error": "API call failed"})
continue
# 保存原始1440×2560
raw_name = f"CHAR-004-R2-candidate-{i+1:02d}-1440x2560.jpg"
raw_path = run_dir / raw_name
if not download_image(img_url, raw_path):
results.append({"candidate": i+1, "status": "failed", "error": "download failed"})
continue
# 缩放至1080×1920
scaled_name = f"CHAR-004-R2-candidate-{i+1:02d}-1080x1920.jpg"
scaled_path = candidates_dir / scaled_name
if not scale_to_1080(raw_path, scaled_path):
results.append({"candidate": i+1, "status": "failed", "error": "scale failed"})
continue
results.append({
"candidate": i+1,
"status": "generated",
"raw": str(raw_path),
"scaled": str(scaled_path),
"seed": seed
})
print(f" ✓ 候选 {i+1} 完成")
time.sleep(2) # 避免请求过快
# ── 汇总 ──
print("\n── 生成汇总 ──")
success = sum(1 for r in results if r["status"] == "generated")
failed = len(results) - success
print(f"成功: {success}/4")
print(f"失败: {failed}/4")
if failed > 0:
for r in results:
if r["status"] != "generated":
print(f" ✗ 候选{r['candidate']}: {r.get('error', 'unknown')}")
# 写入元数据
meta = {
"spec": "VA-05-001-R2",
"asset_id": "CHAR-004-ZhugeFeng",
"timestamp": timestamp,
"model": IMAGE_MODEL,
"resolution_raw": "1440x2560",
"resolution_final": "1080x1920",
"strategy": "pure_text_prompt_no_image_reference",
"results": results
}
meta_path = candidates_dir / "generation-meta.json"
with open(meta_path, "w") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
print(f"\n元数据已保存: {meta_path}")
return 0 if success >= 4 else 1
if __name__ == "__main__":
sys.exit(main())