fix(core): 脚本 agent 兼容模型 ScriptDraft/shots 变体,修旁白/画面全空
豆包 Doubao-Seed-2.0-Lite 常不按扁平契约输出,而是回
{"ScriptDraft":{"basicInfo":...,"shots":[{scene,dialogue,subtitle}]}}。
normalize_draft 在顶层找不到 segments → 走补占位镜兜底 → 旁白/画面全空(只剩 role)。
normalize_draft 增加容错:
- 解开 {"ScriptDraft":{...}} 外壳;basicInfo 的 camelCase 时长/比例拍平
- shots → segments;每镜 scene→画面、dialogue/subtitle→旁白
- 防 dialogue 为整句字符串时被逐字符遍历
原扁平契约路径不受影响。新增 apps/ai/tests.py 3 个回归用例,全套 21 测试通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -220,6 +220,25 @@ def normalize_draft(raw_text: str, *, aspect_ratio: str, total_duration: int) ->
|
||||
if not isinstance(draft, dict):
|
||||
raise ValueError("脚本 JSON 顶层不是对象")
|
||||
|
||||
# 兼容模型不按契约的常见变体:① 把内容裹进 {"ScriptDraft": {...}} 外壳;
|
||||
# ② 用 basicInfo(camelCase)放时长/比例;③ 用 shots 代替 segments。
|
||||
# 解开外壳并把别名拍平到契约字段,避免「找不到 segments → 全填空占位镜」(旁白/画面全空)。
|
||||
for wrapper in ("ScriptDraft", "script_draft", "scriptDraft", "draft"):
|
||||
inner = draft.get(wrapper)
|
||||
if isinstance(inner, dict):
|
||||
draft = {**inner, **{k: v for k, v in draft.items() if k != wrapper}}
|
||||
break
|
||||
basic = draft.get("basicInfo") if isinstance(draft.get("basicInfo"), dict) else {}
|
||||
if basic:
|
||||
draft.setdefault("total_duration", basic.get("totalDuration") or basic.get("total_duration"))
|
||||
draft.setdefault("aspect_ratio", basic.get("aspectRatio") or basic.get("aspect_ratio"))
|
||||
draft.setdefault("theme", basic.get("theme"))
|
||||
if not isinstance(draft.get("segments"), list):
|
||||
for alias in ("shots", "scenes_list", "shotList"):
|
||||
if isinstance(draft.get(alias), list):
|
||||
draft["segments"] = draft[alias]
|
||||
break
|
||||
|
||||
draft["aspect_ratio"] = (draft.get("aspect_ratio") or aspect_ratio or "9:16").strip()
|
||||
dur = _nearest_duration(draft.get("total_duration") or total_duration)
|
||||
draft["total_duration"] = dur
|
||||
@@ -268,19 +287,29 @@ def normalize_draft(raw_text: str, *, aspect_ratio: str, total_duration: int) ->
|
||||
speaker = seg.get("speaker")
|
||||
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}];默认空 = 纯口播
|
||||
# 对白(剧情向):[{speaker(合法 entity id 或 null=旁白), line}];默认空 = 纯口播。
|
||||
# 模型变体里 dialogue 可能是字符串(整句口播)而非数组——只在数组时按结构解析。
|
||||
raw_dialogue = seg.get("dialogue")
|
||||
dialogue = []
|
||||
for d in (seg.get("dialogue") or []):
|
||||
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})
|
||||
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()
|
||||
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()
|
||||
norm_segments.append(
|
||||
{
|
||||
"index": i,
|
||||
@@ -288,7 +317,7 @@ def normalize_draft(raw_text: str, *, aspect_ratio: str, total_duration: int) ->
|
||||
"role": role,
|
||||
"narration": narration,
|
||||
"speaker": speaker,
|
||||
"visual": (seg.get("visual") or seg.get("visual_prompt") or "").strip(),
|
||||
"visual": visual,
|
||||
"product_exposure": (seg.get("product_exposure") or "").strip(),
|
||||
"entity_refs": refs,
|
||||
"dialogue": dialogue,
|
||||
|
||||
Reference in New Issue
Block a user