优化生成视频出现字幕问题
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:近景,包装上的品牌字样清晰"}]})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -113,7 +113,7 @@ description: >
|
||||
- **`visual` 固定使用导演层级**(四栏,逐栏写满,缺栏视为不合格):
|
||||
`【本镜任务】` 这一段要完成的情绪 / 信息变化 →
|
||||
`【光线氛围】` 光源方向与性质(窗光 / 顶光 / 台灯 / 逆光)、色温冷暖、明暗对比、整体色调 →
|
||||
`【声音】` 台词 / 旁白状态、音效、背景音乐(没有写「无」)→ **不写字幕栏**,本系统成片一律无字幕 →
|
||||
`【声音】` 台词 / 旁白状态、音效、背景音乐(没有写「无」)→ **没有字幕栏,也不要提这两个字** →
|
||||
`【画面内容】` 下列 3–5 条秒级分镜。
|
||||
不要把基础设定、画面风格、镜头动作、声音、旁白混成一段散文。
|
||||
- **每镜 `visual` 必须按秒拆分镜**:15 秒一场里至少 3 刀、目标 4 刀,写成多行,
|
||||
@@ -130,9 +130,10 @@ description: >
|
||||
- **禁止评价词** —— 「展示商品」「呈现质感」「体现高级感」「氛围到位」「传递温暖」不是画面,
|
||||
要写成「杯壁的水珠顺着往下滑」「她眉头松开,肩膀塌下来」。
|
||||
- **禁止心理描写** —— 模型拍不出「她内心纠结」,要写「她拿起又放下,手指在包装边缘停了两秒」。
|
||||
- **禁止画面里出现文字** —— 本系统出的**任何视频都不带字幕**。不写字幕、花字、标题、片头片尾、
|
||||
水印、角标、价格贴片、优惠标签、弹幕、对话气泡、贴纸、参数表、对比图表、UI、按钮、购物浮层;
|
||||
口播和台词只以声音存在。商品包装上原本就有的真实文字除外。
|
||||
- **画面描述里一个「字幕」都不许出现** —— 本系统出的**任何视频都不带字幕**,这由系统统一保证,
|
||||
脚本里不需要声明。所以 `visual` 既不要要求加字幕 / 花字 / 标题 / 贴片 / 弹幕 / 角标 / 水印 / 购物浮层,
|
||||
**连「无字幕」「不加花字」这种否定说法也不要写** —— 出片模型不区分肯定否定,画面描述里出现这个词本身就更容易把文字画上屏。画面只描述实拍到的东西。
|
||||
口播和台词只以声音存在;只有商品包装上原本印着的真实文字可以出现在画面里。
|
||||
- **禁止一镜内跨场景 / 跨时间蒙太奇** —— 15 秒是**一个连续动作链**,同一地点、同一光线、同一套衣服。
|
||||
要换环境就换到下一镜,并在 `entity_refs` 里换成另一个 scene。
|
||||
- **一致性靠文字锁死**:同一角色 / 商品 / 场景的外观一律引用 entities 里的既定设定,
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
- [ ] **四栏齐全**:每镜 `visual` 都有 `【本镜任务】【光线氛围】【声音】【画面内容】`,没有缺栏。
|
||||
- [ ] **相邻镜光线衔接**:同一场戏各镜的光源方向、色温、色调一致,没有忽冷忽暖。
|
||||
- [ ] **画面可拍**:没有「展示商品/呈现质感/体现高级感」这类评价词,没有心理描写,没有一镜内跨场景跨时间。
|
||||
- [ ] **全片无字幕**:任何一镜的 `visual` 都没有要求字幕、花字、标题、水印、角标、价签、购物浮层或其它画面文字,`【声音】` 里也没有「字幕」这一栏。
|
||||
- [ ] **画面描述里搜不到「字幕 / 花字 / 贴片 / 弹幕 / 角标 / 水印」这几个词**(要求加的、声明没有的都算不合格)。成片无字幕由系统统一保证,脚本不需要写。
|
||||
- [ ] **商品原词**:旁白/对白出现了输入给出的至少一个勾选卖点原词;商品名全片点名 1–2 次即可,其余镜写描述/卖点细节,没有用空话替换。
|
||||
- [ ] 每镜 `visual` 能撑满 15 秒(至少 110 字),写清哪只手、从哪个方向入画、握商品哪里,以及容器/材质的真实状态,不是一句静态动作。
|
||||
- [ ] 口语化,无书面腔/AI 腔("综上""不仅…而且""值得一提"等已清除)。
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
```
|
||||
【本镜任务】本段要让观众看懂的变化或悬念
|
||||
【光线氛围】光源方向与性质(窗光/顶光/台灯/逆光);色温冷暖;明暗对比;整体色调(各镜保持一致)
|
||||
【声音】台词/旁白状态;音效;背景音乐(没有写无)—— 不写字幕栏,成片一律无字幕
|
||||
【声音】台词/旁白状态;音效;背景音乐(没有写无)—— 没有字幕栏,也不要提这两个字
|
||||
【画面内容】
|
||||
0-3s:景别;机位;运镜;主体在画面中的位置;具体动作;信息变化
|
||||
3-8s:景别;机位;运镜;手与商品的空间关系;可见细节
|
||||
@@ -169,7 +169,7 @@
|
||||
- **每镜至少出现一个运镜词**(手持跟拍 / 推近 / 拉远 / 横摇 / 环绕 / 固定机位);
|
||||
没有运镜词,出片会拍成呆板的静止画面。
|
||||
- **只写拍得出来的东西**:禁止「展示商品 / 呈现质感 / 体现高级感」这类评价词,禁止心理描写,
|
||||
禁止画面里出现任何文字叠加(字幕 / 花字 / 标题 / 水印 / 角标 / 价签 / 购物浮层),口播只以声音存在;禁止一镜内跨场景跨时间。
|
||||
画面描述里一个「字幕」都不要出现(要求加的、声明没有的都不要写),口播只以声音存在;禁止一镜内跨场景跨时间。
|
||||
- 商品用法必须真实:茶要热水和蒸汽,不要把茶包丢进冷白开;手不要悬浮。
|
||||
- **一场一个动作链**:把 15 秒拆成同一件事的起因 → 操作 → 可见变化 → 反应,不能把无关的
|
||||
剧情、多个卖点、跳场景硬塞进同一场。画面提供证据,台词提供判断或转折,不要互相复述。
|
||||
|
||||
Reference in New Issue
Block a user