拉片:把一条成片拆成逐镜头的分析表——每个镜头的时长、景别、类别、运镜、画面。 分工刻在骨子里:**能量的都由代码量**(切点来自 ffmpeg 场景检测,时长是切点相减, 运动量是逐帧差分的中位数),模型只判断它真正该判断的那几件事(景别 / 类别 / 运镜 / 画面 / 节奏), 然后每一条判断都被代码当场对账——**声称推拉摇移却实测几乎不动,门直接拦**。 看片走联系表(每镜起手帧 + 收尾帧各拼一张大图,一屏二十几个镜头,a/b 对照就是运镜), 不是一张张翻。检测漏刀多刀用 recut 补刀并刀,自动重编号重算时长,手改边界过不了门。 产出 shots.json + Markdown 镜头表 + **单页交互式拉片报告**:内嵌播放器(播放时同步高亮镜头、 点镜头跳转)、镜头节奏带、可搜索可筛选可排序的镜头表(列表 / 卡片两种视图、首尾关键帧并排、 点图开大图)、景别类别运镜分布、出场人物、质量门、导出 JSON。单文件零依赖,离线双击能开。 15 道质量门全部由脚本确定性检查。 零依赖、零 API key,只要 node 和 ffmpeg。 Use when as...
Scanned 9/13/2026
Install to Claude Code
npx -y skills add eternityspring/reelbench-skills --skill video-shots --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Video Shots?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/eternityspring-video-shots)More formats (shields.io, HTML) on the badges page.
---
name: video-shots
version: 1.0.0
description: |
拉片:把一条成片拆成逐镜头的分析表——每个镜头的时长、景别、类别、运镜、画面。
分工刻在骨子里:**能量的都由代码量**(切点来自 ffmpeg 场景检测,时长是切点相减,
运动量是逐帧差分的中位数),模型只判断它真正该判断的那几件事(景别 / 类别 / 运镜 / 画面 / 节奏),
然后每一条判断都被代码当场对账——**声称推拉摇移却实测几乎不动,门直接拦**。
看片走联系表(每镜起手帧 + 收尾帧各拼一张大图,一屏二十几个镜头,a/b 对照就是运镜),
不是一张张翻。检测漏刀多刀用 recut 补刀并刀,自动重编号重算时长,手改边界过不了门。
产出 shots.json + Markdown 镜头表 + **单页交互式拉片报告**:内嵌播放器(播放时同步高亮镜头、
点镜头跳转)、镜头节奏带、可搜索可筛选可排序的镜头表(列表 / 卡片两种视图、首尾关键帧并排、
点图开大图)、景别类别运镜分布、出场人物、质量门、导出 JSON。单文件零依赖,离线双击能开。
15 道质量门全部由脚本确定性检查。
零依赖、零 API key,只要 node 和 ffmpeg。
Use when asked to 拉片、拆镜头、分析视频镜头、镜头时长、景别、运镜、镜头表、
video shot breakdown、shot list from video。
allowed-tools:
- Read
- Write
- Edit
- Bash
- Glob
triggers:
- video-shots
- 拉片
- 拆镜头
- 镜头分析
- 分析视频
- 镜头表
- 景别
- 运镜
- shot breakdown
- shot list
metadata:
license: Apache-2.0
requires:
bins:
- node # >= 18,只用标准库,无 npm 依赖
- ffmpeg # 场景检测、运动测量、抽帧、联系表
- ffprobe # 片长、帧率、分辨率
runtimes:
- claude-code
- codex
---
## video-shots
给成片**拉片**——把一条片子拆成逐镜头的分析表:**时长、景别、类别、运镜、画面**。
**前提刻在骨子里:镜头边界是量出来的,不是看出来的。** 模型看视频最不可靠的就是报时间——
「这个镜头大概 3 秒」和「2.97 秒」差的不是精度,是这份表能不能用。所以这里划一条死线:
| 谁来定 | 什么 | 怎么定 |
| --- | --- | --- |
| **代码** | 切点、时长、片长、帧率 | ffmpeg 场景检测 + ffprobe,两位小数 |
| **代码** | 每个镜头的实测运动量 | 逐帧差分曲线的区间中位数(两端剔除,避开切点尖峰) |
| **模型** | 景别、类别、运镜、画面、节奏 | 看关键帧判断——**只有这几件事是模型的活** |
| **代码** | 判断对不对 | 15 道质量门,逐条对账 |
最硬的一道门是**运镜实测对账**:摄影机真动了,像素不可能不变。所以「声称推/拉/摇/移/跟,
实测帧间变化接近 0」这一向直接拦——这是模型拉片最常见的幻觉。反过来(声称固定、实测很动)
**不拦**,只出提示:固定机位前面有人跳舞,帧间差一样会爆。
`{baseDir}` = 本文件所在目录。脚本 `{baseDir}/scripts/video-shots.mjs`,零依赖,`node` 直接跑。
**边界(不做的事)**:不转写语音(没有 ASR,台词靠画面上烧录的字幕读,读不到就留空并说明)、
不做人脸识别与人物自动归并(`cast` 是人工编号)、不评价片子好坏(报告只给事实和统计)、
不剪辑不导出片段、不做镜头内的物体检测。
---
### Step 0 — 定输入与范围
只要一个视频文件。先问清两件事,问不到就按默认走并在汇报里说明:
- **拉全片还是拉一段**:全片是默认。只要某一段就先用 ffmpeg 裁出来再拉,别在整片上标一半。
- **拉来干什么**:做仿写参考(重画面与运镜)、做剪辑节奏分析(重时长与类别)、
做投放素材盘点(重产品镜与字卡)。用途不同,`note` 里该多记什么不同——**表的结构是一样的**。
### Step 1 — seed 工作底稿 ⛔ 切点在这一步定死
```bash
cd <输出目录>
node {baseDir}/scripts/video-shots.mjs seed <video> --track track.json --title "<片名>" > shots.json
```
stderr 会报:片长、帧率、分辨率、检测到几个切点、合并后几个镜头。**先看这一行再往下走**:
- 平均镜长十几秒、镜头数明显偏少 → 阈值高了,`--threshold 0.15` 重跑(暗戏、慢片、
同机位对话多的片子都要往下调)
- 镜头数比肉眼数的多出一截 → 阈值低了,往 `0.4` 调,或留到 Step 4 用 `recut --merge` 并
- 一条 3 分钟的片子跑完只要几秒钟,**多跑两遍比将就一份烂底稿划算**
底稿里 `start` / `end` / `seconds` / `motion` 已经填好,`size` / `category` / `camera` / `frame` 是空的——
**那四个空格子才是模型的活**。
### Step 2 — 抽关键帧与联系表
```bash
node {baseDir}/scripts/video-shots.mjs frames shots.json --video <video>
node {baseDir}/scripts/video-shots.mjs sheet shots.json --cols 4 --rows 6
node {baseDir}/scripts/video-shots.mjs sheet shots.json --cols 4 --rows 6 --pick b
```
每镜两张:`frames/S01a.jpg`(起手 15% 处)和 `S01b.jpg`(收尾 85% 处)。
联系表把它们各拼成一张大图(行优先,S01 在左上),**a 表看内容,b 表看运镜**——
同一格前后对照,取景变没变一眼就知道。
**先看联系表,再看单帧。** 一张张翻完整部片是浪费额度:一张联系表 = 二十几个镜头,
只在判不准的那几个镜头上回去看单帧(`Read frames/S07a.jpg`)。
### Step 3 — 逐批填四个字段
一批 ≤ 25 个镜头(正好一张联系表)。每批拿到:
- `{baseDir}/references/taxonomy.md`(四张词表 + 判据,**照着填**)和 `{baseDir}/references/analysis-pass.md`(怎么看、常见病)
- 这一批的镜头底稿(镜号、起止、时长、**实测运动**)
- 这一批的 a / b 两张联系表
填的顺序:**景别 → 类别 → 运镜 → 画面 → 节奏**。运镜看 a/b 取景差 + 实测运动值,
两者打架时**信实测**。顺带记 `subjects` / `onscreenText` / `audio`——
**画面上烧录的对白字幕算台词**,进 `audio` 并带上说话人;片名、字卡、界面文字进 `onscreenText`。
**节奏(`rhythm` + `rhythmNote`)回答的是「观众为什么还没划走」**:
钩子 / 铺垫 / 递进 / 重音 / 转折 / 兑现 / 换气 / 收口,八个角色选一个,
再用一句话写清**观众这一刻看到什么**(判据见 `taxonomy.md` 第五节)。
**可选字段:不做节奏分析就一镜都别标;标了就得整片标全**,门会拦半张表。
标满之后校验会顺带提示三件事:开篇有没有钩子、兑现前面有没有铺垫、有没有连着六镜节奏发平。
编辑 `shots.json` 时**只动那几个字段**:`start` / `end` / `seconds` / `motion` / `seedCuts` / `meta`
是机器字段,改了就是伪造证据,门会点名。
### Step 4 — 补刀与并刀(发现漏切就修,别将就)
场景检测必然在两个地方出错:叠化和暗场对暗场**漏刀**,手持晃动和闪光**多刀**。
a 帧和 b 帧根本是两个场景,就是漏刀的铁证。
```bash
node {baseDir}/scripts/video-shots.mjs recut shots.json --track track.json \
--split 63.5 --split 127.37 --merge 45.97 > shots.new.json && mv shots.new.json shots.json
```
自动重编号、重算时长与实测运动,补的刀记进 `manualCuts`(`boundary` 门认它)。
**边界没动过的镜头标注原样保留;被拆被并的镜头标注清空并在 `note` 里写明出身**——
这两半是不是一回事,得重新看画面,不许把旧描述顺下去。
改完重抽这些镜头的帧(`frames` 会覆盖整个目录,直接重跑就行),把清空的格子补上。
### Step 5 — 校验 ⛔ 不能跳
```bash
node {baseDir}/scripts/video-shots.mjs validate shots.json --track track.json --frames frames
```
15 道门全是代码:时间轴连续(按序、首尾相接、从 0 到片尾)、时长自洽(`seconds` = `end − start`,
短镜必须带 note)、镜号连号、**景别/类别/运镜三张词表**、转场枚举、**画面描述可核对**
(中文 ≥12 字 / 英文 ≥8 词 + 空话词表 + 不许「这个镜头…」「This shot…」开头)、**画面描述不重复**、主体对账 `cast`、
**类别要有证据**(对话必须有台词、字卡必须有画面文字、反应必须写是谁、空镜里不许有人)、
**运镜实测对账**、**边界来自检测**(自己加的刀必须在 `manualCuts` 里声明)、关键帧齐全、
**节奏分析可核对**(可选字段:整片标或整片不标;标了就得写清为什么,空话照拦)。
**有违规逐条修,改完重跑,直到通过。** 跳过的门会明说原因(没给 `--track`、没建 `cast`、
关键帧目录不存在)——**跳过不是通过**,汇报时要讲。拉英文片加 `--lang en`,
门的名字和违规信息都会用英文说。
「提示(不拦)」那一栏不是错误,是需要人判断的地方:固定机位实测偏高,
多半是主体在动,也可能是你把一次缓推看漏了,自己回去看一眼那一镜。
### Step 6 — 出报告与汇报
```bash
node {baseDir}/scripts/video-shots.mjs render shots.json --md --track track.json > shots.md
node {baseDir}/scripts/video-shots.mjs render shots.json --html --track track.json \
--video <原片相对报告的路径> > shots-report.html
```
`--video` 给报告里的播放器指原片(默认用 JSON 里的 `source`;观众也能在页面上现场选本地文件)。
界面语言用 `--lang zh|en`(默认中文)。`render` 自动去 `frames/` 找关键帧,
**先抽帧再 render**,缺图明说缺、不摆占位图充数。
报告是**单文件交互页**(样式与交互来自 `{baseDir}/scripts/report.css` 和 `report.js`,
render 时整段内联;这三个文件必须一起拷走):
- **播放器**:播放时同步高亮当前镜头、填充时间轴进度;点任意镜头或时间轴片段跳过去
- **镜头节奏带**:片宽 = 时长占比,颜色深浅 = 景别远近
- **镜头表**:列表 / 卡片两种视图,首尾关键帧并排(对照着看就是运镜),可搜索(镜号、画面、
台词、人物)、可按类别筛选、可按时长排序;点关键帧开大图
- **统计分布 / 出场人物 / 质量检查**:默认收起,按需展开;人物卡点一下筛出他的全部镜头
- 页头一行给结论(`14 项通过 · 1 条提示`),提示里点名的镜号可以点着跳过去
汇报一句话说清:**多少镜、平均镜长、每分钟切次、景别与运镜的大头、最长和最短的镜头在哪、
报告路径**;补了几刀并了几刀、哪几道门跳过了、哪几条提示需要人看,明说。
最终落地:
```
<输出目录>/
├── shots.json ← 拉片主数据
├── track.json ← 运动曲线(机器证据,别手改)
├── shots.md
├── shots-report.html ← 双击就能开
├── frames/ ← S01a.jpg / S01b.jpg …
└── sheets/ ← sheet-a01.jpg / sheet-b01.jpg …(联系表)
```
---
## 边界
- **没有语音转写。** 台词只来自画面上烧录的字幕;没有字幕的片子 `audio` 大面积留空是正常的,
这时把 `dialogue` 判成 `subject` 更诚实——类别证据门会逼你做这个选择
- **实测运动不区分机位动还是主体动。** 所以运镜门只拦「声称大动却实测不动」这一向,
反向只出提示。想改松紧调 `params.staticMaxMotion` / `busyMinMotion`
- **场景检测不认叠化。** 叠化段落的切点取中点,`transitionIn` 写 `dissolve`
- **镜头数上限取决于耐心不是脚本。** 一部 90 分钟的片子能拉,但那是几十张联系表;
长片建议按章节裁段分次拉
- **中英双语**:`--lang zh|en` 贯穿所有命令——门的名字、违规信息、命令行输出、报告界面、
四张词表全跟着切(优先级 `--lang` > JSON 顶层 `lang` > 中文)。**切的是标签,
画面描述、台词、人物名原样不动**——那是内容不是标签。
`seed --lang en` 会把 `lang: "en"` 写进底稿,后面的 `recut` / `validate` / `render`
不带 `--lang` 也跟着走英文(连补刀写的 `note` 和英文表格里的标点都是半角的)
- **画面描述的判据跟着描述本身的语言走**:中文数字数(≥12 字)、英文数词数(≥8 词),
空话词表和废话开头各有一套。拉英文片就用英文写,门照样拦
- 报告要看视频得有原片:`--video` 指对路径,或在页面上现场选文件。报告本身不嵌视频数据
## 自测
```bash
node {baseDir}/scripts/selftest.mjs
```
449 项断言,不调模型、不花额度、不碰 ffmpeg。**15 道门每一道都有击穿用例**——证明它真的会拦。
改完脚本先跑这个。
## 自带样例
`{baseDir}/examples/demo-shots.json` + `demo-track.json`:一条 202.9 秒的 AI 短片完整拉片——
53 个镜头,平均镜长 3.83 秒,每分钟 15.7 切;对话占 58%、固定机位占 55%;
最短 0.33 秒(雪地奔跑的闪切),最长 16.06 秒(结尾光柱下的长镜头)。
`seedCuts` 之外补了 10 刀(叠化和片尾字卡各占一半,全部记在 `manualCuts` 里),
15 道门全绿,留着两条提示当范例(运镜实测偏高、片尾连着六镜节奏发平)。它是质量基准,也是自测夹具。
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!