1. 痛点:为什么"一键生成分镜"总是翻车
做 AI 短剧的人几乎都踩过同一个坑:把一段小说丢给大模型,让它"拆成分镜",结果出来的东西看着像模像样,一拿去生成画面就崩了。
典型问题有三个:
- 分镜数量不稳定:同一段文本,跑两次一次给 12 个镜头,一次给 47 个,没法批量生产。
- 字段结构不统一:这次返回
shot,下次返回scene,下游解析代码天天改。 - 角色描述漂移:第 3 个镜头写"穿红裙的少女",第 8 个镜头变成"红衣女子",生成出来的角色完全不是一个人。
这篇文章不讲"我们的产品多强",而是把从自由文本到结构化分镜表这条链路拆开,给出可复现的方案、可运行的代码,以及我们真实踩过的坑。
2. 方案总览:三段式流水线
整体思路是"先切分、再结构化、最后校验",把一次大模型调用拆成三个职责清晰的阶段:
这样拆的好处是:每个阶段都能单独测试、单独重试,出错时能定位到具体是哪一步的问题,而不是"模型又抽风了"。
3. 第一步:场景切分,别让模型一次吃整本小说
很多人失败的第一步就错了——直接把几千字丢进去。上下文一长,模型对后半段的注意力就下降,分镜质量断崖式下跌。
正确做法是先按叙事单元切分。这里不需要多复杂的算法,按段落 + 长度阈值切即可:
def split_scenes(text: str, max_chars: int = 800) -> list[str]:
"""按空行切段,再合并到不超过 max_chars 的场景块"""
paragraphs = [p.strip() for p in text.split("\n") if p.strip()]
scenes, buf = [], ""
for p in paragraphs:
if len(buf) + len(p) > max_chars and buf:
scenes.append(buf)
buf = p
else:
buf = f"{buf}\n{p}" if buf else p
if buf:
scenes.append(buf)
return scenes
关键参数是 max_chars。我们的经验值是 600–900 字:太短会导致一个场景被切碎、上下文不足;太长则模型开始"自由发挥"。
4. 第二步:结构化抽取,用 JSON Schema 锁死输出
这一步是整个方案的核心。不要用"请返回 JSON"这种口头约束,而是用模型的结构化输出能力(如 JSON Mode / Function Calling)把字段固定下来。
先定义 Schema:
STORYBOARD_SCHEMA = {
"type": "object",
"properties": {
"shots": {
"type": "array",
"items": {
"type": "object",
"properties": {
"shot_id": {"type": "integer"},
"scene_desc": {"type": "string"},
"characters": {"type": "array", "items": {"type": "string"}},
"camera": {"type": "string"},
"dialogue": {"type": "string"},
"duration_sec": {"type": "number"}
},
"required": ["shot_id", "scene_desc", "characters", "camera"]
}
}
},
"required": ["shots"]
}
再写抽取函数:
import json
import requests
def extract_shots(scene_text: str, api_key: str) -> dict:
"""把单个场景文本抽取为结构化分镜"""
prompt = (
"你是短剧分镜师。请把下面的场景拆成 3-8 个镜头,"
"每个镜头给出画面描述、出场角色、机位、台词与预估时长。\n\n"
f"场景原文:\n{scene_text}"
)
resp = requests.post(
"https://api.jifeng.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": "jifeng-storyboard",
"messages": [{"role": "user", "content": prompt}],
"response_format": {"type": "json_schema", "schema": STORYBOARD_SCHEMA},
"temperature": 0.3,
},
timeout=60,
)
resp.raise_for_status()
return json.loads(resp.json()["choices"][0]["message"]["content"])
两个细节值得强调:
temperature调到 0.3 左右。分镜是结构化任务,不需要创意发散,低温能显著提升字段稳定性。- 在 prompt 里给出镜头数量区间(3–8 个),比"拆成 30 个"这种全局数字更可控——全局数量由场景数自然累加得到。
5. 第三步:Schema 校验与自动修复
即使有 JSON Schema,模型偶尔还是会漏字段或类型不对。所以拿到结果后必须校验,失败就带着错误信息重试一次:
def validate_and_repair(data: dict, scene_text: str, api_key: str, retries: int = 1) -> dict:
"""校验分镜结构,失败时把错误回传给模型修复"""
try:
assert isinstance(data.get("shots"), list) and data["shots"], "shots 为空"
for s in data["shots"]:
assert isinstance(s.get("shot_id"), int), "shot_id 类型错误"
assert s.get("scene_desc"), "scene_desc 缺失"
return data
except AssertionError as e:
if retries <= 0:
raise
repair_prompt = (
f"上一次输出不符合要求:{e}。请基于原文重新输出合法 JSON。\n原文:{scene_text}"
)
# 复用 extract_shots 的调用逻辑,此处省略重复代码
fixed = extract_shots(repair_prompt, api_key)
return validate_and_repair(fixed, scene_text, api_key, retries - 1)
这一步能把我们的解析失败率从约 8% 压到 1% 以下。不要指望一次成功,要设计好失败路径。
6. 第四步:角色一致性,靠词典而不是靠模型记忆
角色漂移是最难缠的问题。我们的做法是:不让模型自由命名角色,而是先建角色词典,再让模型从词典里选。
流程是:
- 第一遍抽取时,收集所有
characters字段的原始描述; - 用一次轻量调用把同义描述聚类(“红裙少女” / “红衣女子” →
char_01); - 后续所有抽取都要求模型只能输出词典中的 ID。
def normalize_characters(raw_names: list[str], api_key: str) -> dict[str, str]:
"""把角色描述聚类归一,返回 原始描述 -> 角色ID 的映射"""
prompt = (
"下面是一组角色描述,请把指代同一角色的描述归为一组,"
"每组分配一个 ID(char_01、char_02...),返回 JSON 映射。\n"
+ "\n".join(f"- {n}" for n in set(raw_names))
)
resp = requests.post(
"https://api.jifeng.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={"model": "jifeng-lite", "messages": [{"role": "user", "content": prompt}],
"response_format": {"type": "json_object"}},
timeout=30,
)
resp.raise_for_status()
return json.loads(resp.json()["choices"][0]["message"]["content"])
配合参考图 + 固定种子,角色一致性基本能稳定在可接受范围。这部分在无限画布里做可视化微调会更直观。
7. 效果与踩坑记录
在 200 段测试文本上的实测数据(仅供参考,不同模型差异较大):
| 指标 | 优化前 | 优化后 |
|---|---|---|
| JSON 解析成功率 | 92% | 99% |
| 单场景平均耗时 | 6.8s | 4.1s |
| 角色指代一致率 | 约 65% | 约 93% |
踩过的坑,按痛苦程度排序:
- 坑 1:一次性喂整篇。上下文超过 4k 字后,后半段分镜质量明显下降,且数量失控。切分是必须的。
- 坑 2:用自然语言约束字段。写"请返回包含 shot_id 的 JSON"根本不管用,必须用 Schema。
- 坑 3:忽略重试成本。一开始没做校验,下游解析报错才发现问题,排查成本远高于加一层校验。
- 坑 4:让模型自己保证角色一致。模型没有跨调用记忆,必须外部维护词典。
8. 小结
把小说拆成稳定分镜,本质不是"提示词写得好",而是工程化地把不确定性关进笼子:切分控制上下文、Schema 锁死结构、校验兜住失败、词典保证一致。
这套方案在极风AI 的短剧链路里已经跑通,从剧本到分镜表再到无限画布编排,整条链路都能复用。如果你也在做类似的事,建议先把第二步的 Schema 和第五步的校验做扎实,收益最直接。
243



被折叠的 条评论
为什么被折叠?



