# 常见问题（按"你会怎么描述它"排序）

> **本文按 0.6.x 校对过。** 0.6.0 重写了切点之后的整条链路：`cells` / `sheets` / `need` /
> `maxPerSheet` / `adaptive` / `tileFirst` 已删除，换成 `maxFrames` / `strategy` / `maxSheets` / `tierMap`；
> 一格现在等于一个镜头，格子大小由切点密度决定（1/1 … 1/8）。
> 参数与输出格式以 [README](../README.md) 和 [CHANGELOG](../CHANGELOG.md) 为准。

> 0.5.12 及更早版本有一个**和声音无关**的坑：出图的最后一步会报
> `Cannot read properties of undefined (reading 'compression')`。那是插件把宿主的
> `saveImage` 从服务上取下来裸调、丢了 `this` —— 0.6.0 已修。**升级到 0.6.0 就好，
> 不用去查音轨**（没有音轨的视频本来也不会因此报错）。
>
> 装了两个插件的话，版本会互相顶（`boardOptions` 的参数名在 0.6.0 换过）。
> 确认命令：`node bin/watch.mjs doctor`，第二行 `storyboard vX.Y.Z` 必须是 **0.6.0 以上**。

> 这份 FAQ 的条目**全部来自真实反馈**（2026-10-28 ~ 10-30 的评论区与私信）。
> 每条都写清三件事：**症状原话 / 真正的原因 / 你要做什么**。
> 标 ✅ 的表示 0.5.0 已经修好，不用再自己打补丁。

---

## 一、关于"听不见声音"

### Q1. 装了插件，能出图，但 AI 说"没听见声音 / 听不到台词"，是我哪里错了？

> **先别跳过这一条 —— 它决定的不是"多一个功能"，是"能不能引用一句原话"。**
>
> 没配转写时，模型只能**看图说话**：它能告诉你"第 3 格有个女孩在跑"，
> 但永远说不出"她说的是『我不想再忘了』"。
>
> | 你问 | 没配 | 配了 |
> |---|---|---|
> | "她说了什么？" | "画面里没有文字，我不知道" | 原话 + 秒数 |
> | "这段在讲什么？" | 只能描述画面 | 画面和台词一起判断 |
> | "哪一段提到了『记忆』？" | 找不到 | `video_locate` 直接定位 |
> | "帮我总结这集" | 逐格描述画面，很空 | 有对白，总结得出东西 |
>
> **一次配好、每部片子都受益**。本机服务免费（不要 key、不联网），装一次大约 15 分钟。
> 真的不想装也行 —— 那就接受"它只能描述画面"这个上限，别事后问"为什么它听不懂"。

不是 bug，是**转写没配**。

**先说清楚四条路，别指望"还有字幕和 OCR 兜底"**：

| 你手里有什么 | 它能拿到什么 | 要不要额外准备 |
|---|---|---|
| 影片**内嵌**字幕轨（mkv 里的 srt/ass） | 作者原话，最准 | 不用，默认就用 |
| 站点的 **CC 字幕**（B 站"字幕"） | 作者原话 | 浏览器登录过 B 站，或配一次 cookies |
| **本机 ASR**（sherpa-onnx / FunASR） | 听出来的句子：术语、方言、重叠说话会错 | 装一次，免费、不联网，约 15 分钟 |
| 上面都没有 | 只能看图说话 | —— |

**OCR 不在这张表里**：它做的是"用画面里烧死的硬字幕去校正 ASR 的错字"和"找字幕带"，
前提是**已经有转写**。它不能当耳朵 —— OCR 开着、ASR 没配，照样听不到人声。

插件出图完全不需要 ASR。只有"想知道视频里说了什么"才需要它。没配的时候清单里
`transcript="no-key"`，意思很明确：**我压根没去听**（不是"听了但没人说话"）。

**怎么办**：跑 `node bin/doctor.mjs`，看第 3 段。二选一：

```json
// 本机服务（免费，不需要 key）
{ "baseUrl": "http://127.0.0.1:8000/v1" }
```

```json
// 云端（要 key，按量计费）
{ "key": "sk-...", "baseUrl": "https://api.siliconflow.cn/v1",
  "model": "FunAudioLLM/SenseVoiceSmall" }
```

写在 `~/.dsh/asr.json`。**0.5.0 起改完即生效，不用重启。**

### Q2. 我配了本机服务，它还要我填 key？

本机地址（`127.0.0.1` / `localhost` / `[::1]` / `0.0.0.0`）**不需要 key**，这是设计。

如果它仍说没配，多半是 `baseUrl` 写了 `http://127.0.0.1:8000`（少了 `/v1`）导致被当成未知端点，
或者环境变量 `DSH_ASR_BASE_URL` 指到了别处（**环境变量优先级高于 `asr.json`**）。

`node bin/doctor.mjs` 第 3 段会打印"实际在用的 baseUrl 和它的来源（env / file / default）"。

### Q3. 台词全程返回 `asr-empty`，但视频里明明有人说话

`asr-empty` = **跑了 ASR，但没听出内容**。三种常见原因：

1. **下载下来的文件没有音轨**（见 Q5）。先确认：`node bin/asr-check.mjs 视频.mp4` 第一行有没有 `NO AUDIO`。
2. **说话是重叠的/尖叫/唱歌**。ASR 会把非语音段当噪声丢掉（这是故意的，避免把歌词当台词）。
3. **片段太短**。几十毫秒的气口听不出来。

### Q4. 转写结果和 B 站的 AI 字幕差很多，有的地方还多一句少一句

**设计如此，不是错。** 优先级是：**影片自带字幕轨 > B 站字幕 > 我们自己听（ASR）**。

- 有字幕轨时，我们抄的是作者原话，几乎不会错；
- 只有没字幕时才去听，听出来的东西对**术语、人名、方言、重叠说话**都不稳。

要引用原话（做字幕、写文案），就明确让它"用字幕轨"或"zoom 到那一格再核对"。

**已知限制（还没做）**：字幕轨本身可能是**错的**（平台 ASR 自动生成的字幕经常错到离谱），
而我们目前**不会**把它和画面上的硬字幕做交叉校验，也就不会告诉你"这两版不一样"。
临时办法有两个：

1. `detail: "high"` 会开 OCR，用画面硬字幕**校正命中的那一句**（只校正、不并列展示两版）；
2. 关键结论自己看一眼画面 —— 交叉校验需要额外定义一个"什么算不一致"的判据，
   现在还没有可靠样本集，所以先不做（见 issue #70 的记录）。

---

## 二、关于安装与"装了但用不了"

### Q5. 下下来的视频没声音，mp4 正常能播但音轨是空的 ✅

原因：yt-dlp 合流音视频要调用 ffmpeg，而 DSH 桌面版把 ffmpeg 放在
`~/.dsh/tools/ffmpeg/bin`（**不在 PATH 上**）。找不到 ffmpeg 时 yt-dlp **不报错**，
静默只下视频轨，退出码还是 0 —— 于是你拿到一个没声音的 mp4，谁都没喊。

0.5.0 起：下载时会自动把解析到的 ffmpeg 路径通过 `--ffmpeg-location` 传给 yt-dlp。
另外只要 `node bin/doctor.mjs` 第 1 段是绿的，就不会再遇到。

### Q6. 装完 ffmpeg 还是报错，重启 DSH 才好

**这不是 bug，是必须做的动作。** DSH 在启动时把"有没有 ffmpeg"记在进程里，
装完不重启的话它仍然按"没有"走。表现就是一直卡在 ffprobe。

**规矩**：凡是装/改了 ffmpeg、yt-dlp 这类外部程序，**完整退出 DSH 再打开**。

### Q7. 贴链接下载很慢、下到一半清零重来、越下越慢 ✅

0.4.x 的 bug：下载失败时把整个缓存目录 `rm -rf` 掉，于是网络一抖就从头再来，
多次失败后表现为"越下越慢"。0.5.0 起：带 `--continue` 断点续传，失败**不删目录**，
残留的 `.part` 由缓存清理按时间回收。

还是慢的话，更省事的办法：**自己先把视频下下来，再把本地文件丢给它**。

### Q8. 改了 `asr.json` 完全没反应，得重启整个 DSH ✅

0.4.x 的 `ASR_DEFAULTS` 是模块加载时的一次快照。0.5.0 起按文件修改时间热加载，
改完下一次调用就生效。

### Q9. 改完台词设置再跑，还是旧的转写（吃到旧缓存）✅

转写缓存的键里现在包含：ASR 地址、模型、切片长度、`transcript` 模式、热词。
这几项任一变都会重新转写。0.4.x 的键缺 `transcript`，所以"先关掉台词跑一次、再打开"会命中旧结果。

---

## 三、关于"工具参数没生效"

### Q10. `video_locate` 一调就报 `ReferenceError: recallSegments is not defined` ✅

0.4.1 的 bug：`src/index.js` 用了 `recallSegments / matchSegments / formatMatches` 却没
`import './locate.js'`。0.4.4 起已修（导入在文件顶部），不需要再手工补。

### Q11. `video_zoom` 的 `minGap` 改了没用，永远按 1.2 秒走 ✅

0.4.3 的 bug：zoom 的 handler 是逐个字段手写 `runStoryboard` 参数的，列表里漏了 `minGap`，
`schema` 里声明了、也校验了，但到参数装配那一步就丢了（**"写了没接线"**）。

0.5.0 起三个入口（storyboard / zoom / watch）的参数统一由 `board.js` 装配，只有一份定义。
`test/schema-audit.test.mjs` 会扫 `args.X` 并断言都在 schema 里，防止再漏。

### Q12. 我要 16 格，实际只出了 8 格，头一行却写 `16 cells` ✅

两件事：

1. **出不到 16 格**是正常的：格与格之间要留最小间隔（默认 1.2 秒），15 秒的片子最多拆 8 格。
2. **头一行写 16 是错的**：0.5.0 起头一行写**实际格数**，被卡住时追加 `(asked 16)`，
   例如 `grid="8 cells x 1 image(s) (asked 16)"`。

### Q13. 有的宿主里报 `TypeError: Cannot read properties of undefined (reading 'length')`，看起来像 ffprobe 坏了 ✅

真凶是 `promisify(execFile)`：某些宿主的 `promisify` 会丢掉 execFile 的 custom symbol，
只把 stdout 字符串交回来，于是取 `stderr.length` 就炸。

0.5.0 起 probe 改成**回调式 `execFile`**，不再经过 `promisify`。

---

## 四、关于"为什么只给这么几格"

### Q14. 104 秒 53 个镜头，只给 4 格，是不是太抠了？

这是 **0.4.4 的默认值**，不是 0.5.0 的行为。0.4.4 对"字/秒 > 3"的口播片会一路降档，
沧野那支（299s / 122 镜）就被压到 4 格 = 只覆盖 126 个镜头的 3%。

0.5.0 的默认格数按"每格不超过 90 秒 + 镜头数"算，同一支片子给 9 格；
明说"要 16 格"也给得出。

### Q15. 同一张图里，为什么有的格子是 4 张小图、有的是 9 张？

那是**二重细分**：一格里面再切几刀，专治"一个格子里其实有好几个镜头"。
切几×几由**这一格自己的画面变化强度**决定（`meanMotion`，段内逐帧差的均值）：

| 这一格画面 | 摊成 | 每小块大小 |
|---|---|---|
| 很热闹（快剪、剧烈运动，≥ 8） | 3×3 = 9 张 | 110×61 |
| 在动（摇镜、正常剪辑，≥ 3） | 2×2 = 4 张 | 165×92 |
| 几乎不动（静态画面、字幕页） | 整帧 1 张 | 331×184 |

所以一张图是**混着来的**：快的地方 9 张小图、平稳的地方 4 张大图。扫一眼就知道这段片子
哪里快哪里慢——这正是它想要的效果，不是排版出错。

**为什么不一律摊 9 张**：9 个位置要把格子切成 110×61，只在"每张都不一样"时才划算；
安静的段落里 9 张本来就有几张是同一个画面，切小了反而都看不清。

**想让每格更大、摊得更细？** 用 `tileFirst`（CLI `--tile-first`）：少给几格（250 秒的片子
从 16 格降到 9 格），每格就大了。代价是**画面总数变少**（同一支片子 113 → 51 个画面）——
它是取舍，不是优化，默认关。

### Q15b. 那"重复帧"是怎么回事？同一格里有两张几乎一样

先看那一格的 `meanMotion`（清单里每格都带着）。两种可能：

1. **这格本来就安静**（`meanMotion` < 3）：说明这段画面确实没怎么变，两张相似是如实的。
   想避免就给 `tileFirst` 或者手工指定更少的 `cells`。
2. **这格镜头很密但位置不够**：9 个位置去覆盖 5 个镜头，多出来的位置必然是同一镜头的
   不同时刻。0.5.0 起有一条 `格子数 ≤ 镜头数 × 1.5` 的约束，超过就降一档，专门治这个。

---

## 五、关于钱和清晰度（两张表，按需选）

### 价位表：花的是**图**的钱，不是分钟的钱

一张宫格图 ≈ **384 token**（宿主按图计价，跟它多长多宽无关）。
所以费用只跟**要几张**有关，跟片子多长没有直接关系 —— 长片之所以贵，是因为张数多。

| 你怎么说 | 出几张 | 约多少 token | 约多少钱* |
|---|---|---|---|
| 看这 5 分钟（默认） | 1 张 | 384 | **≈ 0.011 元** |
| 看整部 5 分钟短片 | 1 张 | 384 | ≈ 0.011 元 |
| 看整部 45 分钟剧集 | 2 张 | 768 | ≈ 0.022 元 |
| 看整部 66 分钟讲座 | 4 张 | 1,536 | ≈ 0.047 元 |
| 只看第 12–18 分钟 | 1 张 | 384 | ≈ 0.011 元 |
| 再放大看第 5 格 | 1 张 | 384 | ≈ 0.011 元 |

\* 按 384 token ≈ 0.011 元折算（当前主流模型输入价量级）。**图内格数不影响价格**：
9 格和 16 格都是同一张图、同一个价 —— 所以"要更多格"通常是免费的，只影响每格大小。

**省钱的两招**：① 只给窗口（`from`/`to`），别让整部片出图；② 别为了省钱把 66 分钟压成 1 张，
每格 4 分钟等于没看。

### 清晰度表：链接才需要选，本地文件无所谓

本地文件**永远读原片**，这张表对它们无效。只有贴链接时才下载一份，那才要选清晰度。

| `quality` | 下载高度 | 体积/分钟（约） | 下载耗时（约） | 什么时候选 |
|---|---|---|---|---|
| `low` | 360p | 3 MB | 最快 | 只想看结构、不在乎字 |
| **`medium`（默认）** | 720p | 10 MB | 中 | 大多数情况 |
| `high` | 1080p | 25 MB | 最慢 | **用户说"看不清"**、格子里有字要看、有细节 |

> ⚠️ **以前默认是 360p**，贴链接出的宫格图会糊 —— 这是真实反馈。0.5.3 起默认改成 720p。
> 如果你觉得"图很清楚但下载太慢"，显式传 `quality: "low"`。

**清楚 vs 大小是唯一要权衡的东西**：`high` 不会让图变大（还是 1 张 384 token），
只让下载更久。所以**只要不赶时间，选 high 没有额外成本**。

## 五、关于钱

### Q16. "一分钱看五分钟"到底怎么算的？

按**图**算，不是按分钟算：一张宫格图 ≈ 384 token（DSH 宿主口径，每图约 384）。
5 分钟的片子默认出 1 张 ≈ 0.011 元；66 分钟的讲座出 4 张 ≈ 0.047 元。

所以"越长越贵"其实是"**张数越多越贵**"。想省钱：分段看（只让你关心的那 10 分钟出图），
别一次要全片。

### Q17. 为什么长片的张数是它自己定的，我不能指定？

可以指定（`sheets` 参数，1~16）。但默认档是按"每张塞多少帧"算的，比手填更省。
不建议为了省钱把 66 分钟压成 1 张 —— 每格 4 分钟，等于没看。

---

## 五点半、关于"能不能下"

### Q18. 我可以让它去下别人的视频吗？

**能不能下取决于你有没有那个权限**，这个项目不替你判断，也不提供任何绕过手段：

- 不碰付费墙、不绕 DRM、不绕区域限制；
- 不做批量抓取（一次一个链接，没有爬虫、没有账号池）；
- 不换代理、不规避站点风控——被限流就如实报错（`412` / `429` / `567`）；
- 下载的缓存只在本地，有大小上限且会自动淘汰，不会上传到任何地方。

请自行确认有权处理那段内容，并遵守目标站点的条款与当地法律。完整说明见 [NOTICE](../NOTICE)。


## 六、还是没解决

收集三样发出来，基本一次定位：

1. `node bin/doctor.mjs` 的**完整**输出
2. 出错那条命令的**完整**输出（不要只截中间一段）
3. 素材信息：时长、有没有人说话、本地文件还是链接、什么平台
