Video to Article 全能视频转图文工作流
这个技能用于把视频内容转换成可读、可复用的图文笔记,偏重实际可落地的生产流程。
安全边界与执行原则
默认先走只读预览模式,不要一上来就下载、写文件、执行脚本或提交 Git。
只读预览模式
适用于用户只想:
- 看视频是否可下载
- 获取元信息
- 评估转录方案
- 规划截图和文章结构
- 基于字幕做总结、问答、术语提取
在这个模式下:
- 不下载视频
- 不写入 Vault 或仓库
- 不执行生成脚本
- 不做
git commit或git push
执行模式
只有在用户明确确认后,才允许执行以下有副作用的操作:
- 下载视频或字幕
- 使用 Cookie / 登录态
- 写入 Obsidian Vault、课程目录或仓库
- 执行
_batch_screenshots.sh这类生成脚本 - 修改
index.md、写日志、git commit git push
明确限制
- 如果用户没有明确给出输出目录,不要擅自写入默认路径
- 如果内容涉及付费、私有或登录后资源,先确认用户有权访问
- 不要在回复、日志、笔记或仓库中泄露 Cookie、Token 或其他认证信息
- 对任何将要执行的 shell 脚本,先展示用途、输出路径和关键命令,再执行
git push必须单独确认,不能视为默认收尾步骤
核心能力矩阵
| 模式 | 输入 | 输出 | 适用场景 |
|---|---|---|---|
| A: 单视频笔记 | 视频 URL 或本地视频文件 | 图文混排 Obsidian 笔记,可选 Git 推送 | 影评、教程、做饭视频、Vlog |
| B: 字幕学习 | 已有字幕文件(.srt / .vtt) | 摘要、测验、词汇表、章节笔记、问答 | 基于已有转录内容学习 |
| C: 大课程导入 | 含大量视频的课程文件夹 | Section 级图文文章和索引 | Udemy / 慕课 / 培训课程批量导入 |
模式 A:单视频转图文笔记
常见触发词
只有在用户明确表达“要落盘执行”时再触发完整工作流,例如:
- “把这个视频整理成可落盘的图文笔记”
- “下载视频并生成 Obsidian 图文文章”
- “把整个课程目录批量导入成文章”
- “基于字幕生成学习材料,但不要修改本地文件”
如果用户只是说“总结一下视频”或“帮我看看讲了什么”,优先走只读分析,不要自动进入下载和写文件流程。
工作流
Step 1:识别平台和认证需求
| 平台 | 是否通常需要 Cookie | 格式策略 | 备注 |
|---|---|---|---|
Bilibili (b23.tv / bilibili.com) | 通常需要 | 先跑 -F,再显式选择格式 ID | BV 号视频,可能遇到 412 反爬 |
YouTube (youtube.com / youtu.be) | 通常不需要 | 优先 bestvideo[height<=720]+bestaudio | 下载相对直接 |
| TED / Coursera / Udemy | 视情况 | 先跑 -F | 付费内容可能要求登录 |
| 抖音 / TikTok / 小红书 | 通常不需要 | 自动选择通常够用 | 短视频居多 |
| 其他 1700+ 平台 | 视情况 | 先跑 -F | 需要时用平台特定兜底策略 |
Bilibili Cookie 导出示例:
# 将浏览器 Cookie 导出成 Netscape 格式
python3 path/to/bili_cookie_to_netscape.py
Cookie 安全要求:
- Cookie 属于敏感认证信息,等同于登录态
- 仅在用户明确授权访问其账号可见内容时使用
- 不要把 Cookie 写入仓库、笔记、日志或聊天输出
- 如果只是公开内容预览,优先先尝试无 Cookie 方案
Step 2:解析视频 ID 并获取元信息
# 解析 Bilibili 短链
VIDEO_ID=$(curl -sL "https://b23.tv/XXXXX" -o /dev/null -w "%{url_effective}" | sed 's/.*\/video\///;s/[\/?].*//')
# 不下载直接读取元信息
yt-dlp --no-download --print "%(title)s|%(duration)s|%(uploader)s" "<URL>"
Step 3:下载视频
这是有副作用操作。只有在用户明确同意下载,并且已经确认输出目录后再执行。
实用默认策略:
- 小于 4 分钟:优先 1080p
- 大于等于 4 分钟:优先 720p
# 先查看可用格式
yt-dlp -F "<URL>"
# 下载(Bilibili 需要时加 --cookies)
mkdir -p "<TEMP_DIR>/<VIDEO_ID>"
yt-dlp -o "<TEMP_DIR>/<VIDEO_ID>/video.%(ext)s" \
-f "<chosen-format-id>" --merge-output-format mp4 "<URL>"
Step 4:用 Whisper 转录
如果用户只是要摘要或问答,优先复用现有字幕或已有转录,不要默认重新下载并转录整段视频。
python3 << 'PYEOF'
from faster_whisper import WhisperModel
model = WhisperModel("base", device="cpu", compute_type="float32",
download_root="<CACHE_DIR>")
segments, _ = model.transcribe("<TEMP_DIR>/<ID>/video.mp4", language="zh", beam_size=5)
with open("<TEMP_DIR>/<ID>/transcript.txt", "w") as f:
for seg in segments:
f.write(f"[{seg.start:.1f}s - {seg.end:.1f}s] {seg.text.strip()}\n")
PYEOF
语言建议:
- 中文:
language="zh" - 英文:
language="en" - 自动识别:省略语言参数
模型取舍:
tiny:最快base:推荐默认small:更准但更慢
Apple Silicon 加速:
device="mps", compute_type="float16"
Step 5:先分类内容,再规划截图
先根据转录内容判断视频类型,再决定截图密度。
| 视频类型 | 特征 | 截图数量 | 密度规则 |
|---|---|---|---|
| 讲演 / 影评 / 长分析 | 论点推进,口播为主 | 8-12 | 大约每 30-40 秒 1 张 |
| 教程 / 实操演示 | 步骤递进,UI 或代码变化明显 | 3-8 | 每个关键步骤 1 张 |
| 论坛 / 对谈 | 多人发言,话题切换明显 | 5-8 | 说话人切换或话题转折时截 |
| Vlog / 随口杂谈 | 叙事松散 | 3-5 | 按场景变化截 |
| 30 秒以内 | 单点信息 | 1 | 开头帧即可 |
| 30-120 秒 | 短内容分段明显 | 3-5 | 按段落切分 |
截图时间点经验:
- 讲演类:标题画面、引出核心概念、每个主要论点首帧、金句时刻、结尾
- 教程类:每个关键步骤变化、UI 切换、结果展示画面
- 避免截图:填充性口播、画面没变化的 talking head、重复画面
命名规范:
- 使用英文 kebab-case
- 使用两位数字前缀
- 例如:
01-intro.jpg、02-core-concept.jpg
Step 6:批量抽帧
这是文件写入操作。执行前先确认截图输出目录和命名规则。
VIDEO="<TEMP_DIR>/<ID>/video.mp4"
OUTDIR="<TEMP_DIR>/<ID>/screenshots"
mkdir -p "$OUTDIR"
ffmpeg -y -ss <seconds> -i "$VIDEO" -frames:v 1 "$OUTDIR/01-intro.jpg"
Step 7:生成图文笔记
这是持久化写入操作。只有在用户明确要求写入 Vault 或指定目录后再执行。
mkdir -p "<VAULT>/assets/video/<VIDEO_ID>"
cp <TEMP_DIR>/<ID>/screenshots/*.jpg "<VAULT>/assets/video/<VIDEO_ID>/"
推荐笔记结构:
---
title: <核心主题>
source: <Bilibili|YouTube|...>
author: <作者>
type: 文献笔记
tags: [视频笔记, <主题标签>]
created: <YYYY-MM-DD>
---
# <作者>:<标题>
> **来源**:<平台> @<作者>
## 核心洞见
1-2 段概括
![[../../../assets/video/<ID>/02-xxx.jpg]]
## <分节标题>
...
![[../../../assets/video/<ID>/xxx.jpg]]
> "<转录中的引用原文>"
## 行动清单
- [ ] ...
## 金句
> ...
## 相关笔记
- [[已有笔记]] - 关联原因
图片引用格式:
![[../../../assets/video/<ID>/<filename>.jpg]]
Step 8:把笔记接入知识库
默认只给出关联建议。只有在用户明确要求更新知识库时,才执行全文搜索和实际写入动作。
find "<VAULT>" -name "*.md" | xargs grep -li "<core keyword>"
Step 9:提交并推送
这一步必须拆开处理:
git add/git commit仅在用户明确要求版本化本次结果时执行git push必须单独确认,不能默认执行
cd "<VAULT>" && git add -A && git commit -m "video note: <title>" && git push
模式 B:基于字幕学习
常见触发词
“学习这个字幕”、“总结这个转录”、“根据这个字幕出题”、“整理术语”、“考考我这个视频”
输入来源
| 来源 | 获取方式 | 预处理 |
|---|---|---|
自带 .srt / .vtt 字幕 | 直接读取 | 优先使用 |
| 下载的视频但没有字幕 | 用 yt-dlp 字幕下载或 Whisper | 先去时间码转纯文本 |
| 用户直接粘贴字幕 | 直接使用 | 清洗后可直接分析 |
学习模式
B1:知识提取(默认)
输出结构化学习指南,包括:
- Key Concepts
- Key Takeaways
- Examples
- Glossary
B2:章节摘要
按主题切换拆章,每章总结 2-3 句。
主题切换信号:
- 过渡语,例如 “Now let's talk about...”
- 大于 5 秒的长停顿
- 突然引入大量新术语
B3:关键词汇表
提取领域术语、高频词、明确定义的概念和缩写。输出表格包含:
- 术语
- 需要时的翻译
- 上下文解释
- 出现位置
字幕预处理
原始字幕文件通常大部分都是元数据,先清理再分析。
import re
def strip_srt(srt_text):
lines = srt_text.split('\n')
result = []
for line in lines:
line = line.strip()
if re.match(r'^\d+$', line): continue
if re.match(r'^\d{2}:\d{2}:\d{2}', line): continue
if not line: continue
result.append(line)
return ' '.join(result)
常见陷阱
- 字幕非常大:分块读取,单次最好不超过 1500 行
- ASR 误识别:标记为
[possible transcription error] - 内容强依赖视觉:标记为
[visual content here] - 多人对话:自动字幕可能会把说话人混在一起
模式 C:大型课程批量导入
六阶段流程
| Phase | 目的 | Token 策略 |
|---|---|---|
| 1. 目录分析 | 建立 section 和 lesson 树 | Token 很低 |
| 2. 字幕预处理 | 把字幕批量转成纯文本 | 终端脚本,零 token |
| 3. 截图规划 | 预计算 slug 和 ffmpeg 命令 | 终端脚本,零 token |
| 4. 批量截图 | 后台运行 ffmpeg | 终端脚本,零 token |
| 5. 并行写文章 | 最多 3 个子代理并发处理 section | 只传纯文本和 slug 表 |
| 6. 收尾 | 生成 index.md 和导入日志 | Token 很低 |
Phase 1:目录分析
遍历课程目录,建立如下结构:
{Section: [video list]}
课程名和课时名要按数字前缀精确匹配。
Phase 2:字幕预处理
使用 scripts/preprocess-srt.py:
- 修改课程配置
- 运行脚本
- 读取
_preprocessed/section-<slug>.txt
如果用户只想先评估课程结构或输出效果,先展示计划,不要立即批量写文件。
Phase 3:截图规划
使用 scripts/plan-screenshots.py 根据时长和内容类型决定截图点。
预期输出:
_screenshot_plan.json_batch_screenshots.sh
Phase 4:批量截图
_batch_screenshots.sh 属于生成脚本。执行前必须:
- 预览脚本内容
- 确认输出目录仅落在预期课程或资源目录
- 获得用户对执行批处理命令的明确确认
bash _batch_screenshots.sh
顺序运行更稳。ffmpeg 比较吃 CPU,不建议并发抽帧;如果用户未确认,也不要默认后台启动。
Phase 5:并行写文章
子代理上下文写法会强烈影响 token 浪费情况,遵守这三条:
- 明确说明截图已经准备好,不要重复验证
- 提供完整的 slug 到 label 映射,不让子代理自己算名字
- 明确列出哪些课不放截图
示例上下文:
context = """
用英文写课程笔记。
Input: _preprocessed/section-01.txt
All screenshots are already prepared. Do not verify files.
Reference them directly as:
![[../../../assets/<course>/<slug>-<label>.jpg]]
Slug table (each slug has overview/detail/result unless noted otherwise):
- Installation -> installation (3 images)
- Intro -> intro (no screenshots)
Density: Intro gets no screenshots. Other lessons get 3 screenshots each.
Output: sources/<course>/section-01-xxx.md
"""
并行分配建议:
- 最多 3 个子代理
- 每个子代理最多 4 个 section
Phase 6:收尾
- 只允许主代理更新
index.md - 写导入日志
- 如用户明确要求,再提交到 Git
这里涉及仓库文件修改,默认应先展示将要变更的文件范围,再执行实际写入。
前置条件
yt-dlp:下载视频ffmpeg:音频处理和截图faster-whisper:语音转录- 一个最好由 Git 管理的 Obsidian Vault
git:版本控制和同步
环境检查:
which yt-dlp ffmpeg 2>&1
python3 -c "from faster_whisper import WhisperModel; print('OK')" 2>&1
路径约定
| 变量 | 用途 | 示例 |
|---|---|---|
<TEMP_DIR> | 临时下载和处理目录 | 项目内临时目录 |
<VAULT> | Obsidian 库根目录 | ~/Documents/Obsidian |
<ATTACH_DIR> | Vault 内截图目录 | <VAULT>/assets/video/<VIDEO_ID>/ |
<NOTE_DIR> | 笔记输出目录 | <VAULT>/reference-notes/ |
排错
| 问题 | 解决方式 |
|---|---|
Bilibili 412 反爬 | 刷新 Cookie 并重新登录 |
| Bilibili 格式 ID 失效 | 先跑 -F,因为每个视频的 ID 可能不同 |
| YouTube 下载失败 | 处理地区限制,或降级到 -f "best[height<=720]" |
| Whisper 太慢 | 切到 tiny,或在 Apple Silicon 上用 device="mps" |
| 截图文件名导致链接混乱 | 使用英文 kebab-case |
| Git push 失败 | 先确认用户仍然希望推送,再试 git pull --rebase && git push |
| 子代理反复验证截图 | 硬编码 slug 表,并明确写 do not verify files |
评论
加载中…