Video Reader — 给大模型配的一副"看视频的眼镜"
这个 skill 解决什么问题
你(大模型)能看图,但看不了视频。视频本质就是一串按时间排好的图片。 本 skill 的脚本帮你做两件你做不了或做不好的事:
- 初筛:用帧差(相邻帧像素差异,纯数学,不花 token)算出"哪几秒画面在动", 自动跳过静止段。用户经常从"盘古开天辟地"开始录,前面几十秒对着桌子没动—— 这些会被整段折叠,一帧都不喂给你。
- 智能抽帧:只在有动作的地方抽帧,而且支持"先粗后细"两轮下钻,既不漏关键帧, 又不会把上下文撑爆。
重要边界:这个 skill 不含任何业务逻辑。 它不懂"卡顿""面板""跟手""中间态"是什么。 它只负责把视频变成"你能消化的帧 + 时间线"。看懂画面、判断对错、定位 bug——那是你的活。
四个子命令,按需要选(别只会 scan)
本 skill 有四个能力,接到视频任务先想清楚要哪个,不要永远只用 scan:
| 子命令 | 什么时候用 | 一句话 |
|---|---|---|
scan |
默认起点;要定位"哪几秒在动/出问题" | 帧差初筛+运动时间线+稀疏抽帧 |
zoom |
已知可疑区间,要看那几秒的细节 | 指定区间高密度抽帧 |
grid |
想先要个全片概览、一张图看节奏,或视频较长先扫一眼 | 均匀取帧拼成九宫格大图,一次 Read 看全片 |
transcribe |
画面看不出、需要听(旁白/口述/报错语音/"他说了啥") | 语音转文字(带时间戳,可选,需 ffmpeg+whisper) |
常见组合:长视频/不确定从哪看 → 先 grid 看全片节奏 → scan 看运动时间线 → zoom 下钻可疑段;涉及"说了什么" → 加 transcribe 与画面时间线对齐。 用户即使没点名,你也应根据意图主动选对子命令并告诉他你用了哪个、为什么。
核心工作流:粗扫 → 下钻(由你驱动)
不要一上来就把整段视频高密度抽帧,那样既慢又炸上下文。标准打法是两步,你来决定抽哪、抽多细:
第1步 scan(粗扫)
└─ 脚本输出:运动时间线(文字) + 活动段稀疏抽的几帧
└─ 你看时间线 + 帧,判断"问题大概在 X-Y 秒"
↓
第2步 zoom(下钻)
└─ 脚本在 X-Y 秒密集抽帧
└─ 你看细节;若还不够,继续在更小区间 zoom(可多轮)
关键心法:先读时间线,再决定要不要看帧、看哪段的帧。 很多时候 scan 的运动时间线 (纯文字)就足够让你判断该往哪下钻,这是最省 token 的方式。
上传被拦怎么办(很多平台禁视频格式,务必知道)
很多平台(如 Mira 等)在上传时直接禁掉视频/音频格式,用户传 .mp4/.mov 会被提示
"不支持的文件类型",根本传不进来。这不是死路,有两个绕过办法,你必须主动告诉用户:
- 改后缀:把
xxx.mp4重命名成平台允许的后缀(如xxx.txt/xxx.bin)再上传。 你拿到后不要被后缀骗了——它本质还是视频,先把它改回.mp4(或直接用原始路径)再喂给脚本即可。 OpenCV 按文件内容解码,不认后缀,所以哪怕后缀是.txt,只要内容是视频就能读; 实在不放心就cp 那个文件 /tmp/v.mp4再处理。 - 压成 zip:把视频压成
.zip上传(zip 通常不被拦)。你收到后先解压拿到里面的视频文件, 再喂给脚本。解压是你(大模型)调用解压能力完成的,脚本本身不处理 zip。
一句话:平台拦的是"后缀/格式",不是"内容"。改后缀或套个 zip 壳就能绕过, 拿到真身后照常 scan/zoom。遇到"视频传不上去"先想到这两招,别让用户卡在上传这一步。
怎么调用
脚本路径(用绝对路径调用):
<SKILL_DIR>/scripts/video_frames.py
依赖:Python3 + opencv-python-headless + numpy(matplotlib 仅 --debug 画曲线图时用)。OpenCV 自带视频解码,不依赖系统 ffmpeg。
脚本会自动检测并安装缺失依赖(pip install --user --break-system-packages,不污染系统),无需手动准备;只有自动安装失败时才会打印一条人话提示让你手动装。
脚本每次启动都会在 stderr 先自报家门(当前解释器路径 / 版本 / user-site)。若遇到"依赖装了却 import 不到",99% 是机器上多个 python3 错配(装包用解释器 A、跑脚本命中解释器 B,而 pip --user 按版本号分目录存包)。这时看启动打印的 python: 那行,把跑脚本的解释器对齐到装了包那个(用绝对路径,或建 venv)即可。报错提示里也会带上当前解释器路径,照着做不用手敲 which -a 排查。
scan —— 粗扫全片
python3 <SKILL_DIR>/scripts/video_frames.py scan <视频路径>
输出:stdout 是结构化 JSON(含 timeline、active_segments、frames 列表及每帧路径), stderr 是给你看的运动时间线概要。先读 timeline 决定下一步。
常用参数:
--start <秒> --end <秒>:只扫某段(用户给了大概范围时用)。--density <N>:活动段每秒抽几帧,默认 2(粗扫够用)。--max-width <px>:帧最大宽度,默认 900(省 token);要看清小字可调大。
zoom —— 对可疑区间高密度抽帧
python3 <SKILL_DIR>/scripts/video_frames.py zoom <视频路径> --start 10.0 --end 12.0 --density 8
--density 默认 8(每秒 8 帧),要看某个瞬间(如手指抬起那一刻)可加到 12~15,
区间也尽量收窄(如 10.5–11.0)。
grid —— 九宫格概览(一张图看全片节奏)
全片(或区间)均匀取帧拼成一张大图,每格左上角标秒数。一次 Read 一张图就能把握整段视频的节奏/概貌,省 token、好定位;看完再用 zoom 对可疑那一格的时间段下钻。
python3 <SKILL_DIR>/scripts/video_frames.py grid <视频路径>
python3 <SKILL_DIR>/scripts/video_frames.py grid <视频路径> --rows 4 --cols 4 --start 0 --end 30
--rows/--cols:网格行列,默认 3×3=9 格;长视频可加大(如 4×4)。--cell-width每格宽,默认 320。- 输出:JSON 里
grid_path是拼好的大图路径,cells[]是每格的t(秒)/行列号。Read 这张grid_path即可,按"从左到右、从上到下"读,每格角上的秒数就是它在视频里的时间。 - 和 scan 互补:scan 的运动时间线擅长"哪几秒在动",grid 擅长"整段长啥样"。不确定从哪看、或视频较长时,先 grid 毛估再 scan/zoom。
transcribe —— (可选)语音转写,补画面看不到的信息
画面只告诉你"看到什么",但旁白、客诉口述、报错语音提示这些只能听到的信息,靠这个子命令补。它是可选软能力,缺依赖只提示并跳过,不影响 scan/zoom:
python3 <SKILL_DIR>/scripts/video_frames.py transcribe <视频路径> --model turbo
- 依赖:系统
ffmpeg(抽音轨) +openai-whisper(pip,带 torch 较重,脚本会"用到才按需装")。任一缺失会打印安装方法并以退出码 4 跳过,你据此降级到只看画面即可。 --model:whisper 模型,默认turbo(快且准);要更准可用medium/large。--language zh/en可指定语言,默认自动检测。- 输出:stdout 是 JSON(
text全文 +segments带时间戳分段),stderr 是带时间戳的逐段文字。 - 用法心法:把转写的时间戳和
scan的画面运动时间线对齐,就能说出"第 X 秒画面在做什么、同时说了什么",定位更准。无音轨的纯录屏会自动跳过。
--debug —— 调试模式(默认关闭)
平时不用开。调试技能本身、或想搞清楚"为什么这段被判成静止/运动"时加上 --debug:
python3 <SKILL_DIR>/scripts/video_frames.py scan <视频路径> --debug
开了之后:
- 帧不再进随机临时目录,而是存到 当前目录的
video_reader_debug/<视频名>_<模式>/,稳定可复查。 - 额外产出
_debug/motion_data.json(每个采样点的时间+运动分、阈值、分段)和_debug/motion_curve.png(帧差曲线图,带阈值线和活动段底纹)。看这张图就能一眼判断 阈值定得对不对、该不该的段有没有被漏掉或误判,据此调--threshold。
看帧
JSON 里 frames[].path 是每帧的绝对路径,frames[].t 是它在视频里的秒数。
用 Read 工具读这些帧时,务必在心里(或回复里)把每张图和它的 t 时间戳绑定,
这样你才能说出"第几秒发生了什么"。读完即弃,不必保留。
怎么把帧"讲"给自己和用户
你的产出不是"我看到一个面板",而是带时间线的客观叙述,例如:
3.1s 面板在底部,处于全屏列表态
3.5s 面板开始向上滑动(用户在拖动)
3.8s 手指离开,面板停在约半屏位置
4.5s 面板没有继续吸附,停在中途不动 —— 与"应锚定半屏/全屏"的预期不符
如果用户给了上下文(同事的吐槽、预期行为),把它当作对照的参照系: 先客观描述实际发生了什么,再指出哪一帧/哪一秒和预期不一致。 如果用户没说用户干了啥,就别瞎猜对错,只做客观复述,把"事实时间线"摆出来, 让用户结合业务去判断。
几条实战经验(为什么这么设计)
- 静止段为什么要折叠:截图是不会"卡"的,静止段对排查交互问题没信息量, 只会浪费你的注意力和上下文。让代码把它们扔掉,你只看有动作的部分。
- 为什么时间戳用文字绑定而不是烧进画面:你读文字是 100% 准的,而认画面里烧的字 可能糊、可能挡画面、可能读错。所以脚本把时间放在文件名/JSON 里,你 Read 时对应上即可。
- 翻拍视频(带手、反光、抖动)怎么办:帧差用了"缩小+模糊+分块"来吸收噪点和轻微抖动, 所以翻拍视频也能大致定位到运动段。但手指位置这类细节,翻拍下精度有限,只能定性看方向, 必要时在回复里说明"这是翻拍视频,手指位置为估计"。
- GIF / 无音轨 / 元数据缺失:脚本对 fps 异常做了兜底(默认按 30fps),GIF 也能读。
不要做的事
- 不要把整段长视频一次性高密度抽帧再全部 Read —— 这就是上下文爆炸的根源。永远先 scan。
- 不要在 skill 里写死任何业务判断(什么算"正常滑动")。判断交给你和用户,skill 只供帧。
- 不要依赖"屏幕录制小白点"才能工作 —— 大多数视频没有,有就当福利,没有也得能干活。