From 0280c2e192dbf2755a614862291dcdf70166c594 Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Wed, 17 Jun 2026 10:24:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E8=84=9A=E6=9C=AC=20agent=20?= =?UTF-8?q?=E5=86=8D=E5=85=BC=E5=AE=B9=20lines/caption=20=E5=8F=98?= =?UTF-8?q?=E4=BD=93,=E8=A1=A5=E5=85=A8=E6=97=81=E7=99=BD=E6=BC=8F?= =?UTF-8?q?=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 豆包不同型号吐不同 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 --- core/backend/apps/ai/script_agent.py | 33 ++++++++++++++-------------- core/backend/apps/ai/tests.py | 20 +++++++++++++++++ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/core/backend/apps/ai/script_agent.py b/core/backend/apps/ai/script_agent.py index 7f4161e..21b5b7d 100644 --- a/core/backend/apps/ai/script_agent.py +++ b/core/backend/apps/ai/script_agent.py @@ -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, diff --git a/core/backend/apps/ai/tests.py b/core/backend/apps/ai/tests.py index 186f24b..c3ab3b9 100644 --- a/core/backend/apps/ai/tests.py +++ b/core/backend/apps/ai/tests.py @@ -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({