完成极速成品和脚本优化
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user