diff --git a/core/backend/apps/ai/generation_errors.py b/core/backend/apps/ai/generation_errors.py index 379745e..28826f8 100644 --- a/core/backend/apps/ai/generation_errors.py +++ b/core/backend/apps/ai/generation_errors.py @@ -417,6 +417,7 @@ def classify_generation_error( "切两次景别", "机位与运镜", "平台指令腔", + "一律没有字幕", ): return _build_error("processing_failed", operation, reference_id=reference_id) return _build_error("unknown", operation, reference_id=reference_id) diff --git a/core/backend/apps/ai/script_agent.py b/core/backend/apps/ai/script_agent.py index fe71e5a..224c2cf 100644 --- a/core/backend/apps/ai/script_agent.py +++ b/core/backend/apps/ai/script_agent.py @@ -540,9 +540,11 @@ _CREATIVE_DIRECTION = """ - **禁止抽象词**:「展示商品」「呈现质感」「体现高级感」「氛围到位」「传递温暖」—— 这些不是画面,是评价。要写成「杯壁上的水珠顺着往下滑」「她眉头松开,肩膀塌下来」。 - **禁止心理描写**:模型拍不出「她内心很纠结」,要写「她拿起又放下,手指在包装边缘停了两秒」。 -- **禁止画面里出现文字**:本系统出的**任何视频都不带字幕**。不要写字幕、花字、标题、片头片尾、 - 水印、角标、价格贴片、优惠标签、弹幕、对话气泡、贴纸、参数表、对比图表、UI、按钮、购物浮层; - 口播和台词只以声音存在。商品包装上原有的真实文字除外。 +- **画面描述里一个「字幕」都不许出现**:本系统出的**任何视频都不带字幕**,这件事由系统统一保证, + 不需要你在脚本里声明。所以 `visual` 里既不要要求加字幕 / 花字 / 标题 / 贴片 / 弹幕 / 角标 / 水印 / + 购物浮层,**也不要写「无字幕」「不加花字」这类否定说法** —— 出片模型不区分肯定否定, + 画面描述里出现这个词本身就更容易把文字画上屏。 + 口播和台词只以声音存在。只有商品包装上原本印着的真实文字可以出现在画面里。 - **禁止一镜内跨场景/跨时间蒙太奇**:15 秒是**一个连续的动作链**,同一地点、同一光线、 同一套衣服。要换环境就换到下一镜,并在 entity_refs 里换成另一个 scene。 @@ -754,7 +756,7 @@ def build_agent_messages( "并写清哪只手、从哪个方向入画、握商品哪里,以及真实用法" "(茶=热水+蒸汽+茶汤变色,禁止茶包丢进冷白开;禁止悬浮肢体、商品凭空出现)。" "只写拍得出来的东西:禁止「展示商品/呈现质感/体现高级感」这类评价词,禁止心理描写," - "禁止画面里出现任何文字叠加(字幕/花字/标题/水印/角标/价签/购物浮层),口播只以声音存在;" + "画面描述里一个「字幕」都不要出现(要求加的、声明没有的都不要写),口播只以声音存在;" "禁止一镜内跨场景跨时间。" f"禁止一句空画面撑满 {SEGMENT_DURATION_MAX} 秒。允许另给 beats 数组,后端会折进 visual。\n" ) @@ -1061,6 +1063,26 @@ def assert_script_has_a_hook(draft: dict) -> None: raise ValueError("开场像泛泛推荐,请直接从具体场景、反差或问题切入") +# 脚本里只要写了「字幕/花字」,下游就有两种坏结果:一是被清洗器删掉(用户在脚本页看到的和实际出片不一致), +# 二是万一漏网就直接画到成片上。所以从源头就不许写 —— 本系统出的任何视频都没有字幕。 +_CAPTION_WORDS_IN_SCRIPT = ("字幕", "花字", "艺术字", "贴片", "弹幕", "角标", "水印") + + +def assert_no_caption_requests(draft: dict) -> None: + """全片任何一镜都不许提「字幕 / 花字」—— 无论是要求加还是声明没有。 + 出片模型不区分肯定否定,看到这个词本身就更容易把文字画上屏。""" + for index, seg in enumerate(draft.get("segments") or []): + if not isinstance(seg, dict): + continue + text = f"{seg.get('visual') or ''}\n{seg.get('product_exposure') or ''}" + hit = next((w for w in _CAPTION_WORDS_IN_SCRIPT if w in text), None) + if hit: + raise ValueError( + f"第 {index + 1} 镜写到了「{hit}」。本系统出的视频一律没有字幕," + f"画面描述里不要出现这类词(连「无字幕」也不要写),把这一镜的画面改成只描述实拍内容" + ) + + def assert_cta_is_not_shouty(draft: dict) -> None: """拦住「点下方小黄车」那套平台指令腔:CTA 要回到情境、点名谁适合,不是喊口号。 扫全片口播/对白(不只 CTA 镜),因为这类话经常顺手塞进末镜旁白的最后一句。""" @@ -1763,6 +1785,7 @@ def stream_script_agent( ) assert_shot_density(candidate, fmt) assert_cta_is_not_shouty(candidate) + assert_no_caption_requests(candidate) return candidate routed_stream = stream_routed_text_request( diff --git a/core/backend/apps/ai/services.py b/core/backend/apps/ai/services.py index a9dee5e..c9342f3 100644 --- a/core/backend/apps/ai/services.py +++ b/core/backend/apps/ai/services.py @@ -874,32 +874,46 @@ class VideoSubmissionStateUnknown(ProviderOutcomeUnknownError): # 本系统出的任何视频都不带字幕。这条是最高优先级的成片硬性禁令,写得穷尽一点 —— # 实测只写「不要字幕」时,模型仍会自作主张加花字 / 价格贴片 / 购物浮层。 +# ★ 这条的写法本身就是功能的一部分,改之前先读这段: +# 视频扩散模型对「否定」很弱、对「名词」很强。旧版禁令把「字幕」念 7 次、「花字」念 3 次, +# 等于反复把这个视觉概念喂给模型 —— 实测连续两条成片都因此带上了字幕。 +# 所以新版反过来:**以正面陈述为主**(画面应该长什么样),负面名词全片只出现一次, +# 并把「台词只出声、不上屏」单独讲明白(这是最大的诱因,见 _segment_script_text 的人声区)。 +# 维护提醒:往这条里加词 = 提高负面名词词频 = 更容易出字幕。要加先想清楚。 NO_EMBEDDED_CAPTIONS_REQUIREMENT = ( - "【字幕硬性禁令 · 最高优先级】从第0秒的开场首帧起直到片尾,本片必须是**无字幕成片**。" - "尤其前3秒不得出现任何标题、开场文案、花字或贴片;画面中绝对不能出现任何后期文字或图形叠加,包括但不限于:" - "字幕、台词字幕、双语字幕、注音、标题、片头、片尾、花字、艺术字、水印、角标、台标、" - "logo 贴片、价格贴片、优惠券、促销标签、倒计时、弹幕、对话气泡、贴纸、表情包文字、" - "参数表、成分表、对比图表、评分条、UI 界面、按钮、购物车 / 购买浮层 / 商品卡、进度条、时间码。" - "口播、台词、旁白、音效只以**声音**存在,绝不转写成画面上的文字。" - "画面里唯一允许出现的文字,是参考图中商品包装 / 标签上原本就有的真实物理文字与品牌标识," - "且必须与参考图完全一致,不得放大、重排或新增。" - "即使本提示词别处提到了「字幕」「花字」「标注」等字样,也一律以本条为准:不生成任何文字叠加。" + "【画面洁净 · 最高优先级】成片是一条纯实拍素材:画面上只有真实拍到的人、商品和环境," + "没有任何后期叠加层。全片从开场首帧到结尾都保持这个状态,开头也不加标题页。" + "画面里能看到的文字只有一种 —— 参考图中商品包装、标签上本来就印着的那些," + "保持与参考图一致,不放大、不重排、不新增。" + "人物说的话通过口型和声音表达,不写到画面上;不要字幕,也不要任何贴片、浮层或界面元素。" ) +# 结尾再补一句极短的正面复述:末位权重高,但只用「叠加」这类中性词,不再重复负面名词。 +NO_EMBEDDED_CAPTIONS_TAIL = "画面全程保持纯实拍,无任何叠加层。" + # 提示词正文里残留的「字幕:…」「花字:…」会和上面的禁令打架 —— 模型看到具体字幕内容就会画出来。 # 来源:脚本【声音】栏、视频提炼稿逐字照抄的画面花字、用户自己写的提示词。这里在送出前统一抹掉。 -_CAPTION_FIELD_LINE_RE = re.compile(r"^\s*[-•*]?\s*(?:字幕|花字|艺术字|贴片|水印|角标|标题栏)\s*[::].*$") -_CAPTION_FIELD_INLINE_RE = re.compile(r"[,,;;、]?\s*(?:字幕|花字|艺术字|贴片|水印|角标)\s*[::][^;;。\n]*") +_CAPTION_WORDS = r"(?:字幕|花字|艺术字|贴片|水印|角标|标题栏|弹幕)" +_CAPTION_FIELD_LINE_RE = re.compile(rf"^\s*[-•*]?\s*{_CAPTION_WORDS}\s*[::].*$") +_CAPTION_FIELD_INLINE_RE = re.compile(rf"[,,;;、]?\s*{_CAPTION_WORDS}\s*[::][^;;。\n]*") +# 无冒号的写法同样要清:「右下角加字幕」「配上花字」「画面无字幕」等。 +# 历史项目早就把这些落进了 ScriptSegment.visual_prompt,光改 skill 管不到存量数据。 +# 按**子句**整段删(以 ;;,,。 断句),否则会留下「右下角」「画面无」这种断头残渣让模型犯迷糊。 +_CAPTION_CLAUSE_RE = re.compile(rf"[,,;;、]?\s*[^,,;;。\n]*{_CAPTION_WORDS}[^,,;;。\n]*[。]?") def strip_caption_directives(prompt: str) -> str: - """抹掉提示词里「字幕:xxx / 花字:xxx」这类会诱使模型画文字的栏位(整行或行内片段)。 - 只删这类字段,不动其余正文;删空的行直接丢掉。""" + """把正文里所有会让模型联想到「画面文字」的字样抹掉 —— 无论它是要求加字幕还是声明没有字幕。 + 模型不区分肯定否定,看到「字幕」这个词本身就更容易画出来,所以一律清掉, + 真正的规则只由 NO_EMBEDDED_CAPTIONS_REQUIREMENT 一处统一表述。 + + 来源:脚本【声音】栏、视频提炼稿逐字照抄的原片花字、用户自己写的提示词、存量项目的历史脚本。 + 只清这些字样,不动其余正文;整行被清空则丢掉该行。""" out: list[str] = [] for line in (prompt or "").split("\n"): if _CAPTION_FIELD_LINE_RE.match(line): continue - cleaned = _CAPTION_FIELD_INLINE_RE.sub("", line) + cleaned = _CAPTION_CLAUSE_RE.sub("", _CAPTION_FIELD_INLINE_RE.sub("", line)) if cleaned.strip() or not line.strip(): out.append(cleaned) return "\n".join(out) @@ -909,10 +923,13 @@ def enforce_no_embedded_captions(prompt: str) -> str: """所有视频入口(专业创作 / 一键成片 / 自由创作 / 视频复刻 / 提炼改写)最终汇入这里: 先洗掉正文里的字幕字段,再把最高优先级的无字幕禁令置于提示词第一行。 首镜最容易被模型自动加标题,所以不能再把禁令放在长提示词末尾。""" - base = strip_caption_directives(prompt or "").strip() - # 统一把规则前置;即便调用方已经加过,也避免第二次拼接或把禁令淹没在正文中。 - base = base.replace(NO_EMBEDDED_CAPTIONS_REQUIREMENT, "").strip() - return f"{NO_EMBEDDED_CAPTIONS_REQUIREMENT}\n{base}".strip() + # 顺序要紧:先摘掉可能已存在的规则本身,再洗正文,最后统一拼回去。 + # 反过来的话,清洗器会把规则里「不要字幕…」那半句也当成字幕字样吃掉 → 摘不干净 → 规则拼两遍 + # (重复 = 负面词频翻倍 = 更容易出字幕,正是本次要根治的毛病)。 + base = (prompt or "").replace(NO_EMBEDDED_CAPTIONS_REQUIREMENT, "").replace(NO_EMBEDDED_CAPTIONS_TAIL, "") + base = strip_caption_directives(base).strip() + # 首帧最容易被模型自动加标题页 → 规则放开头;末位权重高 → 结尾补一句中性的正面复述。 + return f"{NO_EMBEDDED_CAPTIONS_REQUIREMENT}\n{base}\n{NO_EMBEDDED_CAPTIONS_TAIL}".strip() def execute_routed_video_submit( @@ -2649,9 +2666,12 @@ def _scene_context(project) -> str: def _segment_script_text(segment, entities=None, *, with_dialogue: bool = False, with_exposure: bool = True) -> str: - """本镜脚本文本(画面 + 台词/口播 + 商品露出),拼进故事板/视频提示词的【分镜脚本】。 + """本镜脚本文本(画面 + 人声 + 商品露出),拼进视频提示词的【脚本】。 with_dialogue:有结构化对白时按「说话人:台词」原样放(说话人 id 用 entities 解析成名字),回退扁平 narration。 - with_exposure:是否带「商品露出:…」一行。""" + with_exposure:是否带「商品露出:…」一行。 + + ★ 人声一律带「仅音频…不出现在画面上」的抬头:裸摆一段台词时,出片模型会把这段可见文本 + 当成需要渲染的画面文字 —— 这是成片带字幕最常见的来源,比缺少禁令更致命。""" parts = [] visual = (segment.visual_prompt or "").strip() if visual: @@ -2670,11 +2690,12 @@ def _segment_script_text(segment, entities=None, *, with_dialogue: bool = False, spk = name_by_id.get(d.get("speaker")) or "" lines.append(f"{spk}:{line}" if spk else line) if lines: # 有对白 → 用带说话人的台词 - parts.append("台词:\n" + "\n".join(lines)) + # 「仅音频」这个抬头是防字幕的关键:裸摆一段台词,模型会当成要显示的文本渲染上屏 + parts.append("人声(仅音频,由人物口型与配音表达,不出现在画面上):\n" + "\n".join(lines)) else: # 无对白 → 回退扁平口播/旁白 narration = (segment.narration or "").strip() if narration: - parts.append(f"口播/旁白:{narration}") + parts.append(f"人声(仅音频,由人物口型与配音表达,不出现在画面上):{narration}") if with_exposure and getattr(segment, "product_exposure", ""): parts.append(f"商品露出:{segment.product_exposure.strip()}") return "\n".join(parts) @@ -2710,7 +2731,7 @@ def build_video_segment_prompt(project, video_segment, scene, refs, user_prompt: "商品用法必须是真人会做的(茶/咖啡用热水、有蒸汽与茶汤渐染;护肤品挤出并涂抹;食品打开并入口)," "禁止诡异姿势、错误容器或违背常识的操作。" "\n【画质】真人实拍质感,自然光影,肤色与材质真实,焦点始终落在脚本指定的主体上,画面稳定不糊。" - "\n电商带货短视频,商品露出清晰,节奏有转化感。不要字幕,不要背景音乐,但是要有音效,逼真的音效。" + "\n电商带货短视频,商品露出清晰,节奏有转化感。不要背景音乐,但是要有音效,逼真的音效。" ) rendered = render_prompt( "video_segment", default, diff --git a/core/backend/apps/ai/test_script_duration_combo.py b/core/backend/apps/ai/test_script_duration_combo.py index 8b5e8f0..191b52b 100644 --- a/core/backend/apps/ai/test_script_duration_combo.py +++ b/core/backend/apps/ai/test_script_duration_combo.py @@ -24,6 +24,7 @@ from apps.ai.script_agent import ( assert_product_facts_used, assert_script_has_a_hook, assert_cta_is_not_shouty, + assert_no_caption_requests, assert_shot_density, build_agent_messages, coerce_combo, @@ -667,3 +668,25 @@ class CtaToneTests(SimpleTestCase): def test_natural_contextual_cta_passes(self): assert_cta_is_not_shouty(self._draft("你要是也天天下午三点犯困,可以试试这个,我一盒能喝小半个月。")) assert_cta_is_not_shouty(self._draft("链接放这儿了,先看看合不合适再说,不着急。")) + + +class NoCaptionInScriptTests(SimpleTestCase): + """成片无字幕由系统统一保证,脚本里连提都不该提 —— 出片模型不区分肯定否定, + 画面描述里出现「字幕」这个词本身就更容易把文字画上屏。""" + + def test_asking_for_captions_is_rejected(self): + with self.assertRaises(ValueError) as ctx: + assert_no_caption_requests({"segments": [{"visual": "0-3s:近景,右下角加字幕"}]}) + self.assertIn("一律没有字幕", str(ctx.exception)) + + def test_even_the_negated_form_is_rejected(self): + with self.assertRaises(ValueError): + assert_no_caption_requests({"segments": [{"visual": "0-3s:特写;画面无字幕"}]}) + + def test_other_overlay_words_are_rejected(self): + for word in ("花字", "贴片", "弹幕", "角标", "水印"): + with self.assertRaises(ValueError, msg=word): + assert_no_caption_requests({"segments": [{"visual": f"0-3s:中景;配{word}"}]}) + + def test_real_packaging_text_still_passes(self): + assert_no_caption_requests({"segments": [{"visual": "0-3s:近景,包装上的品牌字样清晰"}]}) diff --git a/core/backend/apps/ai/test_video_caption_policy.py b/core/backend/apps/ai/test_video_caption_policy.py index eabafdb..9f2253b 100644 --- a/core/backend/apps/ai/test_video_caption_policy.py +++ b/core/backend/apps/ai/test_video_caption_policy.py @@ -2,6 +2,7 @@ from django.test import SimpleTestCase from apps.ai.services import ( NO_EMBEDDED_CAPTIONS_REQUIREMENT, + NO_EMBEDDED_CAPTIONS_TAIL, enforce_no_embedded_captions, strip_caption_directives, ) @@ -11,49 +12,78 @@ class VideoCaptionPolicyTests(SimpleTestCase): """全系统出的视频一律无字幕。所有入口(专业创作 / 一键成片 / 自由创作 / 视频复刻 / 提炼改写) 都汇入 execute_routed_video_submit → enforce_no_embedded_captions,这里守住那一道。""" - def test_always_appends_no_embedded_captions_requirement(self): - prompt = enforce_no_embedded_captions("一位用户在厨房展示商品") + def test_rule_is_appended_once_at_both_ends(self): + prompt = enforce_no_embedded_captions("一位用户在厨房使用商品") - self.assertIn(NO_EMBEDDED_CAPTIONS_REQUIREMENT, prompt) - self.assertIn("一位用户在厨房展示商品", prompt) - self.assertTrue(prompt.startswith(NO_EMBEDDED_CAPTIONS_REQUIREMENT)) + self.assertTrue(prompt.startswith(NO_EMBEDDED_CAPTIONS_REQUIREMENT)) # 首帧最容易被加标题页 + self.assertTrue(prompt.endswith(NO_EMBEDDED_CAPTIONS_TAIL)) # 末位权重高 + self.assertIn("一位用户在厨房使用商品", prompt) - def test_requirement_is_not_duplicated(self): - prompt = enforce_no_embedded_captions(NO_EMBEDDED_CAPTIONS_REQUIREMENT) + def test_rule_is_never_duplicated(self): + once = enforce_no_embedded_captions("画面内容") + twice = enforce_no_embedded_captions(once) - self.assertEqual(prompt.count(NO_EMBEDDED_CAPTIONS_REQUIREMENT), 1) + self.assertEqual(twice, once) + self.assertEqual(twice.count(NO_EMBEDDED_CAPTIONS_REQUIREMENT), 1) - def test_requirement_covers_the_overlays_seedance_likes_to_invent(self): - """只写「不要字幕」时实测仍会冒出花字 / 价格贴片 / 购物浮层,禁令必须逐个点名。""" - for term in ("第0秒", "开场首帧", "前3秒", "花字", "水印", "角标", "价格贴片", "弹幕", "购物车", "进度条", "片尾"): - self.assertIn(term, NO_EMBEDDED_CAPTIONS_REQUIREMENT, msg=f"禁令漏了「{term}」") - # 提示词别处若还残留「字幕」字样,以本条为准 —— 解决自相矛盾导致的偶发出字幕 - self.assertIn("以本条为准", NO_EMBEDDED_CAPTIONS_REQUIREMENT) + def test_negative_nouns_stay_rare(self): + """★ 这条守的是本次修复的核心结论:视频扩散模型对否定弱、对名词强, + 禁令里反复点名「字幕 / 花字」反而更容易画出字幕(实测连续两条成片中招)。 + 所以规则以正面陈述为主,负面名词全片最多出现一次 —— 谁想往禁令里加词,先过这一关。""" + prompt = enforce_no_embedded_captions("0-3s:近景;平视;女主捧杯对镜头") + + self.assertLessEqual(prompt.count("字幕"), 1, "禁令里的「字幕」词频过高,反而会诱发字幕") + self.assertEqual(prompt.count("花字"), 0) + # 正面陈述必须在:告诉模型画面「应该」长什么样,比罗列不要什么更有效 + self.assertIn("纯实拍", prompt) + + def test_speech_is_declared_audio_only(self): + """裸摆一段台词时模型会把它当成要渲染的画面文本 —— 必须讲明只出声。""" + self.assertIn("口型", NO_EMBEDDED_CAPTIONS_REQUIREMENT) + self.assertIn("不写到画面上", NO_EMBEDDED_CAPTIONS_REQUIREMENT) def test_strips_caption_field_lines(self): - """整行的「字幕:xxx」直接丢掉 —— 模型看到具体字幕内容就会把它画进画面。""" cleaned = strip_caption_directives("字幕:限时8折\n0-3s:近景;平视;女主抬眼") self.assertNotIn("限时8折", cleaned) self.assertIn("0-3s:近景;平视;女主抬眼", cleaned) def test_strips_inline_caption_field_inside_sound_row(self): - """脚本【声音】栏里的「字幕:…」片段要摘掉,同一行其它内容留着。""" cleaned = strip_caption_directives("【声音】口播;音效:倒水声;字幕:熬夜救星;背景音乐:无") self.assertNotIn("熬夜救星", cleaned) self.assertIn("音效:倒水声", cleaned) self.assertIn("背景音乐:无", cleaned) + def test_strips_caption_mentions_without_a_colon(self): + """存量项目的历史脚本里全是这种写法,光改 skill 管不到它们。""" + self.assertEqual( + strip_caption_directives("0-3s:中近景;固定机位;女主捧杯,右下角加字幕"), + "0-3s:中近景;固定机位;女主捧杯", + ) + + def test_strips_even_the_negated_form(self): + """「画面无字幕」也要清:模型不区分肯定否定,看到这个词就更容易画出来。""" + self.assertEqual( + strip_caption_directives("12-15s:近景;推近;她放下杯子,画面无字幕"), + "12-15s:近景;推近;她放下杯子", + ) + + def test_removes_the_whole_clause_not_just_the_word(self): + """只删词会留下「右下角」「画面无」这种断头残渣,反而让模型犯迷糊。""" + cleaned = strip_caption_directives("0-3s:特写;俯拍;茶汤渐染,底部打上花字") + + self.assertNotIn("底部", cleaned) + self.assertIn("茶汤渐染", cleaned) + def test_does_not_touch_real_packaging_text(self): - """包装上的真实文字是允许出现的,别被误伤。""" line = "0-3s:近景,包装上的品牌字样清晰" self.assertEqual(strip_caption_directives(line), line) - def test_scrubbing_runs_before_the_requirement_is_appended(self): + def test_scrubbing_runs_before_the_rule_is_attached(self): prompt = enforce_no_embedded_captions("花字:手慢无\n0-3s:特写;推近;倒入") self.assertNotIn("手慢无", prompt) self.assertIn("0-3s:特写;推近;倒入", prompt) - self.assertIn(NO_EMBEDDED_CAPTIONS_REQUIREMENT, prompt) + self.assertTrue(prompt.startswith(NO_EMBEDDED_CAPTIONS_REQUIREMENT)) diff --git a/core/backend/skills/ecommerce-video-script/SKILL.md b/core/backend/skills/ecommerce-video-script/SKILL.md index 010b74e..7c42880 100644 --- a/core/backend/skills/ecommerce-video-script/SKILL.md +++ b/core/backend/skills/ecommerce-video-script/SKILL.md @@ -113,7 +113,7 @@ description: > - **`visual` 固定使用导演层级**(四栏,逐栏写满,缺栏视为不合格): `【本镜任务】` 这一段要完成的情绪 / 信息变化 → `【光线氛围】` 光源方向与性质(窗光 / 顶光 / 台灯 / 逆光)、色温冷暖、明暗对比、整体色调 → - `【声音】` 台词 / 旁白状态、音效、背景音乐(没有写「无」)→ **不写字幕栏**,本系统成片一律无字幕 → + `【声音】` 台词 / 旁白状态、音效、背景音乐(没有写「无」)→ **没有字幕栏,也不要提这两个字** → `【画面内容】` 下列 3–5 条秒级分镜。 不要把基础设定、画面风格、镜头动作、声音、旁白混成一段散文。 - **每镜 `visual` 必须按秒拆分镜**:15 秒一场里至少 3 刀、目标 4 刀,写成多行, @@ -130,9 +130,10 @@ description: > - **禁止评价词** —— 「展示商品」「呈现质感」「体现高级感」「氛围到位」「传递温暖」不是画面, 要写成「杯壁的水珠顺着往下滑」「她眉头松开,肩膀塌下来」。 - **禁止心理描写** —— 模型拍不出「她内心纠结」,要写「她拿起又放下,手指在包装边缘停了两秒」。 - - **禁止画面里出现文字** —— 本系统出的**任何视频都不带字幕**。不写字幕、花字、标题、片头片尾、 - 水印、角标、价格贴片、优惠标签、弹幕、对话气泡、贴纸、参数表、对比图表、UI、按钮、购物浮层; - 口播和台词只以声音存在。商品包装上原本就有的真实文字除外。 + - **画面描述里一个「字幕」都不许出现** —— 本系统出的**任何视频都不带字幕**,这由系统统一保证, + 脚本里不需要声明。所以 `visual` 既不要要求加字幕 / 花字 / 标题 / 贴片 / 弹幕 / 角标 / 水印 / 购物浮层, + **连「无字幕」「不加花字」这种否定说法也不要写** —— 出片模型不区分肯定否定,画面描述里出现这个词本身就更容易把文字画上屏。画面只描述实拍到的东西。 + 口播和台词只以声音存在;只有商品包装上原本印着的真实文字可以出现在画面里。 - **禁止一镜内跨场景 / 跨时间蒙太奇** —— 15 秒是**一个连续动作链**,同一地点、同一光线、同一套衣服。 要换环境就换到下一镜,并在 `entity_refs` 里换成另一个 scene。 - **一致性靠文字锁死**:同一角色 / 商品 / 场景的外观一律引用 entities 里的既定设定, diff --git a/core/backend/skills/ecommerce-video-script/references/checklist.md b/core/backend/skills/ecommerce-video-script/references/checklist.md index 546b594..3e3997b 100644 --- a/core/backend/skills/ecommerce-video-script/references/checklist.md +++ b/core/backend/skills/ecommerce-video-script/references/checklist.md @@ -55,7 +55,7 @@ - [ ] **四栏齐全**:每镜 `visual` 都有 `【本镜任务】【光线氛围】【声音】【画面内容】`,没有缺栏。 - [ ] **相邻镜光线衔接**:同一场戏各镜的光源方向、色温、色调一致,没有忽冷忽暖。 - [ ] **画面可拍**:没有「展示商品/呈现质感/体现高级感」这类评价词,没有心理描写,没有一镜内跨场景跨时间。 -- [ ] **全片无字幕**:任何一镜的 `visual` 都没有要求字幕、花字、标题、水印、角标、价签、购物浮层或其它画面文字,`【声音】` 里也没有「字幕」这一栏。 +- [ ] **画面描述里搜不到「字幕 / 花字 / 贴片 / 弹幕 / 角标 / 水印」这几个词**(要求加的、声明没有的都算不合格)。成片无字幕由系统统一保证,脚本不需要写。 - [ ] **商品原词**:旁白/对白出现了输入给出的至少一个勾选卖点原词;商品名全片点名 1–2 次即可,其余镜写描述/卖点细节,没有用空话替换。 - [ ] 每镜 `visual` 能撑满 15 秒(至少 110 字),写清哪只手、从哪个方向入画、握商品哪里,以及容器/材质的真实状态,不是一句静态动作。 - [ ] 口语化,无书面腔/AI 腔("综上""不仅…而且""值得一提"等已清除)。 diff --git a/core/backend/skills/ecommerce-video-script/references/methodology.md b/core/backend/skills/ecommerce-video-script/references/methodology.md index 9c5e061..178cbea 100644 --- a/core/backend/skills/ecommerce-video-script/references/methodology.md +++ b/core/backend/skills/ecommerce-video-script/references/methodology.md @@ -142,7 +142,7 @@ ``` 【本镜任务】本段要让观众看懂的变化或悬念 【光线氛围】光源方向与性质(窗光/顶光/台灯/逆光);色温冷暖;明暗对比;整体色调(各镜保持一致) -【声音】台词/旁白状态;音效;背景音乐(没有写无)—— 不写字幕栏,成片一律无字幕 +【声音】台词/旁白状态;音效;背景音乐(没有写无)—— 没有字幕栏,也不要提这两个字 【画面内容】 0-3s:景别;机位;运镜;主体在画面中的位置;具体动作;信息变化 3-8s:景别;机位;运镜;手与商品的空间关系;可见细节 @@ -169,7 +169,7 @@ - **每镜至少出现一个运镜词**(手持跟拍 / 推近 / 拉远 / 横摇 / 环绕 / 固定机位); 没有运镜词,出片会拍成呆板的静止画面。 - **只写拍得出来的东西**:禁止「展示商品 / 呈现质感 / 体现高级感」这类评价词,禁止心理描写, - 禁止画面里出现任何文字叠加(字幕 / 花字 / 标题 / 水印 / 角标 / 价签 / 购物浮层),口播只以声音存在;禁止一镜内跨场景跨时间。 + 画面描述里一个「字幕」都不要出现(要求加的、声明没有的都不要写),口播只以声音存在;禁止一镜内跨场景跨时间。 - 商品用法必须真实:茶要热水和蒸汽,不要把茶包丢进冷白开;手不要悬浮。 - **一场一个动作链**:把 15 秒拆成同一件事的起因 → 操作 → 可见变化 → 反应,不能把无关的 剧情、多个卖点、跳场景硬塞进同一场。画面提供证据,台词提供判断或转折,不要互相复述。