diff --git a/core/backend/apps/adminpanel/serializers.py b/core/backend/apps/adminpanel/serializers.py index 29ddcc7..881b4eb 100644 --- a/core/backend/apps/adminpanel/serializers.py +++ b/core/backend/apps/adminpanel/serializers.py @@ -32,7 +32,7 @@ PROMPT_PLACEHOLDERS = { "person_triview": [], "product_triview": ["商品", "补充"], "scene": ["场景描述"], - "video_segment": ["设定", "风格", "脚本", "时长"], + "video_segment": ["开场", "设定", "风格", "脚本", "时长"], } diff --git a/core/backend/apps/ai/__init__.py b/core/backend/apps/ai/__init__.py index 8b13789..e69de29 100644 --- a/core/backend/apps/ai/__init__.py +++ b/core/backend/apps/ai/__init__.py @@ -1 +0,0 @@ - diff --git a/core/backend/apps/ai/script_agent.py b/core/backend/apps/ai/script_agent.py index 224c2cf..48ec5fc 100644 --- a/core/backend/apps/ai/script_agent.py +++ b/core/backend/apps/ai/script_agent.py @@ -489,6 +489,8 @@ _CREATIVE_DIRECTION = """ 全片只围绕这一个情境推进。不要在一条短视频里罗列所有卖点。 ### 每一镜都必须有推进,禁止平铺直叙 +- 钩子镜的**画面**必须从一个正在进行的动作或一个可见状态开场,第一条秒级分镜不能是 + 「对着镜头说话」这种静态口播开场(观众先看到事,再听到话;静态对镜口播也是成片被自动加大字的高发语境)。 - 钩子镜:前 15 个字就抛出反常、尴尬、选择或结果;禁止「大家好」「今天分享」 「给你们推荐」「这款很好用」「值得买」等任何寒暄或泛推荐开场。 - 痛点镜:拍得到的细节,而非抽象感受。例如「开会前刘海粘成一绺」而不是「头发很油」。 diff --git a/core/backend/apps/ai/services.py b/core/backend/apps/ai/services.py index c9342f3..76b50a8 100644 --- a/core/backend/apps/ai/services.py +++ b/core/backend/apps/ai/services.py @@ -891,6 +891,17 @@ NO_EMBEDDED_CAPTIONS_REQUIREMENT = ( # 结尾再补一句极短的正面复述:末位权重高,但只用「叠加」这类中性词,不再重复负面名词。 NO_EMBEDDED_CAPTIONS_TAIL = "画面全程保持纯实拍,无任何叠加层。" +# ★ 实测:同一条片子出四段,只有**开场那一段**带字幕,后三段干净。 +# 原因不在我们的提示词结构(四段模板完全一致),而在模型的先验:在「电商口播开场」这个语境下, +# 它学到的强关联是「钩子 = 屏幕上打一行大字」;使用过程 / 特写 / CTA 段几乎不触发这个先验。 +# 所以给开场段单独一句**正面**指令,把这个先验掰到「钩子靠画面和表演」上 —— +# 注意全句不含任何负面名词(字幕/花字…),否则又会掉进「越说越画」的坑。 +OPENING_SHOT_DIRECTIVE = ( + "【开场】这是全片的第一段:第一帧就是实拍画面本身,人物已经在做第一条秒级分镜里的那件事," + "镜头直接开拍,像从一段连续素材里截出来的。开场的吸引力全部来自人物的表情、动作和环境," + "屏幕保持干净。" +) + # 提示词正文里残留的「字幕:…」「花字:…」会和上面的禁令打架 —— 模型看到具体字幕内容就会画出来。 # 来源:脚本【声音】栏、视频提炼稿逐字照抄的画面花字、用户自己写的提示词。这里在送出前统一抹掉。 _CAPTION_WORDS = r"(?:字幕|花字|艺术字|贴片|水印|角标|标题栏|弹幕)" @@ -2718,10 +2729,11 @@ def build_video_segment_prompt(project, video_segment, scene, refs, user_prompt: extra = (user_prompt or "").strip() if extra: script_text = (script_text + "\n" + extra).strip() if script_text else extra - # 正文可在 admin「提示词·视频」页改。占位符:{设定}=@图N点名、{风格}=全片风格锚点、{脚本}=本段脚本、{时长}。 + # 正文可在 admin「提示词·视频」页改。占位符:{开场}=仅第一段有的开场指令、{设定}=@图N点名、 + # {风格}=全片风格锚点、{脚本}=本段脚本、{时长}。 # 时长/比例靠 API 参数传(duration/ratio),不写进正文;尾句给风格 + 音效/字幕要求。 default = ( - "{设定}{风格}【脚本】{脚本}\n" + "{开场}{设定}{风格}【脚本】{脚本}\n" "【执行纪律】严格按脚本里的秒级分镜拍:每一个时间段都要拍到,景别、机位、运镜、动作按写的来;" "不要把整段并成一个静止长镜头,也不要加脚本里没有的转场、人物或道具。" "\n【一致性】参考图是本片唯一的视觉依据:角色保持同一张脸、同一发型、同一套服装;" @@ -2733,8 +2745,11 @@ def build_video_segment_prompt(project, video_segment, scene, refs, user_prompt: "\n【画质】真人实拍质感,自然光影,肤色与材质真实,焦点始终落在脚本指定的主体上,画面稳定不糊。" "\n电商带货短视频,商品露出清晰,节奏有转化感。不要背景音乐,但是要有音效,逼真的音效。" ) + # 开场段(全片第一段)额外挂一条正面开场指令 —— 字幕基本只在这一段冒出来 + opening = OPENING_SHOT_DIRECTIVE + "\n" if video_segment.sort_order == 0 else "" rendered = render_prompt( "video_segment", default, + 开场=opening, 设定=("【设定】" + ",".join(setup_parts) + "。\n" if setup_parts else ""), 风格=(f"【风格】{_scene_context(project)}。\n" if _scene_context(project) else ""), 分镜="", # 兼容:旧模板行里可能还留着 {分镜}(故事板时代的占位符),渲染成空串而不是原样漏出 diff --git a/core/backend/apps/ai/test_video_caption_policy.py b/core/backend/apps/ai/test_video_caption_policy.py index 9f2253b..05855e5 100644 --- a/core/backend/apps/ai/test_video_caption_policy.py +++ b/core/backend/apps/ai/test_video_caption_policy.py @@ -3,6 +3,7 @@ from django.test import SimpleTestCase from apps.ai.services import ( NO_EMBEDDED_CAPTIONS_REQUIREMENT, NO_EMBEDDED_CAPTIONS_TAIL, + OPENING_SHOT_DIRECTIVE, enforce_no_embedded_captions, strip_caption_directives, ) @@ -87,3 +88,15 @@ class VideoCaptionPolicyTests(SimpleTestCase): self.assertNotIn("手慢无", prompt) self.assertIn("0-3s:特写;推近;倒入", prompt) self.assertTrue(prompt.startswith(NO_EMBEDDED_CAPTIONS_REQUIREMENT)) + + +class OpeningShotDirectiveTests(SimpleTestCase): + """实测:一条片子出四段,只有开场那段带字幕,后三段干净 —— 模型在「电商口播开场」语境下 + 有「钩子 = 打一行大字」的强先验。开场段单独给一条正面指令把它掰回来。""" + + def test_directive_is_purely_positive(self): + """★ 关键约束:这句里不能出现任何负面名词,否则又回到「越说越画」的老路。""" + for word in ("字幕", "花字", "标题", "贴片", "水印", "文字"): + self.assertNotIn(word, OPENING_SHOT_DIRECTIVE, msg=f"开场指令里不该出现「{word}」") + self.assertIn("实拍画面", OPENING_SHOT_DIRECTIVE) + self.assertIn("屏幕保持干净", OPENING_SHOT_DIRECTIVE) diff --git a/core/backend/apps/projects/test_quick_create.py b/core/backend/apps/projects/test_quick_create.py index b58a556..617904e 100644 --- a/core/backend/apps/projects/test_quick_create.py +++ b/core/backend/apps/projects/test_quick_create.py @@ -1333,3 +1333,4 @@ class QuickCreateCoordinatorTests(TestCase): + diff --git a/core/backend/skills/ecommerce-video-script/references/checklist.md b/core/backend/skills/ecommerce-video-script/references/checklist.md index 3e3997b..6021651 100644 --- a/core/backend/skills/ecommerce-video-script/references/checklist.md +++ b/core/backend/skills/ecommerce-video-script/references/checklist.md @@ -32,6 +32,7 @@ ## B. 内容质检(钩子 / 结构 / 一致性) - [ ] 钩子镜第一句在前 3 秒抛出钩子,套用了 `hook-library.md` 的某个公式。 +- [ ] **钩子镜 0-3s 是动作或可见状态,不是「对着镜头说话」的静态口播开场**(见 `hook-library.md`)。 - [ ] 顶层 `hook` 与钩子镜口径一致。 - [ ] 卖点镜的卖点**挂在前面铺的痛点上**,不是干罗列参数。 - [ ] CTA 回到了钩子那个情境,点名了谁适合,用一句自然口语收尾。**场景种草结构的 CTA 要更软**。 diff --git a/core/backend/skills/ecommerce-video-script/references/hook-library.md b/core/backend/skills/ecommerce-video-script/references/hook-library.md index 71af18e..745c845 100644 --- a/core/backend/skills/ecommerce-video-script/references/hook-library.md +++ b/core/backend/skills/ecommerce-video-script/references/hook-library.md @@ -9,6 +9,19 @@ 动作、后果或反差中的至少一个。禁止「大家好」「今天分享」「给大家推荐」「这款很好用」 「值得买」等寒暄和泛推荐;它们没有任何让人停下来的理由。 +### 钩子镜的画面必须从「动作」开场(硬规则) + +钩子镜 `visual` 的**第一条秒级分镜(0-3s)必须是一个正在进行的动作或一个可见的状态**, +不能是「女主对着镜头说话」这种静态口播开场。 + +- ✅ `0-3s:中近景;平视;手持轻晃;女主从电脑前抬眼,指腹按着太阳穴,屏幕的光打在脸上` +- ✅ `0-3s:特写;俯拍桌面;缓慢推近;第三杯冷掉的咖啡摆在键盘边,杯壁挂着水珠` +- ❌ `0-3s:近景;平视;固定机位;女主看着镜头开口介绍商品` + +**为什么是硬规则:**静态的对镜口播开场,正是出片模型学到「开场要打一行大字」的那个语境 —— +实测一条片子出四段,只有这样开场的第一段带上了字幕,后三段全都干净。 +从动作切入既避开这个坑,也本来就是更好的钩子写法(观众先看到事,再听到话)。 + --- ## 公式 1 · 痛点提问(最稳,默认首选)