¿Qué hace 网球回合剪辑(tennis-rally-cut)?
沉淀自 2026-11-08 的一次真实剪辑:两段对墙练球视频,剪出 14 个回合。 核心思路:听声音找回合,看画面做核对。
用户要的是什么(硬规则)
- 一个回合 = 从第一拍到球打死。整个回合放进一个片段。
- 不许把回合剪碎。 连续对墙打了 10 拍,就是一个片段,不能拆成几个 2 拍、3 拍。
- 捡球、走动、停顿、拿手机、收机器,都剪掉。
- 原视频不动。成品放在原视频同目录的
剪辑/文件夹。
为什么用声音不用画面
- 每一拍都有「拍子击球」和「球撞墙/落地」两声脆响。捡球、走路时没有。
- 画面动静不行:捡球走动时画面变化比打球还大,分不出来。
- 画面只用来抽查核对。
工具
脚本:scripts/rally_cut.py。只依赖 ffmpeg、ffprobe 和 numpy。
| 子命令 | 作用 |
|---|---|
probe | 看分辨率、码率、方向;提示是否被聊天软件压缩 |
analyze | 找击球声,串成回合,写 <名字>.segments.json |
sheets | 每个回合开头、结尾各拼一张帧图,用来核对 |
cut | 按回合剪成独立 mp4,写 片段清单.txt |
align | 换了高清原片时,算新旧两版的时间偏移 |
下文 RC 指脚本的绝对路径。中间文件放会话的临时目录,不要放进用户的视频目录。
流程
1. 先查画质
python3 -I $RC probe <视频...>
- 出现「疑似被聊天软件压缩」时,先告诉用户。剪辑本身几乎不损失画质,原片糊成品就糊。
- 微信传来的视频,原片常在微信缓存里,文件名同名加
_raw后缀:~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<账号>/msg/video/<年-月>/<名字>_raw.mp4用户在微信里点「查看原视频」之后才会下载。可用mdfind按加入时间搜:mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<日期>)' - 找到高清原片后,用
align确认偏移为 0,就能直接沿用已经算好的切点。
2. 找回合
python3 -I $RC analyze <视频...> --work <临时目录>
它会打印每秒击球声数量的时间条和回合列表。默认参数:
| 参数 | 默认 | 含义 |
|---|---|---|
--k | 2.5 | 比背景响几倍算一声。环境吵就调大 |
--gap | 6 | 两声间隔超过 6 秒才算回合结束。这是「不剪碎」的关键 |
--min-hits | 6 | 少于 6 声的串当噪声丢掉(脚步、收手机) |
--pre / --post | 1 / 1.5 | 回合前后多留的秒数,保证挥拍和落点完整 |
--gap 的取舍:
- 4 秒会把「球死后几秒内立刻重新开球」拆成两段。
- 6 秒把它们留在一起。用户要求不剪碎,所以默认 6。
- 用户明确说「每次重新开球都单独一段」时,才改成 4。
3. 看画面核对(不可跳过)
python3 -I $RC sheets <视频> --segments <json> --work <临时目录>/sheets
逐张看图,重点查:
- 视频最后一个回合:结尾常有人走向镜头、拿起手机,声音会被当成击球。看到就把
end改到最后一拍后约 1.5 秒。 - 只有个位数击球声的短回合:可能是噪声。画面里没人在挥拍就删掉。
- 回合之间 5 秒左右的空档:确认是球死重开,而不是漏检。
直接编辑 segments.json 里的 segments 数组:改 start/end,或删整条。
4. 剪
python3 -I $RC cut <视频> --segments <json> --out <视频目录>/剪辑 --label A
- 多个源视频用不同
--label(A、B…),文件名形如视频A_03_2m54s-3m22s.mp4,时间是原片中的起止。 - 必须重新编码(默认 H.264、CRF 17)。原片关键帧常隔 2~3 秒,直接拷贝会切偏。
- 竖拍视频的方向信息 ffmpeg 会自动处理,成品就是正的。
- 目标目录已有旧成品时,先改名成
剪辑_旧版备份,不要直接覆盖或删除。 - 1080p 60 帧的片子编码较慢,放后台跑。
5. 验收再汇报
- 用 ffprobe 核每个成品的时长、分辨率、帧率。
- 从一两个成品里截帧看一眼:方向正、画面清楚。
- 汇报时给:片段数、每段时长范围、成品目录(附 Finder 能点开的
file://链接,中文路径要百分号编码)。 - 写明拿不准的片段(例如击球声很少的短回合),让用户自己决定删不删。
- 写明只抽查了画面,没有逐段从头看到尾。
踩过的坑
| 坑 | 后果 | 做法 |
|---|---|---|
循环里调 ffmpeg 没加 -nostdin | ffmpeg 吃掉清单后续行,切点错乱、崩溃 | 脚本已加 |
| 时间按「格数×10ms」换算 | 一格其实是 220 个采样点,7 分钟漂 1 秒 | 脚本按采样点换算 |
| 用画面动静判断打球 | 捡球比打球动得还厉害 | 只用声音判断 |
| 用压缩过的转发视频 | 成品发糊,用户以为是剪坏了 | 第 1 步先 probe |