完成极速成品和脚本优化

This commit is contained in:
Azmat@qq.com
2026-08-25 11:13:07 +08:00
parent 00fc454db7
commit e2ec2d14af
46 changed files with 4734 additions and 432 deletions
+63 -23
View File
@@ -52,6 +52,8 @@ VIDEO_STRUCTURES: dict[str, str] = {
"contrast": "前后对比",
"review": "测评验证",
"scene": "场景种草",
"promo": "促销抢购",
"knowledge": "知识分享",
}
DEFAULT_PRESENTATION_FORMAT = "oral"
DEFAULT_VIDEO_STRUCTURE = "pain"
@@ -62,7 +64,9 @@ FORBIDDEN_COMBOS: set[tuple[str, str]] = {("drama", "review")}
# 表现形式推荐的默认总时长:口播短平快,短剧要装下三幕,Vlog 要铺氛围。
FORMAT_DEFAULT_DURATION: dict[str, int] = {"oral": 30, "drama": 45, "vlog": 30}
# 各结构能压到的最短总时长(须落在 15 秒步进上),见 playbooks/combo-matrix.md。
STRUCTURE_MIN_DURATION: dict[str, int] = {"pain": 15, "contrast": 15, "review": 30, "scene": 30}
STRUCTURE_MIN_DURATION: dict[str, int] = {
"pain": 15, "contrast": 15, "review": 30, "scene": 30, "promo": 15, "knowledge": 30,
}
# 设定卡人物 key → 中文(与前端 WIZ_PERSONA_LABEL / 模板 coerce_persona 对齐)。
PERSONA_LABELS: dict[str, str] = {
@@ -83,11 +87,11 @@ PERSONA_BRIEFS: dict[str, str] = {
}
_PERSONA_KEY_BY_LABEL = {label: key for key, label in PERSONA_LABELS.items()}
# 成片有同期画面与自然停顿,3.5 字/秒会让 15 秒口播在约 10 秒时说完
# 电商真人口播以 3.6–4.5 字/秒为可用区间:既留出换气,也不会让画面空转
NARRATION_CHARS_PER_SECOND = 4.5
NARRATION_CHARS_PER_SECOND_MIN = 3.6
NARRATION_CHARS_HARD_CAP = 68
# 电商口播需要同时承载观点、证据和转折;15 秒以 4.3–5.2 字/秒为可用区间
# 这比日常闲聊快,但仍给句间停顿和镜头切换留出空间
NARRATION_CHARS_PER_SECOND = 5.2
NARRATION_CHARS_PER_SECOND_MIN = 4.3
NARRATION_CHARS_HARD_CAP = 78
VISUAL_CHARS_MIN = 72
SHOT_BEATS_MIN = 3
BEAT_SPAN_RE = re.compile(
@@ -161,7 +165,7 @@ def coerce_combo(fmt: str | None, structure: str | None) -> tuple[str, str]:
def narration_limit(duration: int) -> int:
"""这一镜旁白的字数上限:秒数 × 4.5,且不超过硬上限 68。"""
"""这一镜旁白的字数上限:秒数 × 5.2,且不超过硬上限 78。"""
return max(1, min(NARRATION_CHARS_HARD_CAP, int(duration * NARRATION_CHARS_PER_SECOND)))
@@ -342,14 +346,15 @@ _CREATIVE_DIRECTION = """
每个 segment 的 `visual` 采用下面的层级;这不是给用户看的散文,而是会原样交给故事板和视频模型的执行指令:
```
【本镜任务】这一段要让观众看懂的变化或悬念。
【声音】只写可发生的同期声 / 人声状态;没有配乐就明确写「无配乐,仅同期声」。
【声音】台词/旁白状态;音效;背景音乐;字幕。没有就写「无」;没有配乐写「无配乐,仅同期声」。
【画面内容】
0-3s:景别;构图;运镜;人物/商品动作;情绪或信息变化。
3-8s:景别;构图;运镜;手与商品的空间关系;可见细节。
8-12s:景别;构图;运镜;动作结果或卖点证据。
12-15s:景别;构图;运镜;反应/悬念/自然收束。
0-3s:景别;机位;运镜;人物/商品动作;情绪或信息变化。
3-8s:景别;机位;运镜;手与商品的空间关系;可见细节。
8-12s:景别;机位;运镜;动作结果或卖点证据。
12-15s:景别;机位;运镜;反应/悬念/自然收束。
```
每条秒级分镜都要明确写出 **景别、构图、运镜、动作、信息变化** 五项中的至少四项。
每条秒级分镜都要明确写出 **景别、机位、运镜、动作、信息变化** 五项中的至少四项。
输入如果是 `【镜头 01】` 导演分镜稿,把原稿的景别、机位、运镜、动作、表情、音效、背景音乐、字幕、备注折进对应栏,不要压成一句画面摘要。
同一角色、商品、场景的外观一律引用 entities 里的既定设定,不在每一镜随意换发型、服装、包装、光线或地点。
"""
@@ -492,7 +497,7 @@ def build_agent_messages(
beats_line = (
f"【秒级分镜】每个 {SEGMENT_DURATION_MAX} 秒场必须拆成 3–5 个分镜,visual 必须按「导演说明书」写成多行:"
"先写【本镜任务】、【声音】、【画面内容】,再写秒级分镜;"
f"每条格式`0-3s:景别;构图;运镜;谁在做什么;信息变化`,最后一条接到 {SEGMENT_DURATION_MAX}s。"
f"每条格式`0-3s:景别;机位;运镜;谁在做什么;信息变化`,最后一条接到 {SEGMENT_DURATION_MAX}s。"
"每条写清手/商品/容器的空间关系和真实用法"
"(茶=热水+蒸汽+茶汤变色,禁止茶包丢进冷白开;手从真实方向入画,禁止悬浮肢体)。"
"禁止一句空画面撑满 15 秒。允许另给 beats 数组,后端会折进 visual。\n"
@@ -519,6 +524,18 @@ def build_agent_messages(
"【场景种草骨架】按镜走完:场景建立(商品先不出现)→商品被使用着入场→场景因它更好/软CTA。"
"每镜写出具体时刻+地点+光/声音/物件。口播时每镜仍要说满字数,留白靠停顿不是靠少写字。\n"
)
elif structure == "promo":
structure_line = (
"【促销抢购合规】只可使用商品资料中明确给出的价格、优惠、库存、赠品和活动时间;"
"没有明确数据时,绝不写原价、最低价、限时、限量、领券或赠品。"
"结尾只给一个清楚的购买入口和操作动作,不制造虚假的紧迫感。\n"
)
elif structure == "knowledge":
structure_line = (
"【知识分享骨架】先提出一个与商品相关的选购/使用误区或判断方法,再用实物与步骤证明,"
"最后把商品作为符合该判断的具体实例。只能讲商品资料可支持的事实,"
"禁止编造专业身份、数据、标准或功效承诺。\n"
)
combo_line = (
f"严格按已加载的「{PRESENTATION_FORMATS[fmt]} × {VIDEO_STRUCTURES[structure]}」套路写,"
f"不要串成别的结构的套话。\n"
@@ -562,15 +579,30 @@ def build_agent_messages(
)
else:
extra_block = f"\n\n【补充要求】{extra}\n" if extra else "\n"
user = (
"【任务】全自动(模式①):仅凭上面的商品事实与前置条件,按指定的表现形式与视频结构套路"
"自动定镜/选 tone/造 entity/填结构骨架"
"不要另起一个空主题;钩子、痛点、卖点必须能对上这份商品,而不是品类套话。"
"每镜旁白要能撑满 15 秒;画面必须按秒拆分镜,够导演在 15 秒里切 3–5 刀。\n"
f"{head}"
f"{extra_block}\n"
"请按技能流程一次性产出 ScriptDraft"
)
if extra and _looks_like_shot_digest(extra):
user = (
"【任务】参考视频改写:用户给了一份逐镜拆解稿"
"照搬它的镜头顺序、每镜时长比例、景别、机位、运镜、人物动作和声音层次,"
"把人物、商品、品牌、台词全部换成当前商品。"
"写 visual 时把拆解稿里的景别/机位/运镜/人物动作/表情/音效/背景音乐/字幕/备注"
"折进【声音】和【画面内容】的秒级分镜,不要压成一句画面摘要。"
"原稿写「无 / 不可见 / 听不清」的栏不要编造"
"钩子、痛点、卖点必须能对上这份商品。"
"每镜旁白要能撑满指定时长;画面必须按秒拆分镜,够导演在 15 秒里切 3–5 刀。\n"
f"{head}"
f"{extra_block}\n"
"请按技能流程一次性产出 ScriptDraft。"
)
else:
user = (
"【任务】全自动(模式①):仅凭上面的商品事实与前置条件,按指定的表现形式与视频结构套路"
"自动定镜/选 tone/造 entity/填结构骨架。"
"不要另起一个空主题;钩子、痛点、卖点必须能对上这份商品,而不是品类套话。"
"每镜旁白要能撑满 15 秒;画面必须按秒拆分镜,够导演在 15 秒里切 3–5 刀。\n"
f"{head}"
f"{extra_block}\n"
"请按技能流程一次性产出 ScriptDraft。"
)
return [{"role": "system", "content": system}, {"role": "user", "content": user}]
@@ -607,6 +639,14 @@ def _balanced_object(text: str) -> str | None:
return None
def _looks_like_shot_digest(text: str) -> bool:
"""用户贴进来的是视频提炼分镜稿,不是一句补充要求。"""
blob = text or ""
has_shot = "【镜头" in blob or ("【第" in blob and "镜】" in blob)
has_picture = "画面:" in blob or "画面:" in blob
return has_shot and has_picture
def _looks_like_draft(blob: str) -> bool:
try:
d = json.loads(blob)