fix(core): 脚本 agent 再兼容 lines/caption 变体,补全旁白漏空
豆包不同型号吐不同 schema:Lite 用 dialogue(字符串)+subtitle;Pro 用
lines:[{role,content}]+caption。上一版只认 dialogue/subtitle,导致 Pro 变体
画面能填、旁白仍空。normalize_draft 再扩容:
- 口播来源并入 lines(每项 content/line/text)、voiceover;subtitle/caption 兜底
- 画面来源并入 visual_description
新增 lines/caption 变体回归用例,全套 22 测试通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -288,28 +288,29 @@ def normalize_draft(raw_text: str, *, aspect_ratio: str, total_duration: int) ->
|
||||
speaker = speaker if (speaker in valid_ids) else None
|
||||
refs = [r for r in (seg.get("entity_refs") or []) if r in valid_ids]
|
||||
# 对白(剧情向):[{speaker(合法 entity id 或 null=旁白), line}];默认空 = 纯口播。
|
||||
# 模型变体里 dialogue 可能是字符串(整句口播)而非数组——只在数组时按结构解析。
|
||||
# 模型变体的口播字段五花八门:dialogue(字符串/数组)/ lines(数组)/ 每项 line|text|content。
|
||||
# 这里把任意数组形态归一成结构化对白,字符串形态留给下面当整句旁白。
|
||||
raw_dialogue = seg.get("dialogue")
|
||||
dialogue = []
|
||||
if isinstance(raw_dialogue, list):
|
||||
for d in raw_dialogue:
|
||||
if not isinstance(d, dict):
|
||||
continue
|
||||
line = (d.get("line") or d.get("text") or "").strip()
|
||||
if not line:
|
||||
continue
|
||||
sp = d.get("speaker")
|
||||
dialogue.append({"speaker": sp if sp in valid_ids else None, "line": line})
|
||||
# 旁白:narration > 字符串 dialogue(整句口播)> subtitle(字幕)> 结构化对白拼接
|
||||
narration = (seg.get("narration") or "").strip()
|
||||
dialogue_array = raw_dialogue if isinstance(raw_dialogue, list) else (seg.get("lines") if isinstance(seg.get("lines"), list) else [])
|
||||
for d in dialogue_array:
|
||||
if not isinstance(d, dict):
|
||||
continue
|
||||
line = (d.get("line") or d.get("text") or d.get("content") or "").strip()
|
||||
if not line:
|
||||
continue
|
||||
sp = d.get("speaker")
|
||||
dialogue.append({"speaker": sp if sp in valid_ids else None, "line": line})
|
||||
# 旁白:narration > 字符串 dialogue(整句口播)> 结构化对白/lines 拼接 > subtitle/caption(字幕兜底)
|
||||
narration = (seg.get("narration") or seg.get("voiceover") or "").strip()
|
||||
if not narration and isinstance(raw_dialogue, str):
|
||||
narration = raw_dialogue.strip()
|
||||
if not narration:
|
||||
narration = (seg.get("subtitle") or "").strip()
|
||||
if not narration and dialogue:
|
||||
narration = " ".join(d["line"] for d in dialogue) # 扁平拼接,兼容下游字幕/配音
|
||||
# 画面:visual > visual_prompt > scene(模型变体常用 scene 描述画面)
|
||||
visual = (seg.get("visual") or seg.get("visual_prompt") or seg.get("scene") or "").strip()
|
||||
if not narration:
|
||||
narration = (seg.get("subtitle") or seg.get("caption") or "").strip()
|
||||
# 画面:visual > visual_prompt > scene > shot/visual_description(模型变体常用 scene 描述画面)
|
||||
visual = (seg.get("visual") or seg.get("visual_prompt") or seg.get("scene") or seg.get("visual_description") or "").strip()
|
||||
norm_segments.append(
|
||||
{
|
||||
"index": i,
|
||||
|
||||
@@ -31,6 +31,26 @@ class NormalizeDraftTests(SimpleTestCase):
|
||||
self.assertEqual(draft["segments"][0]["narration"], "卡裆太社死!")
|
||||
self.assertEqual(draft["segments"][0]["visual"], "更衣室扯卡裆旧裤")
|
||||
|
||||
def test_lines_and_caption_variant_fills_narration(self):
|
||||
"""另一种变体(Doubao-Seed-2.0-P):shots 用 lines:[{role,content}] 放口播、caption 放字幕。
|
||||
必须把 lines 的 content 当对白/旁白,caption 兜底,而不是只填画面留旁白空。"""
|
||||
raw = json.dumps({
|
||||
"ScriptDraft": {
|
||||
"basicInfo": {"totalDuration": 30, "aspectRatio": "9:16"},
|
||||
"shots": [
|
||||
{"shotNo": 1, "scene": "化妆台两闺蜜", "lines": [{"role": "女主", "content": "防晒泛白太尴尬!"}, {"role": "闺蜜", "content": "试试这个!"}], "caption": "防晒踩雷?"},
|
||||
{"shotNo": 2, "scene": "手背挤膏体", "lines": [{"role": "闺蜜", "content": "质地清透不泛白"}], "caption": "清透质地"},
|
||||
],
|
||||
}
|
||||
}, ensure_ascii=False)
|
||||
draft = normalize_draft(raw, aspect_ratio="9:16", total_duration=30)
|
||||
self.assertEqual(len(draft["segments"]), 2)
|
||||
for seg in draft["segments"]:
|
||||
self.assertTrue(seg["narration"], "旁白不应为空")
|
||||
self.assertTrue(seg["visual"], "画面不应为空")
|
||||
self.assertIn("防晒泛白太尴尬", draft["segments"][0]["narration"])
|
||||
self.assertEqual(len(draft["segments"][0]["dialogue"]), 2) # lines→结构化对白
|
||||
|
||||
def test_string_dialogue_not_iterated_as_chars(self):
|
||||
"""dialogue 为整句字符串时,要当作旁白而不是逐字符遍历。"""
|
||||
raw = json.dumps({
|
||||
|
||||
Reference in New Issue
Block a user