播客剪輯與母帶處理
把使用者自己錄的播客、口播錄音處理成可以發佈的成片:降噪,修掉首尾靜音,人聲均衡和壓縮,把響度標準化到約 -16 LUFS,匯出 MP3。需要時再加片頭片尾配樂和分段配樂,並給出剪輯建議(哪些長停頓可以收緊、哪裡是重錄段),由使用者確認後再剪。
下文的 scripts/、references/ 都在本技能目錄裡,執行時寫成完整路徑,例如 bash <本技能目錄>/scripts/edit_podcast.sh。
一、開始前先跟使用者講清楚
- 要在本機裝好 ffmpeg(免費開源的音訊工具)。沒有的話請使用者自己安裝:macOS 用
brew install ffmpeg,Linux 用系統的套件管理器(如apt install ffmpeg)。腳本按這個順序找:環境變數FFMPEG→ 系統 PATH 裡的ffmpeg→ 常見安裝位置/opt/homebrew/bin、/usr/local/bin。實測過 6.0 和 8.1 兩個版本。 - 要用 bash 和 Python 3.9 以上,只用標準庫,不用另裝 Python 套件。
- 只支援 macOS 和 Linux:在 macOS 上實測過;Linux 應可用;Windows 需要 WSL,沒有測過。
- 首次降噪會聯網下載一個約 300KB 的公開降噪模型(RNNoise),存到
~/.cache/ethan-podcast-post/,下載後按 SHA-256 校驗。下載不了或校驗不對,自動改用 ffmpeg 自帶的 afftdn 降噪,腳本會列印用的是哪一種,你要把這一點告訴使用者。模型出處見references/third-party.md。 - 全程在本機處理:不上傳錄音,不收費。
- 不做的事:不產生或克隆人聲,不配音,不轉寫(需要字幕時請使用者提供 SRT 字幕檔案),不發佈到任何平台,不提供音樂(背景音樂由使用者自備,並確認有使用授權)。
二、檔案安全(必須遵守)
- 原始錄音唯讀:腳本只讀取,不改、不移動、不刪。
- 輸出一律寫到新目錄:在錄音旁邊新建一個目錄(例如
第3期_後期/),不要和錄音、背景音樂放在同一個目錄。腳本發現輸出目錄和輸入檔案在同一目錄時會停下。 - 不擅自覆蓋:輸出目錄裡已有成片、
audio_raw.wav、voice_master.wav時,腳本停下不覆蓋。要覆蓋,先告訴使用者哪幾個檔案會被覆蓋、覆蓋後舊檔案找不回來,使用者同意後再加OVERWRITE=1重跑。剪輯建議、剪切和人聲處理的腳本同理:已有同名輸出時停下,使用者同意後才加--overwrite。 - 刪除前先確認:任何刪除(包括清理
audio_raw.wav這類大檔案)都先向使用者說明刪哪個檔案、為什麼,取得同意後再刪。腳本的中間檔案都放在輸出目錄下本次執行專用的臨時子目錄裡,結束時只清理這個子目錄。 - 剪什麼由使用者定:剪輯建議只是候選清單,使用者確認之前不剪。
三、處理流程
先跑一次環境檢查,把結果告訴使用者:
bash <本技能目錄>/scripts/edit_podcast.sh --check
它會列出找到的 ffmpeg、版本、250Hz 動態均衡是否啟用(見第六節),以及降噪模型是否已就緒。找不到 ffmpeg 時會提示怎麼安裝,先裝好再繼續。
第 1 步:第一遍處理,出人聲母帶和試聽版
KEEP_MASTER=1 bash <本技能目錄>/scripts/edit_podcast.sh <錄音檔案> <輸出目錄>/pass1 <名稱>
- 錄音可以是 wav、m4a、mp3、mp4 等 ffmpeg 能讀的格式。雙人或多人對談加
INTERVIEW_MODE=1(壓縮更強,並自動拉平幾個人的音量差)。 - 處理順序:提取成 48kHz WAV → 品質檢查 → 修剪首尾靜音(中間的停頓一處不動)→ 去唇齒咔噠 → 80Hz 高通 → 降噪 → 250Hz 動態均衡 → 去齒音 → 壓縮 → 5kHz 提亮 → 線性增益到 -16 LUFS → 限幅 → 匯出 MP3 → 復測響度。
- 產出:
<名稱>_final.mp3:沒有配樂的成片(也是試聽版);voice_master.wav:人聲母帶(已修剪首尾),後面的轉寫、剪輯建議和剪切都以它為準;audio_raw.wav:原始錄音轉成的 WAV 副本(處理全部成功後才寫進輸出目錄),以後重做可以用,檔案較大。
- 看輸出裡的這幾項,告訴使用者:
- 尾部音量低於 -80dB:錄音可能中途斷了,先和使用者確認;
- 降噪方式:RNNoise 還是 afftdn;
- 成片響度應在 -16 LUFS ±1 以內,真峰值不高於 -1.5 dBTP。超出時腳本會提示,照實告訴使用者。
- 錄音太輕或者是空錄音時,腳本會報錯停下,不會出空成片。報錯裡會按錄音峰值給出建議值:先和使用者確認錄音裡確實有說話,再按提示加
INPUT_GAIN=<建議值>(單位 dB,已經算上當前設的 INPUT_GAIN)把整段抬到正常電平後重跑;報錯時輸出目錄裡不會留下檔案,可以直接用同一個目錄重跑。同時建議使用者以後錄音時把輸入音量調高。
第 2 步:剪輯建議(只列候選,不動音訊)
字幕要從第 1 步的 voice_master.wav 轉寫(或者轉寫第 1 步沒加配樂的成片 MP3,兩者時間軸一致)。不要用原始錄音的轉寫:原始錄音首尾沒修剪,時間對不上,找出來的位置會偏。
找可以收緊的長停頓:
python3 <本技能目錄>/scripts/edit_cuts.py suggest <輸出目錄>/pass1/voice_master.wav \
[--srt 字幕.srt] [--protect 15:00.0-15:03.0 ...]
- 預設把長於 1.5 秒的停頓列為候選,建議收緊到 1.2 秒(停頓兩頭各留一半,保住房間底噪)。
- 使用者特意留的停頓(比如一句重要的話之後的沉默),用
--protect 開始-結束標出來,前後各留 0.5 秒緩衝,不會被建議剪掉。 - 明顯的長停頓沒被找到,說明底噪偏高,把
--noise從預設的 -45 調到 -40 再跑。 - 每 40 分鐘的候選超過 15 處或累計超過 60 秒,腳本會提醒參數太緊:調大
--min-pause,不要照單全剪。
找重錄口令標出的重錄段(需要字幕):
python3 <本技能目錄>/scripts/edit_cuts.py retakes <輸出目錄>/pass1/voice_master.wav --srt 字幕.srt [--phrase 你的口令]
- 錄音時的約定:說錯了 → 停 1 秒以上 → 平靜地說一句重錄口令 → 停 1 秒以上 → 從整句開頭重說。
- 預設口令是「重來重來」。換成你自己的口令就用
--phrase;轉寫軟體常把口令寫成同音字時,可以寫多次--phrase,把幾種寫法都加上。口令選一個正文裡不會出現的短語,連說兩遍更不容易誤判。 - 腳本在字幕裡找口令,用重說那句的開頭去前面匹配說錯的那句,給出剪切區間和把握程度(high / mid / low)。low 的一定要讓使用者試聽確認。
兩個命令分別寫出 voice_master_suggested_cuts.tsv 和 voice_master_retake_cuts.tsv(已存在時不覆蓋,使用者同意後加 --overwrite)。把候選逐條講給使用者(時間、前後文、能省幾秒),使用者確認要剪哪些之後,把這些行抄進一個新的 cuts.tsv(tab 分隔:開始、結束、備註;時間寫 MM:SS.s)。
第 3 步:剪切
python3 <本技能目錄>/scripts/edit_cuts.py apply <輸出目錄>/pass1/voice_master.wav --cuts cuts.tsv [--srt 字幕.srt]
- 按取樣剪切,接縫做 15 毫秒等功率交叉淡化,輸出 24-bit 的
voice_master_edited.wav和剪切圖voice_master_edited.edit_map.json(記錄每段保留音訊的來源和位置)。 - 給了
--srt時,完整保留的字幕會跟著平移;有字幕被剪掉一半時腳本拒絕執行——去掉--srt重跑,剪完請使用者對剪後母帶重新轉寫。 - 剪完請使用者試聽每個接縫前後幾秒:沒有咔噠聲、沒有兩次吸氣、音色不跳。
第 4 步(可選):給引文、回憶等片段換一種聲音質感
python3 <本技能目錄>/scripts/voice_fx.py <剪後母帶.wav> --fx fx.tsv
python3 <本技能目錄>/scripts/voice_fx.py --list # 看五個預設
- 預設:quote(唸信件、他人文字)、archive(舊報紙、舊公文)、telephone(轉述電話)、memory(回憶、夢境)、radio(舊廣播,一期最多一次)。
- fx.tsv 每行:預設、開始、結束、淡入、淡出、備註(tab 分隔,淡入淡出寫 - 用預設)。
- 處理後時長按取樣不變、響度不變;輸出
<原名>_fx.wav。
第 5 步(可選):配樂和 cue 表
只有使用者提供了背景音樂時才做。先寫 cue 表,再預檢。cue 表放在第二遍的輸出目錄裡,檔案名必須是 <名稱>_cues.tsv。
cue 表:tab 分隔 7 列,# 開頭的行忽略,某列寫 - 表示用預設值。time 寫 MM:SS.s,相對人聲母帶的時間軸。下面的時間都是範例:
# type time src dur vol fade_in fade_out
stinger 02:00.0 - 3 - - -
break 12:00.0 -@20 8 0.5 2 2
bed 18:00.0 -@40 30 0.08 - -
ending 40:00.0 -@90 - - - -
| 類型 | 作用 | dur | 預設音量 |
|---|---|---|---|
| stinger | 一小段音樂記號,疊在人聲上 | 預設 6 秒 | 0.12 |
| bed / post | 在人聲下面鋪一段音樂,人聲出現時自動壓低 | 必填 | 0.12 |
| break | 在這點把人聲切開,插一段純音樂,放完再接回人聲;成片變長 dur 秒 | 必填 | 0.5 |
| ending | 結尾音樂:說話時壓低,說完升上來,最後淡出 | 寫 - | 0.22 |
src寫-表示用這次傳入的背景音樂;-@20表示從背景音樂第 20 秒開始取;也可以寫另一個音樂檔案的完整路徑,同樣可以加@秒(不加就從頭取)。- stinger 的 src 寫
-而不帶@時,預設從背景音樂第 60 秒取段;其他類型不帶@時從頭取。 - 寫完先預檢,有 ❌ 先改:
python3 <本技能目錄>/scripts/cues_lint.py <名稱>_cues.tsv --master <剪後母帶.wav> --bg <背景音樂>
它會檢查格式、越界、素材是否存在、視窗重疊、音樂總佔比(建議不超過 15%),並給出「母帶 → 成片」時間戳換算表,寫節目時間線時直接用。
第 6 步:第二遍出成片
MASTER_WAV=<剪後母帶.wav> bash <本技能目錄>/scripts/edit_podcast.sh - [背景音樂] <輸出目錄>/final <名稱>
- 第一個參數寫
-。有MASTER_WAV時跳過提取、降噪和標準化,直接疊 cue、混配樂、匯出。 - 有背景音樂且音訊長於 80 秒時,自動加片頭片尾配樂:片頭音樂先響 3 秒再進人聲,人聲結束後音樂留約 7 秒淡出,人聲出現時音樂自動壓低。
- 沒有剪輯、沒有配樂時,第 1 步的成片就是最終成片,不用跑這一步。
第 7 步:驗收
腳本最後已經復測過一次。要再獨立量一遍(ffmpeg 用 --check 顯示的那個路徑):
"<ffmpeg 路徑>" -hide_banner -nostats -i <成片.mp3> -filter_complex ebur128=peak=true -f null - 2>&1 | tail -12
- 達標:Integrated 在 -16 LUFS ±1 以內,True peak 不高於 -1.5 dBFS(即 dBTP)。
- 請使用者戴耳機聽:音樂沒蓋過人聲;停頓時音樂沒有突然跳回來;每個剪切點自然;結尾沒有被切斷;特意留的停頓還在。
- 交付時告訴使用者:成片位置、降噪方式、響度和真峰值的實測數字、剪了哪幾處。沒達標就照實說,不要說成通過。
四、剪輯原則
- 只剪事故,不剪呼吸:口誤、看稿走神、重錄的廢段要剪;呼吸、思考時的「嗯」、句間遲疑、特意留的沉默不剪。拿不準的,留下。
- 0.8 秒以內的停頓不動;0.8–1.5 秒的句間停頓預設全留;更長的停頓要有卡頓或走神的證據才收緊。
- 剪口選在自然氣口,不切傷字頭、尾音;呼吸要嘛整留要嘛整刪。
- 背景音樂低於人聲,避開有人聲的曲子和中頻(1–3kHz)很滿的曲子;有旋律的音樂優先放在段落之間(break),不壓在人聲下面;音樂總時長一般不超過成片的 15%。
- 詳細做法見
references/editing-style-guide.md(風格和包裝)、references/editing-essentials.md(剪輯執行要點和驗收),術語見references/audio-glossary.md。
五、環境變數(都可以不設)
| 變數 | 預設 | 作用 |
|---|---|---|
FFMPEG | 自動尋找 | 指定 ffmpeg 路徑 |
DENOISE | rnnoise | rnnoise / afftdn / off |
RNNOISE_MODEL | ~/.cache/ethan-podcast-post/bd.rnnn | 降噪模型位置;已有模型可直接指向它 |
RNNOISE_URL | GitHub 上的公開模型 | 網路不通時可換成鏡像地址(下載後仍校驗 SHA-256) |
INPUT_GAIN | 0 | 處理前把錄音整體抬高(或降低)多少 dB;錄音太輕時按報錯裡的建議值設 |
TRIM_THRESHOLD | -50 | 修剪首尾靜音的閾值(dB);只是開頭結尾的輕聲被切掉時調低,如 -60 |
INTERVIEW_MODE | 0 | 1 = 雙人或多人對談 |
KEEP_MASTER | 0 | 1 = 保留人聲母帶 voice_master.wav |
MASTER_WAV | 空 | 第二遍用的人聲母帶 |
MUSIC_VOLUME | 0.12 | 背景音樂音量比例 |
INTRO_DURATION | 40 | 片頭、片尾配樂視窗秒數;音訊短於它的 2 倍時不加 |
PREROLL / TAIL | 3 / 7 | 片頭音樂先行秒數 / 人聲結束後音樂留的秒數 |
OUTRO_SEEK | 空 | 片尾音樂從曲子第幾秒取 |
MP3_BITRATE | 160k | 成片碼率 |
OVERWRITE | 0 | 1 = 允許覆蓋已有成品(先徵得使用者同意) |
六、常見問題
- 找不到 ffmpeg:請使用者先安裝(macOS
brew install ffmpeg,Linux 用套件管理器),或用FFMPEG=/完整路徑/ffmpeg指定。 - ffmpeg 版本:腳本會讀版本號。250Hz 動態均衡這一級的參數含義在 ffmpeg 6 和 8 之間變了,兩種寫法都用正弦訊號標定過,效果一致;遇到 7.x 等沒驗證過的版本會跳過這一級並提示,其餘處理照常。修剪首尾靜音的參數也按新版行為調過,6.x 和 8.x 切得一樣。
- 報錯「修剪首尾靜音後幾乎什麼都不剩」或「處理後測不到響度」:錄音太輕或是空的。確認有說話後,按報錯裡給的
INPUT_GAIN建議值重跑(響度低於 -70 LUFS 時無法測量,只調修剪閾值不夠);錄音裡真的沒有聲音時會一直報錯,不會輸出空成片。 - 降噪用了 afftdn:說明模型沒下載成功。可以設
RNNOISE_URL換鏡像,或用RNNOISE_MODEL指向已有的模型檔案,再重跑(覆蓋已有成品前先問使用者)。 - 尾部音量低於 -80dB:錄音很可能中途斷了,先和使用者確認,不要當成剪輯問題。
- 重錄段一個都沒找到:確認字幕是從
voice_master.wav轉寫的,再看轉寫裡口令被寫成了什麼字,用--phrase補上這種寫法。 - 想看每一步的命令:
bash -x <本技能目錄>/scripts/edit_podcast.sh ...。 - 在 zsh 裡自己拼 ffmpeg 濾鏡參數:變數後面緊跟冒號時寫成
${VAR}:...,否則 zsh 會把$VAR:a、$VAR:r當成修飾符,參數被悄悄改掉。
許可:程式碼 MIT,文件 CC BY 4.0。