@ninkch/dsh-artcraft 0.2.10 → 0.3.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/README.md +38 -13
- package/README.zh-CN.md +33 -10
- package/apps/catalog.json +53 -1
- package/artcraft.config.json.example +5 -2
- package/client.js +11 -2
- package/cordis.patch.yml +4 -0
- package/index.js +418 -17
- package/package.json +6 -2
- package/recipes/recipes.json +315 -32
- package/skills/README.md +43 -0
- package/skills/artcraft-batch-pipeline/SKILL.md +96 -0
- package/skills/artcraft-color-light/SKILL.md +96 -0
- package/skills/artcraft-color-light/references/develop-controls.md +43 -0
- package/skills/artcraft-color-light/references/lumetri-and-scopes.md +65 -0
- package/skills/artcraft-delivery-qc/SKILL.md +105 -0
- package/skills/artcraft-delivery-qc/references/platform-specs.md +74 -0
- package/skills/artcraft-edit-rhythm/SKILL.md +110 -0
- package/skills/artcraft-edit-rhythm/references/transitions.md +53 -0
- package/skills/artcraft-film-study/SKILL.md +109 -0
- package/skills/artcraft-motion-vfx/SKILL.md +117 -0
- package/skills/artcraft-motion-vfx/references/effect-catalogue.md +429 -0
- package/skills/artcraft-motion-vfx/references/scene-to-effect.md +103 -0
- package/skills/artcraft-multicam/SKILL.md +79 -0
- package/skills/artcraft-ops/SKILL.md +116 -0
- package/skills/artcraft-ops/references/command-families.md +57 -0
- package/skills/artcraft-shot-language/SKILL.md +112 -0
- package/skills/artcraft-shot-language/references/shot-vocabulary.md +98 -0
- package/skills/artcraft-sound-design/SKILL.md +95 -0
- package/skills/artcraft-sound-design/references/essential-sound.md +77 -0
- package/skills/artcraft-style-bible/SKILL.md +84 -0
- package/skills.js +84 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: artcraft-sound-design
|
|
3
|
+
description: 声音设计决策——对白优先原则、清理链顺序(降噪/去齿音/EQ/压缩/响度)、Essential Sound 四类素材的正确用法、响度标准(-14 LUFS 流媒体 / -23 LUFS 广播 / -16 播客)与真峰值、音乐与音效的进入退出点、声音先行、混音空间与自动化,映射到 FilmCraft essentialSound/mixer/clipMixer/tts 与 SoundCraft mix/automation/track/session.audio_health。用于配音、混音、响度归一与成片音质把关。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 声音设计
|
|
7
|
+
|
|
8
|
+
**第一原则:对白永远优先。** 任何处理只要让台词听不清,就是错的——音乐、音效、氛围全部让路。
|
|
9
|
+
|
|
10
|
+
## 一、三层结构
|
|
11
|
+
|
|
12
|
+
| 层 | 内容 | 目标 |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| 对白 | 台词、旁白 | 清晰、响度稳定、无噪声 |
|
|
15
|
+
| 音乐 | 主题曲、铺底 | 情绪推进,不抢对白 |
|
|
16
|
+
| 音效/环境 | 动效、氛围、拟音 | 真实感与强调 |
|
|
17
|
+
|
|
18
|
+
**层级纪律**:对白 −12~−6 dBFS 峰值区间;音乐比对白低 6–12 dB(对白段落);音效点可瞬时突出但不超过对白。
|
|
19
|
+
|
|
20
|
+
## 二、对白清理链(顺序不能乱)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
1 降噪(先去底噪,否则后续放大噪声)
|
|
24
|
+
2 去齿音 / 去咝声
|
|
25
|
+
3 EQ(高通去隆隆声;中频提清晰度;去箱音)
|
|
26
|
+
4 压缩(控制动态,让小声也听得见)
|
|
27
|
+
5 响度归一(最后一步,全片一致)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**FilmCraft 落点**:`essentialSound.setType {type:"dialogue"}` → `essentialSound.set {key:"repair.noise.on|repair.noise.amount|…"}` → `essentialSound.applyPreset {preset, type}` → `essentialSound.autoMatch {target:lufs}`(响度匹配)→ `essentialSound.inspect` 核对。
|
|
31
|
+
|
|
32
|
+
**SoundCraft 落点**:`mix.insert`(插件插入)· `mix.insert_param`(插件参数)· `track.bypass_delay/dynamics`(旁通对比)· `session.audio_health`(工程音频体检)· `file.bounce_stems`(分轨导出)· `automation.set_point / write_range / thin`(自动化写点与稀疏化)。
|
|
33
|
+
|
|
34
|
+
## 三、音乐:进入、铺底与退出
|
|
35
|
+
|
|
36
|
+
- **进入点**:在情绪转折前 0.5–1s 进入(不要与切点完全同帧,易"撞")。
|
|
37
|
+
- **铺底**:对白段落把音乐压 6–12 dB;有歌词的音乐会与台词抢注意力——**对白段落避免歌词**。
|
|
38
|
+
- **退出**:在段落落点后留 0.5–1s 尾音或干脆切掉,制造呼吸。
|
|
39
|
+
- **自动闪避**:FilmCraft `essentialSound.generateDucking {clips}` 自动生成音乐避让对白的音量关键帧(比手写快且不会漏)。
|
|
40
|
+
|
|
41
|
+
## 四、音效与环境
|
|
42
|
+
|
|
43
|
+
- 音效服务画面:动作 → 落点音;转场 → 过渡音;情绪 → 心跳/耳鸣等主观音。
|
|
44
|
+
- 环境声是"真实感的地基":即使很轻也要铺(否则对白像在真空里)。
|
|
45
|
+
- **不要每个动作都配音效**(音效过密 = 廉价);重点动作才给强调。
|
|
46
|
+
|
|
47
|
+
## 五、响度标准(客观交付判据)
|
|
48
|
+
|
|
49
|
+
| 平台/场景 | 目标响度 | 真峰值上限 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| 流媒体/Web(B 站、YouTube、抖音等) | **−14 LUFS** | −1 dBTP |
|
|
52
|
+
| 广播(EBU R128) | **−23 LUFS** | −2 dBTP |
|
|
53
|
+
| 播客 | −16 LUFS | −1.5 dBTP |
|
|
54
|
+
|
|
55
|
+
**做法**:先做动态处理(压缩/限制),**最后**统一响度;`essentialSound.autoMatch {target}` 一键匹配,或用 `mix.*` / SoundCraft `automation.*` 手工到位。交付前必须复核真峰值,避免削波。
|
|
56
|
+
|
|
57
|
+
## 六、混音空间与自动化
|
|
58
|
+
|
|
59
|
+
| 目标 | 手法 | 落点 |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| 音量层级 | 轨道推子 + 关键帧 | FilmCraft `mixer.setStrip {volumeDb}` · `mixer.setValue {lane:"volume\|pan\|mute"}` |
|
|
62
|
+
| 声像定位 | 对白居中,环境铺宽 | `mixer.setValue {lane:"pan"}` |
|
|
63
|
+
| 分组处理 | 对白/音乐/音效各走子混音 | `mixer.addSubmix {name, channels}` |
|
|
64
|
+
| 自动化写入 | 实时推子录制或写点 | `mixer.recordStart/recordStop` · `mixer.touch/release` · SoundCraft `automation.write_range/set_point` |
|
|
65
|
+
| 片段级微调 | 单条片段增减益 | FilmCraft `clipMixer.set {effect:"volume"\|"panner"}` · SoundCraft `clip.*` |
|
|
66
|
+
| **响度一致化** | 片段增益 ↔ 音量自动化互转 | SoundCraft `automation.clip_gain_to_volume / volume_to_clip_gain` |
|
|
67
|
+
| 分轨交付 | 给混音师 stem | `file.bounce_stems {dir, format, bit_depth}` · `file.export_clip_groups` |
|
|
68
|
+
| 整混导出 | 母版 | `file.bounce_mix` · `file.export_selected_tracks_as_session` |
|
|
69
|
+
|
|
70
|
+
## 七、旁白合成(家族自带 TTS)
|
|
71
|
+
|
|
72
|
+
FilmCraft `tts.voices`(列音色)→ `tts.preview`(试听)→ `tts.create {text, voice, pitch, pace}` → `tts.render`(合成)→ `tts.edit`(改文本/音色/语速)。`tts.downloadVoices` 可下载自然音色。
|
|
73
|
+
|
|
74
|
+
**用法要点**:旁白语速 pace 与画面节奏匹配;段落间留气口;合成后仍要走上面的清理与响度链。
|
|
75
|
+
|
|
76
|
+
## 八、声音质检清单
|
|
77
|
+
|
|
78
|
+
- [ ] 台词全程清晰,无被音乐/音效盖住
|
|
79
|
+
- [ ] 无爆音、无削波(真峰值达标)
|
|
80
|
+
- [ ] 底噪已处理,静音段不是"死寂"(有环境底)
|
|
81
|
+
- [ ] 全片响度一致(跨段落无忽大忽小)
|
|
82
|
+
- [ ] 切点处声音连续(无断点/无爆音)
|
|
83
|
+
- [ ] 转场有声音动机(不是画面变了声音才跟上)
|
|
84
|
+
- [ ] 交付:`session.audio_health` 无异常 + 分轨/母版齐全
|
|
85
|
+
|
|
86
|
+
## 九、作业模板
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
1) 分类 essentialSound.setType {type:"dialogue|music|sfx|ambience"}(逐类处理)
|
|
90
|
+
2) 清理 repair.* → EQ → 压缩 → autoMatch(响度)
|
|
91
|
+
3) 铺陈 音乐进入/退出点 + generateDucking + 环境底噪
|
|
92
|
+
4) 混音 子混音分组 → 推子自动化 → 片段级微调
|
|
93
|
+
5) 复核 session.audio_health + 真峰值 + 全片响度一致性
|
|
94
|
+
6) 交付 file.bounce_mix / file.bounce_stems(wav/aiff/flac, 16/24/32f)
|
|
95
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Essential Sound 接口与参数键(FilmCraft)
|
|
2
|
+
|
|
3
|
+
> 来源:官方 `docs/control-protocol.md`(已验证)+ 本机 `commands --json` 实测。
|
|
4
|
+
> 所有命令都接受 `clips`(默认作用于**当前选中的音频片段**),因此**先选片段再执行**。
|
|
5
|
+
> 不适合在 CLI 一次跑完时,可用 `describe <id>` 看 `enabled` 与 `disabled_reason` 确认前置条件。
|
|
6
|
+
|
|
7
|
+
## 一、命令面
|
|
8
|
+
|
|
9
|
+
| 命令 | 参数 | 用途 |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `essentialSound.inspect` | `{clips?}` | 读取当前音频类型与各分区设置(**先 inspect 再 set** 是最稳的姿势) |
|
|
12
|
+
| `essentialSound.setType` | `{clips?, type:"dialogue\|music\|sfx\|ambience"}` | 给片段打类型 → 决定默认处理链 |
|
|
13
|
+
| `essentialSound.clearType` | `{clips?}` | 清除类型 |
|
|
14
|
+
| `essentialSound.set` | `{clips?, key, value}` 或 `{clips?, values:{key:value,…}}` | 设置单个/多个键 |
|
|
15
|
+
| `essentialSound.applyPreset` | `{clips?, preset, type?}` | 套用预设 |
|
|
16
|
+
| `essentialSound.savePreset` / `deletePreset` | `{clips?, name}` / `{name, type?}` | 预设的存/删(**一致性锁**用这个) |
|
|
17
|
+
| `essentialSound.autoMatch` | `{clips?, target?}`(target 为目标 LUFS) | **一键响度匹配**(-14 流媒体 / -23 广播 / -16 播客) |
|
|
18
|
+
| `essentialSound.generateDucking` | `{clips?}` | 为音乐生成避让对白的自动闪避关键帧 |
|
|
19
|
+
|
|
20
|
+
## 二、已验证的参数键
|
|
21
|
+
|
|
22
|
+
| 键 | 说明 |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `volume.levelDb` | 片段音量(dB) |
|
|
25
|
+
| `mute` | 静音开关 |
|
|
26
|
+
| `<section>.enabled` | 任意分区总开关(如 `repair.enabled`) |
|
|
27
|
+
| `repair.noise.on` | 降噪开关 |
|
|
28
|
+
| `repair.noise.amount` | 降噪强度 |
|
|
29
|
+
| `repair.humHz.<i>` | 交流声(hum)抑制频率项 |
|
|
30
|
+
| `ducking.against.<Type>` | 闪避依据的素材类型(如 `ducking.against.Dialogue`) |
|
|
31
|
+
|
|
32
|
+
> 分区名(`<section>`)可由 `essentialSound.inspect` 的返回键得知;键与 UI 行一一对应,UI id 形如
|
|
33
|
+
> `essentialSound.repair.noise.on`、`essentialSound.volume.levelDb`、`essentialSound.section.<Name>[.toggle]`。
|
|
34
|
+
> **未在本表列出的键不要凭记忆写死**——先 inspect,再按返回的键名设置。
|
|
35
|
+
|
|
36
|
+
## 三、UI 面板 id(控制通道 `ui.set` 用)
|
|
37
|
+
|
|
38
|
+
| id | 作用 |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `essentialSound.tab.<Browse\|Edit>` | 切换 Browse / Edit 页 |
|
|
41
|
+
| `essentialSound.type.<Dialogue\|Music\|SFX\|Ambience>` | 点击类型按钮 |
|
|
42
|
+
| `essentialSound.preset[.save\|.delete\|.name\|.ok]` | 预设操作 |
|
|
43
|
+
| `essentialSound.section.<Name>[.toggle]` | 分区展开/开关 |
|
|
44
|
+
| `essentialSound.autoMatch` / `generateDucking` | 对应按钮 |
|
|
45
|
+
| `essentialSound.ducking.against.<Type>` | 闪避目标 |
|
|
46
|
+
| `essentialSound.volume.on` / `volume.levelDb` / `mute` | 音量行 |
|
|
47
|
+
| `essentialSound.browse.<Type>.<preset>` | Browse 页预设 |
|
|
48
|
+
| `mixer.<A1\|S1\|Mix>.<fader\|value\|pan\|panValue\|mode\|mute\|solo\|…>` | 调音台推子/声像/模式(自动化写入用 `mixer.touch/release/recordStart/recordStop`) |
|
|
49
|
+
|
|
50
|
+
## 四、处理链与顺序(实践)
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
1 setType dialogue / music / sfx / ambience(分类是前提)
|
|
54
|
+
2 inspect 读回当前键值,确认起点
|
|
55
|
+
3 repair.* 降噪 / 去 hum(先去底噪,否则后续放大噪声)
|
|
56
|
+
4 EQ / 动态 applyPreset 或 set(分区键)
|
|
57
|
+
5 volume.levelDb 片段级增益(对白清晰度)
|
|
58
|
+
6 autoMatch {target:-14|-23|-16} 统一响度
|
|
59
|
+
7 generateDucking 音乐避让对白(对白段落必做)
|
|
60
|
+
8 inspect 复核 键值与目标一致;跨片段一致
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 五、响度与真峰值
|
|
64
|
+
|
|
65
|
+
- 目标:**-14 LUFS**(流媒体)· **-23 LUFS**(广播 EBU R128)· **-16 LUFS**(播客);真峰值 ≤ -1 dBTP(广播 -2)。
|
|
66
|
+
- `autoMatch` 给出集成响度对齐;**真峰值**要在导出后复核(`file.exportMedia` 产出再次探查)。
|
|
67
|
+
- SoundCraft 侧可作为**独立验证**:`file.bounce_mix` / `file.bounce_stems` 的返回里带 `lufs`、`peak_db`、`true_peak_db`——用它交叉验证 FilmCraft 的处理结果。
|
|
68
|
+
|
|
69
|
+
## 六、常见错误
|
|
70
|
+
|
|
71
|
+
| 错误 | 症状 | 纠正 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| 没打类型就设键 | 设置无效或行为不符 | 先 `setType` |
|
|
74
|
+
| 直接 `set` 猜键名 | 命令报错或静默无效 | 先 `inspect` 取真实键名 |
|
|
75
|
+
| 忘记选片段 | 作用于错误片段/无作用 | 命令前先在时间线选中目标音频 |
|
|
76
|
+
| 对白段落音乐有歌词 | 台词被抢 | 换纯器乐铺底 + `generateDucking` |
|
|
77
|
+
| 只做 autoMatch 不做动态 | 小声仍听不清 | 先压缩/EQ,再 autoMatch |
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: artcraft-style-bible
|
|
3
|
+
description: 一致性锁(美学圣经)——为一部片/一个项目锁定影调、图形、排版、声音的"配方",保证跨镜跨集像同一部作品。含七步定稿法(渲染域/命门句/视觉根脉/hex 色彩配方/差异化签名/分区应用表/跨集检查表)、每类资产该用哪种家族机制加锁(LightCraft 预设与版本、FilmCraft Lumetri 预设与 .fcgt 模板、PhotoCraft 动作、EffectCraft Essential Graphics、VectorCraft 符号与色板、FigCraft 变量与样式、DeckCraft 主题、WordCraft 样式),以及一致性检查清单。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 一致性锁:美学圣经
|
|
7
|
+
|
|
8
|
+
**没有圣经,不动机位。** 一致性的敌人不是审美差,而是"每条镜头各调各的"。本技能把"风格"变成可复用的配方与可核对的锁。
|
|
9
|
+
|
|
10
|
+
## 一、七步定稿法(每个项目做一次,10–30 分钟)
|
|
11
|
+
|
|
12
|
+
| 步 | 产出 | 说明 |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| 1 | **渲染域声明** | 画幅/帧率/色彩空间/交付平台(例:9:16 · 30fps · Rec.709 · 抖音) |
|
|
15
|
+
| 2 | **命门句** | 一句话锁定创作意图(例:"冷雨里的暖灯,规矩人的困局") |
|
|
16
|
+
| 3 | **视觉根脉** | 3–5 个参考作品/画作/摄影集,写清"取它什么"(构图?色温?质感?) |
|
|
17
|
+
| 4 | **hex 锁死色彩配方** | 主色/辅色/点缀色给十六进制值 + 各自出现比例与位置(禁止"差不多暖") |
|
|
18
|
+
| 5 | **差异化签名** | 本片独有的一个视觉动作(例:每次权力反转必有一次低机位仰拍) |
|
|
19
|
+
| 6 | **分区应用表** | 按场景类型分配配方(日戏/夜戏/回忆/高潮),每类给参数档位 |
|
|
20
|
+
| 7 | **跨集检查表** | 每集交付前逐项核对(见第五节) |
|
|
21
|
+
|
|
22
|
+
> 命门句与 hex 配方一旦定稿,**后续所有产出都长在它上面**;改配方=改全片,必须显式决策。
|
|
23
|
+
|
|
24
|
+
## 二、每类资产用哪种锁(家族机制对照)
|
|
25
|
+
|
|
26
|
+
| 资产 | 锁机制 | 命令落点 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 影调(视频) | Lumetri 预设 + 输入 LUT + Look | FilmCraft `lumetri.presets` · `lumetri.applyPreset` · `lumetri.setInputLut` · `lumetri.setLook` · `presets.save/apply` · `lut.import/export` |
|
|
29
|
+
| 影调(照片/RAW) | develop 预设 + 版本 | LightCraft `preset.create/apply` · `version.create` · `history.*` |
|
|
30
|
+
| 影调(批量图像) | 动作 + 批处理 | PhotoCraft `actions.record/save` · `file.automate.batch` · `batch --actions` |
|
|
31
|
+
| 图形/文字(片头、花字、下三分之一) | 图形模板(.fcgt)+ 参数控件 | FilmCraft `graphics.template.list/apply/export` · `graphics.template.set/controls`;EffectCraft `essential.*`(暴露控件供复用) |
|
|
32
|
+
| 矢量元素 | 符号 + 色板 + 数据驱动 | VectorCraft `symbol.new/place/update` · `swatch.*` · `dataset.*` |
|
|
33
|
+
| 界面/图文资产 | 变量 + 样式 + 组件 | FigCraft `variable.createCollection/setMode/bind` · `style.create/bind` · `component.create/combineVariants` |
|
|
34
|
+
| 演示模板 | 主题与设计 | DeckCraft `design.theme/themes/colors/fonts` · `slide.*` 母版式复用 |
|
|
35
|
+
| 排版出版物 | 样式与对象样式 | DesignCraft `style.*` · `object.attributes` · `library.*` |
|
|
36
|
+
| 文档 | 样式与参考文献规范 | WordCraft `styles.apply/create` · `references.*` |
|
|
37
|
+
| 声音 | 预设 + 类型化处理 | FilmCraft `essentialSound.savePreset/applyPreset` · `mixer.*`;SoundCraft `session.save_template` |
|
|
38
|
+
| 时间线结构 | 交换格式固定结构 | FilmCraft `file.exportInterchange`(otio/fcpxml)作为结构基线 |
|
|
39
|
+
| **文案与命名** | 命名与元数据规范 | FilmCraft `metadata.set`(Name/Label/Description/Scene) |
|
|
40
|
+
|
|
41
|
+
## 三、色彩配方怎么写(可直接执行)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
主色 #1B2A38(夜戏基底,占比 ~60%)
|
|
45
|
+
辅色 #D4A854(暖光源/道具,占比 ~25%)
|
|
46
|
+
点缀 #E8E3D9(肤色高光/文字,占比 ~15%)
|
|
47
|
+
肤色线 保护:矢量示波器落在肤色线,允许 ±5°
|
|
48
|
+
禁止 中性灰大面积(发灰=廉价);高饱和红绿同框(抢注意力)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
落点:LightCraft `mixer.*`(8 色相定点)与 `grading.*`(分区)把配方写成数值;FilmCraft 把成品存成 `lumetri.presets` 并在每集复用;`lut.export` 把配方固化成 .cube 供其他成员/外部使用。
|
|
52
|
+
|
|
53
|
+
## 四、差异化签名的实现
|
|
54
|
+
|
|
55
|
+
签名 = **一条可执行规则**,例如:
|
|
56
|
+
- "每次权力反转:低机位仰拍 + 冷色偏移 −10"→ EffectCraft `camera.*` + `effect.*`(Color Correction)
|
|
57
|
+
- "每集开场:三秒内一次由暗到亮的曝光爬升"→ FilmCraft `effects.addKeyframe`(曝光参数)
|
|
58
|
+
- "回忆段落:颗粒 +8、饱和 −15、色温 +300K"→ LightCraft `grain.amount` · `color.saturation` · `wb.temp`
|
|
59
|
+
|
|
60
|
+
签名要**少而准**(1–2 条),且写进分区应用表,否则执行时必被遗忘。
|
|
61
|
+
|
|
62
|
+
## 五、跨集检查表(交付前逐项核对)
|
|
63
|
+
|
|
64
|
+
- [ ] 画幅/帧率/色彩空间与渲染域声明一致(`artcraft_qc` + `color.spaces`)
|
|
65
|
+
- [ ] 影调预设为同一版本(FilmCraft `lumetri.presets` 核对;LightCraft `version.*`)
|
|
66
|
+
- [ ] 主/辅/点缀色在示波器与矢量图上与配方一致(`scopes.read`)
|
|
67
|
+
- [ ] 字幕字体/字号/描边/位置全片统一(`captions.setStyle`)
|
|
68
|
+
- [ ] 图形模板版本一致(`graphics.template.list` 核对,模板改动要显式升级)
|
|
69
|
+
- [ ] 声音:对白处理链与响度目标一致(`essentialSound.inspect` + `autoMatch`)
|
|
70
|
+
- [ ] 元数据(标题/集号/场景标签)完整(`metadata.set`)
|
|
71
|
+
- [ ] 差异化签名在本集出现且不滥用
|
|
72
|
+
|
|
73
|
+
## 六、反模式(一致性杀手)
|
|
74
|
+
|
|
75
|
+
| 反模式 | 后果 | 纠正 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| 每镜单独调色 | 跳色、拼接感 | 先 `lumetri.applyMatch` 再套预设 |
|
|
78
|
+
| 直接把 LUT 当风格 | 全片同质且不通用的"滤镜味" | LUT 只做技术转换(Log→709),风格靠参数配方 |
|
|
79
|
+
| 图形逐镜手搓 | 版本漂移、无法批量改 | 一律用模板(.fcgt / Essential Graphics) |
|
|
80
|
+
| 字体靠系统默认 | 跨机不一致、缺字形 | 显式字体 + 内置字体渲染(FigCraft `--no-system-fonts`) |
|
|
81
|
+
| 改配方不记录 | 已交付集与新集不一致 | 配方版本化(导出 LUT/预设并归档) |
|
|
82
|
+
| 声音每段独立处理 | 响度忽大忽小 | 统一 `autoMatch` + 预设复用 |
|
|
83
|
+
|
|
84
|
+
> 一句话:**把风格写成数值与模板,把一致性写成检查表**——这样"像同一部作品"才是可执行的,而不是靠记忆。
|
package/skills.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ninkch/dsh-artcraft — 技能提供者(独立插件行)
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独一行:`inject: ['skills']` 是硬依赖。把它与主插件(tools/webServer)分开,
|
|
5
|
+
* 即使某个环境没有 skills 服务,也只影响本行,不会拖垮 artcraft 的工具与 HTTP API。
|
|
6
|
+
*
|
|
7
|
+
* 技能来源:包内 skills/<name>/SKILL.md(YAML frontmatter: name + description)。
|
|
8
|
+
* rank 600 与内置技能同级 —— 项目级(100/200)与用户级(400/500)技能可覆盖同名技能。
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const SKILLS_DIR = path.join(HERE, 'skills');
|
|
16
|
+
const PROVIDER = 'artcraft';
|
|
17
|
+
const RANK = 600;
|
|
18
|
+
|
|
19
|
+
/** Cordis 插件名 */
|
|
20
|
+
export const name = 'artcraft-skills';
|
|
21
|
+
/** 需要技能服务 */
|
|
22
|
+
export const inject = ['skills'];
|
|
23
|
+
|
|
24
|
+
/** 解析 SKILL.md 的 YAML frontmatter(只取扁平的 name/description 标量,够用且不引入依赖) */
|
|
25
|
+
function parseSkill(raw) {
|
|
26
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw);
|
|
27
|
+
if (!m) return null;
|
|
28
|
+
const meta = {};
|
|
29
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
30
|
+
const kv = /^([A-Za-z_][\w-]*)\s*:\s*(.+)$/.exec(line);
|
|
31
|
+
if (!kv) continue;
|
|
32
|
+
let v = kv[2].trim();
|
|
33
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
|
34
|
+
meta[kv[1]] = v;
|
|
35
|
+
}
|
|
36
|
+
if (!meta.name || !meta.description) return null;
|
|
37
|
+
return { name: meta.name, description: meta.description, content: raw.slice(m[0].length).trim() };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 扫描包内技能目录,产出候选清单 */
|
|
41
|
+
async function loadCatalog() {
|
|
42
|
+
const out = [];
|
|
43
|
+
let entries = [];
|
|
44
|
+
try {
|
|
45
|
+
entries = await fs.readdir(SKILLS_DIR, { withFileTypes: true });
|
|
46
|
+
} catch {
|
|
47
|
+
return out; // 无 skills/ 目录 → 空目录(不报错)
|
|
48
|
+
}
|
|
49
|
+
for (const ent of entries) {
|
|
50
|
+
if (!ent.isDirectory()) continue;
|
|
51
|
+
const file = path.join(SKILLS_DIR, ent.name, 'SKILL.md');
|
|
52
|
+
try {
|
|
53
|
+
const raw = await fs.readFile(file, 'utf8');
|
|
54
|
+
const parsed = parseSkill(raw);
|
|
55
|
+
if (!parsed) continue;
|
|
56
|
+
out.push({
|
|
57
|
+
name: parsed.name,
|
|
58
|
+
description: parsed.description,
|
|
59
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
60
|
+
provider: PROVIDER,
|
|
61
|
+
source: 'bundled',
|
|
62
|
+
rank: RANK,
|
|
63
|
+
resourceBase: { kind: 'directory', path: path.dirname(file) },
|
|
64
|
+
locator: file,
|
|
65
|
+
});
|
|
66
|
+
} catch { /* 单个技能读取失败不影响其余 */ }
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function apply(ctx) {
|
|
72
|
+
const provider = {
|
|
73
|
+
name: PROVIDER,
|
|
74
|
+
list: () => loadCatalog(),
|
|
75
|
+
async get(candidate, options) {
|
|
76
|
+
const { rank: _rank, locator, ...summary } = candidate;
|
|
77
|
+
const raw = await fs.readFile(locator, { encoding: 'utf8', signal: options?.signal });
|
|
78
|
+
const parsed = parseSkill(raw);
|
|
79
|
+
// 每次加载都重读文件:改技能正文无需版本号或缓存失效
|
|
80
|
+
return { summary, content: parsed ? parsed.content : raw };
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
ctx.skills.registerProvider(() => provider);
|
|
84
|
+
}
|