dsh-motion 0.1.1 → 0.2.0

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/NOTICE.md CHANGED
@@ -8,6 +8,7 @@
8
8
  | [Manim Community](https://github.com/ManimCommunity/manim) | MIT | 可选渲染后端 |
9
9
  | [manimgx](https://github.com/academa-labs/manimgx) | MIT | 默认渲染后端 |
10
10
  | [SymPy](https://github.com/sympy/sympy) | BSD-3-Clause | `formula_check` |
11
+ | [Lemo-Opuscar](https://github.com/lemomo-ai/lemo-opuscar) | MIT(风格文档/代码)· CC BY 4.0(demo 静帧) | `swiss-motion` / `dataviz` / `blueprint` 三个风格的改编来源;只取语法与规范,不打包 demo 代码 |
11
12
  | [FFmpeg](https://ffmpeg.org) | LGPL/GPL(取决于构建) | 渲染、后期、抽帧;**不打包**,由用户自行安装 |
12
13
 
13
14
  字体不随包分发;若你的 `DESIGN.md` 指定了第三方字体,请确认其许可(OFL / 商用授权)。
package/README.md CHANGED
@@ -8,17 +8,54 @@
8
8
  **代码生成的数学 / 科普动画**,给 [DeepSeek Harness](https://github.com/deepseek-ai) 用的插件。
9
9
  画面由代码逐帧生成,不经过任何文生视频(text-to-video)模型。
10
10
 
11
- 提供四个工具:
11
+ 提供五个工具:
12
12
 
13
13
  | 工具 | 作用 |
14
14
  |---|---|
15
+ | `motion_frames` | **极简模式(默认)**:直接从场景出帧,不生成视频、不用 HF;几秒拿到 storyboard PNG 与 layout 问题 |
15
16
  | `motion_doctor` | 环境自检(Node / FFmpeg / Python venv / manimgx / sympy / Chrome / HyperFrames) |
16
17
  | `motion_render` | 渲染 Manim / manimgx 场景,可加运动模糊、颗粒、暗角、调色 |
17
- | `motion_probe` | 抽帧拼 contact sheet,报告全黑 / 过曝的空镜 |
18
+ | `motion_probe` | 成片抽帧拼 contact sheet,报告全黑 / 过曝的空镜 |
18
19
  | `formula_check` | 用 SymPy 判断两个表达式是否恒等 |
19
20
 
20
21
  外加三个技能:`animation-router`(题材路由)、`design-spec`(写 DESIGN.md)、`qa-rubric`(交付自检)。
21
22
 
23
+ ## 两种模式
24
+
25
+ | | 极简模式(默认) | 交付模式 |
26
+ |---|---|---|
27
+ | 画面来源 | `motion_frames`:按时间点直接出 PNG | `motion_render` → MP4 → HyperFrames 合成 |
28
+ | 是否需要 HF | 不需要 | 需要(字幕、旁白、混音、编码) |
29
+ | 检查方式 | 看帧(读图),没有"渲染完再抽帧" | `motion_probe` / `hyperframes check` |
30
+ | 典型耗时 | 2–6 秒/次 | 数秒–数十秒;HF 合成为分钟级 |
31
+ | 什么时候用 | 写场景、调节奏、改版式 | 定稿后出片 |
32
+
33
+ 先出帧、改到满意,再决定要不要渲染成片。`motion_probe` 是给已经存在的视频做体检的,不是默认步骤。
34
+
35
+ ## 风格库
36
+
37
+ 默认 **瑞士动态排版(swiss-motion)**;另外两套按题材选。全部改编自 [Lemo-Opuscar](https://github.com/lemomo-ai/lemo-opuscar)(MIT),不是自创。
38
+
39
+ | 风格 | 适合 | 规范 |
40
+ |---|---|---|
41
+ | `swiss-motion`(默认) | 公式、定义、数据结论 | [STYLE](styles/swiss-motion/STYLE.md) · [tokens](styles/swiss-motion/tokens.json) |
42
+ | `dataviz` | 趋势、比较、分布、科学数据 | [STYLE](styles/dataviz/STYLE.md) · [tokens](styles/dataviz/tokens.json) |
43
+ | `blueprint` | 几何证明、结构、尺寸关系 | [STYLE](styles/blueprint/STYLE.md) · [tokens](styles/blueprint/tokens.json) |
44
+
45
+ **参考与示例统一用 HyperFrames 的库**:[examples](https://hyperframes.heygen.com/examples)、[catalog](https://hyperframes.heygen.com/catalog)、[frame.md](https://hyperframes.heygen.com/prompting/design-systems)。本仓库不再内置样片/分集;`showcase/` 只放可复现的演示。
46
+
47
+ 项目 `DESIGN.md` 头部写 `style: swiss-motion` + `style_version: 1.0.0`。风格来源、HF 集成、抽帧能力与 Lemo 调研见 [docs/ai-native-motion.md](docs/ai-native-motion.md)。
48
+
49
+ ## 演示
50
+
51
+ 三个类别各一支 10 秒内短片,记录出帧/渲染耗时:
52
+
53
+ - 公式:[showcase/swiss-euler](showcase/swiss-euler)
54
+ - 数据:[showcase/dataviz-secant](showcase/dataviz-secant)
55
+ - 几何:[showcase/blueprint-pythagoras](showcase/blueprint-pythagoras)
56
+
57
+ 汇总见 [showcase/README.md](showcase/README.md)。
58
+
22
59
  ## 安装
23
60
 
24
61
  ```bash
@@ -39,10 +76,10 @@ motion_doctor
39
76
  | 依赖 | 是否必需 | 说明 |
40
77
  |---|---|---|
41
78
  | Node.js ≥ 22 | 必需 | `engines` 声明 |
42
- | FFmpeg + ffprobe | 必需 | 渲染与后期;不随包分发 |
43
- | Python 3 + venv | 数学功能需要 | `manimgx`、`sympy` |
44
- | Chrome | 成片渲染需要 | HyperFrames 优先复用系统 Chrome |
45
- | HyperFrames 0.8.193 | 成片需要 | `npx hyperframes@0.8.193`,不随包分发 |
79
+ | Python 3 + venv | 数学功能需要 | `manimgx`、`sympy`;极简出帧只需这一项 |
80
+ | FFmpeg + ffprobe | 成片/探针需要 | 后期与抽帧;出帧不需要;不随包分发 |
81
+ | Chrome | 用了 HF 才需要 | HyperFrames 优先复用系统 Chrome |
82
+ | HyperFrames | 交付模式需要 | `npx hyperframes`,不随包分发 |
46
83
 
47
84
  ### 准备 Python 环境
48
85
 
@@ -85,18 +122,20 @@ class Derivative(Scene):
85
122
  self.wait(1)
86
123
  ```
87
124
 
88
- ### 2. 渲染
125
+ ### 2. 先出帧(默认,不需要 HF)
89
126
 
90
127
  ```jsonc
91
- // motion_render
92
- { "file": "/abs/path/scenes/derivative.py", "scene": "Derivative", "width": 1920, "height": 1080, "fps": 30 }
128
+ // motion_frames
129
+ { "file": "/abs/path/scenes/derivative.py", "times": ["1.2", "4.5", "end"] }
93
130
  ```
94
131
 
95
- ### 3. 自检
132
+ 返回 `storyboard` PNG 路径与 layout 问题清单(实测 3–4 帧约 3–5 秒)。看图改场景,不满意就再来一轮;这一步不生成 mp4,也不需要 HyperFrames。
133
+
134
+ ### 3. 需要成片时再渲染
96
135
 
97
136
  ```jsonc
98
- // motion_probe
99
- { "video": "/abs/path/scenes/derivative.mp4", "frames": 12 }
137
+ // motion_render
138
+ { "file": "/abs/path/scenes/derivative.py", "scene": "Derivative", "width": 1920, "height": 1080, "fps": 60 }
100
139
  ```
101
140
 
102
141
  ### 4. 公式先验
@@ -108,14 +147,20 @@ class Derivative(Scene):
108
147
 
109
148
  ## 和 HyperFrames 的关系
110
149
 
111
- 本插件**不是**渲染框架。成片(合成、字幕、音频混流、编码)交给 HyperFrames:
150
+ 本插件**不是**渲染框架,只产出画面素材;合成、字幕、旁白、混音、编码交给 HyperFrames:
112
151
 
113
152
  ```bash
114
- npx hyperframes@0.8.193 init <project> --example swiss-grid --video <clip>.mp4
115
- npx hyperframes@0.8.193 check <project>
116
- npx hyperframes@0.8.193 render <project> -o out.mp4 --quality delivery
153
+ npx hyperframes init my-video
154
+ npx hyperframes add data-chart # 约 450 个现成 block / component
155
+ npx hyperframes check # 运行时、版式、对比度
156
+ npx hyperframes render -o out.mp4
117
157
  ```
118
158
 
159
+ - **风格契约**:HF 用 `frame.md`(`DESIGN.md` 的超集)做 brand truth;本仓库的 `DESIGN.md` / `styles/<name>/` 是同一套思路的轻量版,两边可以互读。
160
+ - **参考库**:不要把参考片塞进本仓库;直接看 HF 的 [examples](https://hyperframes.heygen.com/examples)、[catalog](https://hyperframes.heygen.com/catalog) 和 [frame.md templates](https://hyperframes.dev/design)。
161
+ - **分工**:manimgx 负责公式/坐标/几何这类确定性画面;HF 负责排版、图文、字幕、音频与交付。
162
+ - **HF 不必先出片再截图**:`npx hyperframes snapshot <project> --at 2.9,10.4` 直接抓指定时刻的 PNG;`--frames 10 --review` 出 contact sheet;只有要完整序列时才 `render --format png-sequence`。
163
+
119
164
  字幕建议“一份 transcript 出两个产物”:烧录进画面 + 独立 `.srt`。
120
165
 
121
166
  ## 故障排除
@@ -124,28 +169,66 @@ npx hyperframes@0.8.193 render <project> -o out.mp4 --quality delivery
124
169
  |---|---|---|
125
170
  | `找不到 Python 虚拟环境` | 未建 venv | 按上文创建,并 `pip install manimgx sympy` |
126
171
  | `找不到渲染器: …/manimgx` | venv 里没装 | `pip install manimgx` |
172
+ | `motion_frames` 报 layout 问题 | 压字 / 出画 / 字号过小 | 按返回的 `[n]` 逐条改场景;退出码 1 是“有问题”,不是崩溃 |
127
173
  | 渲染断字 / 字体回退 | 字体未声明 `@font-face` | 把 `.woff2` 放工程 `assets/fonts/` 并声明 |
128
174
  | `ffprobe` 缺失 | 只装了 ffmpeg | 装完整 FFmpeg,插件会退回 `ffmpeg -i` 解析 |
129
175
  | 抽帧全黑 | 片头/片尾空镜 | 用 `tpad=stop_mode=clone` 补帧,或剪掉 |
130
176
 
177
+ ## 本地开发(维护者)
178
+
179
+ 桌面 App **独占** `desktop` profile,CLI 不能管理它(`dsh plugin --profile desktop` 会直接报错)。
180
+ 把插件切到当前 checkout 的完整步骤、验证命令和禁忌清单记在
181
+ [MAINTAINING.md](https://github.com/cogito0823/dsh-motion/blob/main/MAINTAINING.md)。
182
+
183
+ 一句话版本:插件管理器里**卸载** `dsh-motion` → **安装本地目录** `/path/to/dsh-motion` → `⌘⇧R` 重启;
184
+ 之后 `motion_doctor` 第一行的「插件来源」会直接说明当前跑的是本地 checkout 还是 npm 安装。
185
+
186
+ 日常改代码**不需要重装**:link 安装一次后,改 `lib/ops.js` / `lib/doctor.js` 会在下一次工具调用时按 mtime 热加载;只有改工具名/参数 schema 才需要 `⌘⇧R`。风格、`tokens.json`、场景文件本来就是运行时读取,不用重启。详见 [docs/dev-loop.md](docs/dev-loop.md),也可以完全绕开 Harness:
187
+
188
+ ```bash
189
+ node scripts/dev.mjs frames showcase/swiss-euler/scene.py 1,3,end
190
+ node scripts/dev.mjs render showcase/swiss-euler/scene.py --fps 30
191
+ ```
192
+
131
193
  ## 发版(维护者)
132
194
 
133
- 首次发布会把名字与 2FA 绑好;之后用 GitHub Actions 的 Trusted Publishing,不需要 token,也不需要 OTP。
195
+ 首次发布会把包名与 2FA 绑好;之后走 GitHub Actions 的 **Trusted Publishing**(OIDC),不需要 token、不需要 OTP,自动附带 **provenance 签名**。
196
+
197
+ ### 一次性绑定
198
+
199
+ npm 包设置 → **Settings → Trusted Publisher → GitHub Actions**:
134
200
 
135
- 1. 在 npm 包设置里绑定可信发布者:**Package → Settings → Trusted Publisher → GitHub Actions**
136
- - owner: `cogito0823`
137
- - repository: `dsh-motion`
138
- - workflow: `release.yml`
139
- 2. 然后:
201
+ | 字段 | 值 |
202
+ |---|---|
203
+ | Organization or user | `cogito0823` |
204
+ | Repository | `dsh-motion` |
205
+ | Workflow filename | `release.yml` |
206
+ | Environment | 留空 |
207
+
208
+ Allowed actions:勾 **`npm publish`** 即可(`npm dist-tag` 用不到)。
209
+
210
+ ### 发一版
140
211
 
141
212
  ```bash
142
213
  npm version patch # 0.1.0 -> 0.1.1
143
- git push --follow-tags # 触发 release.yml
214
+ git push --follow-tags # 触发 .github/workflows/release.yml
144
215
  ```
145
216
 
146
- 工作流会校验 tag 与 `package.json` 一致、跑 check/smoke,再 `npm publish --provenance`。
217
+ 工作流会:校验 tag 与 `package.json` 版本一致 → `check` + `smoke` → `npm publish --provenance --access public`。
218
+
219
+ ### 两个必须知道的坑
220
+
221
+ 1. **npm CLI 必须 ≥ 11.5.1**。Node 22 自带的 npm 10 不支持 OIDC,会把占位 token 发出去,registry 返回误导性的 `404 not in this registry`。工作流里已加 `npm install -g npm@latest`。
222
+ 2. **发布后不是立刻可见**。CLI 会打印 `Your package is being processed and may take a few minutes to become available`,通常 1–2 分钟后 `npm view` 才查得到。这不是失败。
223
+
224
+ ### 验证
225
+
226
+ ```bash
227
+ npm view dsh-motion version
228
+ npm view dsh-motion@<version> dist.attestations # 应含 provenance / SLSA v1
229
+ ```
147
230
 
148
- 若工作流失败,退回手动发布:`npm publish --access public`(需 passkey 或 OTP)。
231
+ npm 包页面会出现 **“Built and signed on GitHub Actions”**。
149
232
 
150
233
  ## 许可
151
234
 
@@ -0,0 +1,233 @@
1
+ # AI 原生动效:风格、HyperFrames 与极简模式
2
+
3
+ 调研时间 2026-10-25。结论先行:
4
+
5
+ 1. **风格不是形容词,是外部输入。** 通行做法是给 agent 一份可引用的 truth(`frame.md` / `DESIGN.md` / 参考片),把它写成规范,再用**真实代码出的风格帧**验收;不是让模型"感觉一下"。
6
+ 2. **风格库是常见基础设施。** HyperFrames 有 catalog(约 450 个 block/component)和 `frame.md` 模板;[Lemo-Opuscar](https://github.com/lemomo-ai/lemo-opuscar) 有 59 个纯代码影片风格,每个是一份 `STYLE.md`(style prompt)+ 一支 demo。
7
+ 3. **极简模式 = 先出帧。** 在写场景的阶段直接渲染关键帧(不编码视频、不进 Chrome/HF),看过再继续;视频只在定稿后生成。
8
+
9
+ ## 1. 通行做法:风格怎么确定
10
+
11
+ ### 1.1 先给 truth,再谈风格
12
+
13
+ HyperFrames 的 [design systems 文档](https://hyperframes.heygen.com/prompting/design-systems) 说得很直接:让 agent "make it on-brand" 是最模糊的指令,agent 只能自己发明一个 brand。修法永远是给一份 **source of brand truth**:design spec、线上站点或 Figma 文件。
14
+
15
+ `frame.md` 的 frontmatter 是"机器可读的品牌":精确 hex、字体家族、字重关系;正文写 intent 与 Do/Don't。文档给了一个可复用的边界:
16
+
17
+ - **严格**:品牌色、字体、字重关系、底色;
18
+ - **为视频放大**:字号、装饰透明度、描边宽度;
19
+ - **交给 agent 编排**:版面、间距、运动。
20
+
21
+ 原话是 "Pin the palette and typography. Delegate the frame."——这也解释了为什么我们的 `STYLE.md` 把版面坐标写死、却允许分集覆盖局部:**token 是契约,构图是创作**。
22
+
23
+ ### 1.2 风格帧先于视频
24
+
25
+ Lemo-Opuscar 的 [DIRECTOR.md](https://github.com/lemomo-ai/lemo-opuscar/blob/main/DIRECTOR.md) 把这一步叫 **"Prove the look"**:
26
+
27
+ - 有角色:一张 model sheet + 两张风格帧;
28
+ - 无角色:三张风格帧,其中一张是 signature shot;
29
+ - 必须用**真实绘制代码**渲染,不是 mock-up;先对照 `STYLE.md` 自检,再往下走;
30
+ - 分镜(6–9 张关键镜头拼一张 sheet)**只在用户要求时**才做,做完等确认。
31
+
32
+ 这与"渲染完再抽帧检查"是两件事:前者在写代码的循环里,成本是秒级;后者在成片之后,成本是分钟级。
33
+
34
+ ### 1.3 风格库的两种形态
35
+
36
+ | 形态 | 例子 | 作用 |
37
+ |---|---|---|
38
+ | 组件/区块库 | HF catalog(约 450 个 block / component) | 不手搓转场、字幕条、图表、3D 相机 |
39
+ | 风格库 | Lemo 59 个 `STYLE.md` | 定语气、材质、颜色逻辑、运动语法 |
40
+
41
+ Lemo 的每个风格文件是一份 **style prompt**:定位与"不是什么"、材料与渲染、颜色逻辑、字体与字幕、运动质量、相机语法、声音、native moves、媒介陷阱、引擎、变化空间。它不写具体故事——故事是用户的。这和本仓库 `STYLE.md` 的结构同源。
42
+
43
+ ### 1.4 都建立在确定性上
44
+
45
+ HF 的卖点是 "same input, same frames, same output":HTML + 可 seek 的动画,headless Chrome 逐帧截,FFmpeg 编码。Lemo 同样要求 `render(t)` 是纯函数:没有 `Date.now()`、没有未播种随机、任意帧可单独渲染。**风格可复现的前提是画面可复现。**
46
+
47
+ ## 2. 要不要用 Lemo 这类风格库
48
+
49
+ 用它的**方法**,不必用它的**素材**:
50
+
51
+ - Lemo-Opuscar 明确写着风格是为 Claude Opus 5.5 调的,别的模型不一定复现得好;代码 MIT、demo 与静帧 CC BY 4.0;
52
+ - 它的 59 个风格覆盖纸感、印刷、像素、3D、纪录片等,适合当"语言菜单",不适合直接当品牌规范;
53
+ - 可借鉴的三件事:`STYLE.md` 作为 style prompt、三张风格帧验收、分镜只在被要求时做;
54
+ - 授权上,demo 静帧可商用但需署名 LemoLab;风格文件本身是 MIT。
55
+
56
+ 本仓库的选择:删除自创的 `dark-editorial`,改为 3 套改编自 Lemo 的风格(`swiss-motion` / `dataviz` / `blueprint`),每套按 `STYLE.md` + `tokens.json` + `theme.py` + `preview.jpg` 落地;外部参考统一指向 HF。
57
+
58
+ ## 3. 和 HyperFrames 怎么结合
59
+
60
+ HF 是"HTML → MP4"的合成/渲染层,不是绘制层;本插件是绘制层。
61
+
62
+ | 层 | 负责 | 产物 |
63
+ |---|---|---|
64
+ | manimgx / dsh-motion | 公式、坐标、几何、逐帧确定性画面 | PNG 帧 / MP4 clip |
65
+ | HyperFrames | 排版、字幕、旁白、混音、转场、编码 | 成片 MP4 / WebM / PNG sequence |
66
+ | 共享契约 | `DESIGN.md` / `frame.md` | 颜色、字体、禁用项、语气 |
67
+
68
+ 接入方式(按成本从低到高):
69
+
70
+ 1. **只借规范**:HF 的 `frame.md` 与本仓库 `DESIGN.md` 互读;不装 HF 也能用同一套 token。
71
+ 2. **借组件**:`npx hyperframes add <block>` 取现成图表、字幕、转场,不必重画。
72
+ 3. **整片合成**:把 `motion_render` 的 clip 放进 HF composition,字幕走 composition 层,最后 `npx hyperframes render`。
73
+
74
+ ```bash
75
+ npx hyperframes init my-video
76
+ npx hyperframes add data-chart
77
+ npx hyperframes check
78
+ npx hyperframes render -o out.mp4
79
+ ```
80
+
81
+ HF **不需要先生成视频再截图**。它有三条不产出 mp4 的路径:
82
+
83
+ | 命令 | 产出 | 用途 |
84
+ |---|---|---|
85
+ | `npx hyperframes snapshot <project> --at 2.9,10.4,18.7` | 指定时刻的 PNG | 版式/关键帧检查,最接近"抽帧" |
86
+ | `npx hyperframes snapshot <project> --frames 10 --review` | 等间隔帧 + contact sheet + 转场接缝帧 | 通看全片 |
87
+ | `npx hyperframes render --format png-sequence` | 完整 RGBA 帧序列 | 需要每一帧时 |
88
+ | `npx hyperframes check` | 运行时/版式/对比度/运动的报告 | 不开视频的体检 |
89
+
90
+ `snapshot` 的说明原话是 "Capture specific frames as PNGs, without waiting for a full render":它会 bundle 项目、起 headless Chrome、seek 到指定时刻截图,但不编码视频。`--zoom` 还能裁局部、`--against` 能和参考片逐帧对比。
91
+
92
+ 所以结论是:**HF 可以抽帧,而且 snapshot 就是为这件事做的**;只有需要完整序列时才用 png-sequence。对本插件来说,`motion_frames`(manimgx inspect)仍然是更轻的一层,因为它连浏览器都不用起。
93
+
94
+ ## 4. 极简模式:直接拿到帧
95
+
96
+ ### 4.1 通用实现
97
+
98
+ manimgx 自带 `inspect`:只画请求的时刻,做版式检查,**不编码视频**。
99
+
100
+ ```bash
101
+ manimgx inspect scene.py -t 1.2,4.5,end
102
+ ```
103
+
104
+ - 输出一张(或多张)storyboard PNG,每格标注 `#play t=… file:line`;
105
+ - 同时报 layout 问题:压字、出画、字号过小、线/填充盖字;
106
+ - 有 layout 问题时退出码为 1,但报告仍然完整。
107
+
108
+ 本仓库把它包成 `motion_frames`,返回结构化 JSON(`storyboard` 路径、`layoutProblems`、`durationSec`、`wallSec`),模型可以直接读图。实测 4 帧约 3–5 秒;同一支 10 秒 / 60fps 场景渲染约 6 秒。渲染成本随**时长**线性增长,看帧成本只跟**采样数**有关,长片差距才明显(对比 HF 合成则是分钟级)。
109
+
110
+ ### 4.2 推荐循环
111
+
112
+ ```
113
+ DESIGN.md
114
+ → motion_frames(3–6 帧:开场 / 转折 / 结论)
115
+ → 改场景 → 再出帧(秒级)
116
+ → 需要给用户看分镜时:6–9 帧(真实代码)
117
+ → formula_check
118
+ → motion_render(定稿后)
119
+ → 需要字幕/旁白/混音:进 HF
120
+ → motion_probe / hyperframes check(只对成片)
121
+ ```
122
+
123
+ ### 4.3 为什么快
124
+
125
+ | 做法 | 成本 | 什么时候发现版式问题 |
126
+ |---|---|---|
127
+ | `motion_frames` | 2–6 秒(与时长无关) | 写场景时 |
128
+ | `motion_render` + `motion_probe` | 5 秒–1 分钟(随时长) | 渲染后 |
129
+ | HF `check` / `png-sequence` | 分钟级(起浏览器 + 跑 composition) | 合成后 |
130
+
131
+ 关键不是"抽帧"本身,而是**把检查点前移**:帧是绘制的直接产物,视频只是帧的编码;在编码之前检查,就省掉了编码、起浏览器、解码抽帧这几次搬运,而且检查成本不再随时长膨胀。
132
+
133
+ ### 4.4 建议的默认
134
+
135
+ - 默认工具链:`motion_frames`(不装 HF 也能跑);
136
+ - 默认检查:看 storyboard PNG + `layoutProblems`,不看 `blankFrames`(还没有视频);
137
+ - 默认不生成:mp4、HF 工程、视频抽帧 contact sheet;
138
+ - 需要交付时才升级到 `motion_render` / HF;
139
+ - 成片检查(`motion_probe`)只在已经有 mp4 时运行,不做"为了检查而渲染"。
140
+
141
+ ## 参考
142
+
143
+ - [HyperFrames design systems](https://hyperframes.heygen.com/prompting/design-systems)
144
+ - [HyperFrames examples](https://hyperframes.heygen.com/examples) · [catalog](https://hyperframes.heygen.com/catalog)
145
+ - [HyperFrames CLI rendering](https://hyperframes.heygen.com/guides/rendering)
146
+ - [Lemo-Opuscar](https://github.com/lemomo-ai/lemo-opuscar) · [DIRECTOR.md](https://github.com/lemomo-ai/lemo-opuscar/blob/main/DIRECTOR.md) · [TECHNIQUE.md](https://github.com/lemomo-ai/lemo-opuscar/blob/main/TECHNIQUE.md)
147
+ - 本地 `manimgx inspect --help`(manimgx 0.1.1)
148
+
149
+ ## 4.5 效率:大家怎么缩短时间
150
+
151
+ 共识是**把检查点前移、把渲染延后**:
152
+
153
+ | 做法 | 出处 | 效果 |
154
+ |---|---|---|
155
+ | 先出静帧/contact sheet,不先出片 | Lemo `still.mjs` + `sheet.py`;HF `snapshot`;本仓库 `motion_frames` | 秒级看版式,省掉编码 |
156
+ | 指定时刻抽帧,而不是等整段 | HF `snapshot --at` / `--frames` / `--review` | 不渲染整条时间线 |
157
+ | 低分辨率 / draft 预览,定稿才 full | HF `--quality draft`、`--resolution`;Lemo 先 still | 迭代快、画质最后再提 |
158
+ | 并行渲染 | Lemo `video.mjs --workers 3`;HF GPU / Lambda / Cloud Run | 线性缩短编码时间 |
159
+ | 只重渲改动镜头 | 帧是 `render(t)` 纯函数,可单帧/单段重跑 | 不重做全片 |
160
+ | 独立 rubric 评审代替反复肉眼 | mg-styles-15 的 7 维 rubric、Lemo 的 review loop | 一轮改到点上 |
161
+ | 确定性 + 缓存 | HF `same input, same frames`;Lemo 无随机/无时钟 | 可回归、可并发 |
162
+
163
+ 本仓库三个 demo 的实测(同一台机器):
164
+
165
+ - 出 3 帧:**2.4–4.3 s**;渲染 6–10 秒成片:**2.8–5.0 s**;
166
+ - 关键不是"帧比视频快多少",而是**看帧成本与片长无关**,且不经过编码、起浏览器、抽帧三次搬运;
167
+ - 如果已经在 HF 里,用 `hyperframes snapshot` 而不是"先 render 再 ffmpeg 截图"。
168
+
169
+ ## 4.6 开发调试
170
+
171
+ 服务端插件没有 HMR,但也不需要每次重装:
172
+
173
+ - `link:` 安装一次即可;改 `lib/ops.js` / `lib/doctor.js` 由 mtime 热加载,下一次工具调用生效;
174
+ - 改 `styles/*`、场景、tokens 本来就不用重启;
175
+ - 只有工具名/描述/参数 schema 变化才 `⌘⇧R`;
176
+ - 可以完全绕开 Harness:`node scripts/dev.mjs frames|render|probe|doctor ...`。
177
+
178
+ 细节见 [dev-loop.md](dev-loop.md)。
179
+
180
+ ## 5. 现成的风格体系(2026-10 可直接采用)
181
+
182
+ 不必从零写风格文档。这个生态已经沉淀出几套公开、可复用的体系:
183
+
184
+ | 体系 | 规模 | 提供什么 | 适合拿什么 |
185
+ |---|---|---|---|
186
+ | [Lemo-Opuscar](https://github.com/lemomo-ai/lemo-opuscar) | 59 个库内风格 + OPUSCAR 98 的 98 个 | 每个风格 `style.json`(slug / 中英文名 / 分类 / uses / 时长)+ `STYLE.md`(11 节:定位、材料、颜色逻辑、字体、运动、相机、声音、native moves、陷阱、引擎、变化空间)+ demo 片 + `DIRECTOR.md` / `TECHNIQUE.md` + 改风格流程 | 整套 schema、风格索引、选风格与改风格的方法 |
187
+ | [mg-styles-15](https://github.com/Vincentwei1021/mg-styles-15) | 15 个 10 秒风格 | 每个风格:完整 prompt + 全部源码 + 成片;[template.md](https://github.com/Vincentwei1021/mg-styles-15/blob/main/template.md) 四段式(Output / Technical route / Creative seed / Signature features);[rubric.md](https://github.com/Vincentwei1021/mg-styles-15/blob/main/rubric.md) 独立评审 + 两轮修改 | 写新风格的模板、可检验的 signature features、评审 rubric |
188
+ | [javascript-animation-skills](https://github.com/iart-ai/javascript-animation-skills) | 1 个 skill 包,多种画风 | 从题材推导 look、beat grid / shot list 数据化、contact sheet 自检、零素材审计 | agent 工作流与自检清单 |
189
+ | [opus-video-skills](https://github.com/tuzhechen2005/opus-video-skills) | 2 个风格 | 每个风格一个 skill:`SKILL.md` + 可运行 `template/` + worked example | 风格即 skill 的工程组织方式 |
190
+ | [HyperFrames frame.md](https://hyperframes.heygen.com/prompting/design-systems) | 一组设计模板(画廊,数量未逐一核对) | 品牌 token(frontmatter)+ 意图/Do-Don't + 模板画廊 | 品牌/版式契约与交付层 |
191
+ | [ClaudeAnimationBase](https://github.com/JohnHeibel/ClaudeAnimationBase) | 基础引擎 | 帧为时间纯函数、headless 渲染、逐镜头评审 | 底层渲染约定 |
192
+
193
+ ### 共同做法
194
+
195
+ 1. **风格 = 不变量 + 自由故事。** `STYLE.md` 锁死 look、颜色、字体、运动、声音;故事、镜头、节奏由导演决定(Lemo 的 AGENTS.md 原话:`STYLE.md` is fixed;everything else is yours to direct)。
196
+ 2. **风格按名字选,不按形容词选。** 用户说 "swiss-motion" / "瑞士动态排版",agent 查索引、读 `STYLE.md`;没选就推荐 2–3 个最接近的。
197
+ 3. **风格文档有固定骨架。** Lemo 是 11 节;mg-styles-15 是四段式,其中 signature features 必须可检验("cubic-bezier(0.34,1.56,0.64,1),错峰 2–4 帧",不是"要有弹性")。
198
+ 4. **先风格帧,后分镜,最后成片。** 三张真实代码出的风格帧 → 用户要看才做 6–9 张分镜 → 成片;风格帧不过不往下走。
199
+ 5. **独立评审。** 另一个 AI 用 rubric 按 7 个维度打分(风格保真、概念、运动、排版、质感、声画同步、技术),给 5–10 条带时间码的修改,再改两轮。
200
+ 6. **确定性。** 帧是 `render(t)` 的纯函数;同一输入同一输出,便于批量与回归。
201
+
202
+ ### 对本仓库的建议
203
+
204
+ 不要搬 59 套,也不要再自己发明一套写法。按 Lemo 的 schema 升级 `styles/`。
205
+
206
+ **筛选标准**(按顺序):
207
+
208
+ 1. **内容原语**:公式/排版、图表/数据、几何/工程、流程/空间、概念/文字——每个原语一套,避免重叠;
209
+ 2. **可实现性**:只需 2D 矢量、文字、坐标轴、标注;不依赖照片、实拍、角色绑定、重着色器;
210
+ 3. **去重**:已有 `swiss-motion` 占住“排版/公式”,就不再接同角色的风格;
211
+ 4. **证据**:`style.json` 的 `uses` 对得上,再用 `motion_frames` 出 3 张风格帧验收;不过就换。
212
+
213
+ 按这套标准(2026-10-25 查 `style.json`):
214
+
215
+ | 风格 | `uses` | 判定 |
216
+ |---|---|---|
217
+ | `swiss-motion` | Brand identity / Events / Design education | **核心**:公式与排版 |
218
+ | `dataviz` | Data journalism / Reports / Climate & science | **核心**:图表与数据 |
219
+ | `blueprint` | Engineering / Hardware launches / Tech explainers | **核心**:几何与工程 |
220
+ | `whiteboard` | Explainers / Online courses / Science | 可选:科学讲解;手绘笔画成本中等 |
221
+ | `iso-infographic` | Process explainers / Supply chains / Annual reports | 可选:流程/空间;等距投影成本中等 |
222
+ | `pictogram-motion` | Sports events / Visual identity / Event openers | 不选:偏活动/体育,公式场景弱 |
223
+ | `dark-keynote` | Product launches / SaaS / Keynotes | 不选:与 `swiss-motion` 的排版角色重叠 |
224
+
225
+ 已落 **3 个核心**(`swiss-motion` / `dataviz` / `blueprint`);`whiteboard`、`iso-infographic` 留作可选,等有真实题材再验。这个列表是筛选结果,不是永久名单——风格帧验收不过就换。
226
+
227
+ 落地方式:
228
+
229
+ 1. 每个风格一个目录:`style.json`(元数据)+ `STYLE.md`(不变量,按 Lemo 结构)+ `tokens.json` + `theme.py` + `preview.jpg`;
230
+ 2. 用 mg-styles-15 的四段式模板写**新**风格,用它的 rubric 做评审;
231
+ 3. 画面仍由 manimgx 画,不复制它们的 Canvas/WebGL demo 代码,只取语法与 prompt;
232
+ 4. 交付层继续交给 HF,`frame.md` 只做品牌/版式契约;
233
+ 5. 标注来源与许可:Lemo 代码/文档 MIT,demo 静帧 CC BY 4.0(署名 LemoLab);mg-styles-15 MIT;HF Apache-2.0。
@@ -0,0 +1,55 @@
1
+ # 开发调试:不重启插件
2
+
3
+ ## 先分清哪些改动需要重启
4
+
5
+ | 改动 | 是否需要重启 Harness | 原因 |
6
+ |---|---|---|
7
+ | `styles/*`(STYLE / tokens / theme / preview) | **不需要** | 场景运行时才读 Python/JSON |
8
+ | `showcase/*` 场景 | **不需要** | manimgx 每次执行都重新加载 |
9
+ | `lib/ops.js`、`lib/doctor.js` | **不需要**(本仓库已做 mtime 热加载) | 工具每次调用按 mtime 重新 import |
10
+ | 工具名 / 描述 / 参数 schema(`lib/index.js`) | 需要 | 注册发生在 `apply()` 启动时 |
11
+ | `lib/fresh.js`、`lib/env.js` | 需要 | 前者是加载器本身,后者被静态 import 缓存 |
12
+ | 安装方式变化(link ↔ npm) | 需要,且要重装 | profile 清单只有一个依赖字段 |
13
+
14
+ 所以"改代码就要重装"是误判:**link 安装一次之后不用再重装**;只有改工具 schema 才需要 `⌘⇧R`。
15
+
16
+ ## 方案 1:mtime 热加载(已内置)
17
+
18
+ `lib/fresh.js` 按文件 mtime 重新 import:
19
+
20
+ ```js
21
+ const mtime = statSync(new URL(href)).mtimeMs
22
+ if (hit && hit.mtime === mtime) return hit.module
23
+ return import(`${href}?t=${mtime}`)
24
+ ```
25
+
26
+ `lib/index.js` 里所有工具实现都走 `importFresh()`:
27
+
28
+ - 改 `ops.js` 的函数体 → 下一次 `motion_frames` / `motion_render` 调用就生效;
29
+ - 改 `doctor.js` → 下一次 `motion_doctor` 生效;
30
+ - 没有变化时命中缓存,额外开销是一次 `statSync`。
31
+
32
+ 一次性的代价:这个机制本身要重启一次才能加载;之后改实现不用再重启。
33
+
34
+ ## 方案 2:不走 Harness 的 dev CLI
35
+
36
+ ```bash
37
+ node scripts/dev.mjs frames showcase/swiss-euler/scene.py 1,3,end
38
+ node scripts/dev.mjs render showcase/swiss-euler/scene.py --fps 30
39
+ node scripts/dev.mjs probe out.mp4
40
+ node scripts/dev.mjs doctor
41
+ ```
42
+
43
+ 它直接 import `lib/ops.js`,不经过插件注册,适合调参数、量耗时、跑回归。改完再让工具层调用同一份代码。
44
+
45
+ ## 方案 3:把变化关在数据里
46
+
47
+ - 风格:只改 `tokens.json` / `STYLE.md`,场景 import theme,本来就不用重启;
48
+ - 场景:改 `.py` 直接重跑 `manimgx`;
49
+ - 交付:HF 的 `snapshot` / `check` 也不依赖本插件。
50
+
51
+ ## 不要做的事
52
+
53
+ - 每次改代码都卸载/重装插件:link 安装不需要;
54
+ - 在 `lib/index.js` 里写实现:它一改就要重启;实现放 `ops.js` / `doctor.js`;
55
+ - 改 `env.js` 后指望热加载:它是静态 import,仍要重启。
package/lib/doctor.js CHANGED
@@ -4,7 +4,9 @@
4
4
  */
5
5
 
6
6
  import { execFile } from 'node:child_process'
7
- import { existsSync } from 'node:fs'
7
+ import { existsSync, readFileSync } from 'node:fs'
8
+ import { dirname, join, sep } from 'node:path'
9
+ import { fileURLToPath } from 'node:url'
8
10
  import { promisify } from 'node:util'
9
11
 
10
12
  import {
@@ -19,6 +21,25 @@ import {
19
21
 
20
22
  const exec = promisify(execFile)
21
23
 
24
+ /** 当前这份代码所在的 lib 目录与包根(npm 安装还是本地 checkout 一眼可辨)。 */
25
+ const LIB_DIR = dirname(fileURLToPath(import.meta.url))
26
+ const PACKAGE_ROOT = dirname(LIB_DIR)
27
+
28
+ /**
29
+ * 报告"正在跑的是哪一份 dsh-motion":版本 + lib 路径 + 安装形态。
30
+ * 用 import.meta.url 推导,符号链接会被 Node 解析成真实路径,所以 link 安装会显示仓库路径。
31
+ */
32
+ export function pluginSource() {
33
+ let version = 'unknown'
34
+ try {
35
+ version = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version ?? 'unknown'
36
+ } catch {
37
+ // 读不到 package.json 时只报路径
38
+ }
39
+ const kind = LIB_DIR.includes(`${sep}node_modules${sep}`) ? 'npm 安装' : '本地 checkout'
40
+ return { version, kind, lib: LIB_DIR }
41
+ }
42
+
22
43
  async function runVersion(bin, args) {
23
44
  try {
24
45
  const { stdout, stderr } = await exec(bin, args, { timeout: 15_000 })
@@ -44,6 +65,9 @@ export async function doctor(config = {}) {
44
65
  const rows = []
45
66
  const add = (item, ok, detail, fix) => rows.push({ item, ok, detail, fix })
46
67
 
68
+ const source = pluginSource()
69
+ add('插件来源', true, `${source.version} · ${source.kind} · ${source.lib}`, null)
70
+
47
71
  const nodeMajor = Number(process.versions.node.split('.')[0])
48
72
  add('Node.js', nodeMajor >= 22, `v${process.versions.node}`, nodeMajor >= 22 ? null : '升级到 Node 22 或更高')
49
73
 
@@ -81,6 +105,7 @@ export async function doctor(config = {}) {
81
105
 
82
106
  return {
83
107
  ready,
108
+ source,
84
109
  summary: ready.math
85
110
  ? '数学渲染与成片可用'
86
111
  : ready.delivery
package/lib/fresh.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 开发期热加载:按文件 mtime 重新 import,避免每次改 lib 都重启 Harness。
3
+ *
4
+ * 只对"实现"生效(ops/doctor);工具名、描述、参数 schema 在 apply() 时
5
+ * 注册,改这些仍然需要重启。样式、场景、tokens 是运行时读取的,本来就不用重启。
6
+ */
7
+
8
+ import { statSync } from 'node:fs'
9
+
10
+ const cache = new Map()
11
+
12
+ export async function importFresh(url) {
13
+ const href = typeof url === 'string' ? url : url.href
14
+ if (process.env.DSH_MOTION_NO_FRESH === '1') return import(href)
15
+ const mtime = statSync(new URL(href)).mtimeMs
16
+ const hit = cache.get(href)
17
+ if (hit && hit.mtime === mtime) return hit.module
18
+ const module = await import(`${href}?t=${mtime}`)
19
+ cache.set(href, { mtime, module })
20
+ return module
21
+ }