網球回合剪輯(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 |