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