YAML Screenplay Schema v1.0
适用于电影 (movie)、电视剧 (tv_series)、短剧 (short_drama) 三种格式的结构化剧本定义。
快速示例
yamlmeta: format: movie title: "流浪地球" genre: [sci-fi] characters: - id: CH-001 name: "刘启" category: main gender: male description: "地下城居民" scenes: - id: SC-001 source_chapter: 1 heading: interior: false location: "地下城 - 公路" time_of_day: day blocks: - type: dialogue character_id: CH-001 text: "希望,像钻石一样珍贵。" delivery: voice_over - type: transition text: "CUT TO:"
设计理念
三层分离
meta(元信息)→ characters(角色表)→ scenes(场景集),对应剧本工业标准的"故事大纲 → 人物小传 → 场次表"。
单一数据源
角色通过 CH-001 格式 ID 引用。改角色名只需改一处,杜绝数据不一致。
discriminatedUnion
3 种 block 类型(action / dialogue / transition),每种只携带自己的字段。Zod 在编译期拒绝非法组合。
格式靠 Schema,内容靠 Prompt
LLM 输出 JSON + Zod 校验保证格式正确;Prompt 专注于内容决策(切场景、提角色、转对白)。
枚举归一化
AI 枚举输出不稳定 → 后端别名映射表自动修正(如 protagonist→main, evening→dusk),转换不因枚举值偏差而失败。
系统字段注入
language / version / generated_at / generator 由后端注入,LLM 不负责生成,减少无效认知负担。
meta — 元信息
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| format | enum | ✓ | movie | tv_series | short_drama — 顶层分支开关,决定 episode 字段是否必填、幕结构、场景粒度 |
| title | string | ✓ | 剧本标题 |
| subtitle | string | — | 副标题 |
| genre | string[] | — | 类型标签:sci-fi / romance / mystery / history / fantasy / other |
| source | object | — | 改编来源:{ type, title, author? } |
| language | string | ✓ | 语言代码,默认 zh-CN(系统注入) |
| version | string | ✓ | Schema 版本号(系统注入) |
| generated_at | string | ✓ | ISO 8601 生成时间(系统注入) |
| generator | string | ✓ | 生成工具标识(系统注入) |
三种格式的 Schema 差异:
• 电影 (movie):episode 字段省略,按 act(1/2/3 幕)组织,8-20 个场景,transition 用 CUT TO
• 电视剧 (tv_series):episode 字段必填(从 1 递增),每集 5-10 个场景,每集末 transition 用 FADE OUT 或 END OF EPISODE
• 短剧 (short_drama):episode 固定为 1,5-10 个场景,action block 极度精简(1-2 句),全用 CUT TO 快速切换
• 电影 (movie):episode 字段省略,按 act(1/2/3 幕)组织,8-20 个场景,transition 用 CUT TO
• 电视剧 (tv_series):episode 字段必填(从 1 递增),每集 5-10 个场景,每集末 transition 用 FADE OUT 或 END OF EPISODE
• 短剧 (short_drama):episode 固定为 1,5-10 个场景,action block 极度精简(1-2 句),全用 CUT TO 快速切换
characters — 角色表
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | string | ✓ | CH-001 格式,全局唯一引用键 |
| name | string | ✓ | 角色名 |
| aliases | string[] | — | 别名/化名/绰号 |
| category | enum | ✓ | main | supporting | guest | cameo | extras |
| gender | enum | ✓ | male | female | other |
| age_display | string | — | 展示年龄,如 "28岁" |
| archetype | string | — | 角色原型:hero / mentor / love_interest / villain… |
| traits | string[] | — | 性格标签 |
| description | string | ✓ | 一句话角色简介 |
| arc | string | — | 角色弧光简述 |
| relationships | array | — | 重要关系:[{ target, type, description? }] |
| notes | string | — | 自由文本扩展口 |
为什么用 ID 引用而非内联? 一个角色可能出现在 30 个场景中。ID 引用 = 数据库范式化在剧本领域的应用。改一处生效全局。
scenes — 场景集
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | string | ✓ | SC-001 格式 |
| scene_number | int | ✓ | 显示序号 |
| episode | int | — | 所属集号(电影格式省略) |
| act | int | — | 幕号(1/2/3) |
| source_chapter | int | ✓ | 来源章节号(小说改编核心字段) |
| heading | object | ✓ | 场景标题(见下方) |
| summary | string | — | 本场一句话概括 |
| characters_present | string[] | ✓ | 出场角色 ID 列表 |
| props | string[] | — | 重要道具 |
| blocks | array | ✓ | 剧本内容块(顺序执行) |
heading 子字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| interior | boolean | ✓ | true = INT.(内景)/ false = EXT.(外景) |
| location | string | ✓ | "大地点 - 具体位置" |
| time_of_day | enum | ✓ | dawn / morning / afternoon / dusk / day / night |
| extra | string | — | 闪回 / 梦境 / 蒙太奇(开放文本) |
time_of_day 可选值: dawn(拂晓)、morning(晨)、afternoon(午后)、dusk(黄昏)、day(日)、night(夜)。默认 day。
blocks — 剧本内容块
三种类型,通过 Zod discriminatedUnion 按 type 字段分发校验:
action — 动作/场景描写
yaml- type: action text: "窗外大雨倾盆。张三坐在沙发上,盯着手中的信。"
| 字段 | 类型 | 说明 | |
|---|---|---|---|
| text | string | 动作/环境/人物入场描述 |
dialogue — 对白
yaml- type: dialogue character_id: "CH-001" text: "你终于来了。" parenthetical: "(低声)" # 可选:表演提示 delivery: on_screen # 可选:on_screen | voice_over | off_screen emotion: "压抑的愤怒" # 可选:情感标注 continuation: false # 可选:被 action 打断后继续
| 字段 | 类型 | 说明 | |
|---|---|---|---|
| character_id | string | 引用 characters[].id | |
| text | string | 对白内容 | |
| parenthetical | string | (低声) 等表演提示 | |
| delivery | enum | on_screen | voice_over | off_screen | |
| emotion | string | 情感标注,比自由 parenthetical 更结构化 | |
| continuation | boolean | 对白被 action 打断后继续同一角色 |
V.O. 硬约束: voice_over 旁白必须在原文有对应文字。禁止将"她很不安"这类概括性心理描述改写为具体独白。
transition — 转场
yaml- type: transition text: "FADE OUT."
| 字段 | 类型 | 说明 | |
|---|---|---|---|
| text | string | CUT TO: / FADE OUT / DISSOLVE TO / MATCH CUT TO |
为什么只有 3 种? character_cue(角色出场)并入 action,parenthetical(表演提示)并入 dialogue。类型越少 → LLM 决策负担越小 → 输出一致性越高。
episodes — 剧集(电视剧/短剧)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | string | ✓ | EP-01 格式 |
| number | int | ✓ | 集号 |
| title | string | ✓ | 本集标题 |
| cold_open | boolean | — | 是否有冷开场(片头前悬念场景) |
| synopsis | string | — | 本集一句话概要 |
电影格式不使用此字段。 不输出 episodes: null 或 episodes: [],直接省略整个键。场景属于哪一集由 scenes[].episode 字段标注,不设反向引用,避免双向数据不一致。
ID 格式规范
| 实体 | 格式 | 示例 | Zod 正则 |
|---|---|---|---|
| 角色 | CH-001 | CH-001, CH-042 | /^CH-\d{3}$/ |
| 场景 | SC-001 | SC-001, SC-099 | /^SC-\d{3}$/ |
| 剧集 | EP-01 | EP-01, EP-12 | /^EP-\d{2}$/ |
枚举归一化
AI 输出的枚举值可能不稳定(如把 main 写成 protagonist,把 dusk 写成 evening)。 后端预处理层内置了别名映射表,自动将非标准值修正为合法枚举,确保转换不会因枚举不匹配而失败。
| 字段 | AI 可能输出(示例) | 自动修正为 |
|---|---|---|
| characters[].category | protagonist, antagonist, villain, hero | main, supporting |
| characters[].gender | man, woman, 男, 女, m, f | male, female, other |
| heading.time_of_day | evening, sunset, midnight, sunrise | dusk, night, dawn |
| relationships[].type | friend, brother, teacher, husband | ally, family, mentor_student, lover |
| meta.genre | science_fiction, thriller, wuxia | sci-fi, mystery, history |
归一化策略: 精确匹配 → 小写匹配 → 别名映射 → 关键词模糊匹配 → 保留原值让 Zod 报错(兜底)。定义见
lib/converter.ts 中的 normalizeEnum() 函数。向后兼容
未来新增 block 类型(如 montage、lyrics)时,在 Zod discriminatedUnion 末尾加 fallback 对象:
typescript// 允许未知 type 通过而不报错 z.discriminatedUnion('type', [ ActionBlockSchema, DialogueBlockSchema, TransitionBlockSchema, z.object({ type: z.string(), text: z.string() }), // catch-all ]);
也可通过 meta.version 字段判读版本,选择对应 Zod Schema 进行校验。