- Makefile 90.6%
- Python 8%
- JavaScript 0.7%
- Rust 0.2%
- HTML 0.2%
- 其它 0.2%
| 文件名 | 最新提交消息 | 最新提交日期 |
|---|---|---|
| .cursor/plans | ||
| docker | ||
| examples | ||
| gui | ||
| src/video2md | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| config.example.yaml | ||
| DEPLOY_LINUX.md | ||
| handoff.md | ||
| OPEN_QUESTIONS.md | ||
| PLAN.md | ||
| pyproject.toml | ||
| README.md | ||
| ROADMAP.md | ||
video2md
把网课视频转换成结构化的 Obsidian 阅读笔记。
本地完成 ffmpeg 抽音频、关键帧抽取(场景切分 + 感知哈希去重)与轻量编排;ASR(faster-whisper)与 OCR(PaddleOCR)放在 GPU 服务器上、按需用 Docker 启停;LLM 仅作为固定 OpenAI 兼容端点接入(不托管),用 map(自有 vLLM)→ reduce(云端强模型)的级联方式按章节生成笔记,尽量省 token。
产出是一个 <课程名>-notes/ 项目库(每章一个子文件夹、每小节一个 Markdown、统一 attachments/、课程 MOC 与每章索引 wikilink),放在课程文件夹内,供你手动复制进 Obsidian。
设计要点
- 工具与数据分离 / 可移植:工具装在任意位置(如
E:\tools\video2md),全局config.yaml只含服务器/SSH/模型/行为配置,不含数据路径。数据路径完全来自 CLI 传入的课程文件夹。 - 课程文件夹自包含:输入、缓存(
.v2m/)、输出(<课程名>-notes/)都在课程文件夹内,不影响其他文件夹。 - 按需启停:运行开始经 SSH
docker compose up -d拉起 ASR/OCR,健康检查,跑完(或异常/空闲超时)docker compose down释放 GPU。整门课共用一次启停。 - 按章节而非课时:同一章被下课截断、延续到下一个视频时,用少量 token 校验并合并。
- 省 token:重活(ASR/OCR/去重/清洗)不耗 token;结构优先用讲义目录/文件名"第X章"标记;map 走自有 vLLM,reduce 才用云端。
flowchart TD
IN["课程文件夹 videos/ + handouts/"] --> A[逐视频处理]
A --> B[ffmpeg 抽音频] --> ASR
A --> C[抽帧+去重] --> OCR
ASR & OCR --> H[对齐时间线]
H --> S[结构化: 章节/小节 + 跨视频合并] --> MAP[map vLLM] --> RED[reduce 云端] --> OUT["写入 课程名-notes/"]
安装
客户端(Windows / 本地编排)
只需 ffmpeg + 轻量依赖即可跑编排和远程后端:
# 1) 安装 ffmpeg 并确保在 PATH 中(ffmpeg / ffprobe)
# 2) 安装本工具
pip install -e .
# 3) 准备配置
copy config.example.yaml config.yaml
copy .env.example .env # 填入云端 API key
faster-whisper/paddleocr不在客户端核心依赖里;只要backends.*.type = remote,本地无需安装它们。
GPU 服务器(ASR/OCR 推理服务)
# 在服务器上克隆工具仓库后:
cd ~/video2md
# 构建镜像(首次较慢);按主机 CUDA 调整 docker/Dockerfile.server 顶部的 CUDA tag
docker compose -f docker/docker-compose.yml build
# 平时无需常开;video2md 会按需 up/down
服务暴露 /asr、/ocr、/health,默认监听容器内 8000,compose 把它绑到服务器 127.0.0.1:8000(只在本机可见,经 SSH 隧道访问)。
也可不用 Docker,直接
pip install -e ".[server]"后video2md serve起服务(需自行配好 CUDA / paddlepaddle-gpu)。
迁移到 Linux 整机自跑
把工具装到 GPU 服务器,配置里 backends.asr.type / backends.ocr.type 改成 local,并安装本地后端:
pip install -e ".[local]" # + 与 CUDA 匹配的 paddlepaddle-gpu
此时不再需要 SSH / Docker / 隧道,整条流水线在本机直跑。
使用
# 处理一整门课(推荐)
video2md course "E:\courses\某门课" --config config.yaml
# 启用讲义辅助
video2md course "E:\courses\某门课" --handout
# 单个视频(调试)
video2md run "E:\courses\某门课\videos\lesson01.mp4"
# 监听下载文件夹,自动处理新视频
video2md watch "E:\downloads"
# 忽略缓存全部重算
video2md course "E:\courses\某门课" --force
课程文件夹布局
E:\courses\<某门课>\
videos/ # 输入视频(或散放在课程文件夹根目录)
handouts/ # 讲义(可选,PDF / PPTX)
course.yaml # 可选:课时顺序/标题/讲义映射/局部覆盖
.v2m/ # 隐藏缓存(转写/OCR/大纲/小节笔记)
<课程名>-notes/ # 输出:复制进 Obsidian 的项目库
_MOC.md
01-<章节名>/
_index.md
01-01-<小节名>.md
attachments/
可选 course.yaml(放在课程文件夹内)
title: "高等数学(上)"
order: manifest # 显式指定顺序;否则按文件名自然排序
lessons:
- video: lesson01.mp4
title: "第1章 极限 (一)"
handout: lecture01.pdf
- video: lesson02.mp4
title: "第1章 极限 (二)" # 与上一课同属第1章,会自动并入同一章
course: # 可覆盖全局 course 配置
chapter_markers: ['第\s*([0-9一二三四五六七八九十]+)\s*章']
handout:
enabled: true
配置(全局 config.yaml)
见 config.example.yaml。要点:
asr_ocr_server:GPU 服务器 SSH、按需up_cmd/down_cmd、健康检查、端口转发。ssh.enabled: false时为手动模式(你自己开隧道,程序只用base_url)。backends:asr/ocr各自选remote(调 GPU 服务器)或local(本机进程)。providers+tasks:多模型接入。每个 provider 有kind:kind: openai→ OpenAI 兼容端点(vLLM / DeepSeek / OpenAI / Qwen…)kind: anthropic→ Anthropic messages 接口(例如本地以 Anthropic 格式暴露的模型)tasks把逻辑任务路由到 provider 名:structure/section_notes(map)→ 自有模型;final_note/translate(reduce)→ 云端强模型。
course/handout/keyframes/notes:扫描约定、讲义、关键帧、笔记语言与截图等行为。
接入模型:加密凭据库(推荐)
API key 不必写进 config.yaml。用 video2md api 把每个 provider 存成独立加密文件(主密码 scrypt+Fernet 加密,默认在 ~/.video2md/api/),随时增删切换:
video2md api add local --kind anthropic # 你本地的 Anthropic 格式模型(map/structure)
video2md api add cloud --kind openai # 云端 key(DeepSeek/OpenAI/…,reduce)
video2md api list # 列出已存 profile
video2md api show local # 查看详情(key 默认打码,--reveal 显示)
video2md api test local # 发一个极小请求验证连通
video2md api remove cloud # 删除
快速切换运行时用哪个 provider(不改 config.yaml,覆盖记录在 ~/.video2md/active.json):
video2md api use cloud # 所有任务都走 cloud
video2md api use local -t section_notes -t structure # 只切指定任务(可重复 -t)
video2md api use --show # 查看当前覆盖
video2md api use --clear # 清除覆盖, 回到 config.yaml 路由
config.yaml 里 tasks 直接引用 profile 名即可;运行时遇到未在 providers 里定义的名字会自动从加密库解析(每次运行让你输一次主密码,或设环境变量 VIDEO2MD_VAULT_PASSWORD 免输):
providers: {} # key 放加密库, 这里留空
tasks:
structure: local # → 加密库里的 local (anthropic)
section_notes: local
final_note: cloud # → 加密库里的 cloud (openai)
translate: cloud
主密码来源优先级:
VIDEO2MD_VAULT_PASSWORD环境变量 → 交互式输入(首次设置时需确认)。库目录可用VIDEO2MD_VAULT_DIR覆盖。
接入模型:内联明文(可选)
也可直接写在 config.yaml(适合无 key 的本地端点;云端 key 建议走 ${ENV}):
providers:
local: { kind: anthropic, base_url: "http://localhost:8001", api_key: "EMPTY", model: "your-local-model" }
cloud: { kind: openai, base_url: "https://api.deepseek.com/v1", api_key: "${DEEPSEEK_KEY}", model: "deepseek-chat" }
vLLM / 本地模型在另一台服务器、已有稳定 SSH 进程时,本工具不管理它,直接连其端口即可(必要时你自己开 ssh -L 8001:127.0.0.1:8001 model-host)。
SSH 隧道
- 手动(默认):
asr_ocr_server.ssh.enabled: false。自己开隧道,例如:
并保证服务已在服务器起好。ssh -L 8000:127.0.0.1:8000 you@gpu-server # ASR/OCR ssh -L 8001:127.0.0.1:8001 you@vllm-server # vLLM - 自动:
ssh.enabled: true并填 host/user/key_file。程序会用 sshtunnel 转发forward.remote -> forward.local,并按lifecycle启停 ASR/OCR 容器。
省 token 策略
- ASR / OCR / 去重 / 清洗全程不耗 token。
- 跨帧 OCR 去重、去口水词、合并重复。
- 以"小节"为 LLM 处理单元,语义完整,减少冗余上下文。
- map 用自有 vLLM(不花外部 token),reduce 才用云端。
- 结构优先用讲义 TOC / 文件名"第X章"标记;缺失或需校验跨视频合并时才用少量 LLM。
- 全程缓存(
.v2m/),重跑/调参不重复花 token;每次运行打印各模型 token 用量。
注意
- 需要系统安装
ffmpeg/ffprobe。 - 远程后端不就绪时会等待健康检查超时并报错;确认服务已起或
ssh.enabled配置正确。 tasks与providers都为空时,结构化与笔记退化为启发式(无 LLM 正文),仍能产出大纲与要点;只要配了tasks(provider 走 config 或加密库),就会调用 LLM 成稿。