From be5f4a60b8681bbf44c89db53b1221422b4b8477 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Tue, 2 Jun 2026 19:29:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=9F=B3=E8=A8=8A=E8=99=95?= =?UTF-8?q?=E7=90=86:=20=E9=9F=B3=E9=87=8F=E5=A2=9E=E7=9B=8A=20/=20?= =?UTF-8?q?=E5=BC=B7=E5=88=B6=E5=A3=93=E9=99=90=20/=20=E4=BD=8E=E9=80=9A(?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E5=8F=83=E8=80=83=20Adobe=20PR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - volume_boost: 音量增益(對應 PR 增益/音量),預設開,幅度 volume_boost_percent 預設 +30%(x1.3) - hard_limiter: 強制壓限(PR Hard Limiter),拉大音量後把峰值壓在 0dB 以下避免破音 - lowpass: 低通(PR Lowpass),濾掉高頻刺耳聲;截止頻率 lowpass_hz 預設 15000Hz - 處理順序: 音量增益 -> 低通 -> 強制壓限(壓限擺最後當煞車) - 套用在重編路徑(轉場/xfade)的 acrossfade 之後;單一片段時畫面照樣無損複製只重編音訊 - transition=false(無損)時音訊不處理並提示;已實測同段處理後平均 +2.7dB - 同步更新 README 參數說明 本次調整由 Claude Opus 4.8 (1M context) 協助開發與處理。 Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 10 ++++++++ auto_remove_redlight.py | 56 +++++++++++++++++++++++++++++++++++++---- config.json | 5 ++++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9deac40..7548d71 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,11 @@ python auto_remove_redlight.py 01.MOV 03.MOV 05.MOV | `transition` | `true` | 剪接點是否加轉場(淡出淡入 + 恆定功率交叉淡化)。**開啟會強制重新編碼**,有 NVIDIA 顯卡時自動用 NVENC 加速 | | `transition_duration` | `0.5` | 轉場長度(秒),畫面與聲音共用以維持同步 | | `video_transition` | `fadeblack` | 畫面轉場類型:`fadeblack`=淡出到黑再淡入;`fade`=交叉溶接;其餘見 FFmpeg `xfade` 文件 | +| `volume_boost` | `true` | **音量增益**(Adobe PR「增益/音量」):是否放大音量 | +| `volume_boost_percent` | `30` | 音量增加幅度(%),`30` = 放大成 130%(×1.3 ≈ +2.3dB) | +| `hard_limiter` | `true` | **強制壓限**(Adobe PR「強制壓限/Hard Limiter」):拉大音量後把峰值壓在 0dB 以下,避免破音 | +| `lowpass` | `true` | **低通**(Adobe PR「低通/Lowpass」):濾掉高頻刺耳聲(風切/嘶聲) | +| `lowpass_hz` | `15000` | 低通截止頻率(Hz) | | `base_dir` | `E:\\videos` | 影片根目錄 | > 參數優先順序:**命令列參數 > config.json > 程式內建預設** @@ -105,6 +110,11 @@ python auto_remove_redlight.py 01.MOV 03.MOV 05.MOV - `transition: false` → 使用 FFmpeg `-c copy` 串流複製,**畫質無損、速度快**,但剪接點是硬切。 - `transition: true` → 在每個剪接點加上**畫面淡出淡入**(`xfade`)與**聲音恆定功率交叉淡化**(`acrossfade`,`c1=qsin:c2=qsin`)。此模式必須重新編碼(非無損),4K 影片較耗時,建議搭配 NVIDIA 顯卡用 NVENC 加速。 +### 關於音訊處理 + +音量增益、強制壓限、低通三項(命名參考 Adobe Premiere Pro)會在重新編碼時一併套用,處理順序為 **音量增益 → 低通 → 強制壓限**(壓限擺最後當煞車)。 +由於需要重新編碼,這些音訊處理**僅在 `transition: true`(重編模式)時生效**;若 `transition: false`(無損快剪)則音訊維持原樣不處理。 + --- ## 輸出結果 diff --git a/auto_remove_redlight.py b/auto_remove_redlight.py index 9143776..84902d9 100644 --- a/auto_remove_redlight.py +++ b/auto_remove_redlight.py @@ -128,6 +128,14 @@ VIDEO_TRANSITION = "fadeblack" # 畫面轉場 (FFmpeg xfade 類型): # 其它如 dissolve, smoothleft... 見 ffmpeg xfade 文件 # 聲音固定用 acrossfade 等功率曲線(c1=qsin:c2=qsin)=恆定功率 +# --- 音訊處理(命名參考 Adobe Premiere Pro 效果;需重新編碼,即 TRANSITION=true 時生效)--- +VOLUME_BOOST = True # 音量增益: 是否放大音量(對應 PR「增益 / 音量」) +VOLUME_BOOST_PERCENT = 30 # 音量增加幅度 (%)。30 = 放大成 130% (x1.3 ≈ +2.3dB) +HARD_LIMITER = True # 強制壓限 (PR「強制壓限 / Hard Limiter」): + # 拉大音量後把峰值壓在 0dB 以下,避免破音(擺在最後當煞車) +LOWPASS = True # 低通 (PR「低通 / Lowpass」): 濾掉高頻刺耳聲(風切/嘶聲) +LOWPASS_HZ = 15000 # 低通截止頻率 (Hz)。低於此保留,高於此衰減 + # --- 批次模式 ----------------------------------------------- # 會被當成影片來處理的副檔名(小寫比較) VIDEO_EXTS = {".mov", ".mp4", ".m4v", ".avi", ".mkv", ".mts", ".m2ts", ".insv"} @@ -476,6 +484,22 @@ def ffmpeg_cut_concat(input_path: str, keeps: List[Tuple[float, float]], shutil.rmtree(tmpdir, ignore_errors=True) # 清掉暫存檔 +def _audio_filter_chain() -> List[str]: + """ + 依設定組音訊濾鏡鏈(對應 Adobe PR 的 音量 / 強制壓限 / 低通)。 + 回傳 ffmpeg 濾鏡片段清單,順序為: 音量增益 → 低通 → 強制壓限(壓限擺最後當煞車)。 + """ + chain: List[str] = [] + if VOLUME_BOOST and abs(VOLUME_BOOST_PERCENT) > 0: + mult = 1.0 + VOLUME_BOOST_PERCENT / 100.0 # +30% → x1.3 + chain.append(f"volume={mult:.4f}") # 音量增益 + if LOWPASS: + chain.append(f"lowpass=f={int(LOWPASS_HZ)}") # 低通 (Lowpass) + if HARD_LIMITER: + chain.append("alimiter=limit=0.95") # 強制壓限 (Hard Limiter) + return chain + + def has_audio(path: str) -> bool: """用 ffprobe 判斷影片是否有音訊軌。出錯時保守假設『有』。""" if shutil.which("ffprobe") is None: @@ -504,12 +528,20 @@ def ffmpeg_cut_concat_xfade(input_path: str, keeps: List[Tuple[float, float]], n = len(keeps) durs = [e - s for (s, e) in keeps] - # 只有一段 → 沒有剪接點,不需轉場,直接無損切出 + # 只有一段 → 沒有剪接點,不需轉場 if n == 1: s, e = keeps[0] - run_cmd(["ffmpeg", "-y", "-ss", f"{s:.3f}", "-t", f"{e - s:.3f}", - "-i", input_path, "-c", "copy", - "-avoid_negative_ts", "make_zero", output_path]) + af = _audio_filter_chain() + if af and has_audio(input_path): + # 仍要做音量處理: 畫面照樣無損複製,只重編音訊 + run_cmd(["ffmpeg", "-y", "-ss", f"{s:.3f}", "-t", f"{e - s:.3f}", + "-i", input_path, "-af", ",".join(af), + "-c:v", "copy", "-c:a", "aac", "-b:a", "256k", + "-avoid_negative_ts", "make_zero", output_path]) + else: + run_cmd(["ffmpeg", "-y", "-ss", f"{s:.3f}", "-t", f"{e - s:.3f}", + "-i", input_path, "-c", "copy", + "-avoid_negative_ts", "make_zero", output_path]) print(f"[INFO] 完成(單一片段,免轉場)! 輸出檔案: {output_path}") return @@ -556,6 +588,11 @@ def ffmpeg_cut_concat_xfade(input_path: str, keeps: List[Tuple[float, float]], f"[{prev_a}][{j}:a]acrossfade=d={d:.3f}:c1=qsin:c2=qsin[{out}]") prev_a = out alabel = prev_a + # 串接完再做音量處理(音量增益 / 低通 / 強制壓限) + af = _audio_filter_chain() + if af: + parts.append(f"[{alabel}]" + ",".join(af) + "[aout]") + alabel = "aout" filtergraph = ";".join(parts) @@ -876,11 +913,15 @@ def process_one(video_path: str, roi: Tuple[int, int, int, int], "removed": removed_total} # 4. FFmpeg 切割 + 拼接 + audio_proc = VOLUME_BOOST or HARD_LIMITER or LOWPASS if TRANSITION and len(keeps) >= 1: - # 有轉場: xfade 淡出淡入 + acrossfade 恆定功率(需重新編碼) + # 有轉場: xfade 淡出淡入 + acrossfade 恆定功率 + 音量處理(需重新編碼) ffmpeg_cut_concat_xfade(video_path, keeps, out_path, use_gpu=args.gpu, tmp_parent=out_dir) else: + if audio_proc: + print("[WARN] 音量處理(音量增益/強制壓限/低通)需重新編碼," + "目前 transition=false 為無損模式,音訊將維持原樣不處理。") # 無轉場: 維持無損快剪 ffmpeg_cut_concat(video_path, keeps, out_path, args.reencode, tmp_parent=out_dir) @@ -910,6 +951,11 @@ _CONFIG_KEYS = { "transition": "TRANSITION", "transition_duration": "TRANSITION_DURATION", "video_transition": "VIDEO_TRANSITION", + "volume_boost": "VOLUME_BOOST", + "volume_boost_percent": "VOLUME_BOOST_PERCENT", + "hard_limiter": "HARD_LIMITER", + "lowpass": "LOWPASS", + "lowpass_hz": "LOWPASS_HZ", "base_dir": "BASE_DIR", } diff --git a/config.json b/config.json index 2c8a282..0ed04f1 100644 --- a/config.json +++ b/config.json @@ -20,5 +20,10 @@ "transition": true, "transition_duration": 0.5, "video_transition": "fadeblack", + "volume_boost": true, + "volume_boost_percent": 30, + "hard_limiter": true, + "lowpass": true, + "lowpass_hz": 15000, "base_dir": "E:\\videos" } \ No newline at end of file