@@ -11,6 +11,20 @@ from __future__ import annotations
|
||||
import re
|
||||
|
||||
CLICK_SWAP_PRESETS = frozenset({"点击换款", "点触换款", "多色商品换款"})
|
||||
PLOT_TWIST_PRESET = "短剧带货"
|
||||
LEGACY_PLOT_TWIST_PRESET = "剧情反转带货"
|
||||
|
||||
|
||||
def is_plot_twist_preset(name: str | None) -> bool:
|
||||
"""新旧预设名称执行同一套短剧流程,兼容已保存的历史会话。"""
|
||||
return str(name or "").strip() in {PLOT_TWIST_PRESET, LEGACY_PLOT_TWIST_PRESET}
|
||||
|
||||
|
||||
def canonical_preset_name(name: str | None) -> str:
|
||||
raw = str(name or "").strip()
|
||||
return PLOT_TWIST_PRESET if raw == LEGACY_PLOT_TWIST_PRESET else raw
|
||||
|
||||
|
||||
_CLICK_SWAP_FINGER_GUIDANCE = (
|
||||
"形态:只出手指出镜。固定机位的商品点击换款短片。必须先确认要展示的颜色/款式/SKU 及顺序,"
|
||||
"画面只围绕同一件商品的逐款切换:机位、背景与灯光稳定,商品构图稳定。"
|
||||
@@ -39,8 +53,8 @@ VIDEO_PRESETS: dict[str, str] = {
|
||||
"没有明确的防漏/承重/容量事实时,不用装水、尖锐物或超载来证明性能。"
|
||||
"用户没给关键功能或使用边界时,只问一次缺失事实;其余镜头用可观察的正常动作补齐。"
|
||||
),
|
||||
"剧情反转带货": (
|
||||
"剧情反转带货不是给商品套一个故事壳。商品必须作为解决困境、解除误会、证明事实、完成翻盘或回收伏笔的关键物,"
|
||||
PLOT_TWIST_PRESET: (
|
||||
"短剧带货不是给商品套一个故事壳。商品必须作为解决困境、解除误会、证明事实、完成翻盘或回收伏笔的关键物,"
|
||||
"并在最终 Prompt、镜头、动作、对白和反转结果中反复可见地承担这个作用。"
|
||||
"不得在故事结束后突然停下来介绍商品,也不能只在最后两秒放商品图。"
|
||||
"开场默认按 60 秒完整短剧结构写人物关系、持续冲突、商品介入时机、反转和情绪落点,不追问 15/30 秒故事深度;"
|
||||
@@ -100,9 +114,6 @@ VIDEO_PRESETS: dict[str, str] = {
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
PLOT_TWIST_PRESET = "剧情反转带货"
|
||||
|
||||
# 保留旧会话 15/30 秒的稳定标识以便回放;新会话不再展示故事深度选择卡。
|
||||
PLOT_TWIST_STORY_DEPTH_OPTIONS = (
|
||||
{
|
||||
@@ -164,7 +175,7 @@ def plot_twist_story_contract(value: str) -> str:
|
||||
depth = _PLOT_TWIST_DEPTH_BY_VALUE["60s"]
|
||||
if depth["value"] == "15s":
|
||||
return (
|
||||
"【剧情反转带货·15秒快节奏反转·强制执行】虽短也要有开端、冲突、反转和明确结局,不能只截取一场戏;"
|
||||
"【短剧带货·15秒快节奏反转·强制执行】虽短也要有开端、冲突、反转和明确结局,不能只截取一场戏;"
|
||||
"只保留一条主冲突,主角 1 人、最多 1 名辅助人物。"
|
||||
"0-3 秒强冲突或意外,3-6 秒问题升级,6-11 秒商品以正常使用动作介入并证明一个核心卖点,"
|
||||
"11-15 秒完成结果反转与自然行动引导。商品必须在中段前出现;对白短、直接、有记忆点;"
|
||||
@@ -172,7 +183,7 @@ def plot_twist_story_contract(value: str) -> str:
|
||||
)
|
||||
if depth["value"] == "30s":
|
||||
return (
|
||||
"【剧情反转带货·30秒轻剧情带货·强制执行】写完人物目标从开端、冲突、反转到结局的因果线,"
|
||||
"【短剧带货·30秒轻剧情带货·强制执行】写完人物目标从开端、冲突、反转到结局的因果线,"
|
||||
"不可只写故事中段;必须交代人物处境,可设置两人互动。"
|
||||
"0-4 秒抛出结果预告或冲突钩子,4-10 秒建立处境,10-17 秒让矛盾升级或一次错误尝试,"
|
||||
"17-24 秒商品在转折点以完整正常使用过程介入,24-28 秒回收反转与人物反应,28-30 秒自然收束。"
|
||||
@@ -180,7 +191,7 @@ def plot_twist_story_contract(value: str) -> str:
|
||||
)
|
||||
if depth["value"] == "60s":
|
||||
return (
|
||||
"【剧情反转带货·60秒完整短剧带货·强制执行】这是一集有开端和结局的独立短剧,不是一部长剧中截取的一段。"
|
||||
"【短剧带货·60秒完整短剧带货·强制执行】这是一集有开端和结局的独立短剧,不是一部长剧中截取的一段。"
|
||||
"先写【完整故事线】,依次写清开端、冲突、升级、反转、结局;每一段都有前因后果,人物目标和关系最终有结果。"
|
||||
"0-5 秒可以用结果闪前作钩子,但随后必须回到故事起因;5-15 秒交代关系、目标与处境;"
|
||||
"15-28 秒发生具体阻碍并升级;28-38 秒主角尝试、失败或作出选择;"
|
||||
@@ -191,7 +202,7 @@ def plot_twist_story_contract(value: str) -> str:
|
||||
)
|
||||
if depth["value"] == "180s":
|
||||
return (
|
||||
"【剧情反转带货·180秒三幕完整故事·强制执行】全片按三个连续 60 秒章节推进,不能把三条短片简单拼接。"
|
||||
"【短剧带货·180秒三幕完整故事·强制执行】全片按三个连续 60 秒章节推进,不能把三条短片简单拼接。"
|
||||
"第一章 0-60 秒:0-8 秒以结果预告或关系冲突抓人,8-28 秒建立人物目标与现实阻力,"
|
||||
"28-48 秒埋入商品/SKU 与一个可见卖点证据,48-60 秒用第一次选择或失败把行动推入下一章。"
|
||||
"第二章 60-120 秒:承接上一章未完成动作,扩大矛盾并安排一次错误尝试;商品通过正常使用过程提供新的证据,"
|
||||
@@ -207,7 +218,7 @@ def plot_twist_story_contract(value: str) -> str:
|
||||
def apply_plot_twist_story_contract(name: str, story_depth: str, prompt: str) -> str:
|
||||
"""在最终出片指令里确定性追加所选故事深度,避免模型漏写关键结构。"""
|
||||
base = str(prompt or "").strip()
|
||||
if (name or "").strip() != PLOT_TWIST_PRESET or not base:
|
||||
if not is_plot_twist_preset(name) or not base:
|
||||
return base
|
||||
contract = plot_twist_story_contract(story_depth)
|
||||
if not contract or contract in base:
|
||||
@@ -277,7 +288,7 @@ def apply_plot_twist_direction_contract(prompt: str, *, title: str = "", conflic
|
||||
VIDEO_PRESET_WORKFLOWS: dict[str, str] = {
|
||||
"痛点解决演示": "先确认商品真实解决的具体问题与正常用法;生成前核对痛点、过程和结果都有可见证据。",
|
||||
"真实使用演示": "优先核对商品真实用途、关键步骤和可证实卖点;不为氛围而增加不合理测试。",
|
||||
"剧情反转带货": "默认按 60 秒完整短剧带货创作,不在开头追问或展示 15/30 秒故事深度选择;核对商品信息后给三个完整剧情方向。商品必须成为解决问题、解除误会、证明事实、回收伏笔或完成翻盘的关键。",
|
||||
PLOT_TWIST_PRESET: "默认按 60 秒完整短剧带货创作,不在开头追问或展示 15/30 秒故事深度选择;核对商品信息后给三个完整剧情方向。商品必须成为解决问题、解除误会、证明事实、回收伏笔或完成翻盘的关键。",
|
||||
"商品拟人广告": "先确认商品外观和性格表达方式;默认无脸拟人,商品保持真实完整,台词用画外声。",
|
||||
"达人口播种草": "优先确认人物、多人出镜关系、真实体验和主卖点;生成前核对口播字数能在时长内说完,每个卖点都有画面证明。",
|
||||
"商品图一键成片": "优先从商品参考图锁定外观;自动补场景和动作,但不替换或改变用户商品图里的结构、颜色和包装。",
|
||||
@@ -304,8 +315,8 @@ VIDEO_PRESET_DELIVERY_CONTRACTS: dict[str, str] = {
|
||||
"【预设执行层·真实使用演示】像一镜到底可复核的日常实拍:先交代真实场景,再按正确用途完成步骤,"
|
||||
"用近景展示材质、动作或结果。商品全程保持正常完整;不以破损、漏液、渗水、超载或超出用途的测试制造戏剧性。"
|
||||
),
|
||||
"剧情反转带货": (
|
||||
"【预设执行层·剧情反转带货】按同一条完整故事线连续推进开端、冲突、升级、反转和结局;"
|
||||
PLOT_TWIST_PRESET: (
|
||||
"【预设执行层·短剧带货】按同一条完整故事线连续推进开端、冲突、升级、反转和结局;"
|
||||
"人物关系、目标与情绪在每个关键镜头都有因果变化,结尾回收开头问题而非停在故事中段。"
|
||||
"商品不是摆拍道具:必须充当解决问题的工具、解除误会的证据、关系变化的礼物或回收伏笔的关键物;"
|
||||
"商品出现、使用和结果必须直接改变剧情走向,禁止剧情结束后再硬插卖点。"
|
||||
@@ -411,12 +422,12 @@ ALL_PRESETS: dict[str, str] = {**VIDEO_PRESETS, **IMAGE_PRESETS}
|
||||
def preset_guidance(name: str) -> str:
|
||||
"""预设名 → 拍法约束。认不出的名字返回 "" —— 前端加了新卡但这里还没写时,
|
||||
退回「只有名字」的行为,不要报错。"""
|
||||
return ALL_PRESETS.get((name or "").strip(), "")
|
||||
return ALL_PRESETS.get(canonical_preset_name(name), "")
|
||||
|
||||
|
||||
def preset_workflow_guidance(name: str) -> str:
|
||||
"""预设名 → Agent 的素材、追问和检查重点。"""
|
||||
return VIDEO_PRESET_WORKFLOWS.get((name or "").strip(), "")
|
||||
return VIDEO_PRESET_WORKFLOWS.get(canonical_preset_name(name), "")
|
||||
|
||||
|
||||
_CLICK_SWAP_FINGER_CONTRACT = (
|
||||
@@ -439,7 +450,7 @@ _CLICK_SWAP_CHARACTER_CONTRACT = (
|
||||
|
||||
def video_preset_delivery_contract(name: str, *, click_swap_mode: str = "") -> str:
|
||||
"""预设名 → 最终视频生成阶段必须执行的结构与镜头约束。"""
|
||||
preset = (name or "").strip()
|
||||
preset = canonical_preset_name(name)
|
||||
if preset in CLICK_SWAP_PRESETS:
|
||||
if (click_swap_mode or "").strip() == "character":
|
||||
return _CLICK_SWAP_CHARACTER_CONTRACT
|
||||
@@ -546,13 +557,13 @@ def scrub_click_swap_same_position_wording(text: str) -> str:
|
||||
def apply_video_preset_prompt(name: str, prompt: str, *, click_swap_mode: str = "") -> str:
|
||||
"""把视频预设确定性并入最终出片指令,避免只依赖对话模型主动复述。"""
|
||||
base = str(prompt or "").strip()
|
||||
preset = (name or "").strip()
|
||||
preset = canonical_preset_name(name)
|
||||
mode = (click_swap_mode or "").strip()
|
||||
contract = video_preset_delivery_contract(preset, click_swap_mode=mode)
|
||||
if not base or not contract:
|
||||
return base
|
||||
marker = f"【视频预设】{preset}"
|
||||
if marker in base:
|
||||
if marker in base or (is_plot_twist_preset(name) and f"【视频预设】{LEGACY_PLOT_TWIST_PRESET}" in base):
|
||||
return base
|
||||
if is_click_swap_preset(preset):
|
||||
if mode == "character":
|
||||
|
||||
Reference in New Issue
Block a user