镜头大师 Shot Master · 视频扒镜头 / 关键帧提取
把一段视频"扒"成一张张能读懂剧情的帧:按镜头切分、抽关键帧、按需精准取帧,
并输出带精确时间码的分镜表和故事板,方便回到时间轴上剪辑。
> 本技能 v2 起更名自 shot-ripper(slug shot-ripper-capmom 已弃用,统一使用 shotmaster-capmom)。
何时使用本技能
- 拉片 / 剧情分析:把整片切成一个个镜头,看清叙事节奏与转场
- 剪辑前期:提取关键帧、生成分镜表,快速定位可用素材与切点(支持按最小镜头时长过滤假切点)
- 精准取帧:在指定时间点、按 I 帧、按固定间隔抽缩略图
- 一眼看全片:生成带时间码的故事板 (contact sheet)
环境要求(先检测,再动手)
- 必需:
ffmpeg+ffprobe - 可选(更精准的镜头检测):
PySceneDetect+opencv-python
bash pip install scenedetect opencv-python
未安装时,scene --backend scenedetect会自动回退到 ffmpeg 后端并给出[WARN]提示,不影响使用。 - 可选:
Pillow(做故事板,通常已装)
检测:
ffmpeg -version
ffprobe -version
若缺失,用 qclaw-env skill 安装 ffmpeg(Windows 可 scoop install ffmpeg / winget install Gyan.FFmpeg;
macOS brew install ffmpeg;Linux apt install ffmpeg)。切勿在没有 ffmpeg 时硬跑脚本。
核心脚本
scripts/shot_extract.py— 主驱动,6 种模式scripts/contact_sheet.py— 由 manifest 生成带时间码故事板references/ffmpeg_recipes.md— 裸 ffmpeg 命令速查(想手动跑时用)
> 路径提示:脚本相对本 SKILL.md 所在目录。执行时用该目录的绝对路径,例如
> python <skill_dir>/scripts/shot_extract.py ...。Windows 用 python,macOS/Linux 用 python3。
快速开始
# 0) 先看视频信息(时长/帧率/分辨率/编码)
python scripts/shot_extract.py probe input.mp4
# 1) 扒镜头:按场景切分,每个镜头一帧代表帧(最常用,ffmpeg 后端零依赖)
python scripts/shot_extract.py scene input.mp4 --out shots --threshold 0.3
# 1b) 每镜选最清晰代表帧(避免中点落在模糊处), 混剪拉片更顺眼
python scripts/shot_extract.py scene input.mp4 --out shots_best --bestframe --best-sample 10
# 2) 高精度镜头检测(淡入淡出/慢转场也能识别,需 PySceneDetect)
python scripts/shot_extract.py scene input.mp4 --out shots_sd --backend scenedetect --algo content --threshold 27
# 2b) 按镜头时长过滤假切点(字幕/闪白),混剪常用
python scripts/shot_extract.py scene input.mp4 --out shots_clean --threshold 0.3 --min-dur 1.5
# 2c) 边缘变动类素材换 edge 指标
python scripts/shot_extract.py scene input.mp4 --out shots_edge --metric edge --threshold 0.3
# 3) 均匀抽帧:每 2 秒一帧,快速浏览全片剧情
python scripts/shot_extract.py interval input.mp4 --out skim --every 2
# 4) 生成故事板,一眼看懂全片
python scripts/contact_sheet.py shots/manifest.json -o storyboard.jpg --cols 5
模式详解 (shot_extract.py)
| 模式 | 用途 | 关键参数 |
|---|---|---|
probe |
输出视频元信息(JSON) | — |
scene |
扒镜头:检测画面切换,每个镜头出 1 代表帧 + 分镜表 | --backend / --threshold / --algo / --metric / --min-dur / --bestframe / --best-sample / --best-workers / --no-motion / --export / --tag-file / --organize / --scene-type |
interval |
按固定间隔抽帧,快速拉片 | --every 秒 或 --fps N |
keyframe |
抽编码 I 帧(最快) | — |
timestamps |
在指定时间点精准取帧 | --times "00:00:05,1:23.5,320" |
range |
只在某时间段内按帧率抽帧(精修某段) | --start --end --fps |
通用参数:--out 输出目录(默认 shots)、--quality N(JPEG质量,1最好~31最差,默认2)、
--width 像素(缩放宽度,加速/省空间,高按比例)。
scene 模式:双后端 + 多指标 + 时长过滤
--backend 选择检测引擎:
ffmpeg(默认,零依赖):用 ffmpegselect='gt(scene,X)'在画面切换点取帧。
输出每镜头的代表帧及其时间码;--threshold为 0~1(默认 0.3,越小切得越细)。
优点:无需任何 Python 包;缺点:luma 体系对淡入淡出/慢转场可能漏切。scenedetect(高精度,可选依赖):用 PySceneDetect 检测,输出每个镜头的起止时码
(start_tc → end_tc与duration),更能对付慢转场、淡入淡出、相似色场景。--algo content(默认):内容感知,阈值默认27(0~100,越小切得越细)--algo adaptive:自适应阈值,更耐光照/淡入淡出变化--algo threshold:纯像素阈值--min-len N:过滤短于 N 帧的镜头(默认 15),减少字幕/闪烁造成的假切点- 未安装 PySceneDetect 时自动回退 ffmpeg 并
[WARN]提示。 - 注意:两个后端的
--threshold量纲不同(ffmpeg 0~1;scenedetect 0~100),脚本已分别处理,回退时会重置为 0.3。
--metric(仅 ffmpeg 后端生效,控制检测什么变化):
- luma(默认):亮度场景变化,最通用
- edge:先用 edgedetect 提取边缘,再在边缘图上检测场景变化;对画面边缘/物体轮廓变动敏感
(纯色块视频内部无边缘,edge 会不敏感,属正常现象;此时用 luma 即可)
- 注:本机 ffmpeg 8.1.2 的 select 不支持 motion 变量,故未提供 motion 指标。
--min-dur 秒(两后端通用,控制切镜粒度,混剪最常用):
- 过滤掉时长小于该值的镜头(字幕闪烁、瞬时闪白等假切点会被并入相邻镜头)。
- 语义:以镜头起点序列为准,相邻镜头起点间隔 < --min-dur 则合并。
- 始终保留开场镜头(0.0)。
--bestframe(每镜选最清晰代表帧,两后端通用):
- 默认每个镜头取中点帧作为代表帧;加 --bestframe 后,会在镜头区间内均匀采样若干候选帧,
用拉普拉斯方差(清晰度/锐度)打分,选最清晰的一张作代表帧——避免中点恰好落在运动模糊/虚焦处。
- --best-sample N(默认 8):每镜候选帧数;越大越可能挑到最清晰的一帧(也越慢)。
- --best-workers N(默认自动,min(8, 候选数)):--bestframe 时并行提取候选帧的线程数;设为 1 可禁用并行恢复串行行为(便于调试)。
- 适用:画面有"清晰—模糊"起伏的镜头(人物说话、物体运动、镜头推拉),让分镜表每格都是最清楚的画面。
- 对纯色块/无内部细节的素材,所有候选帧清晰度都≈0,bestframe 与中点帧等价(无副作用、不报错)。
- 只影响"每镜代表帧选哪张",不改变镜头切点;可与 --min-dur / --metric / 双后端任意组合。
--scene-type(自动推断镜头类型,两后端通用):
根据每镜的 cut_confidence / sharpness / motion_level 三指标组合,
自动推断镜头类型并填入 scene_type 字段:
- scene_type ∈ {名场面 / 动 / 大场面 / 近特 / 台词 / 哭 / 笑 / 空镜}(与 --tag-file 的 8 大分类标签对应)。
- 推理逻辑(基于真实 60fps/1080p 动漫+B站 BGM 视频校准):
- motion≥20 + sharp≥6000 + dur<3s → 名场面(高燃短促帧,Hook/高潮首选)
- motion≥20 → 大场面(高运动量,B站混剪核心素材)
- motion≥10 + sharp≥3000 → 大场面(运动+清晰兼备)
- motion≥10 → 动(高运动,战斗/运镜/CGI 特效)
- motion<5 + sharp≥8000 + dur<3s → 近特(人物特写,表情聚焦)
- motion<10 + sharp≥3000 → 近特(清晰+低运动=对话/台词特写)
- motion<5 + dur≥3s → 台词(低运动+中等时长,对话/旁白镜头)
- motion<5 + dur<3s + cc<0.4 → 笑(情绪镜头,切换绵长)
- motion<5 → 空镜(低运动+低清晰=转场/字幕/黑场)
- motion 缺失(--no-motion 或计算失败)时用 sharp+dur 回退:s≥8000+dur<3s→近特;s≥3000→大场面;s≥500→台词;其余→空镜
- scene_type 会写入 manifest.json / shotlist.csv / 全部三种导出格式(EDL / FCP XML / OTTO JSON)。
- 分类标签(--tag-file)与 scene_type 推断互不冲突,可同时使用;--tag-file 的 category 覆盖手动,scene_type 自动兜底。
--no-motion(跳过运动量计算,大幅提速):
- motion_level 计算需逐镜多次调用 ffmpeg 子进程(4对帧/镜),是主要耗时来源。
- 加 --no-motion 跳过该计算,motion_level 字段为 null,scene_type 走 sharp+dur 回退分支(仍有效)。
- 推荐用于:批量扫描、快速拉片、motion 不重要的场景。
- 示例:shot_extract.py scene video.mp4 -o shots --bestframe --no-motion
(实测 emotional 视频 33 镜含 bestframe:~87s;含 motion:~140s)
常用示例
# 细扒(快切/动作片调低阈值),并缩放到 640 宽省空间
python scripts/shot_extract.py scene in.mp4 --out shots --threshold 0.25 --width 640
# 高精度 + 自适应(处理含转场/淡入淡出的成片)
python scripts/shot_extract.py scene in.mp4 --out shots_sd --backend scenedetect --algo adaptive --min-len 12
# 按句切: 过滤掉 <1.5s 的短镜头(字幕/闪白假切点)
python scripts/shot_extract.py scene in.mp4 --out shots_clean --threshold 0.3 --min-dur 1.5
# 每镜挑最清晰的帧(默认中点可能落在运动模糊上)
python scripts/shot_extract.py scene in.mp4 --out shots_best --bestframe --best-sample 10
输出物(每次运行都生成)
输出目录内包含:
- frame_/shot_/kf_/ts_*.jpg — 提取的帧
- manifest.json — 结构化分镜表:视频信息 + 每帧的 index / timecode / seconds / filename (/scene_score)
- scene --backend scenedetect 额外含 start_tc / end_tc / start_seconds / end_seconds / duration
- shotlist.csv — 同数据的表格版,可直接在剪辑软件/表格里对时间轴
时间码格式 HH:MM:SS.mmm,可直接回到编辑器定位。故事板由 contact_sheet.py 读 manifest 生成,
每格标注 #序号 时间码,scenedetect 后端还会标注镜头区间 [start → end]。
建议工作流(拉片 → 分镜 → 交付)
probe看清时长/帧率,估算规模scene扒镜头得到分镜表(阈值按内容微调;含转场用--backend scenedetect)contact_sheet生成故事板,通读剧情、标注要用的镜头- 对重点段落用
range/timestamps精修取帧 - 拿
shotlist.csv的时间码回剪辑软件下料
故事板生成器(contact_sheet.py)
由 manifest.json 自动生成分镜墙,支持多种布局和风格。
基本用法
python scripts/contact_sheet.py shots/manifest.json -o storyboard.jpg --cols 5 --thumb 320
布局(--layout)
| 布局 | 说明 |
|---|---|
grid(默认) |
均匀网格,每格含帧图 + #序号 时间码 |
filmstrip |
时间线条:帧图横排,像电影胶片 |
poster |
大图+小网格:首帧放大展示,剩余帧网格排列 |
分类标签徽章(--badge)
配合 --tag-file 注入的 category 字段,在每帧左上角显示彩色分类徽章:
| 分类 | 颜色 |
|------|------|
| 动 | 🔴 红色 |
| 大场面 | 🟠 橙色 |
| 近特 | 🟢 绿色 |
| 台词 | 🔵 蓝色 |
| 哭 | 🟣 紫色 |
| 笑 | 🩵 青色 |
| 空镜 | ⚪ 灰色 |
| 名场面 | 🟡 金色 |
头部信息栏(--header)
在顶部显示:文件名 | 镜头数 | 时长 | fps。
配色主题(--theme)
| 主题 | 适用场景 |
|---|---|
dark(默认) |
深色背景(#111),适合深色系视频 |
light |
浅色背景(#F5F5F5),适合亮色素材 |
custom |
配合 --bg / --fg / --accent 自定义颜色 |
水印(--watermark-text)
在右下角添加半透明文字水印(如 --watermark-text "版权保留")。
输出格式(--format)
jpg(默认,高压缩)/ png(无损)/ pdf(可直接打印)。
完整示例
# 深色主题 + 分类徽章 + 视频信息头 + 水印
python scripts/contact_sheet.py shots/manifest.json \
-o storyboard.jpg --cols 6 --thumb 320 \
--layout grid --theme dark \
--badge --header \
--watermark-text "仅供学习参考"
# 海报风格(大图展示)+ 浅色主题
python scripts/contact_sheet.py shots/manifest.json \
-o poster_board.jpg --cols 4 --thumb 480 \
--layout poster --theme light --header
# 胶片风格 + 分类徽章
python scripts/contact_sheet.py shots/manifest.json \
-o filmstrip.jpg --thumb 120 \
--layout filmstrip --badge
> 故事板尺寸由 --thumb(缩略图宽度)和 --cols(每行格数)自动计算。
> contact_sheet.py 依赖 Pillow(pip install pillow),通常已随 Python 环境预装。
混剪素材分类标签(对接 anime-mashup-editing)
为什么要分类
混剪大师(anime-mashup-editing)把素材分为 8 大类:
| 分类 | 内容 | 用途 |
|------|------|------|
| 动 | 战斗、打斗、高速动作 | 踩点向、燃向 |
| 大场面 | 爆炸、群像、特效释放 | 高潮段落 |
| 近特 | 面部特写、表情变化 | 情绪向、情感传递 |
| 台词 | 经典台词、念白 | 剧情向、点睛 |
| 哭 | 哭泣、情感爆发 | 情绪向、泪点 |
| 笑 | 笑容、欢乐场景 | 情绪向、节奏调剂 |
| 空镜 | 风景、环境、无人物镜头 | 转场过渡、节奏缓冲 |
| 名场面 | 经典桥段、高光时刻 | Hook 引入、点睛 |
标签 CSV 格式
shot_extract.py scene --tag-file 支持两种列格式(逗号/分号/tab 分隔均可):
# filename,category (按帧图文件名匹配)
shot_00001.jpg,名场面
shot_00002.jpg,动
shot_00003.jpg,空镜
# timecode,category (按 HH:MM:SS.mmm 时间码匹配)
00:00:02.500,名场面
00:00:05.000,动
标签注入后,manifest.json / shotlist.csv / 全部导出格式(EDL / FCP XML / OTTO JSON)
均含 category 字段,剪辑软件可直接读取并按分类筛选/排序镜头。
自动归档帧图
加 --organize 即可把帧图按分类复制到子目录(不改动原文件):
shotlist.csv → shot_00001.jpg → 名场面/
shot_00002.jpg → 动/
shot_00003.jpg → 空镜/
完整工作流
# 1. 扒镜头
python scripts/shot_extract.py scene in.mp4 --out shots_raw --backend scenedetect
# 2. 人工打标签(看故事板 / contact sheet 标注后,生成 tag.csv)
# tag.csv 内容示例:
# shot_00001.jpg,名场面
# shot_00002.jpg,动
# shot_00003.jpg,空镜
# 3. 重新注入标签 + 按分类自动归档
python scripts/shot_extract.py scene in.mp4 --out shots_tagged \
--backend scenedetect --tag-file tag.csv --organize
# 4. 输出目录结构
# shots_tagged/
# manifest.json # 含 category 字段
# shotlist.csv # 含 category 列(可导入剪辑软件)
# timeline.edl # 含 * CATEGORY: 注释
# timeline.otto.json # 含 category 字段
# 名场面/ # 帧图已归档
# 动/
# 空镜/
> 分类标签 CSV 可直接在 Excel / Numbers 里编辑,或从 contact sheet 标注反推生成。
> 分类只影响「代表帧」的标签注入与归档,不改变镜头切点或时间码。
参数调优
- 切太碎:ffmpeg 调高
--threshold(0.4~0.5);scenedetect 调高--threshold(如 40) 或加--min-len;或统一加--min-dur合并短镜 - 漏镜头/慢转场:ffmpeg 调低(0.2~0.25);或改用
--backend scenedetect --algo adaptive - 速度/体积:加
--width(如 640/960) 缩放;--quality 4~6略降质量 - 只要结构性帧:用
keyframe(最快,但不等于镜头切换点) - 长视频:先
interval --every 5粗看,再局部range精抽
注意事项
scene/interval/range会在临时文件解析精确 pts 时间码,运行结束自动清理。timestamps用输入端-ss快速且精确定位单帧;某个时间点失败会[WARN]跳过。- 中文/含空格路径请用引号包裹。
- 不修改源视频;只读取并输出帧到
--out目录。 scene --backend scenedetect未装依赖会自动回退 ffmpeg,不会中断。- 镜头分类标签:
scene模式支持--tag-file tag.csv注入分类标签
(--organize自动按分类归档帧图到子目录),见「混剪素材分类标签」章节。 - 导出时间线:
scene模式可用--export edl/--export fcpxml/--export otto
(可重复指定,如--export edl --export fcpxml),在输出目录生成时间线文件,
方便直接导入 DaVinci Resolve / Premiere / Final Cut Pro / 任意 NLE: timeline.edl— CMX 3600 EDL,含* CATEGORY:/* CUT_CONFIDENCE:/* SHARPNESS:/* MOTION_LEVEL:/* SCENE_TYPE:注释行;timeline.fcpxml— Final Cut Pro X XML 1.10,含 SMPTE 时码和各质量指标的 XML 注释;timeline.otto.json— OTTO JSON 格式,含 source_in/out 秒数 + SMPTE 时码 + 代表帧文件名 + 全部质量字段 + scene_type。- cut_timeline 子命令:
shot_extract.py cut_timeline manifest.json从manifest.json生成混剪专用时间线 JSON(cut_timeline.json),
含所有镜头的 cutting points + 质量护照 + scene_type + 分类标签,另附 hook_candidates(motion_level 最高的前3个镜头,适合做 Hook)
和 static_candidates(motion_level 最低的前3个镜头,适合对话/空镜转场)。用法:python scripts/shot_extract.py cut_timeline shots/manifest.json --out my_timeline.json。 - 进度条与优雅中断:长任务(
scene/interval/keyframe/range)运行时会在 stderr 显示
进度条(按视频时长 0~100%,bestframe 时另显示「镜头 i/N」逐镜进度);处理中按 Ctrl+C
会优雅中断——终止 ffmpeg 子进程、自动清理bf_tmp_*临时目录,已生成的帧保留在输出目录,
并打印「[已取消] 用户中断」提示,不会抛出 Python 堆栈。
可选:更高精度镜头检测 (PySceneDetect)
需要更稳的镜头边界(含镜头起止时码)时安装:
pip install scenedetect opencv-python
再用 scene --backend scenedetect(见上)。其 detect-scenes 命令行也能直接产出
*-Scenes.csv(含每个镜头起止时码/帧号)与代表帧图片,可与本技能的 manifest 互补。
混剪工作流集成:shot_to_timeline.py
一键完成"视频+BGM → 带节拍对齐的混剪时间线":
# 完整流程(镜头检测 + BPM节拍 + 镜头×节拍对齐)
python scripts/shot_to_timeline.py video.mp4 audio.mp3 -o mashup_timeline.json
# 无BGM,只输出镜头cut_points(不调用BPM检测)
python scripts/shot_to_timeline.py video.mp4 -o cut_points.json
# 调参
python scripts/shot_to_timeline.py video.mp4 audio.mp3 \
--min-dur 1.5 # 最小镜头时长(秒)
--bpm-threshold 0.2 # 节拍灵敏度(越低越敏感)
--auto-tag # 自动按scene_type推荐BGM段落
输出 JSON 结构:
- bpm / beat_count / beats[]:BGM 节拍信息(BPM + 时间戳列表)
- timeline[]:每个镜头的 cutting points + 质量护照 + 节拍对齐信息
- nearest_beat_time:对齐到的节拍时间
- beat_index:节拍序号(1-based)
- beat_strength:强拍(1) / 弱拍(2),用于踩点决策
- beat_delta:镜头中点到节拍的偏差(秒),越小越准
- hook_candidates[]:强拍中高清晰度 Top5 镜头,混剪 Hook 优先选
- statistics:scene_type / category 分布统计
实测结果(对视的那一秒,心跳是否也会共鸣.mp4 @ BPM=129.2):
- 93s / 39 shots → 87s(含 bestframe+质量报告)
- beat_delta 示例:clip2 delta=0.11s,clip3 delta=0.04s(精准卡点)
- hook_candidates 精选强拍+高sharpness镜头,排序:sharp=15707/13498/12968/10300/9974
使用建议:
1. 先跑一次 shot_to_timeline 看 beat_delta 分布
2. beat_delta < 0.1s 的镜头可直接用;> 0.2s 的镜头可用 FCP XML 的微调 offset
3. hook_candidates 适合做开场 Hook / 章节转场;static_candidates 适合对话/空镜段落