新增音訊處理: 音量增益 / 強制壓限 / 低通(命名參考 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` | `true` | 剪接點是否加轉場(淡出淡入 + 恆定功率交叉淡化)。**開啟會強制重新編碼**,有 NVIDIA 顯卡時自動用 NVENC 加速 |
|
||||||
| `transition_duration` | `0.5` | 轉場長度(秒),畫面與聲音共用以維持同步 |
|
| `transition_duration` | `0.5` | 轉場長度(秒),畫面與聲音共用以維持同步 |
|
||||||
| `video_transition` | `fadeblack` | 畫面轉場類型:`fadeblack`=淡出到黑再淡入;`fade`=交叉溶接;其餘見 FFmpeg `xfade` 文件 |
|
| `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` | 影片根目錄 |
|
| `base_dir` | `E:\\videos` | 影片根目錄 |
|
||||||
|
|
||||||
> 參數優先順序:**命令列參數 > config.json > 程式內建預設**
|
> 參數優先順序:**命令列參數 > config.json > 程式內建預設**
|
||||||
@@ -105,6 +110,11 @@ python auto_remove_redlight.py 01.MOV 03.MOV 05.MOV
|
|||||||
- `transition: false` → 使用 FFmpeg `-c copy` 串流複製,**畫質無損、速度快**,但剪接點是硬切。
|
- `transition: false` → 使用 FFmpeg `-c copy` 串流複製,**畫質無損、速度快**,但剪接點是硬切。
|
||||||
- `transition: true` → 在每個剪接點加上**畫面淡出淡入**(`xfade`)與**聲音恆定功率交叉淡化**(`acrossfade`,`c1=qsin:c2=qsin`)。此模式必須重新編碼(非無損),4K 影片較耗時,建議搭配 NVIDIA 顯卡用 NVENC 加速。
|
- `transition: true` → 在每個剪接點加上**畫面淡出淡入**(`xfade`)與**聲音恆定功率交叉淡化**(`acrossfade`,`c1=qsin:c2=qsin`)。此模式必須重新編碼(非無損),4K 影片較耗時,建議搭配 NVIDIA 顯卡用 NVENC 加速。
|
||||||
|
|
||||||
|
### 關於音訊處理
|
||||||
|
|
||||||
|
音量增益、強制壓限、低通三項(命名參考 Adobe Premiere Pro)會在重新編碼時一併套用,處理順序為 **音量增益 → 低通 → 強制壓限**(壓限擺最後當煞車)。
|
||||||
|
由於需要重新編碼,這些音訊處理**僅在 `transition: true`(重編模式)時生效**;若 `transition: false`(無損快剪)則音訊維持原樣不處理。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 輸出結果
|
## 輸出結果
|
||||||
|
|||||||
+48
-2
@@ -128,6 +128,14 @@ VIDEO_TRANSITION = "fadeblack" # 畫面轉場 (FFmpeg xfade 類型):
|
|||||||
# 其它如 dissolve, smoothleft... 見 ffmpeg xfade 文件
|
# 其它如 dissolve, smoothleft... 見 ffmpeg xfade 文件
|
||||||
# 聲音固定用 acrossfade 等功率曲線(c1=qsin:c2=qsin)=恆定功率
|
# 聲音固定用 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"}
|
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) # 清掉暫存檔
|
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:
|
def has_audio(path: str) -> bool:
|
||||||
"""用 ffprobe 判斷影片是否有音訊軌。出錯時保守假設『有』。"""
|
"""用 ffprobe 判斷影片是否有音訊軌。出錯時保守假設『有』。"""
|
||||||
if shutil.which("ffprobe") is None:
|
if shutil.which("ffprobe") is None:
|
||||||
@@ -504,9 +528,17 @@ def ffmpeg_cut_concat_xfade(input_path: str, keeps: List[Tuple[float, float]],
|
|||||||
n = len(keeps)
|
n = len(keeps)
|
||||||
durs = [e - s for (s, e) in keeps]
|
durs = [e - s for (s, e) in keeps]
|
||||||
|
|
||||||
# 只有一段 → 沒有剪接點,不需轉場,直接無損切出
|
# 只有一段 → 沒有剪接點,不需轉場
|
||||||
if n == 1:
|
if n == 1:
|
||||||
s, e = keeps[0]
|
s, e = keeps[0]
|
||||||
|
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}",
|
run_cmd(["ffmpeg", "-y", "-ss", f"{s:.3f}", "-t", f"{e - s:.3f}",
|
||||||
"-i", input_path, "-c", "copy",
|
"-i", input_path, "-c", "copy",
|
||||||
"-avoid_negative_ts", "make_zero", output_path])
|
"-avoid_negative_ts", "make_zero", output_path])
|
||||||
@@ -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}]")
|
f"[{prev_a}][{j}:a]acrossfade=d={d:.3f}:c1=qsin:c2=qsin[{out}]")
|
||||||
prev_a = out
|
prev_a = out
|
||||||
alabel = prev_a
|
alabel = prev_a
|
||||||
|
# 串接完再做音量處理(音量增益 / 低通 / 強制壓限)
|
||||||
|
af = _audio_filter_chain()
|
||||||
|
if af:
|
||||||
|
parts.append(f"[{alabel}]" + ",".join(af) + "[aout]")
|
||||||
|
alabel = "aout"
|
||||||
|
|
||||||
filtergraph = ";".join(parts)
|
filtergraph = ";".join(parts)
|
||||||
|
|
||||||
@@ -876,11 +913,15 @@ def process_one(video_path: str, roi: Tuple[int, int, int, int],
|
|||||||
"removed": removed_total}
|
"removed": removed_total}
|
||||||
|
|
||||||
# 4. FFmpeg 切割 + 拼接
|
# 4. FFmpeg 切割 + 拼接
|
||||||
|
audio_proc = VOLUME_BOOST or HARD_LIMITER or LOWPASS
|
||||||
if TRANSITION and len(keeps) >= 1:
|
if TRANSITION and len(keeps) >= 1:
|
||||||
# 有轉場: xfade 淡出淡入 + acrossfade 恆定功率(需重新編碼)
|
# 有轉場: xfade 淡出淡入 + acrossfade 恆定功率 + 音量處理(需重新編碼)
|
||||||
ffmpeg_cut_concat_xfade(video_path, keeps, out_path,
|
ffmpeg_cut_concat_xfade(video_path, keeps, out_path,
|
||||||
use_gpu=args.gpu, tmp_parent=out_dir)
|
use_gpu=args.gpu, tmp_parent=out_dir)
|
||||||
else:
|
else:
|
||||||
|
if audio_proc:
|
||||||
|
print("[WARN] 音量處理(音量增益/強制壓限/低通)需重新編碼,"
|
||||||
|
"目前 transition=false 為無損模式,音訊將維持原樣不處理。")
|
||||||
# 無轉場: 維持無損快剪
|
# 無轉場: 維持無損快剪
|
||||||
ffmpeg_cut_concat(video_path, keeps, out_path, args.reencode,
|
ffmpeg_cut_concat(video_path, keeps, out_path, args.reencode,
|
||||||
tmp_parent=out_dir)
|
tmp_parent=out_dir)
|
||||||
@@ -910,6 +951,11 @@ _CONFIG_KEYS = {
|
|||||||
"transition": "TRANSITION",
|
"transition": "TRANSITION",
|
||||||
"transition_duration": "TRANSITION_DURATION",
|
"transition_duration": "TRANSITION_DURATION",
|
||||||
"video_transition": "VIDEO_TRANSITION",
|
"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",
|
"base_dir": "BASE_DIR",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,5 +20,10 @@
|
|||||||
"transition": true,
|
"transition": true,
|
||||||
"transition_duration": 0.5,
|
"transition_duration": 0.5,
|
||||||
"video_transition": "fadeblack",
|
"video_transition": "fadeblack",
|
||||||
|
"volume_boost": true,
|
||||||
|
"volume_boost_percent": 30,
|
||||||
|
"hard_limiter": true,
|
||||||
|
"lowpass": true,
|
||||||
|
"lowpass_hz": 15000,
|
||||||
"base_dir": "E:\\videos"
|
"base_dir": "E:\\videos"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user