@dsh-gis/dsh-video-studio-skill 0.8.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +582 -0
  2. package/README.md +287 -304
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/package.json +11 -10
  5. package/schemas/design.schema.json +722 -0
  6. package/schemas/film.schema.json +171 -15
  7. package/schemas/proposal.schema.json +133 -0
  8. package/skills/video-studio/SKILL.md +1316 -1066
  9. package/skills/video-studio/assets/design_fixture.json +141 -0
  10. package/skills/video-studio/assets/film.json +1 -2
  11. package/skills/video-studio/assets/golden_frames.json +12 -9
  12. package/skills/video-studio/assets/scenes.py +2207 -1190
  13. package/skills/video-studio/assets/voice_loudness.json +16 -0
  14. package/skills/video-studio/references/audio-variation.md +15 -11
  15. package/skills/video-studio/references/audio.md +36 -34
  16. package/skills/video-studio/references/change-impact.md +3 -3
  17. package/skills/video-studio/references/frame-qa.md +2 -2
  18. package/skills/video-studio/references/proposal.md +177 -0
  19. package/skills/video-studio/references/quality-gates.md +2 -2
  20. package/skills/video-studio/references/scene-grammar.md +311 -0
  21. package/skills/video-studio/references/sfx-design.md +1 -1
  22. package/skills/video-studio/references/style-packs.md +107 -168
  23. package/skills/video-studio/references/v08-quality.md +8 -8
  24. package/skills/video-studio/references/web-engine.md +1 -1
  25. package/skills/video-studio/references/word-timeline.md +2 -2
  26. package/skills/video-studio/scripts/designkit.py +2120 -0
  27. package/skills/video-studio/scripts/filmcheck.py +104 -33
  28. package/skills/video-studio/scripts/filmkit.py +153 -78
  29. package/skills/video-studio/scripts/grammar.py +619 -0
  30. package/skills/video-studio/scripts/musicgen.py +2043 -0
  31. package/skills/video-studio/scripts/preflight.py +10 -3
  32. package/skills/video-studio/scripts/previewkit.py +849 -0
  33. package/skills/video-studio/scripts/propose.py +825 -0
  34. package/skills/video-studio/scripts/qa_frames.py +45 -10
  35. package/skills/video-studio/scripts/qavmaf.py +77 -11
  36. package/skills/video-studio/scripts/render.py +505 -142
  37. package/skills/video-studio/scripts/scorekit.py +50 -1748
  38. package/skills/video-studio/scripts/selfcheck.py +851 -115
  39. package/skills/video-studio/scripts/stagewatch.py +21 -5
  40. package/skills/video-studio/scripts/stylekit.py +293 -570
  41. package/skills/video-studio/scripts/voicekit.py +502 -380
  42. package/skills/video-studio/scripts/webengine.py +15 -14
  43. package/tools/audio_similarity.py +57 -30
  44. package/tools/ci_check.py +9 -1
  45. package/tools/estimate.py +14 -8
  46. package/tools/golden_frames.py +56 -11
  47. package/tools/silhouette_qa.py +1263 -0
  48. package/docs/demo-pil-sheet.png +0 -0
  49. package/docs/styles/demo-results.json +0 -76
  50. package/docs/styles/sheet-8-styles.png +0 -0
  51. package/docs/styles/sheet-ai.png +0 -0
  52. package/docs/styles/sheet-craft.png +0 -0
  53. package/docs/styles/sheet-night.png +0 -0
  54. package/skills/video-studio/assets/score_demo.json +0 -307
@@ -0,0 +1,311 @@
1
+ # 场景语法(Scene Grammar):内容决定构图
2
+
3
+ > v0.9.0 新增。配套文件:`skills/video-studio/scripts/grammar.py`(纯标准库)、
4
+ > `skills/video-studio/assets/scenes.py` 第三节(10 类绘制器)、`tools/silhouette_qa.py`(剪影门禁 G13)。
5
+ > 一句话:**这一段内容该长成什么构图,由内容自己决定;风格只决定"长成什么样"。**
6
+
7
+ ## 0. 为什么需要它
8
+
9
+ v0.5 的"风格包"解决了"每部片的画风/配乐/旁白都差不多",但它只覆盖**皮肤**:
10
+ 配色、字体族、纹理、颗粒、字距、缓动、HUD 三档、3 种版式几何。
11
+
12
+ 画面骨架没有跟着变。证据在代码里:
13
+
14
+ - `assets/scenes.py` 的 v0.5 绘制器只有 3 个(`_DRAWERS` 三项:hook / body / outro);
15
+ - 叙事 kind 里的 quote / emphasis / calm 全部回落到 body 绘制器(`_DRAWERS.get(kind) or _draw_body`);
16
+ - 三个绘制器共用同一套骨架:背景板 → 眉标 → 大字标题 → 副标题 → 强调线 → HUD,
17
+ 差异只在颜色、字号、位移;
18
+ - 于是 8 套风格换完,**剪影(灰度三值化后的前景占比 / 重心 / 边缘占用 / 对称度)几乎不动**。
19
+
20
+ 用户的原话是"每个视频都差不多,只是主题和颜色不同"。这不是审美判断,是可以量化的:
21
+ 改造前用 `tools/silhouette_qa.py` 的判据跑 8 套风格两两对比(28 对),
22
+ 同明暗族 12 对里有 4 对五项指标一项都不达标——也就是说换风格真的只是换色。
23
+
24
+ v0.9 的做法不是"再加几套风格",而是把"内容 → 构图"这条选择逻辑独立成一层。
25
+
26
+ ## 1. 三层正交
27
+
28
+ film.json(内容)
29
+ │ ① 场景语法:这段内容该长什么样(由内容决定,不由风格决定)
30
+ ▼
31
+ scene_type ∈ {hero, statement, list, stat, compare, process, quote, chapter, chart, outro}
32
+ │ ② 设计(design.json):这套设计怎么画(motion / motif / transition / typography / bias)
33
+ ▼
34
+ work/design.json 的 visual 五组签名(designkit.design_to_plan 转成 plan)
35
+ │ ③ 片内编排:同一部片里连续段落怎么不"复制粘贴"(变体轮换 / 装饰密度 / 相位,确定性)
36
+ ▼
37
+ draw_frame(...) → PIL.Image
38
+
39
+ 三层的边界要分清,否则会写出"改了不生效"的配置:
40
+
41
+ | 层 | 由谁决定 | 落在哪 | 改它会怎样 |
42
+ |---|---|---|---|
43
+ | 叙事 kind | 作者写 `lines[].kind` | hook / body / quote / outro / emphasis / calm | 换旁白语气与配乐能量(voicekit / musicgen 读它) |
44
+ | 场景 type | grammar 按内容打分,或作者显式写 `lines[].type` | `work/storyboard.json`、timeline 的 `scene_type` | 换构图(10 个绘制器之一) |
45
+ | 设计 visual | `work/design.json`(由 `propose.py --confirm` 冻结) | `work/style.json` 的 `language` 与 `layout_cycle` | 换母题 / 转场 / 字距 / 重心 / 版式轮换 / 字幕样式 |
46
+
47
+ **为什么没有新增 kind 值**:kind 同时是 voicekit 的语气 id 与 filmcheck 的 `KINDS`
48
+ (两者是同一组 6 个值)。把 hero/stat 塞进 kind 会撞语气枚举,也让"引语"和"引语构图"
49
+ 这种本来可以分开的两件事重新粘死。所以 type 是**新字段**,与 kind 正交:
50
+ 一句话可以是 `kind=quote`(女声念引语)同时 `type=chapter`(画成章节卡)。
51
+
52
+ ## 2. 10 类构图
53
+
54
+ 坐标基于 1920×1080;正文底界 860(下方是字幕带),HUD 顶部 ≤96。
55
+ "最短时长"是 `grammar.MIN_DUR`(`skills/video-studio/scripts/grammar.py:49`)的硬门槛,
56
+ 低于它按降级表换类型(见 §3.3)。类型名单与顺序在 `grammar.SCENE_TYPES`(grammar.py:30),
57
+ 绘制器注册表在 `assets/scenes.py` 的 `_DRAWERS`(搜这个名字,行号会漂)。
58
+
59
+ | 类型 | 构图 / 重心 | 吃哪些字段 | 持续动效 | 自动选型特征 | 最短 |
60
+ |---|---|---|---|---|---|
61
+ | `hero` 封面 | 左栏起标题,右侧留白;variant 0/1/2 分别左/右/中 | `big` / `sub`(lead) / `kicker` / `chapter` / 顶层 `title` | 标题下强调线呼吸 + 背景同心弧缓推 | 首段或 `kind=hook` | 2.4 s |
62
+ | `statement` 金句 | 居中单列 ≤1400,逐字显现(字距随呼吸微动) | `big` 或 `text` 前 24 字;`sub` 作落款 | 下划线从左生长 + 字距呼吸 | 无条目且短句(≤16 字) | 2.6 s |
63
+ | `list` 要点 | 左标题 + 右条目列;variant 1 转全宽序号列 | `items[]`、`big`、`kicker` | 焦点行每 2.2 s 换一条(左侧竖条 + 行底微亮) | `items ≥ 3` | 3.0 s |
64
+ | `stat` 数字卡 | 巨数字居中(初始 240 px,超宽自动缩)+ 单位 + label | `stats[]`;缺省从 `text`/`sub` 正则抽"数字+单位" | count-up + 底部 12 格刻度逐格点亮 | 文本含数字+单位且 `items < 3` | 3.2 s |
65
+ | `compare` 左右对比 | 中缝竖线,左右双栏;栏头缺省时中缝上方画 VS 徽标 | `sides{}`(`left/right` 各 `head/items`);缺省把 `items` 前后对半分 | 中缝高光下扫 + 左右两栏轮流反相高亮(3.2 s) | 命中对比词且 `items ≥ 2` | 4.0 s |
66
+ | `process` 流程轴 | 横向轴 + 最多 5 个节点,标签在轴上方、注在下方 | `steps[]`(`label/note`);缺省按 `items` 顺序当步骤 | 进度光点沿轴匀速走完本段 | 命中顺序词且 `items` 3–6 条 | 4.0 s |
67
+ | `quote` 引语 | 左上巨型引号(字形不是矢量线)+ 引文 46 px 起 + 右下出处 | `quote` / `by`;缺省从 `text` 剥引号与出处 | 引号呼吸上浮 + 末行光标块闪烁 | 命中引语标记(「」""——说:…) | 4.0 s |
68
+ | `chapter` 章节卡 | 左上巨型编号(矩形裁剪揭示)+ 章节名 + 中缝横线 | `chapter`(编号从 `"03 · xxx"` 里取)/ `big` / `kicker` | 细光带在编号上缘横向扫(2.8 s 周期) | 章节变化 + 无条目 + 标题短 + 段短 + 无数字/引语 | 2.2 s |
69
+ | `chart` 数据条 | 左侧标签列 + 横向条 + 数值;最多 6 条 | `series[]`(`label/value/unit`);缺省从 `items` 抽"标签+数字" | 最大条端点呼吸 + 网格高亮扫描 | `series` 非空,或 `items ≥ 3` 且 ≥3 条带数字 | 4.5 s |
70
+ | `outro` 收尾 | 正中心大字 + 副标 + 强调线;光点环绕,数量随母题变 | `big` / 顶层 `end_text` / `sub` / `title` | 强调线呼吸 + 光点环向内收 | 末段或 `kind=outro` | 2.4 s |
71
+
72
+ 四条兜底(绘制器里的运行时兜底,与 §3.3 的段长门是两回事):
73
+
74
+ - `stat` 拿不到任何数字 → 退到 `statement`;
75
+ - `process` 步骤 < 2 条 → 退到 `list`;
76
+ - `chart` 序列 < 2 条 → 退到 `stat`;
77
+ - `compare` 对半分不出两栏 → 退到 `statement`。
78
+
79
+ **容量上限**(超了会被静默截断,画面少几行):`stats` 画前 3 条、`series` 前 6 条、
80
+ `steps` 前 5 个、`sides` 每侧前 3 条。filmcheck 对超容量报 `scene_capacity` **WARN**
81
+ (不阻断,但说明"那几条不会出现在画面上")。
82
+
83
+ ## 3. 自动选型:打分 → 段长门 → 降级 → 去重
84
+
85
+ 选型是纯函数(`grammar.score_types` / `choose_type` / `downgrade_for`,
86
+ `skills/video-studio/scripts/grammar.py:202/303/315`),不读 random / time / hash(),
87
+ 派生量只用 sha1 前 8 位。**同一份稿子永远同一张分镜表。**
88
+
89
+ ### 3.1 打分
90
+
91
+ 原则:**字段存在性 > 关键词 > 结构位置**。字段是作者明说的意图,关键词是猜的。
92
+
93
+ | 信号 | 加权 | 条件 |
94
+ |---|---|---|
95
+ | 显式 `type` | +10.0 | 值在 `SCENE_TYPES` 里;压倒一切 |
96
+ | `stats[]` 非空 | +6.0 | → stat |
97
+ | `series[]` 非空 | +6.0 | → chart |
98
+ | `sides{}` 非空 | +6.0 | → compare |
99
+ | `steps[]` 非空 | +6.0 | → process |
100
+ | `quote` 非空 | +6.0 | → quote |
101
+ | `items ≥ 3` | +5.0 | → list(通用兜底,所以权重低于更具体的形态) |
102
+ | `items ≥ 3` 且 ≥3 条带数字 | +5.5 | → chart |
103
+ | 命中对比词且 `items ≥ 2` | +5.5 | → compare |
104
+ | 命中顺序词且 `items` 3–6 | +5.5 | → process |
105
+ | 命中引语标记 | +5.5 | → quote |
106
+ | 文本含"数字+单位"且 `items < 3` | +3.5 | → stat |
107
+ | 无条目且短句(≤16 字) | +3.5 | → statement |
108
+ | 首段或 `kind=hook` | +4.6 | → hero |
109
+ | 末段或 `kind=outro` | +5.0 | → outro |
110
+ | 章节变化 + 纯标题卡 | +4.2 | → chapter;还要满足无条目、标题 ≤14 字、段 ≤4.0 s、无数字、无引语 |
111
+
112
+ 几个刻意的取舍(都是实测反例逼出来的,别"优化"掉):
113
+
114
+ - **关键词只看叙述文字,不看 `items`**。反例:示例片第 3 段正文讲"画质三件事",
115
+ 条目里写的是 `rawvideo -> libx264 CRF17`,`->` 命中顺序词表,整段被误判成"流程轴"——
116
+ 画面上一条横轴连着四个不相干的要点。条目是载荷不是叙事。
117
+ - **`hero`/`outro` 的结构分刻意低于字段分**:开场如果带 5 条要点,观众要看的是要点,
118
+ 不是封面。结构位置只是"没有更具体形态时"的兜底。
119
+ - **`list` 只给 5.0**:它是"有条目"的通用兜底;一旦有更具体的形态(数字、对比、流程),
120
+ 必须压过它——否则"能画"永远赢过"该画",所有带条目的段都会退化成列表。
121
+ - **年份不算数字**:单位表里有"年",但 `2024 年` 这种纯年份被单独排除,
122
+ 它是时间背景不是"值得做成巨字卡"的数据。真要做数字卡,写 `stats[]`。
123
+
124
+ 平手时按 `grammar.SCENE_TYPES` 的固定顺序取位(**改顺序会让同一稿子的分镜漂移**,
125
+ 所以这个元组有独立语义,不能随便排序)。
126
+
127
+ ### 3.2 段长门
128
+
129
+ 每类有最短时长(`MIN_DUR`,见 §2 表格)。段长不足时**不是降分,是不许选**:
130
+ 候选保留但分数 −6.0 并写明理由,便于审计;若所有候选都被段长门排除,退到 `statement`
131
+ (最耐短的类型)。
132
+
133
+ ### 3.3 降级表
134
+
135
+ 超过段长门后仍可能被降级(例如显式写了 `type=chart` 但这段只有 2 秒):
136
+
137
+ | 原类型 | 降级到 |
138
+ |---|---|
139
+ | `chart` / `compare` / `process` | `list` |
140
+ | 其余(含 `list`、`hero`、`chapter`、`stat`、`quote`、`outro`) | `statement` |
141
+
142
+ 降级表写死在 `grammar.DOWNGRADE`(grammar.py:63)里,防环(配置写错时不会挂死)。
143
+ 降级不是静默的:`storyboard.json` 的 `reason` 里会写
144
+ "段长 x.xxs < y.ys,降级 chart -> list"。
145
+
146
+ ### 3.4 类型去重
147
+
148
+ 连续同类型最多 `MAX_REPEAT = 2` 段(grammar.py:77)。第 3 段如果还是同一类型,该候选 −5.0
149
+ (`chapter` / `outro` 除外——章节卡连排是刻意的分章锚点)。
150
+ 效果是"三连 list"会变成 list → list → 下一个候选(通常是 statement 或别的更具体形态)。
151
+
152
+ ## 4. `work/storyboard.json`:审计产物
153
+
154
+ `--stage layout`(以及 `--stage all`)会把选型结果落盘到 `work/storyboard.json`,
155
+ 并在控制台打印一行分镜摘要 + 每段的"为什么":
156
+
157
+ ```bash
158
+ python render.py --script film.json --scenes scenes.py --work work --stage layout
159
+ # 分镜: 4 段 / 封面×1 要点列表×2 收尾×1
160
+ # l01 hook -> hero v0 d0 big 后缩进
161
+ # l02 body -> list v0 d2 items>=3
162
+ # l03 body -> list v2 d1 items>=3
163
+ # l04 outro -> outro v0 d0
164
+ ```
165
+
166
+ 字段(`grammar.plan_scenes`(grammar.py:467)的返回结构):
167
+
168
+ | 字段 | 含义 |
169
+ |---|---|
170
+ | `version` | 目前恒为 1 |
171
+ | `generator` | `"grammar 0.9.0"`(改版本号时同步,便于区分不同版本的分镜表) |
172
+ | `style` / `legacy` | 本片风格 id / 是否走 v0.4 兼容路径。**legacy 不落 storyboard**(见 §7) |
173
+ | `scenes[]` | 每段一行:`id`、`kind`、`type`、`variant`(0–2)、`decor`(0–2)、`layout`、`dur`、`reason[]` |
174
+ | `type_counts` | `{"hero": 1, "list": 2, ...}`,给了哪些类型用过几次 |
175
+
176
+ 同一份结果也会**就地**写进 timeline 的每个 scene(渲染器读这些字段):
177
+ `scene_type` / `scene_variant` / `scene_decor` / `scene_layout` / `scene_phase` / `scene_reason`。
178
+ timeline 顶层再加一个 `storyboard: {"type_counts": ...}` 摘要。
179
+
180
+ 想只看选型、不跑 TTS:`python grammar.py film.json`(按字数粗估时长,只用于看选型,
181
+ 不是时间轴权威)。
182
+
183
+ ## 5. 显式指定 `lines[].type`
184
+
185
+ 不写 `type` 就自动选;写死在 `film.json` 的行级字段上(全部可选):
186
+
187
+ ```json
188
+ {"id": "l03", "kind": "body", "text": "残差网络在 2015 年把错误率降到 3.6%。",
189
+ "type": "stat",
190
+ "stats": [{"value": "3.6", "unit": "%", "label": "ImageNet top-5 错误率", "note": "2015"}],
191
+ "chapter": "02 · 数据"}
192
+ ```
193
+
194
+ | 字段 | 类型 | 给谁用 | 说明 |
195
+ |---|---|---|---|
196
+ | `type` | 枚举(10 类) | 绘制器派发 | 写了就以它为准(+10 分),但仍受段长门降级 |
197
+ | `quote` | 字符串 | `quote` | 打在屏幕上的引文;`text` 是 TTS 念的那句,两者可以不同 |
198
+ | `by` | 字符串 | `quote` | 出处/作者;缺省不画署名 |
199
+ | `stats[]` | `[{value, unit?, label?, note?}]` | `stat` | `value` 必填(数字或字符串,如 `">3x"`) |
200
+ | `series[]` | `[{label, value, unit?}]` | `chart` | 最多取 6 条 |
201
+ | `steps[]` | `[{label, note?}]` | `process` | 最多取 5 个节点 |
202
+ | `sides{}` | `{"left": {head?, items[]}, "right": {...}}` | `compare` | 每侧最多取 3 条 |
203
+
204
+ **写错 `type` 会被 filmcheck 报 ERROR**(枚举 `SCENE_TYPES`,唯一硬门禁:
205
+ `skills/video-studio/scripts/filmcheck.py:288`,schema 侧在 `schemas/film.schema.json:246`);
206
+ `grammar` 自己遇到非法值时**静默忽略**(当作没写,继续按内容自动选)。
207
+ 其余新字段写错键名会被 filmcheck 报 WARN("是不是想写 X"),不会静默失效。
208
+
209
+ ## 6. 片内编排(防"每屏同一个骨架")
210
+
211
+ | 维度 | 规则 | 现状 |
212
+ |---|---|---|
213
+ | 版式变体 | `variant_of(type, chapter, k)` 派生 0/1/2,并强制与上一段同类型不同 | 写入 storyboard 的每段;**当前只有 hero / statement / list / stat 四个绘制器读取它**,其余六类构图唯一 |
214
+ | 装饰密度 | `decor_of`(grammar.py:349)三档 0.55 / 0.75 / 1.0,每 3 段回落到最低档 | 所有绘制器都读(经 `_apply_motif` → `_decor_scale`) |
215
+ | 相位偏移 | `phase_of(idx, chapter)` = 2π × sha1 派生值 | hero / statement / list / compare / process / quote / chapter / chart / outro 的持续动效都用它,避免同频"复印" |
216
+
217
+ 变体号、密度档、相位都**不按时间/帧号/随机数**派生——同一稿子同一结果,
218
+ 这是"可复现"的一部分。改 `chapter` 字段会改变 `variant_of` 的输入,进而改变变体号。
219
+
220
+ ## 7. 没有 design 就没有画面(v1.0.1 起没有 legacy 路径)
221
+
222
+ v1.0.1 删掉了 legacy 外观:不写 `style` 不再「走 v0.4 三个老绘制器」,而是**直接报错**并给两步迁移指引。
223
+ 现在整条链路只认 `work/design.json`:
224
+
225
+ - `render.st_layout` 调 `grammar.plan_scenes`,按 `design.visual.layout_cycle` 做**按章节的版式轮换**,
226
+ 写 `work/storyboard.json`(每段的 type / variant / decor / layout / reason 都可审计);
227
+ - `scenes.draw_frame` 只走 10 类构图绘制器(`_DRAWERS`),没有 legacy 分支;
228
+ - 字幕样式来自 `design.visual.subtitle`(v1.0.2),字号/位置/背板/圆角不再写死;
229
+ - 金帧基线 `assets/golden_frames.json` 绑的是固定 fixture 设计(`assets/design_fixture.json`),
230
+ 不是「某套内置风格」——改绘制代码后按 `tools/golden_frames.py --write` 重录。
231
+
232
+ 没有 design 时的正确做法(脚本留在技能目录里,零拷贝):
233
+
234
+ ```bash
235
+ python <SKILL_DIR>/scripts/propose.py --brief "题材/要求" --script film.json --work work
236
+ python <SKILL_DIR>/scripts/propose.py --confirm --candidate A --work work
237
+ python <SKILL_DIR>/scripts/render.py --script film.json --work work --out out/film.mp4 --stage all
238
+ ```
239
+
240
+ ## 8. 加第 11 类构图(8 步)
241
+
242
+ 以新增 `timeline`(时间轴图)为例。顺序不能乱,每步都有对应的门禁/断言在盯:
243
+
244
+ 1. **`grammar.SCENE_TYPES`(grammar.py:30)末尾追加** `"timeline"`。**只追加不改顺序**——
245
+ 顺序是平手取位顺序,插队会让所有老稿子的分镜漂移。同时补 `SCENE_LABELS`。
246
+ 2. **`grammar.MIN_DUR` 加最短时长**(按这套构图入场+读完所需的秒数,别照抄别的类型),
247
+ 并在 `DOWNGRADE` 里给它一个降级目标(键必须都在 `SCENE_TYPES` 里,且不能自环)。
248
+ 3. **`grammar.score_types` 加打分规则**:优先"字段存在性"(比如 `timeline: []` 给 +6.0),
249
+ 其次才是关键词/结构位置。加上去之前先问:它和 chart / process 的区别是什么?
250
+ 如果画面上差别不大,就不要加——10 类已经覆盖了"数字/列表/对比/流程/引语/章节"这些基本形态。
251
+ 4. **`assets/scenes.py` 加绘制器** `_draw_timeline(scene, t, dur, tg, script, frame, plan, th)`,
252
+ 签名与现有 10 个一致。约定:
253
+ - 先用 `_bg_plate` / `_apply_motif` 铺底,最后 `_apply_veil` + `_hud`;
254
+ - 坐标过 `_view`(视差),文字用 `_draw_text`(字号不重采样);
255
+ - 持续动效至少一个(相位用 `_phase(scene)`),否则 3–6 s 的静态画面会撞"画面静止"判据;
256
+ - 重的东西预烘焙并合成进底版(参考 `_motif_layer` + `_plate`:一次烘焙,逐帧只 copy/EXTENT
257
+ 约 1.3–2.0 ms)。**不要逐帧全屏 RGBA paste**(实测 5.6 ms,v0.9.0 踩过);
258
+ 也不要每帧全屏 GaussianBlur / 全屏双线性 resize(预算 25 ms)。
259
+ 5. **`assets/scenes.py` 的 `_DRAWERS` 加键**。缺了会在渲染时回落 list 并 warn 一次。
260
+ 6. **`filmcheck.SCENE_TYPES` 加值**(这份只做成员校验,顺序无所谓),
261
+ 以及 **`schemas/film.schema.json` 的 `lines[].type` 枚举**加值(IDE 补全与文档)。
262
+ 7. **`selfcheck.py` 第 54 条(`scripts/selfcheck.py:1526`)会自动兜底**:它断言 grammar / filmcheck / scenes.`_DRAWERS` /
263
+ schema enum 四处**集合相等**(顺序允许不同)。这四份真值来源必须同时改,漏一处立刻红。
264
+ 8. **`tools/silhouette_qa.py` 的 `SCENE_CONTENT` 补一段合成内容**(给足该类型的原料字段),
265
+ 再跑一次剪影门禁(`--selftest` / `--designs N`)确认新类型在不同现场设计下也"看得出来"。这一步不做,等于新加的构图
266
+ 没有进入 G13 的覆盖范围。
267
+
268
+ 跑完的验证顺序:`python selfcheck.py`(**67 条**,第 52–55 条专盯场景语法/视觉语言/单真值/剪影)
269
+ → `python <REPO>/tools/ci_check.py`(语法/包内容/文档路径)→ `python <REPO>/tools/silhouette_qa.py`(不同设计两两仍可区分)。
270
+
271
+ ## 9. 常见误用
272
+
273
+ | 现象 | 原因 | 修法 |
274
+ |---|---|---|
275
+ | 分镜表里一段都没变,全是 list | 没有 `work/design.json`,或 grammar 没跑 | 写 先 `propose.py --confirm` 冻结设计;再 `--stage layout` 看分镜表 |
276
+ | 某段想要数字卡,结果画成列表 | 没有 `stats[]`,文本里的数字是"2024 年"(年份被排除)或没有单位 | 显式写 `stats[]`;或让那句话带"数字 + 单位" |
277
+ | 写了 `type: "stats"` 却没生效,还报错 | `type` 是枚举,`stats` 才是字段名;写错值 filmcheck 报 ERROR | 改 `type` 为 10 类之一,数据放 `stats[]` |
278
+ | 对比段左右两栏一栏空 | 只有 1 条 `items`(对半分不出来),或 `sides{}` 只写了一侧 | 补到 ≥2 条,或显式写 `sides` 两侧 |
279
+ | 流程轴上的箭头/破折号来自条目 | 关键词只看叙述文字,不看 `items`——但如果你把"→"写进了 `text/big`,仍会命中 | 流程用 `steps[]` 或叙述里写"第一步…然后…";条目里的排版符号不算顺序 |
280
+ | 章节卡把整段要点吃掉了 | 章节卡要求"无条目 + 标题 ≤14 字 + 段 ≤4.0 s",但显式 `type=chapter` 会压过这些条件 | 章节卡单独成段(不带 items),要点另起一段 |
281
+ | 段长 2 秒写了 `type=quote`,出来是金句 | 段长门:quote 最短 4.0 s,按降级表落到 statement | 拆句/加停顿把段拉长,或接受降级;`storyboard.json` 的 reason 会写明 |
282
+ | 同一部片里前深后浅,闪烁门禁红了 | 同一部片仍锁一个明暗族(跨族会撞闪烁门禁) | 用同族主题;或拆成两部片 |
283
+ | 写了 5 条 `series`,画面只显示 3 条 | 容量上限:chart 只画前 6 条(stats 3 / steps 5 / sides 每侧 3) | filmcheck 会给 `scene_capacity` WARN;要全画就拆成多段 |
284
+ | 改了 `chapter` 后分镜变了 | `variant_of` / `chapter` 是变体与章节卡的输入 | 这是期望行为;要固定版式就写 `visual.layout` |
285
+
286
+ ## 10. 已知限制(本版没做,写下来别装看不见)
287
+
288
+ - **web 引擎不跟随场景语法**:`--engine web` 的页面由用户自己写(`assets/web/film.js`),
289
+ 本次不改造。web 路线的画风仍只有主题色,没有 10 类构图;
290
+ 剪影门禁也只覆盖 PIL 渲染器。
291
+ - **同一部片仍锁一个明暗族**:段界跨明暗族会整屏换底,精确触发闪烁门禁,观感也像换片。
292
+ 主题覆盖(`scene["theme"]`)只允许同族。
293
+ - **`lines[].type` 写错只有 filmcheck 拦**:grammar 对非法值静默回落(当没写)。
294
+ 不跑 filmcheck 的流水线,写错就只是"没生效"。
295
+ - **场景类型有 4 份真值来源**:`filmcheck.SCENE_TYPES`(`skills/video-studio/scripts/filmcheck.py:51`)/
296
+ `grammar.SCENE_TYPES`(grammar.py:30)/
297
+ `scenes._DRAWERS` / schema 的 enum。靠 selfcheck 第 54 条断言"集合相等"兜底;
298
+ **顺序可以不同**——grammar 的那份顺序用于平手取位,另外三份只看成员。
299
+ - **版式变体只有 4 类绘制器读**:`variant_of` 对每段都算并写进 storyboard,
300
+ 但当前只有 hero / statement / list / stat 用它,其余六类构图唯一。
301
+ 这条不修的话,"每类型 3 个变体"只在四类上成立。
302
+ - **部分视觉语言字段"已声明、已体检、但绘制器没读"**:`motion.enter`、`motion.push`、
303
+ `typography.align`、`typography.ratio`由 `designkit.validate_design` 校验范围,
304
+ 但 `assets/scenes.py` 当前不读它们——写了不会报错,也不会改变画面。
305
+ 详见 [style-packs.md](style-packs.md) 的"视觉语言包"一节。
306
+ - **场景数据超容量会被截断**:`stats` >3 / `series` >6 / `steps` >5 / `sides` 每侧 >3
307
+ 的条目不会出现在画面上。filmcheck 报 `scene_capacity` WARN(容量表在
308
+ `skills/video-studio/scripts/filmcheck.py:652`),但**不阻断渲染**——
309
+ WARN 不是通过,也不是失败,是"画面上没有"。
310
+ - **`work/storyboard.json` 是审计产物,不是输入**:改它不会改变渲染结果;
311
+ 想要固定构图请改 `film.json` 的 `lines[].type`。
@@ -217,7 +217,7 @@ CUE_ALIASES # 语义别名表
217
217
  `build_audio()` 逐条 `SX.render(type, seed=1000+i, **params)`,按 `pan` 展开立体声、
218
218
  按 `CUE_BASE_DB` 与 `gain` 落位到音频总线;未知 type 告警一次并跳过。
219
219
  2. **web 引擎**:页面 `window.getCues()` 导出同 schema 的 `work/cues.json`(CONTRACT §4.2)。
220
- 3. **配乐(scorekit)**:`import sfxkit as S`,直接用 `sine/saw/supersaw/fm/pluck/adsr`、
220
+ 3. **配乐(musicgen)**:和自己写音效一样,直接用 sfxkit 原语;`import sfxkit as S`,有 `sine/saw/supersaw/fm/pluck/adsr`、
221
221
  `lowpass/highpass/tv_filter`、`white/burst/exp_decay/attack_decay`、`smooth_random`、`Mix`。
222
222
 
223
223
  ## 11. 自检
@@ -1,182 +1,121 @@
1
- # 风格包(Style Packs):让每部片的画风 / 配乐 / 旁白都不一样
1
+ # 外观定制:设计 DSL 实操手册(v1.0.2)
2
2
 
3
- > v0.5.0 新增。读这一篇之前先读 SKILL.md 的 §5.6。
4
- > 一句话:**一个 style id 同时决定画风、配乐、旁白声音和节奏**,不能三样各自乱配。
3
+ > **这篇不是「8 套风格选一套」。** v1.0.1 起仓库里没有任何内置风格表——
4
+ > 没有 `STYLES` / `THEMES` / `VISUAL_LANGUAGES` / `presets/`,也没有 `style` 字段。
5
+ > 外观由 `work/design.json` 决定,而它是 `propose.py` 现场采样 + 你确认之后冻结的。
6
+ > 这篇讲的是:**想控制画面的哪一块,就改 design.json 的哪个字段,怎么验证它真的生效。**
5
7
 
6
- ## 0. 为什么需要它(v0.4 的病)
8
+ ## 0. 三条正确姿势
7
9
 
8
- v0.4 及以前,所有"身份参数"都是模块级常量:
9
-
10
- | 层 | 被写死的地方 | 结果 |
11
- |---|---|---|
12
- | 画风 | assets/scenes.py 的 THEME 字典 + 3 个绘制函数 | 每部片都是深蓝底 + 橙强调 |
13
- | 配乐 | scorekit 的 4 个小调 + 3 套和弦进行 + 固定动机 | 每部片都是 Dm / 120BPM 那一套 |
14
- | 旁白 | render.py 的单个 voice/rate/pitch | 每部片都是同一个声音、同一语速 |
15
- | 流程 | SKILL.md 教的是 "cp 模板 -> 改 lines[].text" | 模型自然只会改文案 |
16
-
17
- 结果就是:**内容换了,气质没换**。用户的原话是"背景音乐风格都差不多、旁白声音都差不多、整体画风也差不多"。
18
-
19
- ## 1. 三层结构
20
-
21
- film.json style: "auto" | "tech-deep" | ... | "legacy"
22
- |
23
- v
24
- stylekit.resolve_script_style(script) <- 纯函数,确定性
25
- | +- 关键词打分(title/chapter/text/items)
26
- | +- 三级内容指纹(full / tempo / style)
27
- | +- 显式覆盖(visual.theme / music.style / voice.map / pacing)
28
- v
29
- work/style.json <- 审计产物:能看到"为什么是这套风格"
30
- | +- style / theme_id / chapter_layout
31
- | +- music{bpm,key,mood,seed_effective}
32
- | +- voice{map,policy}
33
- | +- pacing{lead,tail,min_dur,item_stagger}
34
- v
35
- 画面(scenes.py 读主题) 配乐(scorekit 读风格) 旁白(voicekit 读 persona/tone)
36
-
37
- ## 2. 8 套内置风格
38
-
39
- | id | 中文 | 画风 | 配乐 | 旁白 | 适合内容 |
40
- |---|---|---|---|---|---|
41
- | tech-deep | 科技深蓝 | 深蓝渐变 + 橙 | 小调 100–124 / 琶音 + 铃声 | 男声叙述 + 青年开场 | AI、工程、数据、科普 |
42
- | magazine | 杂志暖白 | 米白 + 朱红,衬线体 | 大调 88–100 / 电钢 | 女声叙述 + 权威男声引用 | 报告、复盘、研究 |
43
- | paper | 手绘纸感 | 牛皮纸 + 靛青,楷体 | 大调 84–92 / 马林巴 | 软糯女声 + 男声引用 | 手工、自然、历史 |
44
- | vivid | 活力撞色 | 深紫 + 柠檬黄,大字 | 大调 120–132 / 短促和弦 | 活泼女声开场 | 活动、营销、上新 |
45
- | mono | 极简黑白 | 纯白 + 黑,超宽字距 | 极简 60–68 / 钢片琴 | 沉稳男声 + 女声引用 | 观点、演讲、思辨 |
46
- | cinema | 电影青橙 | 近黑 + 青橙,扫描线 | 小调 76–84 / 弦乐铺底 | 纪录片男声 + 女声引用 | 纪录、叙事、城市 |
47
- | lo-fi | 暖灰薄荷 | 暖灰 + 薄荷绿 | 大调 72–84 / 电钢 + 轻鼓 | 温柔女声 | 日常、学习、专注 |
48
- | epic | 深黑鎏金 | 近黑 + 金,衬线体 | 小调 88–96 / 铜管 + 人声铺 | 有力男声 + 男声开场 | 史诗、宣传、高潮 |
49
-
50
- 外加 legacy:不写 style 字段时的兼容档,画风/配乐/旁白全部等于 v0.4(配乐逐样本一致)。
51
-
52
- 查表:
53
-
54
- python stylekit.py --list # 8 套风格 + 主题 + 对比度
55
- python stylekit.py --show cinema # 单套风格的全部字段
56
- python stylekit.py --lint # 主题对比度门禁(CI 会跑)
57
-
58
- ## 3. film.json 怎么配
59
-
60
- 最小配置(推荐,几乎不用写):
61
-
62
- {"style": "auto"}
63
-
64
- 自动模式:命中题材关键词就锁定对应风格;零命中时按内容指纹稳定落桶
65
- (同一份稿子永远同一个风格,不同稿子大概率不同)。
66
-
67
- 想锁定:
68
-
69
- {"style": "cinema"}
70
-
71
- 想只改某一块(其余跟着风格走):
72
-
73
- {
74
- "style": "magazine",
75
- "visual": {
76
- "theme": "paper",
77
- "layout": "split-right",
78
- "theme_overrides": {"vignette": 0.18, "grain": 8.0}
79
- },
80
- "music": {
81
- "style": "lo-fi",
82
- "bpm": "auto", "key": "auto", "mood": "calm",
83
- "seed": 7, "seed_from_content": true
84
- },
85
- "voice": {
86
- "map": {"hook": "bright_female", "body": "narrator_female"},
87
- "jitter": 0.35,
88
- "max_distinct": 3
89
- },
90
- "pacing": {"lead": 0.6, "tail": 0.9, "item_stagger": 0.3}
91
- }
92
-
93
- 逐句覆盖(最高优先级):
94
-
95
- {"id": "l03", "kind": "body", "text": "他说:「这不可能」",
96
- "voice": "authority_male", "tone": "quote", "lead": 0.8}
97
-
98
- ### 字段速查
99
-
100
- | 字段 | 默认 | 说明 |
10
+ | 你想要 | 怎么做 | 不要这样做 |
101
11
  |---|---|---|
102
- | style | 无(= legacy) | 风格 id / "auto" / "legacy" |
103
- | visual.theme | — | 等价于锁 style;与 style 1:1 |
104
- | visual.layout | 风格默认 | left-stack / center-stack / split-right |
105
- | visual.theme_overrides | — | 深合并覆盖主题单字段 |
106
- | music.style | 跟随 style | 只换配乐风格 |
107
- | music.bpm | "auto" | 数字或 "auto"(按风格梯度 + 指纹) |
108
- | music.key | "auto" | 调名或 "auto"(按风格调池 + 指纹) |
109
- | music.mood | 风格默认 | calm/reflective/hopeful/steady/driving/tense/uplifting |
110
- | music.seed_from_content | 非 legacy 时 true | 音色细节由内容指纹派生 |
111
- | voice.map | 风格默认 | kind -> persona 名 |
112
- | voice.jitter | 0.35 | 语速/音高微扰,0 = 关 |
113
- | voice.max_distinct | 3 | 全片最多几把声音 |
114
- | pacing.* | 风格默认 | 语前留白/语后留白/最短段长/条目落位步长 |
115
- | lines[].persona / lines[].voice / lines[].tone | — | 逐句覆盖(最高优先级,voicekit 真的读这三个) |
116
- | lines[].lead | — | 逐句语前留白(filmkit.build_timeline 读) |
117
- | lines[].rate / pitch / volume / tail | — | **当前不生效**(保留字段):voicekit 只认 persona/voice/tone,filmcheck 会对这三个报"未知键";要改语速/音高请换 persona 或 tone,或在顶层写 rate/pitch/volume |
118
-
119
- ## 4. 三级指纹:改错别字不会换整首曲子
120
-
121
- | 指纹 | 由什么算 | 决定 |
122
- |---|---|---|
123
- | full | 标题 + 全部台词 | 音色细节(seed 派生) |
124
- | tempo | 标题 + kind 序列 + 体量桶 | BPM 与调性 |
125
- | style | 标题(配合关键词评分破平) | 风格 id |
126
-
127
- 所以:改一个错别字 -> full 变(音色纹理微变),tempo/style 不变(还是同一首曲子、同一个调);
128
- 换一部片 -> 三个都变。`music.seed_from_content: false` 只锁住 **seed 派生**(音色细节);
129
- `full` 指纹仍会传给 scorekit 参与结构变奏(和弦替换/动机偏移/鼓型 ghost,见
130
- [audio-variation.md](audio-variation.md) §3.3)。要把声音完全锁回 v0.4 行为,只能不写 `style`(legacy)。
131
-
132
- ## 5. 片内变化:整体是一套,但每段有呼吸
133
-
134
- **锁三件事**(全片一致,否则会"花"):
135
-
136
- 1. 明暗族(全深色 or 全浅色)
137
- 2. accent 色相
138
- 3. 字体族与字号阶梯
12
+ | 换一种气质 | `propose.py --revise "更冷一点,少装饰,换女声"` 重出方案 | 去找"第 9 套风格"——没有这种目录 |
13
+ | 微调某个细节 | 手改 `work/design.json` 的字段 → `designkit.py --lint` 校验 → `render.py --stage stills` 看 | 直接改 `filmkit.py` 里的常量 |
14
+ | 换整个画面语言 | 写自己的 `scenes.py`(放剧本同目录),实现 `draw_frame(scene, t_local, dur, t_global, script, frame)`(完整签名见 SKILL.md §1) | 期望 `--engine web` 跟随 design(它不会) |
139
15
 
140
- **其余可以按章节变**:
16
+ ## 1. design.json 的六块
141
17
 
142
- - 版式骨架轮换(同一 chapter 内不变):left-stack / center-stack / split-right
143
- - 背景明度微移、纹理强度、accent 出现的面积
144
- - 配乐:逐小节能量弧线、配器进出(1 小节渐入渐出)、主奏轮换、动机变形
145
- - 旁白:hook/body/quote/outro 的语气档(语速、音高、停顿)
146
-
147
- **禁止**:同一部片里跨明暗族换主题(深蓝段直接切暖白段)。实测这会精确触发
148
- qa_frames 的单帧闪烁门禁(阈值 6.0 灰度,整屏换底可达 100+ 级),而且观众看着像"换了一部片"。
149
- 真需要强对比,把它做成两个片子,或登记成带 visual_at 的视觉事件。
150
-
151
- ## 6. 门禁:主题必须过对比度体检
152
-
153
- stylekit.theme_lint(theme) 在 --stage style 就拦,不用等渲完 300 帧:
154
-
155
- | 角色 | 对背景的最低对比度 | 理由 |
18
+ | 块 | 决定什么 | 字段 |
156
19
  |---|---|---|
157
- | ink(正文/标题) | 7.0 | WCAG AAA 正文线 |
158
- | muted(副标) | 4.5 | WCAG AA |
159
- | dim(HUD 小字) | 3.0 | 装饰级 |
160
- | accent / accent2 | 4.5 | 强调色常当文字用 |
161
- | good / bad | 3.0(对卡片底) | 只承载图形,靠位置+颜色双编码 |
162
- | bg 与 bg2 亮度差 | >= 0.002 | 防纯色底 |
20
+ | `structure` | 几段、目标时长 | `segments` / `beats` / `hold_policy` / `target_sec` |
21
+ | `visual` | 画面全部外观 | 见 §2 |
22
+ | `audio` | 配乐 / 声线 / 音效 | `music`(scale/bpm/key/mood/seed)/ `voice`(id/map/jitter/max_distinct)/ `sfx`(enabled/density/gain_db) |
23
+ | `pacing` | 节奏 | `lead` / `tail` / `min_dur` / `hold` / `item_stagger` |
24
+ | `id` | 指纹 | `design:<hash>`,由 `designkit.design_fingerprint()` 算出 |
25
+ | `label` / `pitch` | 中文标签 / 立意 | 提案卡上显示的那一句话 |
163
26
 
164
- 加第 9 套风格时:先写 _theme(...),再跑 python stylekit.py --lint,过了再进 STYLES。
27
+ 校验:`python <SKILL_DIR>/scripts/designkit.py --lint work/design.json`;
28
+ 契约:`<REPO>/schemas/design.schema.json`。**fail-closed**:缺字段/越界/对比度不达标一律报错,
29
+ 不会静默回落(这是对 v0.5–v0.9「改了不生效」的直接反思)。
165
30
 
166
- ## 7. 加一套自己的风格(5 步)
31
+ ## 2. visual 字段表(能控制什么)
167
32
 
168
- 1. stylekit.THEMES["my-style"] = _theme(...)(配色 + 材质 + 排版 + 版式 + 动态 + HUD)
169
- 2. stylekit.STYLES["my-style"] = {...}(theme / layouts / music{bpm_ladder,keys,mood} / voice / pacing)
170
- 3. STYLE_IDS 元组末尾追加(只追加不改顺序,否则指纹映射会漂)
171
- 4. python stylekit.py --lint 全绿;配乐侧在 scorekit.MUSIC_STYLES 里加同名条目
172
- 5. 用 3 段样片跑 --stage stills 亲眼看(铁律 6:能出图 != 没问题)
33
+ | 字段 | 取值 | 改它会发生什么 |
34
+ |---|---|---|
35
+ | `palette` | 11 个颜色角色(bg/bg2/surface/ink/muted/dim/accent/accent2/good/bad/rule),每个 `[r,g,b]` | 整片色调。它不是"从 8 套配色里挑",而是**连续色相空间**里的一点:改 `bg` 的色相,其余角色会按对比度网格重新求解 |
36
+ | `family` | `sans` / `serif` / `kai` / `round` | 全片字族(标题优先,字幕默认跟随) |
37
+ | `typography` | `tracking`(字距)/ `line_gap`(行距)/ `number`(编号写法,8 种)/ `ratio`(字号比例)/ `align` | 文字气质;`number` 决定"01 / 壹 / I / 1.0"这类编号外观 |
38
+ | `motif` | `kind`(8 种:brackets / hairlines / handdrawn / slabs / minimal / letterbox / signal / rays)× `density`(0.55–1.00) | 装饰语言:四角括线 / 细线页眉 / 手绘框 / 色块 / 极简 / 遮幅 / 信号条 / 放射线 |
39
+ | `transition` | `kind`(dip / wash / paper / wipe / cut / iris)+ `dur`(秒) | 段与段之间的转场 |
40
+ | `motion` | `ease`(out3/out5/inOut3/outBack)/ `enter`(dx,dy) / `push`(镜头缓推)/ `breathe_hz` | 入场与镜头运动 |
41
+ | `bias` | `x` / `y` / `inset` | 整屏重心偏移与安全区收缩 |
42
+ | `layout` | `left-stack` / `center-stack` / `split-right` | 默认版式 |
43
+ | `layout_cycle` | 版式数组(≥2 种不同) | **v1.0.2**:按章节轮换版式;不写则自动派生 2–3 种 |
44
+ | `hud` | `chip-left` / `corner-line` / `none` | 页眉/角落装饰 |
45
+ | `size` / `fps` | `1920x1080` / `30` | 只支持 16:9 横屏(编码时可下采样到 720p) |
46
+ | `subtitle` | 见 §3 | **v1.0.2**:字幕字号/位置/对齐/背板/圆角/字族/颜色/行数 |
47
+
48
+ > **能组合出多少种画面?** 离散维度是 8 母题 × 6 转场 × 4 缓动 × 8 编号 × 3 版式 × 3 HUD × 4 字族
49
+ > × 7 情绪 × 2 调式(= 774,144 种),再乘配色这个连续量(色相 0–360° 任意取值)。
50
+ > 这个数字只说明"字段可组合空间足够大",**不是推荐你从中挑**——好看与否仍由对比度体检、
51
+ > 分镜语法和你的目检决定。
52
+
53
+ ## 3. 字幕样式(visual.subtitle,v1.0.2)
54
+
55
+ 以前字幕的字号 / 位置 / 背板 / 圆角是 `filmkit` 的写死常量,所以**任何两条片子**的字幕带一模一样。
56
+ 现在进设计 DSL;**不写 = 默认值,与过去逐像素一致**(老 `design.json` 不受影响)。
57
+
58
+ ```json
59
+ "subtitle": {"size": 46, "y": 560, "align": "left", "plate_alpha": 170,
60
+ "radius": 10, "family": "sans", "color": [245, 245, 245], "max_lines": 2}
61
+ ```
62
+
63
+ | 字段 | 默认 | 取值 | 备注 |
64
+ |---|---|---|---|
65
+ | `size` | 46 | 20–96 | 采样器默认跑 40–56 |
66
+ | `y` | null | 0–1030 | null = 贴底安全区;采样器 1/3 概率抬高到 560–880 |
67
+ | `align` | center | center / left | |
68
+ | `plate_alpha` | 170 | 0–255 | 采样器默认 96–194 |
69
+ | `radius` | 10 | 0–48 | 采样器默认 2–20 |
70
+ | `family` | null | sans / serif / kai / round | null = 跟随大标题字族 |
71
+ | `color` | null | [r,g,b] 或 #rrggbb | null = 默认墨色 |
72
+ | `max_lines` | 2 | 1–3 | 决定了折行上限 |
73
+
74
+ 验证:`render.py --stage style` 会打印一行「字幕: ... 对齐=... 背板α=... 圆角=... 最多N行」,
75
+ 改 design 后重跑这一行就能看重算结果,不用等渲完。
76
+
77
+ ## 4. 版式轮换(visual.layout_cycle,v1.0.2)
78
+
79
+ ```json
80
+ "layout_cycle": ["split-right", "left-stack", "center-stack"]
81
+ ```
82
+
83
+ - 元素取 `left-stack` / `center-stack` / `split-right`;**至少 2 种不同版式**,只写一种会被校验拒绝。
84
+ - 由 `grammar.plan_scenes` 按**章节**轮换:同一章内固定,换章才换(观众视角一章是一段整体)。
85
+ - 不写时 `design_to_plan` 按设计指纹自动派生 2–3 种,**默认就不是全片一张版式**。
86
+ - 审计:`--stage layout` 打印每段构图;`work/storyboard.json` 每段都有 `layout` 字段。
87
+
88
+ ## 5. 配色怎么改才不出事
89
+
90
+ 配色不是"挑一个好看的颜色",而是 11 个角色要同时满足对比度:
91
+ `theme_lint` 的门槛是 ink≥7 / muted≥4.5 / dim≥3 / accent≥4.5 / accent2≥4.5(good/bad≥3),
92
+ 另外要求 bg 与 bg2 有可感知的亮度差(防纯色底)。
93
+
94
+ 推荐两条路:
95
+
96
+ 1. **让采样器算**(默认):采样器在 (hue, s, v) 网格里找满足全部门槛的点;
97
+ 实测纯随机取色通过率 35%,网格求解 100%。
98
+ 2. **手改色相**:只改 `palette.bg` 的色相/饱和度,然后跑 `designkit.py --lint`;
99
+ 过不了就调一档明度——报错会直接点名哪个角色不达标。
100
+
101
+ > 片内**不要跨明暗族**:同一部片锁一个明暗族,段界跨族会整屏换底、触发闪烁门禁,观众也看着像换片。
102
+ > 这条在采样器里是硬约束,手改 design 时请自己守住。
103
+
104
+ ## 6. 片内不重复:三层一起用
105
+
106
+ | 层 | 机制 | 防的是什么 |
107
+ |---|---|---|
108
+ | 段内 | `lines[].type`(10 类构图,grammar 自动选) | 每段都长一个样 |
109
+ | 章内 | `layout_cycle`(v1.0.2,按章节轮换) | 全片一张版式 |
110
+ | 片间 | 采样 nonce + 候选 ≥2 维 + 底色色相分叉 + **片间去重台账** | 不同片撞衫 |
173
111
 
174
- ## 8. 常见误用
112
+ ## 7. 常见误用
175
113
 
176
114
  | 现象 | 原因 | 修法 |
177
115
  |---|---|---|
178
- | 画面换了、配乐还是老的 | 只改了 visual.theme 没改 music.style | 直接改 style,或两个都写 |
179
- | 同一部片前后像两部片 | 段界跨了明暗族 | 用同族主题;或拆成两部 |
180
- | 改台词后配乐变了 | 改了标题或 kind 结构 -> tempo 指纹变(BPM/调性跟着换) | 写死 music.key / music.bpm(`seed_from_content:false` 只管音色 seed,压不住 BPM/调性变化) |
181
- | 旁白听感像广播剧 | max_distinct 太大 / 相邻句频繁换人 | 降到 2,靠 tone 而不是换人做变化 |
182
- | 字幕在浅色主题上看不清 | 用了 v0.4 的黑底白字 | 传 theme= 给 draw_subtitle(render.py 已自动传) |
116
+ | 改了 `design.json` 画面没变 | 没重跑阶段 / 没重签核 | `render.py --stage explain` 看哪些阶段过时 |
117
+ | `--stage style` 报「找不到设计」 | 工程里没有 `work/design.json` | 先 `propose.py --brief ... --script film.json --work work` 再 `--confirm` |
118
+ | 手改 design 后 `--confirm` 报校验失败 | 字段越界/对比度不达标 | 按报错逐条修;或干脆 `--revise` 重出 |
119
+ | 想"用某套预设当起点" | v1.0.1 已删除全部预设 | 没有这条路:现场采样 + `--revise` 表达要求,要复现用 `--seed` |
120
+ | `render.py --preset x` 报错 | `--preset` 已删除 | 用 `--brief` 或直接写 design.json |
121
+ | `stylekit.py --list` 只有一段说明、exit 2 | 内置风格表已删除(预期行为) | 看 `designkit.py --list-palettes` 的配色采样示例(那不是可选清单) |