新增音訊處理: 音量增益 / 強制壓限 / 低通(命名參考 Adobe PR)
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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`(無損快剪)則音訊維持原樣不處理。
|
||||
|
||||
---
|
||||
|
||||
## 輸出結果
|
||||
|
||||
+51
-5
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user