Files
yingqing/core/backend/storyboard_style_demo.py
T
zyc de4b20cc7b 模特上身图提示词重构 + 图片创作页 UI 调整
后端(模特上身图提示词):
- build_model_tryon_prompt_refs 重写:穿戴/非穿戴分流(穿戴=真实穿身替换原衣,
  非穿戴=手持/佩戴/使用不动原衣)、每张按 index 变化动作/场景/镜头、负面词尾接、
  多图参考序号自适应(参考图1~N=商品,参考图N+1=模特)
- 新增 _product_reference_urls:商品参考图真实上传图优先、排除 AI 生成图、可多张(≤3),
  无真实图回落 cover
- worker run_standalone_image_task 模特分支改用多图取图 + 传 index/n_product

前端(图片创作/工作室):
- 生成数量改 1/2/4;图片比例新增「手动输入」(宽:高 两输入框)
- 临时隐藏「商品库」按钮
- 模特卡:去掉 // 真人模特,标题改图片底部白字遮罩层 + 单行省略
- 工作室壳负边距对齐 .content padding,修复上下被遮挡/裁切

其他:并入此前未提交的商品页改动、脚本 Agent/格式实测文档与 demo
2026-06-27 09:31:44 +08:00

122 lines
5.7 KiB
Python

#!/usr/bin/env python
"""故事板「画风锚点」对比 demo。
目的:直观演示"锁画风"——同一组分镜,各生成两版:
A. 无锚点(no_anchor) —— 模拟现状故事板提示词,只说"导演故事板…画面清晰",不规定画风
B. 有锚点(with_anchor)—— 同样内容 + 注入一段「统一画风锚点」(写实电商摄影棚风/统一色调光线构图)
每版各出 4 帧(钩子/痛点/卖点/CTA),存盘后肉眼对比:
- no_anchor 组:帧与帧画风易漂(这次写实、那次插画、景别色调各异)
- with_anchor 组:4 帧像同一套片子(统一风格)
走真·图像模型(yunqi/gpt-image-2,纯文生图,隔离出"锚点"这一个变量,不掺参考图)。
输出:../docs/storyboard-style-demo/{no_anchor,with_anchor}/frame{N}.png + prompts.md
用法:cd backend && python storyboard_style_demo.py
"""
from __future__ import annotations
import os
import sys
import time
from pathlib import Path
import django
BASE_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(BASE_DIR))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development")
django.setup()
from apps.ai.models import ModelConfig # noqa: E402
from apps.ai.providers import VolcanoArkProvider # noqa: E402 — media_to_bytes 复用
from apps.ai.services import build_provider, get_default_model # noqa: E402
OUT = BASE_DIR.parent / "docs" / "storyboard-style-demo"
SIZE = "1024x1536" # 9:16 竖屏,与故事板一致
# --------------------------------------------------------------------------- #
# 模拟数据:保温杯 4 分镜(钩子→痛点→卖点→CTA),每帧一句画面描述
# --------------------------------------------------------------------------- #
FRAMES = [
("钩子", "年轻女白领坐在办公室工位,皱眉摸了摸桌上凉掉的水杯,抬头看镜头一脸无奈"),
("痛点", "女白领从通勤包里拿出漏水的旧保温杯,纸巾擦被打湿的笔记本,表情懊恼"),
("卖点", "女白领单手按下保温杯一键弹盖,杯口冒出热气,桌面横放杯子滴水不漏"),
("CTA", "女白领手持保温杯对镜头微笑展示,画面右下角出现购物车引导点击"),
]
# --------------------------------------------------------------------------- #
# A. 无锚点:模拟现状故事板提示词(无任何画风约束)
# --------------------------------------------------------------------------- #
def prompt_no_anchor(role: str, scene: str) -> str:
return (
f"根据以下画面生成一个导演故事板分镜图,用于指导短视频生成。\n"
f"【镜头功能】{role}\n【画面】{scene}\n"
f"电商竖屏 9:16 导演故事板,画面清晰。"
)
# --------------------------------------------------------------------------- #
# B. 有锚点:同内容 + 一段「统一画风锚点」(每帧逐字相同,把风格焊死)
# --------------------------------------------------------------------------- #
STYLE_ANCHOR = (
"【统一画风 · 所有分镜必须严格一致,不可逐帧漂移】\n"
"· 风格:写实电商摄影棚实拍质感(photorealistic),禁止插画/线稿/漫画/3D 卡通;\n"
"· 布光:统一柔和影棚顺光,同一色温(暖白);\n"
"· 色彩:统一明亮干净的电商色彩分级,低饱和高级灰背景;\n"
"· 景别构图:统一中近景、人物居中、相同画面留白比例与镜头高度;\n"
"· 人物:全片同一位年轻女白领(同一张脸、同一发型妆容着装);\n"
"· 商品:全片同一只白色保温杯(同一外形/配色/logo)。"
)
def prompt_with_anchor(role: str, scene: str) -> str:
return (
f"根据以下画面生成一个导演故事板分镜图,用于指导短视频生成。\n"
f"【镜头功能】{role}\n【画面】{scene}\n"
f"{STYLE_ANCHOR}\n"
f"电商竖屏 9:16 导演故事板,画面清晰。"
)
def gen_one(provider, model, prompt: str, save_path: Path) -> str:
resp = provider.image_generation(model=model.name, endpoint=model.endpoint, prompt=prompt, size=SIZE)
media = provider.extract_first_media_url(resp)
fileobj, _ct = VolcanoArkProvider.media_to_bytes(media)
save_path.write_bytes(fileobj.getvalue())
return str(save_path)
def main() -> None:
model = get_default_model(ModelConfig.Capability.IMAGE)
provider = build_provider(model)
print(f"图像模型:{model.provider.name}/{model.name} · 尺寸 {SIZE}")
(OUT / "no_anchor").mkdir(parents=True, exist_ok=True)
(OUT / "with_anchor").mkdir(parents=True, exist_ok=True)
prompts_md = ["# 故事板画风锚点 demo · 用到的提示词\n",
f"> 图像模型:{model.provider.name}/{model.name} · 模拟商品:保温杯 · 4 分镜\n"]
for variant, builder in (("no_anchor", prompt_no_anchor), ("with_anchor", prompt_with_anchor)):
label = "无锚点(现状)" if variant == "no_anchor" else "有锚点(锁画风)"
print(f"\n===== {label} =====")
prompts_md.append(f"\n## {label}\n")
for i, (role, scene) in enumerate(FRAMES, 1):
prompt = builder(role, scene)
path = OUT / variant / f"frame{i}_{role}.png"
t0 = time.time()
try:
gen_one(provider, model, prompt, path)
print(f" ✅ 第{i}{role} {round(time.time()-t0,1)}s → {path.name}")
except Exception as exc: # noqa: BLE001
print(f" ❌ 第{i}{role} {str(exc)[:160]}")
prompts_md.append(f"\n### 第{i}镜 · {role}\n\n```\n{prompt}\n```\n")
(OUT / "prompts.md").write_text("\n".join(prompts_md), encoding="utf-8")
print(f"\n📁 图片+提示词已存:{OUT}")
print(" 对比:no_anchor/ 各帧画风易漂 vs with_anchor/ 4 帧同一套风格")
if __name__ == "__main__":
main()