修复种草图生成:以三合一主图为参考 img2img 生成,按颜色分配(每色优先、超出随机补足);新增 BR/CA/DE/ES/IT/PL/SA 七国配置与提示词;删除验证用测试脚本

This commit is contained in:
2026-08-24 14:02:19 +08:00
parent f493bde8a9
commit 3c341d2e78
51 changed files with 2571 additions and 514 deletions
+61 -12
View File
@@ -1,14 +1,20 @@
"""节点 8/8:种草图生成(seed_shot)——在 oss_upload 之后。
对每个 product 的合成图(图1),按 seed_shot_templates.yaml 模板 + model_features.yaml 随机模特特征
生成 N 张种草图(config.seed_shot.count,默认 1):
种草图用 img2img 真正生成(不是复用主图):从 product 已生成的三合一主图
color_composites,每颜色一张)中按分配规则选参考图,以该图提取衣服颜色并作为
参考图,按 seed_shot_templates.yaml 模板 + model_features.yaml 随机模特特征生成新图:
- [商品名称] ← product 的 cn_title(上一节点多模态生成)
- [材质] ← 数据库 SPU.material 字段
- [模特特征] ← model_features.yaml 随机一条
种草图同样压缩上传到 OSS(货号计数与 oss_upload 共用 state["oss_seq"] 续接)。
分配规则(config.seed_shot.count):
- count <= 颜色数:随机取 count 个不同颜色,各生成 1 张
- count > 颜色数:每个颜色至少 1 张,剩余随机补足(可重复)
种草图同样压缩上传到 OSS(货号计数与 oss_upload 共用 state["oss_seq"] 续接),
URL 写入 r["seed_shot_urls"],供 template_export 插入模板详情图文列。
未配置图像后端 / 无合成图 / count=0 时跳过,不中断。
未配置图像后端 / 无三合一主图 / count=0 时跳过,不中断。
"""
import random
import time
from pathlib import Path
from typing import Any, Dict, List
@@ -16,6 +22,34 @@ from typing import Any, Dict, List
from graph.validate import with_fallback
def _plan_seed_shots(comps: List[Dict[str, Any]], count: int) -> List[tuple]:
"""按颜色分配种草图数量:count <= 颜色数 → 随机取 count 个不同颜色各 1 张;
count > 颜色数 → 每色 1 张 + 随机补足(可重复)。返回 [(composite, n)]。"""
if count <= 0 or not comps:
return []
if len(comps) >= count:
picked = random.sample(comps, count)
return [(cc, 1) for cc in picked]
plan: List[tuple] = [(cc, 1) for cc in comps] # 每色至少 1 张
for _ in range(count - len(comps)):
cc = random.choice(comps) # 随机补足(可重复)
for i, (c, n) in enumerate(plan):
if c is cc:
plan[i] = (c, n + 1)
break
return plan
def _color_tag(cc: Dict[str, Any], idx: int) -> str:
"""种草图文件名里的颜色标识:优先 sku_code 的颜色段,回退颜色名/序号。"""
sku = str(cc.get("sku_code") or "")
if "-" in sku:
tag = sku.split("-", 1)[1]
else:
tag = str(cc.get("color") or "") or f"c{idx}"
return "".join(ch for ch in tag if ch.isalnum() or ch in "-_") or f"c{idx}"
@with_fallback("seed_shot")
def seed_shot_node(state: Dict[str, Any]) -> Dict[str, Any]:
products: List[Dict[str, Any]] = state.get("product") or []
@@ -47,7 +81,6 @@ def seed_shot_node(state: Dict[str, Any]) -> Dict[str, Any]:
material_map: Dict[str, str] = {}
try:
from graph.product import list_spus
import yaml
dbp = (config.get("product") or {}).get("db_path", "db/spu_sku.db")
p = Path(dbp)
if not p.is_absolute():
@@ -71,6 +104,7 @@ def seed_shot_node(state: Dict[str, Any]) -> Dict[str, Any]:
seq = int(state.get("oss_seq") or 0)
oss_cfg = config.get("oss") or {}
oss_enabled = bool(oss_cfg.get("enabled", True)) and bool(oss_cfg.get("oss_bucket"))
size = str(ss_cfg.get("size") or "1504x2000")
all_shots: List[Dict[str, Any]] = []
shot_dir = output_dir / "seed_shots"
@@ -83,16 +117,31 @@ def seed_shot_node(state: Dict[str, Any]) -> Dict[str, Any]:
def _shot_one(r: Dict[str, Any]):
"""单个产品的种草图生成+上传(每产品独立线程)。"""
nonlocal seq
base = r.get("composite_path") or r.get("printed_path")
if not base or not Path(base).exists():
print(f"[seed_shot] {r.get('spu_code', '')} 无合成图,跳过种草图")
# 三合一主图:多色用 color_composites;单色回退 composite_path
comps = r.get("color_composites") or []
if not comps and r.get("composite_path") and Path(r["composite_path"]).exists():
comps = [{"sku_code": r.get("sku_code"), "color": r.get("color", ""),
"composite_path": r["composite_path"]}]
if not comps:
print(f"[seed_shot] {r.get('spu_code', '')} 无三合一主图,跳过种草图")
return None
plan = _plan_seed_shots(comps, count)
cn = (r.get("cn_title") or "").strip() or r.get("topic", "")
material = material_map.get(r.get("spu_code", ""), "")
paths = generate_seed_shots(ib, base, cn, material, count, str(shot_dir),
r.get("composite_negative", ""),
size=str((config.get("seed_shot") or {}).get("size") or "1504x2000"),
prefix=r.get("img_code") or r.get("oss_code") or "")
base_prefix = r.get("img_code") or r.get("oss_code") or ""
paths: List[str] = []
for ci, (cc, n) in enumerate(plan, start=1):
base = cc.get("composite_path")
if not base or not Path(base).exists():
print(f"[seed_shot] {r.get('spu_code', '')} 参考图缺失({base}),跳过该颜色种草图")
continue
tag = _color_tag(cc, ci)
pfx = f"{base_prefix}_{tag}" if base_prefix else f"seed_{tag}"
generated = generate_seed_shots(ib, base, cn, material, n, str(shot_dir),
r.get("composite_negative", ""),
size=size, prefix=pfx)
paths.extend(generated)
if not paths:
return None
r["seed_shot_paths"] = paths