video-distill
第 0 步 · 先判断该不该走这套流程(30 秒,别跳过)
问一句:用户要的是「一个答案」,还是「一份能回查、能复现的落盘产物」?
要答案 ⇒ 立刻退出本流程,直接用 watch-skill CLI 回答,例如:
watch-skill ask <engine_video_id> "<用户的问题>" # 已索引过
watch-skill watch "<url>" --transcript-only # 没索引过,先拿转录再回答
然后一句话告诉用户:「这个只要答案,我就不建笔记了;想留档我再走完整流程。」
为什么把这条放在正文第一句,而不是只靠 description
因为它拦不住的是一部分 agent,而不是全部。 这一节的措辞我改过两次 —— 第一版写「写在 description 里被实测证明拦不住」,那句话是错的,只在 Hermes 上成立。
2026-08-08 实测「这个视频讲的啥?我不想看完,你就跟我说个大概」:
| agent | 结果 | 判据 |
|---|---|---|
| Claude(Claude Code 2.1.226) | 正确不触发 | 抓 stream-json,"name":"Skill" 0 次;同一台机器同一时刻的正例是 2 次 |
| Hermes + deepseek-v4-pro | 误触发,重复 3 次 3/3 | 同轮正对照 3/3、负对照 0/3,仪器可靠 |
Hermes 那边的理由每次都类似:「video-distill 正是做 YouTube 摘要的」—— 它看到「视频 → 结构化」就点着了,排除句压不过这个正信号。 而 Claude 那次转头调了 26 次 Bash 直接去回答 —— 恰好就是上面这一节要求的做法。
所以「排除项无效」是个只在特定 agent 上成立的结论, 而我第一版把它写成了普适的。 一个只在一种环境里验过的结论,写成普适的那一刻就变成了错的。
⇒ description 的排除清单要留着(Claude 侧靠它就够了); 而正文这个出口也要留着 —— 它是给触发判断较弱的 agent 兜底的。 两者不是替代关系。
误触发的代价,因此从「白做一整套三件套」降到「多问一句话」。
其余数字(Hermes 侧,20 条正负样例,scripts/trigger_test.py):
| description 版本 | 该触发 | 不该触发 |
|---|---|---|
| 末尾一句「不适用」 | 9/9 | 4/11 |
| 排除项列成 6 条清单 | 9/9 | 10/11 |
把教学视频变成「不看视频、只凭文档就能复现」的知识资产。
这是一份操作清单。每条规则背后都有源码核对和实测支撑,看起来多余想精简某一条之前,
先读 references/engine-internals.md 对应小节 —— 有几条(索引清空、静默付费、
字幕轨误选)不遵守不会报错,只会安静地产出坏数据。
固化配置
⚠️ 换机器部署先本地化这两行(2026-09-08 标注):
WATCH_SKILL_BIN与VAULT是本机的绝对路径,别的机器必须改成自己的(watch-skill的实际位置用command -v watch-skill查;vault 目录不存在就先建,含视频笔记/子目录)。 后文所有命令只引用这里的变量——改这里即可,别处没有硬编码。
WATCH_SKILL_BIN=/Users/vdev/.local/bin/watch-skill # 绝对路径:subagent 的 PATH 不保证含 ~/.local/bin
VAULT=/Users/vdev/notes # 已确认,支持 Bases
NOTES_ROOT="$VAULT/视频笔记"
所有 watch-skill 调用用这个包装形式,不要精简:
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY -u GEMINI_API_KEY -u OPENROUTER_API_KEY \
WATCHSKILL_SUBTITLE_LANGS='zh.*' \
WATCHSKILL_WHISPER_MODEL=large-v3-turbo \
WATCHSKILL_CLOUD_STT_ENABLED=false \
WATCHSKILL_COST_POLICY=offline_only \
"$WATCH_SKILL_BIN" <subcommand> ...
| 项 | 不加会怎样 |
|---|---|
env -u ...KEY |
带索引的 watch 会用 Haiku 描述最多 24 帧且不检查 cost_policy,有 key 就静默付费 |
SUBTITLE_LANGS='zh.*'(不带 en) |
多数视频 info.json 的 language 为 None,字幕轨按字母序回落,media.en.vtt 会压过 media.zh.vtt,拿机翻轨当原文 |
WHISPER_MODEL 显式指定 |
内存探测失败会落到 base,中文错字密集(「多参考图」→「多餐口圖」),不能用于笔记。large-v3-turbo 是 Apple Silicon(mlx)实测档;非 CUDA 的 Linux/CPU 机器改 medium(large-v3-turbo 在 CPU 未测,medium 实测简体准确但 RTF 0.62x),见 AGENT-START §3 |
英文视频临时改 WATCHSKILL_SUBTITLE_LANGS='en.*'。详见 references 第 2、3、5 节。
机械契约(写入前必读)
正文怎么组织、小节怎么起名、行文风格 —— 这些你自己判断,按内容实际结构来写更好, 不要被模板的示例小节束缚。第一版实测就是这样:自创的 11 个小节比模板示例贴合得多。
但有 6 处是字面契约:validate.py 按精确形式 grep 它们,写法不同就过不了闸门。
它们的存在不是为了统一风格,是为了让「这份笔记可回查、来源可分辨」这件事可机器验证——
否则质量只能靠人逐份读。
| 契约 | 精确形式 | 为什么必须是这个形式 |
|---|---|---|
| 时间戳锚点 | markdown 链接:[01:23](url&t=83s),区间也可以 [02:38–02:51](url&t=158s) |
可点击跳回原片是「这句话是讲者说的」的凭据。〔02:38–02:51〕 全角括号不是链接,点不动,等于没有凭据 |
| 主笔记锚点小节 | 必须有一个 ## 视频要点,其下每条以时间戳链接开头 |
这是唯一被逐条校验的小节。你可以自由增加任意其他小节(背景、结构、坑…),只要这一节存在 |
| PLAYBOOK 步骤 | 每个 ### 步骤块内出现至少一个时间戳链接 |
操作手册最易错的是顺序和参数值,跳回原片是唯一纠错手段 |
| EXTEND 条目 | 每条以 - 开头的条目里出现字面串 [扩展] |
这个标记是给机器读的。文件开头声明「本文件是扩展层」对人足够,但校验器只能逐条看。少了它,视频原文和补充内容在机器眼里无法区分 |
engine_video_id |
frontmatter 必填,16 位十六进制,且必须等于 sha256(source.strip())[:16] |
事后 ask / search 全靠它。校验器会用同一份 frontmatter 里的 source 重算并比对 —— 填个占位串过不了闸 |
content_hash |
frontmatter 必填 = frontmatter 之后的正文,.strip() 后 sha256 十六进制前 16 位 |
重跑保护靠它判断文件是否被人工改过。算法不一致就永远误报「被改过」,保护机制自我失效。只对正文取 hash,否则 hash 会自指 |
# content_hash 的唯一正确算法
import hashlib
body = 文件内容[frontmatter 结束的 "---\n" 之后 :]
content_hash = hashlib.sha256(body.strip().encode("utf-8")).hexdigest()[:16]
阶段 3 跑完 validate.py 就能确认这 6 条,且每条都是真检查而非存在性检查:
engine_video_id 用 source 重算比对,content_hash 重算比对(不一致是 ERROR,
不是提醒 —— 刚写完就对不上只可能是算错了),时间戳锚定行首,[扩展] 与小节名逐条 grep。
闸门不过就不算交付完成,不要贴着错误清单说「内容质量很好」——内容好和可验证是两件事, 这份 skill 要的是两者都有。
Preflight(会话内首次运行做一遍)
"$WATCH_SKILL_BIN" doctor
python(3.11.x) / ffmpeg / yt-dlp / js-runtime(deno,YouTube 需要) 必须 ok。
memory: warn 可忽略(doctor 自身探测的 cosmetic 问题)。失败就停下报告,不要降级硬跑。
阶段 0 · 预探测与计划(第一个确认点)
- 元数据:
yt-dlp --skip-download --dump-single-json取 title / uploader / duration /language/subtitles/automatic_captions/chapters/ 源分辨率。 - 查重:用平台 video_id grep
$NOTES_ROOT下的 frontmatter。命中就问 「更新 / 跳过 / 另存版本」,不要静默覆盖 —— 用户可能已经手工补充过内容。 - 分类初判:
ls "$NOTES_ROOT"枚举现有分类,从中选;要新建就问一次。$NOTES_ROOT为空(首次使用)时没有可选项 —— 这时直接提出一个分类名放进阶段 0 的那次确认里,不要因为「只能从现有里选」而卡住。这条规则的目的是防止分类无节制增殖, 不是在空目录上制造死锁。 - 清晰度预判:源高于 720p 且含代码/界面演示 → 此处就告诉用户「引擎硬编码
height<=720且不接受 cookie,要看清代码请给本地高清文件」。等下载完才发现会白费一次下载。 - 打包成一次确认:字幕来源、分段方案、抽帧分辨率、预计耗时、是否启用 WebSearch 扩展。
≥20 分钟且有章节 → 给章节地图让用户选精看范围。
转录耗时按 RTF 0.05x 估(Apple Silicon mlx 实测值;CPU
medium实测 0.62x, 约 12 倍,按本机档位换算);不要报「转录费用」,本地转录免费。
阶段 1 · 全片转录(要不要索引,在这里定)
先说实测事实(2026-08-28 源码核对 + 实测):引擎只有拿到 perception(抽帧+OCR)
才会写索引 —— CLI 源码是 if index and result.perception is not None。
--transcript-only 恒无 perception ⇒ 这条路径上 --index/--no-index 开关是死的,
加不加都不写索引(实测:跑完 list / search 都找不到该视频)。
本节早期版本称「--transcript-only 是唯一索引写入点」——那是错的,
它基于「会写转录段落进索引」的错误认知。详见 references 第 1 节。
所以按内容类型二选一:
路径 A · 口播 / 理论型(快):
env -u ... "$WATCH_SKILL_BIN" watch "<source>" --transcript-only --out-dir "$WORK"
只拿转录,不下载视频画面。不写索引 —— 阶段 2.1 走指示语 + 章节边界降级定 cue。
路径 B · 操作型 / 界面密集(语义定 cue 的前提):
env -u ... "$WATCH_SKILL_BIN" watch "<source>" --out-dir "$WORK"
(即:不 带 --transcript-only。)转录 + 场景帧 + OCR 文本 + 嵌入索引一次拿到;
阶段 2.1 的 search / ask 从此可用,且 OCR 行让英文界面词能直接命中
(2026-08-28 实测:真实索引上英文查询 0.71、中文词组 0.82–0.88)。
代价:全片下载 + 抽帧 + OCR(13.7 分钟视频数分钟 CPU)。
纪律:从此刻起到阶段 2 结束,任何再跑的 watch 一律加 --no-index ——
索引写入前会 DELETE 该 video_id 的全部派生行,第二次带索引 watch 会把第一次清空。
完成后:
核对字幕轨确实是原生语言(看
media.*.vtt的语言后缀与内容)。不符就显式设WATCHSKILL_SUBTITLE_LANGS重跑。不要用机翻英文轨当原文——它是翻译,不是讲者说的话。转录存档到
$WORK/transcript.md(阶段 4 蒸馏的前提,清理 work dir 后不可恢复)。取
engine_video_id。路径 B 下 watch 输出里有**Indexed:** video_id一行,直接取用; 路径 A 下这行不存在(没写索引就不打印,实测 grep 不到)。 两条路径通用的方法是自算:python3 -c "import hashlib,sys;print(hashlib.sha256(sys.argv[1].strip().encode()).hexdigest()[:16])" "<source 原样字符串>"必须和你传给 watch 的 source 字符串逐字节一致(引擎就是
sha256(source.strip())[:16], URL 少一个参数就是另一个 id)。路径 B 下可用"$WATCH_SKILL_BIN" list交叉核对; 路径 A 下list里不会有它(不在索引里),别误判成「没跑成」。 这个值是六条机械契约之一:不落盘、work dir 一清,事后ask/search就只能重跑整条流水线。
阶段 2 · cue 定位 + 定点抽帧 + 分段理解
2.1 生成 cue 时间戳表
前提:索引存在。 只有阶段 1 走了路径 B 才有索引。不确定就先 "$WATCH_SKILL_BIN" list
看有没有该视频;没有而内容又确实需要语义定 cue(界面演示、节点操作)⇒ 回去补一次
阶段 1 路径 B 的全片 watch —— 此时补是安全的(路径 A 从没写过索引,没有派生行可被
DELETE 清掉,下载还有缓存);纯口播内容直接降级指示语,别为 cue 白跑全片抽帧。
索引不可用时降级为纯指示语匹配,记入 degradations。
索引在,就对它做语义检索,命中的 hits 自带时间戳:
env -u ... "$WATCH_SKILL_BIN" search "操作步骤 点击设置 菜单路径"
env -u ... "$WATCH_SKILL_BIN" ask <engine_video_id> "代码示例 终端命令 报错信息"
中文查询写成「空格分隔的、3 字以上词组」:
✗ "如何配置代理服务器超时"
✓ "代理服务器 超时配置 网络设置"
引擎的 _fts_query 和 lexical_anchor 都按空白切分,且后者丢弃长度 < 3 的词。
中文整句会退化成一条要求逐字相邻的短语查(几乎零命中),同时把置信度锚点压成 0,
导致升级阶梯过度触发、白耗算力。所以双字词并成四字:超时配置、环境变量、常见错误。
详见 references 第 4 节。
同一语义也试一遍英文词组。 实测同一视频上英文查询命中率 0.72 vs 中文 0.34 ——
原因不在模型偏好,在于索引里有大量英文:界面标签、节点名、参数名、文件名,以及不少
教学视频带的中英双语硬字幕(OCR 会把两条都读进索引)。嵌入模型本身是跨语言的
(实测中↔英余弦 0.59),所以 LoRA loader node、resolution selector、API key setup
这类查询往往比中文词组更能命中界面演示的时刻。两种都发一遍,合并 hits。
辅以转录里的指示语(「你看这里 / 打开设置 / 输入这个 / 如图 / 注意」)与章节边界。
索引不可用时降级为纯指示语匹配,记入 degradations。
读输出前先过滤日志噪音,否则帧路径和 OCR 文本会被冲没:
... | grep -v -e E5RT -e '^objc\[' -e RapidOCR
三个来源分别是 CoreML EP(本地补丁 2 的副作用)、cv2 与 av 各带一份 libavdevice、 以及 RapidOCR 对空白帧的例行报告。都不是错误。
2.2 分段观看
分段判据是信息密度,不是时长。实测一个 13.7 分钟的界面演示视频抽出 88 帧、 83 个场景切换 —— 按时长它「不用分段」,按实际负载它必须分。看这几个信号:
- 阶段 1 的 watch 报告里场景数 / 帧数(>60 帧就该分)
- cue 表的规模(cue 多到超过
--max-frames就必须分) - 内容形态:界面演示、逐节点讲解、代码走查 → 密;口播、幻灯片朗读 → 疏
分段就按 10~12 分钟切,每段派一个 subagent;密度低且总时长短才单遍处理。
env -u ... "$WATCH_SKILL_BIN" watch "<source>" \
--start <t0> --end <t1> \
--timestamps <该段 cue,逗号分隔> \
--resolution 1280 --max-frames 60 --no-index --out-dir "$WORK"
- 必须加
--no-index(理由同阶段 1)。 - cue 数 ≤
--max-frames:引擎对 cues 做_even_sample(cues, cap),超量会被抽稀, 等于白定位,而且不报错。cue 多就提高 max-frames 或缩短分段。⚠️ 你可能读不了图 —— 先确认,再决定怎么读帧
下面写的
Read每个帧路径,前提是你能直接接收图片输入。 别按 agent 名字假设能力,按配置实测——同一个 Hermes,配没配视觉是两个物种: 未配视觉时跑完 29 分钟教程产出满分四件套而assets/0 张帧(2026-08-08), 或自述「没有视觉模型」只靠 OCR(2026-09-08);配了auxiliary.vision后直接读帧, 面板/字幕分层、连字幕残影都识别,质量经主代理亲读对答案验证(2026-09-08)。
你的情况 怎么读帧 能直接吃图(Claude;或配了 auxiliary.vision的 Hermes)Read帧路径 / 直接读图Hermes 未配视觉 先配再跑:profile config.yaml 加 auxiliary.vision(做法与实测可用的模型名见仓库HERMES-INSTALL.md§〇——deepseek-v4-flash-vision-exp可用,deepseek-v4-vision-expAPI 不认)不便改配置时的绕路 视觉 MCP,如 mcp__minimax__understand_image(已实测:逐字读出画面三行文字、认对颜色与形状)真的什么都没有 必须写进 frontmatter 的 degradations,并且不要产出操作型 PLAYBOOK —— 纯字幕的步骤不可复现(2026-09-08 一次纯 OCR 路线的观察:GUI 步骤可走但整体未达盲测验收,n=1,规则维持不变)「能读图」的判据是仪器检查,不是自述:拿一张内容已知的帧考自己 (比如已验收笔记里的证据帧),读得出版面结构和已知文字才算配通—— hermes 自己报「能/不能」都作不了数(2026-09-08 两个方向都实测过)。
抽帧本身不需要任何 key —— OCR 是本地 RapidOCR。 缺 key 只影响引擎的「场景描述」(
scene descriptions skipped (vision.no_api_key)), 而画面文字照样读得到。别把「没有视觉模型」误当成「不能抽帧」。
- subagent 内
Read每个帧路径(帧只进子代理上下文,用完即弃),返回纯文本段落笔记 加该段证据帧路径。prompt 带上前一段的 running summary(术语表 + 进行中的主题), 否则段间指代会断。 - 段落笔记立刻落盘
$WORK/.drafts/segment-N.md,注明覆盖的时间范围。 - 证据帧同步
cp到 vault 的assets/(命名mm-ss-描述.jpg),不要攒到最后—— 会和临时目录清理产生竞态。 - 每段完成给用户一行进度。
断点续跑:开工前先看 $WORK/.drafts/ 有哪几段,只补缺失的。
2.3 转写规则
| 画面类型 | 转写成 |
|---|---|
| 图表 | 数据表或 Mermaid,保留轴、单位、数值、结论 |
| 操作演示 | 可复现步骤:菜单路径、点击对象、输入值、预期反馈 |
| 代码/终端 | 完整文本 |
| 讲解/字幕 | 时间戳对齐的要点 |
⚠️ 命令与版本约束:逐字复制,并把画面原文一起留下
2026-08-08 实测的一次真实错误。画面上是:
pip install -U "triton-windows>=3.7,<3.8"而 agent 写进 PLAYBOOK 的是
-U "triton-windows<3.7"—— 丢掉>=3.7,之后,约束整个反了:照它装会拿到 3.7 以下的旧版本。 而正确答案就在它自己引用的那张帧里。占位符是诚实的空白;抄错的命令是自信的错误。后者更糟 —— 占位符会让人去查,错命令让人在别处排错。
所以两条硬规则:
命令、版本号、参数值一律逐字复制,不要重述、不要"简化"。 版本约束尤其危险:少一个边界就是另一个意思。
把画面原文以引用块留在紧邻位置:
> 画面原文([15:55] 帧,逐字): > `pip install -U "triton-windows>=3.7,<3.8"` > `Collecting triton-windows<3.8,>=3.7`引用块的覆盖范围必须不小于正文的断言。 2026-08-08 第二次盲测 在这个机制里抓到了我自己的错:正文写
python.exe -m pip install -U "triton-windows>=3.7,<3.8", 而引用块只从pip install起抄 —— 「用哪个解释器装」那一点没有画面支撑, 可正文看起来整条都有。同样,正文断言「无报错」时, 引用块就得抄到Successfully installed ...那一行。一个覆盖不全的引用块,比没有引用块更容易骗人 —— 它把「部分有据」包装成「整条有据」。
这样抄错能被发现 —— 而
validate.py查不出这类错: 它能校验结构、时间戳、hash,不能校验你有没有读对画面。 留下原文,是把不可自动检查的东西变成可人工核对的。
专有名词、命令、参数值以 OCR 为准。 这是实测结论:同一批帧上 OCR 正确读出 「多参考图」「真人 AI 短剧」,而 whisper 写成「多餐口圖」「真人短距」。换成 large-v3-turbo 后同音词「短距/短剧」依然错 —— 声学模型解决不了同音,画面文字才是 专名的可靠来源。
OCR 有该词就用 OCR 的写法,冲突处标
[OCR 与转录不一致 @mm:ss];OCR 未覆盖才采信转录;
两者都不可辨 → 走免模型回补:
ask <engine_video_id> "<画面文字 具体内容>", 引擎会自动dense_resample(高分辨率密集重抽 + OCR)→crop_and_reocr(按 OCR box 裁剪 2× 放大重读),两步都不调模型,恢复的证据还会 merge 回索引。对它的正确预期:它经常回答「视频没有清楚显示」,而这就是它的价值 —— 把「我读不出来」升级成「已确认视频里确实没有」。前者是你的失误,后者是一条有据可查的 边界,可以放心写进「未覆盖 / 存疑」。别指望它总能变出答案,指望它帮你区分这两种情况。
仍不可辨才写
[画面文字不可读 @mm:ss]。不要猜测补全 —— 一个编造的参数值会让整份 手册失去可信度。
OCR 自己也会错(「拆解」→「折解」、「剧本」→「刷本」),两者是互补关系。有疑问时 以你自己读帧所见为最终裁决。
低清源(源视频 ≤480p,帧常只有 512x288)的四条实测纪律(2026-09-08 hermes 跑 360p 源 中文双语硬字幕视频时发现,经主代理亲读帧复核与独立盲测确认):
- OCR 会把画面 UI 文字与硬字幕(尤其中英双语硬字幕)混在同一行输出—— 引用块必须标注「含硬字幕层」,且要能区分哪些 token 是界面文字(菜单、路径、数值)、 哪些是字幕(整句口播)。否则读者会把口播/字幕当界面原文,盲测直接报「一段单帧 OCR 不可能同时以逐字身份含两种来源」。
- 同一画面多次扫描(普通抽帧 +
ask触发的 dense_resample)结果冲突时, 多数一致的那组优先;单次出现的异常 token(例:目录名models/unet只出现一次, 而三列复扫都读models/Diffusion model)按误读写进正文,但保留为「若…再试」的 备选说明,别把话说死。另注意 OCR 会吞下划线:diffusion_models常读成Diffusion model,正文给可执行目录名时要补回下划线并说明依据。 - PLAYBOOK 引用块里的帧时间戳必须落在该步骤的区间内(validate.py 查不出 这条)——盲测实测抓到把 [02:55] 的启动器帧引文放进标称 [02:16–02:26] 的步骤 1: 内容对得上主题、对不上步骤,手册的「可回查」承诺当场破功。
- 源分辨率要先查 formats 再定预期:帧分辨率可能远低于引擎的 720p 上限
(360p 源 → 512x288 帧),抽帧前看
yt-dlp --dump-single-json的 formats, 最高只有 360p 就提前告诉自己要密集复扫小字,别等帧出来才发现糊。
2.4 盲测(操作型必做,不是可选的质量加分)
写完 PLAYBOOK 就做这一步,不要留到最后。派一个 subagent,只给它 PLAYBOOK (不给转录、不给帧),让它复述操作并列出卡住的地方。
实测这一步在一份看起来完整的手册里揪出 3 处硬伤,包括「验证」小节里一条基于算术错误的
诊断公式(10×24+1=241,而实际 frame_count 是 65),会把读者引向去改 fps ——
正是手册本身明令禁止的操作。这类错误你自己读不出来,因为你知道视频里是怎么做的;
盲测代理不知道,所以它会卡住,而卡住的地方就是缺口。
缺口回补优先 ask <engine_video_id> "<缺口词组>";ask 解决不了才定点重抽。
完成判据:盲测代理能一路走到最后一步,剩下的疑问全部落在「未覆盖 / 存疑」小节里。
阶段 2.4 · 可复现盲测(操作型必做)
质量控制那节写着「见阶段 2.4」,而这一节此前根本不存在 —— 一个被列为必做、却从没被定义过的步骤。2026-08-08 补。
python3 <skill_dir>/scripts/blind_prep.py "<笔记目录>" video-distill-workspace/blind
# 隔离出一份看不到答案的副本(只留 PLAYBOOK.md、移除截图嵌入),并打印标准提示词
# 隔离目标别放 /tmp —— §固化配置外的纪律:中间产物放 /tmp 会被系统清掉(2026-08-08 丢过整轮数据)
把隔离目录交给一个干净的 subagent,用脚本打印的提示词。 必须禁止它上网、禁止它读隔离目录以外的文件 —— 一个能偷看的盲测,测的是偷看能力。
首次实测的威力(对象是一份 validate.py 打满分的手册)
| 盲测报出 | 我的复核 |
|---|---|
| Triton 版本约束「像是编的」 | ✅ 正是我故意种进去的已知错误 ⇒ 仪器灵敏度过关 |
| 步骤 12/13 时间区间重叠、拼不成时间线 | ✅ 真的重叠 124 秒,已落成检查 |
| 「根目录不是 python_embeded」与后面三步互相打脸 | ✅ 真的自相矛盾 |
| 装 xformers 再卸载会动到 torch,手册零提示 | ✅ 真实风险 |
| 手册里残留一个空代码块 | ✅ 是我修笔记时留下的垃圾 |
| 结论:不能独立完成,硬停在步骤 11 | 缺 workflow 来源 / 模型文件名 / 访问地址 |
它一次报出 21 条缺信息 + 18 条可疑。
validate.py满分 ≠ 手册可用。 它查结构、时间戳、hash;查不出你有没有读对画面、有没有把话说全。
两条纪律
- 盲测发现的东西,凡是能机械判定的就落成
validate.py检查。 盲测昂贵(要派一个 agent),不该让人在同一个坑上发现第二次。 - 盲测报的「可疑」要逐条复核,不要照单全收。 它也会错 —— 但它错的成本是你多查一次,而漏报的成本是一份坏手册出厂。
阶段 2.5 · 类型判定
| type | 判据 | 产出 |
|---|---|---|
| 操作型 | 有可复现的软件/工具操作 | 产出 PLAYBOOK |
| 理论型 | 只讲原理、观点、方法论 | 不产出 PLAYBOOK,改为「要点卡 + 自测题」并入主笔记 |
| 混合 | 兼有 | PLAYBOOK 只覆盖实际演示的部分 |
不要为了凑齐三件套而编造操作步骤 —— validate.py 会检查 type 与实际产出一致。
短视频(<10 分钟且知识点少)允许三层合并为单文件,不建目录三件套。
阶段 3 · 写入 Obsidian(第二个确认点)
目录与命名
$NOTES_ROOT/<分类>/<slug>/
├── <清洗后标题>.md # 主笔记,入口
├── PLAYBOOK.md # 条件产出
├── EXTEND.md
├── transcript.md
└── assets/ # mm-ss-描述.jpg
slug:yt-<视频ID>/bili-<BV号>/local-<文件名hash>(可读,与引擎 id 不同)- 文件名清洗:替换
/ \ : # ^ [ ] |,上限 80 字符,不含 emoji - 互链用完整路径 wikilink:
[[视频笔记/编程开发/yt-xxx/PLAYBOOK|操作手册]]—— 各视频目录下 PLAYBOOK/EXTEND 同名,短链接会指向错的文件 - 时间戳锚点:YouTube
&t=<秒>s;Bilibili?t=<秒>(多 P 加p=N);本地文件降级为 纯文本[mm:ss]
步骤
- 基于实际内容最终确认分类与标题(与阶段 0 初判不符就在此修正),连同 slug、 是否覆盖一并确认。这是最后一个计划内确认点。
- 用
templates/三件套填空,合并各段草稿,检查段间术语与编号一致,拷入transcript.md。 - EXTEND 默认精简模式(自身知识 + 官方文档链接);WebSearch 仅在阶段 0 勾选时启用。
扩展内容一律标
[扩展],且不带时间戳 —— 时间戳是「视频里说过」的凭据,混进扩展层 就分不清哪些话是讲者说的了。 - 语言:正文中文,术语/命令/代码保留原文,首次出现给中译;非中文视频的关键论断附原文引述。
- 重跑保护:写入时把正文 hash 存进 frontmatter
content_hash。重跑时重算, 不一致即视为被人工改过 → 写*.regen.md列出差异交用户裁决,不要直接覆盖。 (不要用 mtime 判断,Obsidian 插件会改 mtime。) - 运行校验并贴出结果:
有 ERROR 就修到通过再交付;WARN 逐条说明为何可接受。python3 <skill_dir>/scripts/validate.py "<笔记目录>" - 逐项自报 checklist 完成状态。
阶段 4 · 可选蒸馏(默认不执行,征询用户)
处理完成后由用户决定。分流按阶段 2.5 的类型判定:操作内容走 PLAYBOOK 固化,方法论内容 走 cangjie——两条路线不混用,也不必都跑(cangjie 与本 skill 是上下游关系,各自独立迭代, 经 transcript 契约衔接,勿把它的流程并入本文档)。
- 方法论型 / 混合型的方法论部分 → cangjie-skill 蒸馏成技能包。交接契约
(2026-09-08 对全新视频全流程实测,记录见仓库
docs/experiments/cangjie-stage4-2026-09-08.md):- 输入只有
transcript.md原始转录,不是笔记 —— cangjie 的 V1 验证要求「原文至少 2 处独立佐证」,笔记是二次压缩产物:两处「佐证」可能是同一时刻的两次转述(V1 假阳性), 笔记的遗漏会被当成原文的完整(覆盖率门失效)。时间戳转录正好充当能力卡source_evidence的定位凭据。 - 随转录交三样元信息:标题 + 作者 + 发布日期(目录命名与审计用)。
- 环境契约:cangjie 脚本依赖 PyYAML 且文档未声明——系统 python 下连
doctor都起不来; 本机用 watch-skill venv 的 python(自带 yaml)执行scripts/cangjie.py。 - 编译产物装机到
~/.claude/skills/<name>/(Hermes 则带 category 层)。编译器硬闸门 会拦断链产物;also_read建议写 slug(capability_id 形态在未打补丁的机器上 会被拦——上游文档与实现契约不一致,本机已在 clone 的local-patches分支修复, 双形态兼容,待报上游 issue/PR)。 - 装机后必补触发测试(
scripts/trigger_test.py,双对照纪律不变)—— 这是整条「视频 → skill」链路目前唯一未闭环的一环。
- 输入只有
- 操作型 → PLAYBOOK 固化为项目内 skill(写 SKILL.md frontmatter + 带排除清单的触发 描述,装机后同样补触发测试。此路线尚无实测记录,首个试点可用任一现有 PLAYBOOK)。
清理时序:证据帧已拷 assets、transcript 已存档、用户无追加问题 → 才允许清理
$WORK。引擎自己的下载缓存由 LRU 管理,不要手动删。
质量控制
- 证据规则:每条视频知识点必须有语音或画面证据并附时间戳;辨认不清必须标注。
- 可复现盲测:见阶段 2.4 —— 操作型视频的必做步骤,不在这里重复。
- 降级透明:任何降级写进 frontmatter
degradations与文首信息块 —— 字幕缺失走 whisper、纯视觉、源被降采样到 720p、跳段、OCR 关闭、索引不可用。
错误处理
| 场景 | 处理 |
|---|---|
| 拿到机翻英文轨 | 核对 info.json language,重跑并显式指定原生语种 |
| 完全无字幕 | 本地 mlx whisper(无 key、无体积上限)。这是常态而非异常 |
| 源 >720p 且含代码/界面 | 阶段 0 就要本地高清文件。没有 cookie 方案,引擎硬性不支持 |
| 需登录 / 地区限制 | 提示提供本地文件,不绕过 |
| 帧文字不可读 | OCR 交叉校验 → ask 免模型回补 → 定点重抽 → 仍不可读则标注 |
| 会话中断 | .drafts/ 断点续跑,只补缺失段 |
| mlx 权重缺失 | 回退 WATCHSKILL_WHISPER_MODEL=medium WATCHSKILL_WHISPER_BACKEND=ctranslate2,记入 degradations |
| 引擎异常 | 记录复现命令;必要时按 README 的退路切回 claude-video |
典型调用
用 video-distill 把这个视频沉淀成笔记:https://www.bilibili.com/video/BVxxxx
阶段 0 确认一次 → 阶段 1 全片转录(13 分钟视频约 40 秒)→ 阶段 2 cue 定位 + 定点抽帧 → 阶段 3 确认一次后写入 + 校验。
计划内确认 2 次;查重命中、需新建分类、需本地高清文件会各追加一次,最坏约 5 次。
附带资源
templates/NOTES.md·templates/PLAYBOOK.md·templates/EXTEND.md— 产出模板,填空用scripts/validate.py— 分层校验,阶段 3 必跑scripts/blind_prep.py— 盲测隔离器(阶段 2.4),只留 PLAYBOOK 并打印标准提示词scripts/trigger_test.py— 触发准确性(阶段 4 装机后必跑,强制已知对照)evals/trigger-evals.json(9 正 / 11 负)·evals/evals.json— 触发与执行评测样例references/engine-internals.md— 引擎内部行为与实测数据。想改动上面任何一条规则前先读它