@dsh-gis/dsh-video-studio-skill 0.8.1 → 0.9.1
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.
- package/CHANGELOG.md +158 -0
- package/README.md +37 -14
- package/docs/styles/sheet-8-styles.png +0 -0
- package/docs/styles/sheet-before-after.png +0 -0
- package/package.json +4 -2
- package/schemas/film.schema.json +167 -0
- package/skills/video-studio/SKILL.md +66 -9
- package/skills/video-studio/assets/golden_frames.json +8 -8
- package/skills/video-studio/assets/scenes.py +2162 -1190
- package/skills/video-studio/references/audio-variation.md +1 -1
- package/skills/video-studio/references/audio.md +1 -1
- package/skills/video-studio/references/quality-gates.md +1 -1
- package/skills/video-studio/references/scene-grammar.md +306 -0
- package/skills/video-studio/references/style-packs.md +101 -0
- package/skills/video-studio/scripts/filmcheck.py +97 -0
- package/skills/video-studio/scripts/grammar.py +559 -0
- package/skills/video-studio/scripts/render.py +45 -0
- package/skills/video-studio/scripts/selfcheck.py +209 -0
- package/skills/video-studio/scripts/stylekit.py +199 -0
- package/tools/ci_check.py +3 -1
- package/tools/silhouette_qa.py +910 -0
|
@@ -129,7 +129,7 @@ def build_audio(self, total=None, sr=SR, base_db=None, default_gain_db=0.0,
|
|
|
129
129
|
> 下面 1–4 的路径都按**仓库根**写(`python skills/video-studio/scripts/…`);如果你已经 `cd` 进技能目录,
|
|
130
130
|
> 去掉 `skills/video-studio/` 前缀即可。`<REPO>` = 仓库根 / 装好后的包根。
|
|
131
131
|
|
|
132
|
-
1. `python skills/video-studio/scripts/selfcheck.py` 全 PASS(现在
|
|
132
|
+
1. `python skills/video-studio/scripts/selfcheck.py` 全 PASS(现在 55 条;v0.7.2 时的 43 条、v0.8 的 51 条是旧的)。
|
|
133
133
|
2. `python skills/video-studio/scripts/scorekit.py --selftest` 全 PASS。
|
|
134
134
|
3. `python skills/video-studio/scripts/sfxkit.py --selftest` 全 PASS。
|
|
135
135
|
4. `python <REPO>/tools/ci_check.py` 全 PASS(在包根执行)。
|
|
@@ -477,6 +477,6 @@ D 组构造"有人声段 + 间隙",量人声段旁白/背景响度差。任何
|
|
|
477
477
|
### 13.4 改音频代码后的自检清单
|
|
478
478
|
|
|
479
479
|
1. `python <REPO>/tools/audio_similarity.py --strict` —— A/B/C 三组达标(在包根执行)。
|
|
480
|
-
2. `python selfcheck.py` ——
|
|
480
|
+
2. `python selfcheck.py` —— 55 条全 PASS(第 33 条盯 legacy 逐样本兼容,第 35–38 条盯旁白优先,第 39 条盯音频边界)。
|
|
481
481
|
3. `python scorekit.py --selftest` / `python sfxkit.py --selftest` —— 各自全 PASS。
|
|
482
482
|
4. 改 `assets/scenes.py` 的 `cues()` 后,跑一次 `--stage cues` 看类型分布是否覆盖 hook/body/quote/outro。
|
|
@@ -243,5 +243,5 @@ python webengine.py textqa ./web/index.html --step 0.2
|
|
|
243
243
|
信号:-16.3 LUFS / 真峰 -1.2 dBTP / 锐度比 0.94 / 黑帧 0 / 字幕 86 条 = 剧本 86 句
|
|
244
244
|
时间:单帧闪烁 0 / 画面空窗 0 / 音画同步最大偏差 38 ms
|
|
245
245
|
页面:文字重叠 0 / 出画 0 / 一闪而过 0 / 从没露面 0
|
|
246
|
-
自检:python selfcheck.py -> PASS
|
|
246
|
+
自检:python selfcheck.py -> PASS 55 / FAIL 0 / SKIP 0
|
|
247
247
|
```
|
|
@@ -0,0 +1,306 @@
|
|
|
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
|
+
│ ② 视觉语言包:这套风格怎么画(motion / motif / transition / typography / bias)
|
|
33
|
+
▼
|
|
34
|
+
stylekit.VISUAL_LANGUAGES[style_id]
|
|
35
|
+
│ ③ 片内编排:同一部片里连续段落怎么不"复制粘贴"(变体轮换 / 装饰密度 / 相位,确定性)
|
|
36
|
+
▼
|
|
37
|
+
draw_frame(...) → PIL.Image
|
|
38
|
+
|
|
39
|
+
三层的边界要分清,否则会写出"改了不生效"的配置:
|
|
40
|
+
|
|
41
|
+
| 层 | 由谁决定 | 落在哪 | 改它会怎样 |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| 叙事 kind | 作者写 `lines[].kind` | hook / body / quote / outro / emphasis / calm | 换旁白语气与配乐能量(voicekit / scorekit 读它) |
|
|
44
|
+
| 场景 type | grammar 按内容打分,或作者显式写 `lines[].type` | `work/storyboard.json`、timeline 的 `scene_type` | 换构图(10 个绘制器之一) |
|
|
45
|
+
| 视觉语言 | 风格 id(`style`) | `work/style.json` 的 `language` | 换这套风格的骨架参数(母题 / 转场 / 字距 / 重心) |
|
|
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:1979`(`_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. legacy 边界:不写 `style` = v0.4 三个老绘制器
|
|
221
|
+
|
|
222
|
+
`film.json` 不写 `style` 字段时整条 v0.9 链路都不参与:
|
|
223
|
+
|
|
224
|
+
- `render.st_layout` 不调用 `grammar.plan_scenes`,不写 `work/storyboard.json`,
|
|
225
|
+
timeline 里也不会出现 `scene_type` 等字段;
|
|
226
|
+
- `scenes.draw_frame`(`assets/scenes.py:2003`)读 `plan["legacy"]` 为真,
|
|
227
|
+
直接走 `_LEGACY_DRAWERS`(scenes.py:232,hook / body / outro 三个 v0.4 绘制器),逐字节不变;
|
|
228
|
+
- 金帧基线(`skills/video-studio/assets/golden_frames.json`)就是钉这条的:
|
|
229
|
+
legacy 项目跑出来的 3 帧像素与基线一致,改坏了立刻红。
|
|
230
|
+
|
|
231
|
+
想让老项目享受新构图,写 `"style": "auto"` 或任意风格 id 即可;
|
|
232
|
+
但注意这会同时换掉配色/配乐/旁白(v0.5 的风格包是整体生效的),做好重看一遍的准备。
|
|
233
|
+
|
|
234
|
+
## 8. 加第 11 类构图(8 步)
|
|
235
|
+
|
|
236
|
+
以新增 `timeline`(时间轴图)为例。顺序不能乱,每步都有对应的门禁/断言在盯:
|
|
237
|
+
|
|
238
|
+
1. **`grammar.SCENE_TYPES`(grammar.py:30)末尾追加** `"timeline"`。**只追加不改顺序**——
|
|
239
|
+
顺序是平手取位顺序,插队会让所有老稿子的分镜漂移。同时补 `SCENE_LABELS`。
|
|
240
|
+
2. **`grammar.MIN_DUR` 加最短时长**(按这套构图入场+读完所需的秒数,别照抄别的类型),
|
|
241
|
+
并在 `DOWNGRADE` 里给它一个降级目标(键必须都在 `SCENE_TYPES` 里,且不能自环)。
|
|
242
|
+
3. **`grammar.score_types` 加打分规则**:优先"字段存在性"(比如 `timeline: []` 给 +6.0),
|
|
243
|
+
其次才是关键词/结构位置。加上去之前先问:它和 chart / process 的区别是什么?
|
|
244
|
+
如果画面上差别不大,就不要加——10 类已经覆盖了"数字/列表/对比/流程/引语/章节"这些基本形态。
|
|
245
|
+
4. **`assets/scenes.py` 加绘制器** `_draw_timeline(scene, t, dur, tg, script, frame, plan, th)`,
|
|
246
|
+
签名与现有 10 个一致。约定:
|
|
247
|
+
- 先用 `_bg_plate` / `_apply_motif` 铺底,最后 `_apply_veil` + `_hud`;
|
|
248
|
+
- 坐标过 `_view`(视差),文字用 `_draw_text`(字号不重采样);
|
|
249
|
+
- 持续动效至少一个(相位用 `_phase(scene)`),否则 3–6 s 的静态画面会撞"画面静止"判据;
|
|
250
|
+
- 重的东西预烘焙并合成进底版(参考 `_motif_layer` + `_plate`:一次烘焙,逐帧只 copy/EXTENT
|
|
251
|
+
约 1.3–2.0 ms)。**不要逐帧全屏 RGBA paste**(实测 5.6 ms,v0.9.0 踩过);
|
|
252
|
+
也不要每帧全屏 GaussianBlur / 全屏双线性 resize(预算 25 ms)。
|
|
253
|
+
5. **`assets/scenes.py` 的 `_DRAWERS` 加键**。缺了会在渲染时回落 list 并 warn 一次。
|
|
254
|
+
6. **`filmcheck.SCENE_TYPES` 加值**(这份只做成员校验,顺序无所谓),
|
|
255
|
+
以及 **`schemas/film.schema.json` 的 `lines[].type` 枚举**加值(IDE 补全与文档)。
|
|
256
|
+
7. **`selfcheck.py` 第 54 条(`scripts/selfcheck.py:1526`)会自动兜底**:它断言 grammar / filmcheck / scenes.`_DRAWERS` /
|
|
257
|
+
schema enum 四处**集合相等**(顺序允许不同)。这四份真值来源必须同时改,漏一处立刻红。
|
|
258
|
+
8. **`tools/silhouette_qa.py` 的 `SCENE_CONTENT` 补一段合成内容**(给足该类型的原料字段),
|
|
259
|
+
再跑一次剪影门禁确认新类型在 8 套风格下也"看得出来"。这一步不做,等于新加的构图
|
|
260
|
+
没有进入 G13 的覆盖范围。
|
|
261
|
+
|
|
262
|
+
跑完的验证顺序:`python selfcheck.py`(52–55 条)→ `python tools/ci_check.py`
|
|
263
|
+
(语法/包内容/文档路径)→ `python tools/silhouette_qa.py`(28 对仍全过)。
|
|
264
|
+
|
|
265
|
+
## 9. 常见误用
|
|
266
|
+
|
|
267
|
+
| 现象 | 原因 | 修法 |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| 分镜表里一段都没变,全是 list | 没有写 `style`(走 legacy),grammar 根本没跑 | 写 `"style": "auto"` 或某个风格 id |
|
|
270
|
+
| 某段想要数字卡,结果画成列表 | 没有 `stats[]`,文本里的数字是"2024 年"(年份被排除)或没有单位 | 显式写 `stats[]`;或让那句话带"数字 + 单位" |
|
|
271
|
+
| 写了 `type: "stats"` 却没生效,还报错 | `type` 是枚举,`stats` 才是字段名;写错值 filmcheck 报 ERROR | 改 `type` 为 10 类之一,数据放 `stats[]` |
|
|
272
|
+
| 对比段左右两栏一栏空 | 只有 1 条 `items`(对半分不出来),或 `sides{}` 只写了一侧 | 补到 ≥2 条,或显式写 `sides` 两侧 |
|
|
273
|
+
| 流程轴上的箭头/破折号来自条目 | 关键词只看叙述文字,不看 `items`——但如果你把"→"写进了 `text/big`,仍会命中 | 流程用 `steps[]` 或叙述里写"第一步…然后…";条目里的排版符号不算顺序 |
|
|
274
|
+
| 章节卡把整段要点吃掉了 | 章节卡要求"无条目 + 标题 ≤14 字 + 段 ≤4.0 s",但显式 `type=chapter` 会压过这些条件 | 章节卡单独成段(不带 items),要点另起一段 |
|
|
275
|
+
| 段长 2 秒写了 `type=quote`,出来是金句 | 段长门:quote 最短 4.0 s,按降级表落到 statement | 拆句/加停顿把段拉长,或接受降级;`storyboard.json` 的 reason 会写明 |
|
|
276
|
+
| 同一部片里前深后浅,闪烁门禁红了 | 同一部片仍锁一个明暗族(跨族会撞闪烁门禁) | 用同族主题;或拆成两部片 |
|
|
277
|
+
| 写了 5 条 `series`,画面只显示 3 条 | 容量上限:chart 只画前 6 条(stats 3 / steps 5 / sides 每侧 3) | filmcheck 会给 `scene_capacity` WARN;要全画就拆成多段 |
|
|
278
|
+
| 改了 `chapter` 后分镜变了 | `variant_of` / `chapter` 是变体与章节卡的输入 | 这是期望行为;要固定版式就写 `visual.layout` |
|
|
279
|
+
|
|
280
|
+
## 10. 已知限制(本版没做,写下来别装看不见)
|
|
281
|
+
|
|
282
|
+
- **web 引擎不跟随场景语法**:`--engine web` 的页面由用户自己写(`assets/web/film.js`),
|
|
283
|
+
本次不改造。web 路线的画风仍只有主题色,没有 10 类构图;
|
|
284
|
+
剪影门禁也只覆盖 PIL 渲染器。
|
|
285
|
+
- **同一部片仍锁一个明暗族**:段界跨明暗族会整屏换底,精确触发闪烁门禁,观感也像换片。
|
|
286
|
+
主题覆盖(`scene["theme"]`)只允许同族。
|
|
287
|
+
- **`lines[].type` 写错只有 filmcheck 拦**:grammar 对非法值静默回落(当没写)。
|
|
288
|
+
不跑 filmcheck 的流水线,写错就只是"没生效"。
|
|
289
|
+
- **场景类型有 4 份真值来源**:`filmcheck.SCENE_TYPES`(`skills/video-studio/scripts/filmcheck.py:51`)/
|
|
290
|
+
`grammar.SCENE_TYPES`(grammar.py:30)/
|
|
291
|
+
`scenes._DRAWERS` / schema 的 enum。靠 selfcheck 第 54 条断言"集合相等"兜底;
|
|
292
|
+
**顺序可以不同**——grammar 的那份顺序用于平手取位,另外三份只看成员。
|
|
293
|
+
- **版式变体只有 4 类绘制器读**:`variant_of` 对每段都算并写进 storyboard,
|
|
294
|
+
但当前只有 hero / statement / list / stat 用它,其余六类构图唯一。
|
|
295
|
+
这条不修的话,"每类型 3 个变体"只在四类上成立。
|
|
296
|
+
- **部分视觉语言字段"已声明、已体检、但绘制器没读"**:`motion.enter`、`motion.push`、
|
|
297
|
+
`typography.align`、`typography.ratio`(以及 `layout_cycle`)在
|
|
298
|
+
`stylekit.VISUAL_LANGUAGES` 里声明、`stylekit.visual_lint` 会查范围,
|
|
299
|
+
但 `assets/scenes.py` 当前不读它们——写了不会报错,也不会改变画面。
|
|
300
|
+
详见 [style-packs.md](style-packs.md) 的"视觉语言包"一节。
|
|
301
|
+
- **场景数据超容量会被截断**:`stats` >3 / `series` >6 / `steps` >5 / `sides` 每侧 >3
|
|
302
|
+
的条目不会出现在画面上。filmcheck 报 `scene_capacity` WARN(容量表在
|
|
303
|
+
`skills/video-studio/scripts/filmcheck.py:652`),但**不阻断渲染**——
|
|
304
|
+
WARN 不是通过,也不是失败,是"画面上没有"。
|
|
305
|
+
- **`work/storyboard.json` 是审计产物,不是输入**:改它不会改变渲染结果;
|
|
306
|
+
想要固定构图请改 `film.json` 的 `lines[].type`。
|
|
@@ -180,3 +180,104 @@ stylekit.theme_lint(theme) 在 --stage style 就拦,不用等渲完 300 帧:
|
|
|
180
180
|
| 改台词后配乐变了 | 改了标题或 kind 结构 -> tempo 指纹变(BPM/调性跟着换) | 写死 music.key / music.bpm(`seed_from_content:false` 只管音色 seed,压不住 BPM/调性变化) |
|
|
181
181
|
| 旁白听感像广播剧 | max_distinct 太大 / 相邻句频繁换人 | 降到 2,靠 tone 而不是换人做变化 |
|
|
182
182
|
| 字幕在浅色主题上看不清 | 用了 v0.4 的黑底白字 | 传 theme= 给 draw_subtitle(render.py 已自动传) |
|
|
183
|
+
|
|
184
|
+
## 9. 视觉语言包(v0.9):结构签名,不只是配色
|
|
185
|
+
|
|
186
|
+
### 9.1 为什么还要这一层
|
|
187
|
+
|
|
188
|
+
v0.5 的 `STYLES` / `THEMES` 管的是**皮肤**:配色、字体族、纹理、颗粒、字距、缓动、HUD、
|
|
189
|
+
3 种版式几何。8 套风格共用同一批绘制器与同一套装饰,于是"换风格"在画面上只剩换色——
|
|
190
|
+
灰度量出来的构图骨架(前景占比 / 重心 / 边缘占用 / 左右对称)几乎不动。
|
|
191
|
+
|
|
192
|
+
`stylekit.VISUAL_LANGUAGES` 补的是**结构签名**:同一段内容在两套风格下,剪影必须能区分。
|
|
193
|
+
它是纯数据(不 import PIL / filmkit,否则与 `scenes.py` 成环),与 `STYLES` 同层,
|
|
194
|
+
`--stage style` 一起体检。
|
|
195
|
+
|
|
196
|
+
### 9.2 五组签名字段
|
|
197
|
+
|
|
198
|
+
| 组 | 字段 | 取值 / 例子 | 谁读它 |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| `motion` | `ease` | `out3` / `out5` / `inOut3` / `outBack` | `scenes.py`(所有绘制器的入场缓动) |
|
|
201
|
+
| | `breathe_hz` | 0.28–1.10(Hz) | `scenes.py`(强调线/下划线/光点呼吸频率) |
|
|
202
|
+
| | `enter` | `(起, 止)` 秒,如 `(0.70, 1.20)` | **已声明、visual_lint 会查、绘制器暂未读** |
|
|
203
|
+
| | `push` | 0–0.16(背景缓推幅度) | **已声明、visual_lint 会查、绘制器暂未读**(主题自带的 `push` 仍生效) |
|
|
204
|
+
| `motif` | `kind` | `brackets` / `hairlines` / `handdrawn` / `slabs` / `minimal` / `letterbox` / `signal` / `rays` | `scenes._motif_layer` 烘焙母题、`scenes._plate` 合成进背景底版;v0.9.1 起逐帧只 copy/EXTENT(约 1.3–2.0 ms),不再逐帧 paste(实测 5.6 ms) |
|
|
205
|
+
| | `density` | 0–1(母题浓度;0 只允许 `minimal`) | `scenes._decor_scale`(再乘片内密度档 0.55/0.75/1.0) |
|
|
206
|
+
| | `detail` | 母题专属参数(`corner`/`rule_step`/`wobble`/`top`/`bars`/`rays` …) | 同上 |
|
|
207
|
+
| `transition` | `kind` | `dip` / `wash` / `paper` / `wipe` / `cut` / `iris` | `scenes._apply_veil`(段首 ≤0.45 s 内生效) |
|
|
208
|
+
| | `dur` | 0–0.60 秒 | 同上;超过 0.60 s 会糊掉 3–6 s 节奏下限 |
|
|
209
|
+
| `typography` | `tracking` | 0–8 px(字距;与主题自带字距相加) | `scenes._tracking` |
|
|
210
|
+
| | `line_gap` | 1.15–1.55(行高倍数) | `scenes._gap` |
|
|
211
|
+
| | `number` | `dim` / `serif` / `cn` / `block` / `roman` / `timecode` / `dot` / `gilt` | `scenes._number_text`(章节编号、列表序号、流程节点号) |
|
|
212
|
+
| | `align` / `ratio` | `left`/`center`/`right`;display:body 字号比 1.0–3.6 | **已声明、visual_lint 会查、绘制器暂未读** |
|
|
213
|
+
| `bias` | `x` / `y` / `inset` | px,如 `{"x": -150, "y": 80, "inset": 40}` | `scenes._bias`(整体重心偏移;`inset` 只作用于左对齐起点) |
|
|
214
|
+
|
|
215
|
+
两条设计约束(改的时候别破):
|
|
216
|
+
|
|
217
|
+
1. **每条签名都要"能被剪影看出差别"**:只改颜色/字重/描边的项不算签名。
|
|
218
|
+
2. **受单帧预算约束**:装饰必须在烘焙阶段合成进底版(`_plate`),逐帧只剩 copy/EXTENT;
|
|
219
|
+
禁每帧全屏 GaussianBlur(实测 30–60 ms)、全屏双线性 resize(29 ms)与全屏 RGBA paste
|
|
220
|
+
(实测 5.6 ms——0.9.0 就是栽在这,0.9.1 才改掉),预算 25 ms。
|
|
221
|
+
|
|
222
|
+
### 9.3 8 套风格的签名摘要
|
|
223
|
+
|
|
224
|
+
| 风格 | motion(ease / 呼吸) | motif(母题 / 密度) | transition | typography(字距 / 行高 / 编号) | bias(x,y,inset) |
|
|
225
|
+
|---|---|---|---|---|---|
|
|
226
|
+
| tech-deep | out3 / 0.85 | brackets / 1.00(四角 L 括角 + 刻度尺 + 扫描线) | dip 0.28 s | 0 / 1.30 / dim | (0, 0, 0) |
|
|
227
|
+
| magazine | out5 / 0.50 | hairlines / 0.85(双横线页眉 + 栏间线 + 页码位) | wash 0.35 s | 2 / 1.34 / serif | (+60, −20, 0) |
|
|
228
|
+
| paper | inOut3 / 0.42 | handdrawn / 1.00(手绘内框 + 胶带 + 印章) | paper 0.35 s | 1 / 1.36 / cn(一、二、三) | (−150, +80, 40) |
|
|
229
|
+
| vivid | outBack / 1.10 | slabs / 1.00(粗色块 + 斜切 + 贴纸) | wipe 0.20 s | 1 / 1.26 / block | (0, +40, 0) |
|
|
230
|
+
| mono | inOut3 / 0.35 | minimal / 0.55(极细线 + 直角框) | cut 0.00 s | 6 / 1.40 / roman | (0, 0, 0) |
|
|
231
|
+
| cinema | inOut3 / 0.30 | letterbox / 1.00(上下遮幅 + 光斑) | dip 0.45 s | 3 / 1.38 / timecode | (0, +30, 0) |
|
|
232
|
+
| lo-fi | out3 / 0.60 | signal / 0.90(信号条 + 时间戳) | wash 0.30 s | 1 / 1.36 / dot | (+60, −70, 0) |
|
|
233
|
+
| epic | out5 / 0.28 | rays / 1.00(放射射线 + 金线框) | iris 0.40 s | 4 / 1.32 / gilt | (0, −30, 0) |
|
|
234
|
+
| legacy | out3 / 0.85 | brackets / 1.00(detail 为空) | cut 0.00 s | 0 / 1.30 / dim | 不参与(走老绘制器) |
|
|
235
|
+
|
|
236
|
+
`layout_cycle` 字段也写在每套里(版式轮换顺序),但**当前没有消费者**——
|
|
237
|
+
按章节轮换版式的逻辑在 `STYLES[style]["layouts"]` 那一层(见 §5 与 SKILL §5.5)。
|
|
238
|
+
|
|
239
|
+
### 9.4 `visual_lint` 查什么
|
|
240
|
+
|
|
241
|
+
在 `--stage style` 就拦(`render.py` 解析风格时调用,坏配置直接 `SystemExit`),
|
|
242
|
+
不用等渲到第 300 帧才发现"每段转场糊 1 秒":
|
|
243
|
+
|
|
244
|
+
| 检查 | 线 |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `motif.kind` | 必须在 `VISUAL_MOTIFS` 的 8 个里 |
|
|
247
|
+
| `transition.kind` | 必须在 `VISUAL_TRANSITIONS` 的 6 个里 |
|
|
248
|
+
| `transition.dur` | 0–0.60 s |
|
|
249
|
+
| `typography.number` | 必须在 `NUMBER_STYLES` 的 8 个里 |
|
|
250
|
+
| `typography.ratio` | 1.0–3.6 |
|
|
251
|
+
| `typography.tracking` | 0–8 px |
|
|
252
|
+
| `typography.line_gap` | 1.15–1.55 |
|
|
253
|
+
| `motif.density` | 0–1;为 0 只允许 `minimal` |
|
|
254
|
+
| `motion.enter` | `(起, 止)` 且 0 < 起 ≤ 止 ≤ 1.6 s |
|
|
255
|
+
| `motion.push` | 0–0.16(超过会把内容推出安全区) |
|
|
256
|
+
|
|
257
|
+
`selfcheck` 第 53 条另查两件 `visual_lint` 查不到的:8 套签名齐全(motion / typography 都有),
|
|
258
|
+
且**母题 / 转场 / 重心三元组不重复**(全同 = 这套语言只是复制粘贴)。
|
|
259
|
+
|
|
260
|
+
剪影是否真的可区分,由 `tools/silhouette_qa.py`(门禁 G13)量:同内容同段落在两套风格下
|
|
261
|
+
各渲 3 帧 → 灰度 → 480×270 → 四角亮度中位数三值化 → 比前景占比 F / 质心 (Cx,Cy) /
|
|
262
|
+
四边覆盖率 B / 左右对称度 S;五项里任一项达标即 PASS(ΔF≥6pp、|ΔCx|≥8pp、|ΔCy|≥8pp、
|
|
263
|
+
ΔB≥14pp、ΔS≥0.20),同一指标必须 3 帧全达标。当前实测 **28 对全过(同明暗族 12/12)**;
|
|
264
|
+
改造前基线同族只过 8/12。
|
|
265
|
+
|
|
266
|
+
### 9.5 加第 9 套风格时,视觉语言要补什么
|
|
267
|
+
|
|
268
|
+
主题与风格包的 5 步见 §7;v0.9 起还要多做四步,**否则新风格在剪影上仍然只是换色**:
|
|
269
|
+
|
|
270
|
+
1. **`stylekit.VISUAL_LANGUAGES["my-style"]` 写全五组**:motion(ease / enter / push /
|
|
271
|
+
breathe_hz)、motif(kind + density + detail)、transition(kind + dur)、
|
|
272
|
+
typography(tracking / line_gap / align / number / ratio)、bias(x / y / inset)。
|
|
273
|
+
照抄一套现有签名再改配色 = 白做,第 53 条会报"母题/转场/重心完全相同"。
|
|
274
|
+
2. **母题不要临时发明新名字**:`motif.kind` 必须是 `VISUAL_MOTIFS` 里的 8 个之一。
|
|
275
|
+
确实需要新图形语言时,先在 `scenes._motif_layer` 里实现一个分支、加进 `VISUAL_MOTIFS`,
|
|
276
|
+
再在风格里引用——顺序反了会 `visual_lint` 直接拦。
|
|
277
|
+
3. **跑 `python stylekit.py --lint`**(主题对比度)与**单独跑 `visual_lint`**
|
|
278
|
+
(写个三行的 `.py` 调 `stylekit.visual_lint("my-style")`,或直接 `--stage style`)。
|
|
279
|
+
4. **跑剪影门禁**:`python tools/silhouette_qa.py --styles my-style,tech-deep,...`,
|
|
280
|
+
确认新风格与其余 8 套两两可区分;再跑 `selfcheck`(第 53 条会要求三元组不重复)。
|
|
281
|
+
5. **看片**:`--stage stills` 渲 3 段亲眼确认母题/转场/字距不是"看着像另一套"——
|
|
282
|
+
数字过了不等于观感对了(铁律 6)。
|
|
283
|
+
|
|
@@ -42,6 +42,17 @@ HERE = Path(__file__).resolve().parent
|
|
|
42
42
|
# ============================================================================
|
|
43
43
|
|
|
44
44
|
KINDS = ("hook", "body", "quote", "outro", "emphasis", "calm") # 也是 voicekit 的语气 id
|
|
45
|
+
|
|
46
|
+
# 视觉场景类型(v0.9):决定"这段画面长成什么构图",与叙事 kind **正交**——
|
|
47
|
+
# kind 管声线/语气/配乐能量(voicekit / scorekit 读它);
|
|
48
|
+
# type 只管构图(assets/scenes.py 的 _DRAWERS 读它)。
|
|
49
|
+
# 所以**不新增 kind 值**:hero/stat/... 不是语气,硬塞进 KINDS 会撞 voicekit 的 tone id
|
|
50
|
+
# (TONE_IDS 与 KINDS 是同一组 6 个)。缺省(不写 lines[].type)= 由 grammar.py 按内容推导。
|
|
51
|
+
SCENE_TYPES = ("hero", "statement", "list", "stat", "compare", "process",
|
|
52
|
+
"quote", "chapter", "chart", "outro")
|
|
53
|
+
# 与 assets/scenes.py 的 _DRAWERS 键对齐,改一边必须改另一边。
|
|
54
|
+
# (grammar.SCENE_TYPES 是同一组 10 个值,但那份顺序用于"平手取位";这里只做成员校验,
|
|
55
|
+
# 本元组的顺序只影响报错信息里的枚举列举,不影响判定。三处不要再各写一份新名单。)
|
|
45
56
|
PRESETS = ("ultrafast", "superfast", "veryfast", "faster", "fast",
|
|
46
57
|
"medium", "slow", "slower", "veryslow")
|
|
47
58
|
MUSIC_MODES = ("score", "bed", "file", "off")
|
|
@@ -200,6 +211,59 @@ LINE_ITEM_SPEC = {
|
|
|
200
211
|
},
|
|
201
212
|
}
|
|
202
213
|
|
|
214
|
+
# ---- v0.9 视觉场景字段的嵌套结构(写法照抄 LINE_ITEM_SPEC)----
|
|
215
|
+
# 这些对象的键由 scenes.py 的 10 个绘制器读:拼错就是"写了不生效",所以**不**用
|
|
216
|
+
# open=True(那是留给 sfx 配方参数那种真·透传的)。每层列全 props,才能把拼错报成
|
|
217
|
+
# unknown_key(WARN + "是不是想写 X"),把类型写错报成 ERROR。
|
|
218
|
+
# required 与 schema 表里的问号标记一致:必填 = 绘制器没有它就没法画。
|
|
219
|
+
LINE_STAT_SPEC = { # type=stat 的数字卡
|
|
220
|
+
"t": "dict", "required": ["value"],
|
|
221
|
+
"props": {
|
|
222
|
+
# 数字或字符串("92"、">3x"、"1/2" 这类写法只能当字符串)
|
|
223
|
+
"value": {"t": ("num", "str"), "nonempty": True},
|
|
224
|
+
"unit": {"t": "str"},
|
|
225
|
+
"label": {"t": "str"},
|
|
226
|
+
"note": {"t": "str"},
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
LINE_SERIES_SPEC = { # type=chart 的数据条
|
|
231
|
+
"t": "dict", "required": ["label", "value"],
|
|
232
|
+
"props": {
|
|
233
|
+
"label": {"t": "str", "nonempty": True},
|
|
234
|
+
"value": {"t": ("num", "str"), "nonempty": True},
|
|
235
|
+
"unit": {"t": "str"},
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
LINE_STEP_SPEC = { # type=process 的流程节点
|
|
240
|
+
"t": "dict", "required": ["label"],
|
|
241
|
+
"props": {
|
|
242
|
+
"label": {"t": "str", "nonempty": True},
|
|
243
|
+
"note": {"t": "str"},
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# 对比卡的一侧:items 每项可以写字符串,也可以写 {text}(短条目直接写字符串更省事,两种都认)
|
|
248
|
+
LINE_SIDE_SPEC = {
|
|
249
|
+
"t": "dict",
|
|
250
|
+
"props": {
|
|
251
|
+
"head": {"t": "str"},
|
|
252
|
+
"items": {"t": "list", "item": {
|
|
253
|
+
"t": ("str", "dict"), "required": ["text"],
|
|
254
|
+
"props": {"text": {"t": "str", "nonempty": True}},
|
|
255
|
+
}},
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
LINE_SIDES_SPEC = { # type=compare 的左右对比(left/right 都可省略)
|
|
260
|
+
"t": "dict",
|
|
261
|
+
"props": {
|
|
262
|
+
"left": LINE_SIDE_SPEC,
|
|
263
|
+
"right": LINE_SIDE_SPEC,
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
|
|
203
267
|
LINE_SPEC = {
|
|
204
268
|
"t": "dict", "required": ["text"],
|
|
205
269
|
"props": {
|
|
@@ -218,6 +282,16 @@ LINE_SPEC = {
|
|
|
218
282
|
"persona": {"t": "str"},
|
|
219
283
|
"tone": {"t": "str", "enum": KINDS},
|
|
220
284
|
"voice": {"t": "str"},
|
|
285
|
+
# ---- v0.9 视觉场景字段(7 个全可选)----
|
|
286
|
+
# type 缺省由 grammar.py 按内容推导;其余字段是给绘制器的原料,不写就不画。
|
|
287
|
+
# 注意这些字段与 kind **正交**,不是新 kind:kind 仍只有上面 6 个(voicekit 的语气 id)。
|
|
288
|
+
"type": {"t": "str", "enum": SCENE_TYPES},
|
|
289
|
+
"quote": {"t": "str"},
|
|
290
|
+
"by": {"t": "str"},
|
|
291
|
+
"stats": {"t": "list", "item": LINE_STAT_SPEC},
|
|
292
|
+
"series": {"t": "list", "item": LINE_SERIES_SPEC},
|
|
293
|
+
"steps": {"t": "list", "item": LINE_STEP_SPEC},
|
|
294
|
+
"sides": LINE_SIDES_SPEC,
|
|
221
295
|
},
|
|
222
296
|
}
|
|
223
297
|
|
|
@@ -571,6 +645,29 @@ def check_cross(script, script_path, rep, ctx):
|
|
|
571
645
|
"tail_map)。例如 %s: 1.4" % lid),
|
|
572
646
|
"inert_line_tail")
|
|
573
647
|
|
|
648
|
+
# ---- v0.9 场景语法:绘制器的容量上限(超了会被静默截断) ----
|
|
649
|
+
# 这是本项目最忌讳的一类坑:作者写对了、跑得通、但画面上少了几条,而没有任何提示。
|
|
650
|
+
# 容量由 assets/scenes.py 的实现决定(stats 取前 3 条、series 前 6 条、steps 前 5 条、
|
|
651
|
+
# sides 每侧前 3 条),改那边要同步改这里。
|
|
652
|
+
_CAP = (("stats", 3), ("series", 6), ("steps", 5))
|
|
653
|
+
for i, ln in enumerate(lines):
|
|
654
|
+
for fld, cap in _CAP:
|
|
655
|
+
v = ln.get(fld)
|
|
656
|
+
if isinstance(v, list) and len(v) > cap:
|
|
657
|
+
rep.warn("lines[%d].%s" % (i, fld),
|
|
658
|
+
"%s 有 %d 条,但 %s 构图只画前 %d 条(其余不会出现在画面上)。"
|
|
659
|
+
"要全画请拆成多段,或减到 %d 条以内"
|
|
660
|
+
% (fld, len(v), fld, cap, cap), "scene_capacity")
|
|
661
|
+
sides = ln.get("sides")
|
|
662
|
+
if isinstance(sides, dict):
|
|
663
|
+
for side in ("left", "right"):
|
|
664
|
+
blk = sides.get(side)
|
|
665
|
+
its = blk.get("items") if isinstance(blk, dict) else None
|
|
666
|
+
if isinstance(its, list) and len(its) > 3:
|
|
667
|
+
rep.warn("lines[%d].sides.%s.items" % (i, side),
|
|
668
|
+
"sides.%s 有 %d 条,但左右对比构图每侧只画前 3 条"
|
|
669
|
+
% (side, len(its)), "scene_capacity")
|
|
670
|
+
|
|
574
671
|
# ---- v0.8 video 块:只支持 16:9 横屏 + 下采样输出 ----
|
|
575
672
|
# **不要在这里重写一遍规则**:允许列表 / 宽高比 / 上采样拒绝的权威实现在
|
|
576
673
|
# filmkit.video_plan()。以前这里是第二套手写判据,于是"1600x900"能过 filmcheck
|