视频内容 → Markdown 总结
把任意一个视频变成一份可以读、可以存的 Markdown 文档。
核心不是"让模型看一眼视频",而是三路并行提取 + 时间戳对齐:
口播说的、屏幕打的字、画面里看得见的,三处信息互不重叠,交叉之后才敢下笔。
| 信息源 | 能拿到什么 | 漏掉会怎样 |
|---|---|---|
| 口播音轨(ASR) | 讲述内容、观点、因果、数字 | 主体内容丢失 |
| 屏幕字幕(OCR) | 精确的标题、数字、专有名词 | 只剩模糊叙述 |
| 画面关键帧 | 场景、人物、动作、实物、图表 | 没人说也没打字的全丢 |
三路还能互相纠错:ASR 常把专有名词听错(人名、地名、产品名、术语),
而屏幕字幕通常是准的,可以直接订正;反过来字幕也会有 OCR 误识,靠口播和画面判断。
⚠️ 输出形态:文章式,不是罗列式(用户明确要求)
作用域:仅限本技能。 这条是用户针对"视频转 MD"提的,
不要外推成通用文档偏好——其他交付物按各自领域该有的形态走
(菜谱必须是结构化清单、财务报表/税表/合同同理)。
用户 2026-10-09 二次澄清:"这个改动只对 video2markdown 这个技能生效"。
产出的是一篇能一路读下去的文章,不是一份条目清单。
用户原话(2026-10-09):"能把这个内容总结成文章式的,而不是这种罗列出来一样"。
| ✗ 罗列式(不要) | ✓ 文章式(要) |
|---|---|
| 三个表格并排,每个格子填「误区一:…」 | 一节讲一个误区,段落里说清它拆掉的到底是什么心理 |
| 「A 是…B 是…C 是…」条目并排 | "先…接着…然后…"、"这三根柱子拆完,读者已经被说服 X,但还没相信 Y" |
| 正文内容搬到速查表里重排 | 速查表只放数字/术语/人名/结论,正文不重复 |
| 每个要点孤立成段,无衔接 | 段落有过渡:承接、转折、递进 |
动手前先记住四条(详见「Markdown 文档结构 › 行文要求」):
- 正文用连贯段落,表格只留在元信息头和「关键信息清单」两处
- 每小节至少 2–3 句成段的话,要有行文推进感
- 可以点睛归纳,但必须可从原文推出,推断要标注
- 判断标准:读一遍像文章而不像报告
骨架章节名保持不变(便于检索复用),章节内部用段落填充。
与 video2recipe 的关系(重要)
本技能是从 video2recipe(视频转菜谱)派生出来的独立技能,两者:
- 互不依赖:本技能的
scripts/下是自带副本(extract.py/fetch_toutiao.py/
runners/fetch_douyin_browser.cjs),不引用、不 import、不调用video2recipe目录下的任何文件。
因此任一边的修改都不会影响另一边。 - 职责不同:
video2recipe产出菜谱并写入资料库「菜谱库表」;本技能只产出 Markdown 文档,
不写入任何数据库、不产出 HTML 菜谱卡、不做菜谱结构化。 - 可以并存:同一个视频,用户想要菜谱就用 video2recipe,想要通用总结就用本技能。
- ⚠️ 维护约定:修 bug 时先确认改的是哪一边。两边共有的问题(下载反爬、内存不足等)
需要分别修各自的副本,不要用软链接或相对路径把两者绑在一起。
输入
接受以下任意一种:
| 输入类型 | 形式 | 处理方式 |
|---|---|---|
| 今日头条 / 西瓜视频 | https://www.toutiao.com/video/<id>/、m.toutiao.com、分享短链 |
scripts/fetch_toutiao.py(已解决反爬) |
| 抖音 | https://www.douyin.com/video/<id> |
scripts/runners/fetch_douyin_browser.cjs(浏览器抓取) |
| 本地视频文件 | .mp4 / .mkv / .mov 等 |
复制进输出目录并改成 ASCII 名,跳过下载 |
| 其他平台 | B站、YouTube 等 | yt-dlp 直接下,再走第 2 步 |
| 只有网页/图文 | 非视频链接 | 不适用本技能(直接用 WebFetch 抓网页即可) |
调用时需要用户提供或确认两件事:
- 视频来源(链接或文件路径)——必填;
- 输出目录(可选)——不指定时默认放到当前工作区
video2markdown/out_<视频ID>/。
若用户只给了链接没给输出位置,直接按默认目录执行,不要为了问目录而卡住。
输出
文件位置
<输出目录>/
├── <视频>.mp4 原始视频(本地输入时改名为 ASCII 名)
├── meta.json 来源元数据(平台/作者/时长/链接/标题)
├── transcript.txt/.json 口播转写(带时间戳)
├── ocr.json 屏幕文字(带时间戳)
├── keyframes/ keyframes.json 画面关键帧与时间点
├── bundle.json 三路汇总
└── <标题安全化>.md ★ 最终交付的 Markdown 总结
Markdown 文件名规则:取视频标题,去掉 Windows 非法字符(\ / : * ? " < > |)与换行,
截断到 60 字。标题为空时用 <视频ID>.md。
Markdown 文档结构
先读这一节,它决定文档好不好读。 骨架章节名保持一致(便于批量检索与下游复用),
但章节内部必须用连贯段落叙述,不要写成表格罗列——详见下面「行文要求」。
# <视频标题>
| 项 | 内容 |
|---|---|
| 来源 | <平台> · <作者> |
| 链接 | <原始链接;本地文件写"本地文件"> |
| 时长 | <mm:ss> |
| 整理时间 | <YYYY-MM-DD> |
## 一句话概览
<一到两句话说完这个视频讲的是什么。不要用"本视频主要介绍了…"这类废话开头。>
## 时间轴要点
| 时间 | 内容 |
|---|---|
| 00:00 | … |
## 详细梗概
### <主题一>
…(按视频自身的结构分节,用**段落**叙述,不要硬套模板)
### <主题二>
…
## 关键信息清单
<速查用,允许简表;但不要把上面的正文内容搬下来重排一遍>
## 不确定与备注
- <三路说法不一致、字幕看不清、画面无法确认的地方,逐条列出>
## 附录
### A. 口播全文
<transcript.txt 正文,保留时间戳>
### B. 屏幕文字全文
<ocr.json 文本,按时间顺序>
行文要求(硬性)
用户明确要求过:要文章式,不要罗列式。原话是"能把这个内容总结成文章式的,而不是这种罗列出来一样"。
| ✗ 不要这样写 | ✓ 这样写 |
|---|---|
| 三个表格并排,每个格子里填「误区一:…/误区二:…」 | 一节讲一个误区,段落里说清它拆掉的到底是什么心理 |
| 「A 是…B 是…C 是…」的条目并排 | 「这三根柱子拆完,读者已经被说服 X,但还没被相信 Y,视频这时转身给出 Z」 |
| 把正文内容搬到「关键信息清单」里重排 | 速查表只放数字/术语/结论,正文不重复 |
| 每个要点独立成段,彼此无衔接 | 段落之间有过渡:承接、转折、递进 |
具体规则:
- 正文用段落,不用表格切碎。 「详细梗概」里每个小节至少 2–3 句成段的话,
说清「为什么这么讲」「它针对什么心理」,不要只把结论摆出来。 - 要有行文推进感。 常用这些连接:「先…接着…然后…」、「这三根柱子拆完,读者已经被说服…」、
「值得注意的是…不过…」、「要避开的,是…原因是…而不是…」。让读者能一路读下去。 - 适度点睛,但必须站得住。 可以在视频观点之上补一句归纳(例如"这条不改概率,只改收益结构"),
但必须是可从原文推出的,不能是自创论点。推断性内容明确标注为推断。 - 表格只保留两处:最上面的元信息头,和「关键信息清单」的速查表。
其余一切(包括误区对照、要点清单、数据元素表)一律改写成段落。 - 「关键信息清单」不得复述正文。 它只回答"数字/术语/人名/结论"这类需要跳查的信息,
已经在正文讲透的不再重复。 - 附录保持原样。 口播全文、屏幕文字全文是核对用的素材,不算正文,不改写。
- 短视频(<2 分钟)例外:可以不分节,一气写下来,反而更自然。不要为分节而分节。
判断标准:读一遍正文,如果它像一份"报告"而不像一篇文章,就还没写完。
写文档的硬性口径:
- 不编造。视频里没说、画面里看不到的,一律不写;确实需要交代时放进「不确定与备注」。
- 专有名词以屏幕字幕为准,字幕没有的以口播为准;两者冲突且无法判定时并列标注,
不擅自二选一。 - 区分"视频里说的"和"我推断的"。推断内容要么不写,要么明确标注为推断。
- 排除干扰文字:贴纸水印、包装袋说明、衣服印花、墙上标语都不算视频内容(见"常见坑"第 4 条)。
- 可以删无信息量的口水段(寒暄、闲聊、纯感叹),但不要删任何有实质信息的句子——
哪怕它夹在闲聊中间。
工作流
第 1 步:取视频
PY="C:/Users/kuang/.workbuddy/binaries/python/envs/default/Scripts/python.exe"
SK="C:/Users/kuang/.workbuddy/skills/video2markdown"
# 今日头条 / 西瓜视频
"$PY" "$SK/scripts/fetch_toutiao.py" "<链接>" "<输出目录>"
# 抖音(yt-dlp 必 403,必须用浏览器脚本)
"C:/Users/kuang/.workbuddy/binaries/node/versions/22.22.2-3/node.exe" \
"$SK/scripts/runners/fetch_douyin_browser.cjs" "<输出目录>" "https://www.douyin.com/video/<id>"
# 其他平台
"$PY" -m yt_dlp -f "bv*+ba/b" --merge-output-format mp4 -o "<目录>/video.%(ext)s" "<链接>"
本地文件:复制进输出目录并改成 ASCII 文件名(避开中文路径经 PowerShell→python 被吃参数的老问题):
New-Item -ItemType Directory -Force -Path $dst | Out-Null
Copy-Item -LiteralPath $src -Destination (Join-Path $dst "video.mp4") -Force # -LiteralPath 必加
抖音脚本会落 <输出目录>/_browser_dump.json,从中解析出标题、作者、时长,
以及 video.play_addr.url_list[0](无水印直链),再用 urllib 带
Referer: https://www.douyin.com/ 下载。
第 2 步:三路提取
"$PY" "$SK/scripts/extract.py" "<视频路径>" "<输出目录>" --model medium --ocr-fps 1.0
产出 transcript.json/.txt、ocr.json、keyframes/、keyframes.json、bundle.json。
⚠️ 跑完先点名验证关键帧真的落盘了:len(os.listdir(输出目录/keyframes)) 必须等于
keyframes.json 里的 count。两者不一致(尤其目录为空但 count 非零)说明踩了
常见坑第 5 条的 cv2.imwrite 中文路径问题——老版本会静默丢图,别只看日志。
⚠️ 必须先量内存再决定模型(见常见坑第 1 条)。默认 medium;
可提交内存不足约 4GB 时直接换 --model small,不要靠重试硬扛。
⚠️ 长视频(> 5 分钟)拆两趟跑:先 --skip-ocr 做转写 + 关键帧,
再 --skip-asr 只做 OCR(会复用已有转写)。
⚠️ 必须前台跑,不要 run_in_background:后台进程在回合结束时会被回收,只留半个音频和空日志。
第 3 步:读三路,交叉核对
> 各产物文件(meta.json / transcript.json / ocr.json / keyframes.json / bundle.json)的
> 字段结构、空档扫描片段、以及把 ASR 与 OCR 按时间并排的解析代码,见
> references/extraction-fields.md。
- 通读
transcript.txt与ocr.json,逐条对齐; - 扫
keyframes.json的时间戳,任何超过 20 秒的空档都要补抽(见常见坑第 3 条); - 逐张读关键帧,确认场景、人物、实物,并用来裁决 ASR 与字幕的分歧。
第 4 步:补抽关键帧
"$PY" "$SK/scripts/runners/run_frames.py" "<输出目录>" \
"节名:起始秒:fps:张数:说明" ["节名2:起始秒:fps:张数:说明" ...]
第 5 步:写 Markdown
按上面的骨架 + 「行文要求」写 <标题安全化>.md,写完自检:
行文(这次新增,最容易不过的一关)
- [ ] 正文是段落,不是表格 / 条目罗列——只有元信息头和「关键信息清单」可以用表
- [ ] 段落之间有过渡与衔接,能一路读下来,不是并排陈述
- [ ] 每个小节至少 2–3 句成段的话,不是把结论摆出来就完事
- [ ] 「关键信息清单」没有复述正文,只放数字/术语/人名/结论这类需跳查的
- [ ] 读一遍像文章而不像报告(← 判断标准)
- [ ] 短视频(<2 分钟)是一气写下来,没有为分节而分节
内容准确性
- [ ] 每个章节标题都在,没有漏掉「不确定与备注」
- [ ] 时间轴要点的时间点与视频真实时间对得上
- [ ] 引用原文里没有裸的 Markdown 残留(**加粗**、反引号)
- [ ] 专有名词与屏幕字幕一致
- [ ] 没有"本视频主要介绍了…"这类空话段落
- [ ] 凡点睛/归纳句都是可从原文推出的,推断内容已明确标注
环境(已在本机验证)
Python : C:\Users\kuang\.workbuddy\binaries\python\envs\default\Scripts\python.exe
Node : C:\Users\kuang\.workbuddy\binaries\node\versions\22.22.2-3\node.exe
ffmpeg : C:\Users\kuang\AppData\Local\Microsoft\WinGet\Links\ffmpeg.EXE
Edge : C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
已装齐(无需重复安装):openai-whisper + torch(CPU)、rapidocr-onnxruntime、
opencv-python、yt-dlp、demucs;whisper 模型缓存在 ~/.cache/whisper/(tiny/base/small/medium)。
puppeteer 在 C:\Users\kuang\.workbuddy\binaries\node\workspace\node_modules\puppeteer。
⚠️ pip 装包不要用清华镜像(部分 whl 返回 403),用阿里云 -i https://mirrors.aliyun.com/pypi/simple/。
常见坑
1. Segmentation fault / MemoryError:提交内存不足,不是模型坏了
现象:extract.py 打印「加载 whisper 模型: medium」后立刻段错误(exit 3221225477),重试照样崩。
三步定位:① 单跑 whisper.load_model('medium') 会直接抛 MemoryError()
(在 runner 里被 Win32 包装成访问冲突,表面看不出是内存问题);② 同进程里基本张量运算与
小模型加载都正常 → 不是 torch 坏了;③ 用 GlobalMemoryStatusEx 看 ullAvailPageFile(可提交内存)。
处置:availPageFile 低于约 4GB 时 medium 基本必挂,直接换 --model small,别浪费时间重试。
确需 medium 时请用户先关掉占内存的大程序——不要擅自结束用户的进程。
⚠️ small 的成败取决于有没有人声,不是模型好坏:有清晰口播的视频(访谈、讲解、教程),
small 转写依然相当可用,错误集中在专有名词与同音词,靠屏幕字幕即可订正;
没有口播、只有 BGM 的视频,small 必出"等间隔复读幻觉",那种情况整段弃用 ASR。
THREADS 默认 6,别限到 1(纯属自损,会让 ASR 慢十几倍)。
2. ASR 幻觉的三种形态(都要靠字幕裁决)
| 形态 | 特征 | 处置 |
|---|---|---|
| 等间隔复读 | 同一句以几乎精确的固定间隔反复出现 | 整段弃用,别去挑"可能对的" |
| 翻译式幻觉 | 生硬模板句("我们将X…")、混入第三语言、换宾语重复 | 弃用措辞,只当粗略时间线佐证 |
| 无口播瞎编 | 短、重复、拼不成句,而字幕密集成句 | 整条弃用 ASR |
⚠️ 最危险的一种是"整句干货被压成废话":ASR 可能把一句有信息量的原话
(如"所以调料我们要提前配好",后面紧跟解释为什么)听成一个无意义短语(如"提鲜巧克碗汁"),
句子照样通顺、不报错,但核心信息就这么丢了。
所以对照字幕时不只要挑错字,还要检查"字幕里有、转写里没有"的整句内容。
3. 关键帧平均间隔达标 ≠ 覆盖到位
固定机位、长镜头、无剪辑的视频,关键帧会严重稀疏。
平均间隔好看不代表没有空档——实测有视频平均间隔 5.9 秒,却藏着 55.5 秒和 42 秒两个大空档,
恰好盖住整段核心内容。一律按时间戳扫,超过 20 秒就补抽。
4. 屏幕文字里的"假内容"(噪声)
短视频的噪声密度远高于长视频,一条 3 分钟片子可能同时出现好几种。
ocr.json 里读到下列东西要当噪声剔除,判据是"它是不是在讲视频本身的内容":
| 类型 | 例子 | 去哪确认 |
|---|---|---|
| 贴纸 / 水印 | 每帧都带的固定英文(同一串被 OCR 读成好几种样子) | 位置固定、反复出现、正文却每条不同 → 拉黑整串 |
| 人物衣服印花 | 成串的相关专有名词(角色名、品牌名凑在一起) | 直接去画面里的人物身上找,一张帧就能定案 |
| 包装袋说明 | 像法规术语:食品接触用、执行标准号、配料表、厂名厂址 | 去手里那个包装袋上找 |
| 厨具刻度 / 品牌字 | 锅壁水位刻度、锅盖说明书、柜门品牌标 | 去厨具家电上找 |
| 墙面 / 陈设标语 | 宣传标语、牌匾、挂历 | 去背景墙上找 |
5. Windows 环境下的老问题
- ⚠️ 最关键的一条:
cv2.imwrite在中文路径下静默失败,会悄悄吃掉整条"画面"通道。
症状极具欺骗性:extract.py打印"关键帧抽出 17 张"、keyframes.json里 17 条记录齐全,
但keyframes/目录是空的——cv2.imwrite在 Windows 上用窄字符路径,路径含中文时
返回 False 且不抛异常、不落盘。而本技能默认输出目录就在中文工作区下
(D:\...\视频转MD\video2markdown\out_<id>\),所以这个坑几乎每次都会踩。
后果是三路信息只剩两路,且不会报错。
已修:extract.py内置imwrite_u(),改用cv2.imencode(...).tofile(path)
(走 Python 文件对象,不受路径编码限制),并在写后校验os.path.exists,
未落盘的帧从 keyframes.json 里剔除并打印警告,避免再出现"记录在、图没了"。
自己写脚本时不要用cv2.imwrite写中文路径。(run_frames.py走 ffmpeg,不受影响。) - 中文/特殊字符路径不要经命令行传给 Python:写个 3 行 runner 用
subprocess调,
路径在脚本内用os.listdir自己 glob 出来。经 PowerShell → python 两层传递时参数会被吃掉。 subprocess的所有参数必须是 str:写"-frames:v", 40(int)会报TypeError。- 本机 bash 工具间歇性故障(
dirname/head/grep报 command not found,exit 127):
遇到就立刻改用 PowerShell + Read/Write/Glob/Grep,不要反复重试 bash。
PowerShell 的 stdout 偶尔不回显,可靠做法是Out-File落盘再用 Read 读。
6. 抖音链接必须走浏览器脚本
yt-dlp 对抖音必 403(缺页面 JS 算出的 a_bogus 签名);补匿名 ttwid 也没用;
--cookies-from-browser edge 会因 Edge 运行时锁住 cookies 库而失败;
移动分享页 _ROUTER_DATA 里没有视频数据。只有 puppeteer 拦 aweme/detail 响应这条路可行(脚本已内置)。
主题/关键词通常藏在元数据的 text_extra[].hashtag_name 里,标题正文往往只是生活化文案。
打包与分发
打包产物形式
技能以 zip 包分发,包内是完整的技能目录(SKILL.md + scripts/ + references/)。
CREATOR="C:/Users/kuang/AppData/Local/Programs/WorkBuddy/resources/app.asar.unpacked/resources/plugins/workbuddy-builtin/skills/skill-creator"
"$PY" "$CREATOR/scripts/package_skill.py" \
"C:/Users/kuang/.workbuddy/skills/video2markdown" \
"C:/Users/kuang/WorkBuddy/2026-09-11-19-55-33/dist"
产出 dist/video2markdown.zip。打包脚本会先校验(frontmatter 必填项、命名规范、目录结构、
description 质量),校验不过不会出包。
复制目标路径
打包产出的 zip 复制到共享目录(该目录是既有技能包的分发位置,同目录下已有
cn-contract-review、dlt-lottery-analyzer、ssq-lottery-analyzer 等 zip):
\\Z4-2dx4\171xxxx6709\自制Skills\video2markdown.zip
cp "C:/Users/kuang/WorkBuddy/2026-09-11-19-55-33/dist/video2markdown.zip" \
"//Z4-2dx4/171xxxx6709/自制Skills/video2markdown.zip"
复制后必须读回校验(比对两端文件大小与 MD5),确认落盘完整再报成功。
网络盘写入可能静默失败,不要只看 exit code。