把 B 站视频剪藏自动变成逐字稿和带时间戳可跳转的 AI 摘要(vault-ingest agent)
把 B 站视频/网页剪藏自动转成带时间戳的逐字稿和 AI 摘要;字幕优先、ASR 兜底,锚点校验杜绝 AI 编造
vault-ingest 是跑在 PARA 知识库里的「视频 / 文章网页剪藏」工作流:把 Obsidian Web Clipper 落在 _Capture/ 根目录的 B 站视频与网页剪藏,批量转成「带时间戳逐字稿 + 基于逐字稿的 AI 总结摘要」,写入 _Capture/_Clipper/ 文件夹,每周人工归类。确定性脚本(无 LLM)+ 摘要 agent + 手动命令三件套,用锚点强制校验杜绝 AI 编造。
一句话概括:三阶段工作流(prepare 抓取转录 → agent 写摘要 → finalize 校验记账),字幕优先、ASR 兜底,产物落进 git 忽略的 _Clipper/,源剪藏全程只读。
一、它是什么
vault-ingest 解决的是「剪藏只进不出」问题:你在 B 站看到好视频、微信读到好文章,用 Obsidian Web Clipper 剪藏进 _Capture/,然后就没有然后了——视频舍不得花时间看、文章堆着不消化,_Capture/ 慢慢变成信息垃圾场。
该工作流把这一步自动化:
- 输入:
_Capture/根目录下的视频/网页剪藏初始信息(frontmatter 含sourceURL,在 Google 扩展程序中格式可配置)。自动区分两类——B 站视频链接(bilibili-video)与网页文章(web-article),跳过01-/02-/03-等人工构建笔记与所有子目录(即_Clipper文件夹)。 - 处理:视频优先抓 B 站现成字幕(免费、快、准),无字幕才下载音频走云 ASR 兜底(未测试);文章直接复用剪藏正文。
- 产出:
_Capture/_Clipper/下每篇剪藏生成「摘要笔记(<缩写>-<author>-<title>.md)+ 完整逐字稿(transcripts/<缩写>-<author>-<title>.md,与摘要笔记同名)」。摘要笔记正文完全按剪藏模板组织(**一句话总结**+#### 1. 要点+#### 2. 个人加工+#### 参考链接+#### 编辑历史记录,段间以---分隔);逐字稿 H1 与摘要笔记的来源链接文字统一用<缩写>-<author>-<title>格式(bilibili-video→BV、web-article→WA;author/title按模板规则过滤:中文标点、空格、Windows 禁止字符一律删除)。另生成_本周回顾-<日期>.md供每周归类。
核心设计是摘要绝不脱离逐字稿:视频要点带可点击跳回视频的时间戳链接([MM:SS](视频URL?t=秒#t=MM:SS.ff),配合 media-extended 插件点击即跳转),文章要点带原文引句;--finalize 阶段脚本二次校验每个锚点确实存在于逐字稿,不通过就标记 needs-review,从机制上杜绝「AI 编造内容」。
适合场景:个人知识库的碎片剪藏定期消化、视频课/播客转文字归档、把「收藏夹」变成「可检索的知识」。
二、复现章节(照着做——已拿到资源包)
本节面向已拿到「vault-ingest 资源包」的读者。资源包包括下述 8 个文件。
(一) 资源包构成
| 材料 | 路径 | 作用 |
|---|---|---|
| 管道主脚本 | .opencode/scripts/ingest.py | 扫描/去重/抓取/转录/落盘/校验/报告(确定性、无 LLM) |
| 依赖清单 | .opencode/scripts/requirements.txt | yt-dlp、requests、PyYAML、secretstorage |
| 入口 wrapper | .opencode/scripts/ingest.sh | 固定走 venv 的 Python 调用 ingest.py(规避系统 Python 缺包) |
| cookie 助手 | .opencode/scripts/bili-cookies.sh | 从浏览器导出 B 站 cookie(Netscape 格式) |
| 摘要 agent | .opencode/agents/vault-ingestor.md | 编排管道 + 逐条写摘要(复用 opencode 模型) |
| 触发命令 | .opencode/commands/vault-ingest.md | /vault-ingest 命令入口 |
| git 忽略(库根) | .gitignore | 追加 _Capture/_Clipper/(产物不进 git) |
| git 忽略(opencode) | .opencode/.gitignore | 追加 __pycache__/、*.pyc |
(二) 前置条件
- opencode 已安装,在 PARA 库根目录(
AGENTS.md生效的目录)操作 - Python ≥ 3.10(
yt-dlp已提示弃用 3.10,建议 3.11+ 建 venv) ffmpeg可用(音频时长探测与切分,ASR 接口对音频时长有限制(且长音频一次转写容易超时/截断),所以要先在静音处切段)- B 站账号(Chrome/Firefox 登录态,用于导出 SESSDATA cookie,让管道能免费、快速、准确地直抓 B 站现成字幕)
- SiliconFlow 账号(可选——仅无字幕视频的 ASR 兜底需要;有字幕的视频用不到)
- Obsidian(可选,用于查看产物与周回顾)
(三) 分步骤操作清单(装配)
- 放入资源包:把 8 个文件放到对应位置(
.opencode/scripts/、.opencode/agents/、.opencode/commands/;.gitignore两处按内容合并)- ✅ 验证:
ls .opencode/scripts/显示ingest.py、ingest.sh、bili-cookies.sh、requirements.txt
- ✅ 验证:
- 建 venv 并装依赖:
python3 -m venv ~/.cache/para-ingest/venv ~/.cache/para-ingest/venv/bin/pip install -r .opencode/scripts/requirements.txt- ✅ 验证:
~/.cache/para-ingest/venv/bin/python -c "import yt_dlp; print(yt_dlp.version.__version__)"输出 2026 版本号
- ✅ 验证:
- 导出 B 站 cookie(先确认 Chrome 已登录 bilibili.com):
bash .opencode/scripts/bili-cookies.sh- ✅ 验证:
~/.cache/para-ingest/cookies.txt存在、权限 600、含SESSDATA
- ✅ 验证:
- 配置 SiliconFlow key(仅无字幕视频兜底需要,可后补):
printf '{\n "siliconflow_api_key": "<REDACTED>"\n}\n' > ~/.cache/para-ingest/secrets.json chmod 600 ~/.cache/para-ingest/secrets.json- ✅ 验证:
bash .opencode/scripts/ingest.sh --check显示SILICONFLOW_API_KEY:已设置
- ✅ 验证:
- Obsidian 排除逐字稿目录(避免全文污染搜索):设置 → 文件与链接 → 忽略文件 → 添加
_Capture/_Clipper/transcripts/- ✅ 验证:
_Capture/_Clipper/transcripts/下的文件不再出现在 Obsidian 搜索
- ✅ 验证:
- 运行管道:
- 在 opencode 输入
/vault-ingest(agent 自动完成 prepare → 摘要 → finalize 三阶段) - ✅ 验证:终端依次显示「prepare 完成 / 摘要写入 / finalize 完成」,
_Capture/_Clipper/出现笔记、逐字稿与_本周回顾-*.md
- 在 opencode 输入
(四) 验证 / 自测步骤
bash .opencode/scripts/ingest.sh --check:打印配置路径、cookie 是否存在、API key 是否设置、Python/ffmpeg 版本bash .opencode/scripts/ingest.sh --dry-run:只扫描+分类+判重,不联网不写文件,确认bilibili-video/web-article识别正确、01-/02-/03-被跳过- 首跑
/vault-ingest后抽查:视频笔记每个带链接的时间戳要点能在逐字稿对应行找到(且点击可跳回视频);文章要点引句逐字出现在正文 - 打开
_Capture/_Clipper/_本周回顾-*.md:确认「新增 / 失败」两张表内容正确
(五) 坑与注意事项
- 不要用系统 yt-dlp:系统自带的可能是 2022 年古董版,B 站接口早已变更。必须用 venv 内安装的新版——
ingest.shwrapper 强制走 venv,勿绕过。 - B 站字幕填充开关:
yt_dlp的extract_info(download=False)返回的info['subtitles']默认是空 dict(连 danmaku 都没有),必须在 YoutubeDL 选项里同时开writesubtitles: True和writeautomaticsub: True才触发 extractor 填充。 - 字幕需要登录:不带 SESSDATA cookie 时
--list-subs只显示danmaku,ai-zh等字幕不可见 → 视频会错误地落到 ASR。cookie 约每月过期,过期重跑bash .opencode/scripts/bili-cookies.sh。 - Linux Chrome cookie 解密:Chrome cookie 用系统 keyring 加密,Python 侧需装
secretstorage(否则几百条 cookie 解不开);且新版 yt-dlp 用--cookies落盘的是 JSON,读不回来——所以bili-cookies.sh用extract_cookies_from_browser+jar.save()直接写 Netscape 格式。 - 相对路径:
.opencode/scripts/...只有在库根目录下才有效;在别的目录请用绝对路径或先cd到库根。 - opencode CLI 不在 PATH:
ingest.py刻意不 shell 调opencode(避免嵌套与路径依赖),摘要由 agent 直接编排完成。 - 去重与续跑:已处理的源剪藏留在
_Capture/根目录不动(管道只读源),靠_Clipper/.ingest-ledger.json判重;笔记骨架存在但ingest-status: new时重跑会「续跑」(不再抓取、不覆盖),summarized才跳过。 - 产物不进 git:
_Capture/_Clipper/整体被.gitignore忽略,靠坚果云等同步备份,避免每周提交噪音。
三、架构解析章节(看懂)
(一) 文件职责地图
| 文件 | 职责 | 关键操作 | 跨文件/模块交互 |
|---|---|---|---|
.opencode/scripts/ingest.py | 确定性管道主体 | 扫描 _Capture/*.md → 分类(重命名)/去重 → 抓取转录 → 写骨架+逐字稿+manifest → finalize 校验/记账/周回顾 | 调 yt_dlp、ffmpeg、SiliconFlow API;读写 _Clipper/ 下笔记与两个 dotfile |
.opencode/scripts/ingest.sh | venv 统一入口 | 解析 ~/.cache/para-ingest/venv 的 Python 绝对路径后 exec ingest.py | 保证依赖在 venv 内,规避系统 Python 缺包与版本污染 |
.opencode/scripts/bili-cookies.sh | cookie 导出 | extract_cookies_from_browser + jar.save() 写 Netscape 文件;chmod 600 | 产出 ~/.cache/para-ingest/cookies.txt,供 yt_dlp 与 requests 取用 |
.opencode/agents/vault-ingestor.md | 摘要 agent | 跑 ingest.sh --prepare → 逐条写摘要 → --finalize;只改笔记「一句话+要点」两处 | 调 ingest.sh;读逐字稿写摘要;约束(不改源、不臆造)内嵌于提示词 |
.opencode/commands/vault-ingest.md | 触发命令 | 声明 agent: vault-ingestor 与流程说明 | 在 opencode 输入 /vault-ingest 即进入 agent 流程 |
.gitignore(库根) | 忽略产物 | _Capture/_Clipper/ | 管道产物不进 git 历史 |
.opencode/.gitignore | 忽略 Python 缓存 | __pycache__/、*.pyc | 保持 opencode 工具目录干净 |
关键说明:工作流把「确定性」与「推理」物理分离——ingest.py 全程无 LLM,可幂等、可单测、失败可见;vault-ingestor agent 只做「读逐字稿 → 写摘要」这一件推理事,复用 opencode 当前配置的模型,不新增 API key。
(二) 依赖 / 调用关系
- 运行时依赖:
yt-dlp(B 站元数据/字幕/音频)、ffmpeg(静音检测切段)、SiliconFlow/v1/audio/transcriptions(ASR 兜底)、requests(字幕与 ASR HTTP)、PyYAML(frontmatter 解析)、secretstorage(Linux Chrome cookie 解密,仅 cookie 导出用) - 调用链:
/vault-ingest命令 →vault-ingestoragent →bash ingest.sh→ venvpython ingest.py→yt_dlp/ffmpeg/ SiliconFlow API;bili-cookies.sh→yt_dlp.cookies.extract_cookies_from_browser - 运行时状态:
~/.cache/para-ingest/(config.json、cookies.txt、secrets.json、media/<id>/音频缓存、venv/)+_Clipper/.ingest-ledger.json(去重账本)+_Clipper/.ingest-manifest.json(prepare → finalize 交接) - 被依赖方:产物笔记是用户周回顾与后续转正为原子笔记的素材,是知识库的「上游原料」
(三) 数据流 / 执行时序
用户: /vault-ingest
↓
① prepare ingest.py --prepare
扫描 _Capture/*.md(跳过 01-/02-/03- 编号笔记与子目录)
→ 分类(bilibili-video / web-article)+ canonical id(BV 号 / 去 tracking 参数的 URL)
→ ledger 判重(status=done 跳过;骨架存在且未摘要 → 续跑)
→ 视频:yt_dlp 字幕优先(需 cookie);无字幕 → 下载音频 → ASR 兜底(缺 key 报 asr_key_missing)
→ 文章:复用剪藏正文(正文 <40 字符或含微信墙标记 → clipper_capture_incomplete 失败)
→ 写 _Clipper/<base>.md 骨架 + transcripts/<base>.md 逐字稿
→ 写 .ingest-manifest.json(prepared / failed 清单)
↓
② 摘要 vault-ingestor agent 读 manifest
对每个 prepared 条目:读逐字稿 → 填「**一句话总结**」下的引用块与「#### 1. 要点」
视频要点以带跳转链接的时间戳开头(直接从逐字稿复制 [MM:SS](视频URL?t=秒#t=NPT),点击可跳回视频);文章要点用「原句」引句锚点
—— 只改这两处,不碰 frontmatter / #### 2. 个人加工 / #### 参考链接 / #### 编辑历史记录(正文骨架完全按剪藏模板)
↓
③ finalize ingest.py --finalize
校验每个锚点(时间戳/引句)确实在逐字稿中 → ingest-status: summarized 或 needs-review
→ 更新 ledger → 写 _本周回顾-YYYY.MM.DD.md(新增表 + 失败表)
↓
④ 用户周回顾 → 读 _本周回顾 与各笔记 → 人工 triage(转正原子笔记 / 丢弃 / 保留观望)
| 文件/指令 | 子功能点的描述 | [函数标注] | 文字总结 |
|---|---|---|---|
| 用户 | /vault-ingest 触发命令 | — | 入口 |
| ↓ | |||
| ① prepare(脚本:写逐字稿 + 空骨架) | |||
ingest.py --prepare | 扫描 _Capture/*.md,跳过 01-/02-/03- 编号笔记与子目录 | scan_capture | 定型 |
分类 bilibili-video / web-article + canonical id(BV 号 / 去 tracking 参数 URL) | analyze_file | 定身份 | |
ledger 判重(status=done 跳过;骨架存在且未摘要 → 续跑),四态状态机判断后续进行的动作 | 定做不做 | ||
视频:yt_dlp 字幕优先(需 cookie);无字幕 → 下载音频 → ASR 兜底(缺 key 报 asr_key_missing) | fetch_bilibili | ||
文章:复用剪藏正文(正文 <40 字符或含微信墙标记 → clipper_capture_incomplete 失败) | fetch_article | 定内容 | |
写 _Clipper/<base>.md 骨架 + transcripts/<base>.md 逐字稿 | build_transcript_file + build_note_file | 定产物 | |
写 .ingest-manifest.json(prepared / failed 清单,输入给 finalize) | run_prepare | 定清单/定账 | |
| ↓ | |||
| ② 摘要(agent:读 manifest → 写摘要) | |||
vault-ingestor agent | 对每个 prepared 条目:读逐字稿 → 填「一句话总结」下的引用块与「#### 1. 要点」 | — | 写”一句话总结”和”要点” |
视频要点以带跳转链接的时间戳开头(直接从逐字稿复制 [MM:SS](视频URL?t=秒#t=NPT),点击可跳回视频);文章要点用「原句」引句锚点 | 锚点可回跳/可查证 | ||
只改这两处,不碰 frontmatter / #### 2. 个人加工 / #### 参考链接 / #### 编辑历史记录(正文骨架完全按剪藏模板) | 严守骨架 | ||
| ↓ | |||
| ③ finalize(脚本:校验 + 写周回顾) | |||
ingest.py --finalize | 校验每个锚点(时间戳/引句)确实在逐字稿中 → ingest-status: summarized 或 needs-review | validate_note | 定真伪(校验) |
更新 ledger → 写 _本周回顾-YYYY.MM.DD.md(新增表 + 失败表) | write_weekly_review | 定账(周回顾) | |
| ↓ | |||
| ④ 用户周回顾 | 读 _本周回顾 与各笔记 → 人工 triage(转正原子笔记 / 丢弃 / 保留观望) | — | 定去留 |
(四) 设计意图(为什么这么设计)
- 脚本不做 LLM、agent 不做抓取:抓取/转录是确定性 I/O 活,放脚本里可幂等、可测试、单条失败不拖垮整批;摘要是推理活,交给 agent 复用 opencode 模型。脚本不 shell 调 opencode(CLI 不在 PATH + 会话嵌套有风险),由 agent 编排最稳。
- 字幕优先、ASR 兜底:B 站大部分视频有 CC/AI 字幕(
ai-zh等),直抓字幕免费、快、准确率高;无字幕才下音频转写。云 ASR 选 SiliconFlowSenseVoiceSmall(免费档、OpenAI 兼容、中文效果好)。 - 锚点强制防捏造:视频摘要的时间戳锚点直接复用逐字稿里的跳转链接(点击可跳回 B 站对应位置),
--finalize再机械校验一遍(时间戳在逐字稿中真实存在、引句逐字可查),不过则标needs-review。这是「AI 总结不编造」的机制保障而非提示词愿望。 _Clipper/git 忽略 + 同步备份:管道产物是中间态,进 git 会造成每周噪音;靠坚果云备份,源剪藏保持只读,_Capture/收件箱语义不破。- 去重靠 ledger、续跑靠 ingest-status:源文件不移动(尊重收件箱),用 canonical id(BV 号 / 去 tracking 参数的 URL)判重;笔记骨架存在但未摘要时续跑,避免重抓和覆盖已写内容。
(五) 二次开发入口
| 想做的修改 | 改哪里 |
|---|---|
| 新增输入源(YouTube / 本地视频 / 图片 OCR) | ingest.py 的 classify + 新增 fetch_* 函数 + 输出命名扩展 |
| 换 ASR 引擎(本地 SenseVoice/Paraformer 等) | run_asr(保持 (音频) -> [(ts, text)] 接口,只改这一个函数) |
| 换摘要模型 | vault-ingestor.md(agent 用 opencode 当前模型即可);或改为脚本直调 API |
| 加定时触发 | 命令外另配 cron / systemd timer:prepare → 摘要 → finalize 三段脚本化 |
| 调整摘要锚点规则 | vault-ingestor.md 摘要硬规则 + ingest.py 的 validate_note |
| 改产物笔记 schema(框架、模板) | build_note_file / build_transcript_file |
四、二次开发指南
把「二次开发入口」具体化为可操作指引,含资源包重新打包/构建说明。
(一) 核心改动点
示例:新增 YouTube 输入
classify增加youtube.com分支 → kindyoutube-video,canonical id 用youtube:<videoId>- 新增
fetch_youtube():yt_dlp 对 YouTube 字幕同样走writesubtitles开关;无字幕复用run_asr兜底 - 输出命名按 kind 扩展(如
yt-<videoId>.md),validate_note的时间戳校验规则可复用 - 验证:
--dry-run确认分类正确 → 首跑确认字幕/兜底两条路径 → 抽查周回顾
示例:ASR 从云端换本地
- 在
run_asr内把 SiliconFlow POST 替换为本地funasr/faster-whisper调用,保持返回[(时间戳, 文本)] --check增加本地模型/环境的可用性检查- 验证:用一个无字幕视频走通,确认时间戳与文本正确
示例:改摘要锚点规则
vault-ingestor.md「摘要硬规则」改要求(如视频要点必须同时带时间戳与引句)ingest.py的validate_note同步改校验逻辑- 验证:写一条不合规摘要跑
--finalize,应被标needs-review
(二) 资源包如何打包与构建
让别人在相同环境里复现你改过的管道,需要把改动重新打成资源包。
资源包所需材料:
| 材料 | 来源 | 是否必选 |
|---|---|---|
.opencode/scripts/(4 文件) | 本管道 | ✅ 必选 |
.opencode/agents/vault-ingestor.md | 本管道 | ✅ 必选 |
.opencode/commands/vault-ingest.md | 本管道 | ✅ 必选 |
.gitignore、.opencode/.gitignore 追加行 | 本管道 | ✅ 必选 |
~/.cache/para-ingest/venv | 本机创建 | 环境依赖(不随包分发) |
打包/构建步骤:
- 复制上述 6 个文件 + 2 处
.gitignore追加行到资源包目录 - 随包附
README:前置条件(Python ≥3.10 + ffmpeg + B 站登录态 + SiliconFlow 账号)、venv 创建命令、Obsidian 排除目录设置、cookie/key 配置命令 - 校验:干净库放资源包 → 建 venv 装依赖 →
--check→--dry-run→ 首跑/vault-ingest→ 核对_本周回顾-*.md - 交付时注明:产物笔记的格式约定(原子笔记模板、库整理规范)与 opencode 版本
验证包可复现:
- 在无本管道的干净 opencode 库放入资源包 → 跑
/vault-ingest→ 应能走通 prepare → 摘要 → finalize - 若失败,回查「前置条件」清单:venv 依赖是否装全、cookie 是否含 SESSDATA、是否在库根目录操作
参考资料
- yt-dlp 文档:yt-dlp 是管道里唯一的外部视频/字幕抓取器。
- SiliconFlow 文档