1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| import json import re import subprocess from pathlib import Path
TARGET_I = -14 TARGET_TP = -1.5 TARGET_LRA = 12
FFMPEG = "ffmpeg"
def run(cmd): p = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", errors="replace", ) return p.returncode, p.stdout, p.stderr
def extract_loudnorm_json(stderr_text: str) -> dict: if not stderr_text: raise RuntimeError("ffmpeg 没有返回 stderr 文本(可能被解码错误吞掉了)。")
m = re.search(r'(\{\s*"input_i"[\s\S]*?\})', stderr_text) if not m: tail = stderr_text[-2000:] raise RuntimeError(f"没找到 loudnorm JSON 输出。stderr 尾部如下:\n{tail}")
return json.loads(m.group(1))
def normalize_file(inp: Path, outp: Path): outp.parent.mkdir(parents=True, exist_ok=True)
cmd1 = [ FFMPEG, "-hide_banner", "-nostats", "-y", "-i", str(inp), "-af", f"loudnorm=I={TARGET_I}:TP={TARGET_TP}:LRA={TARGET_LRA}:print_format=json", "-f", "null", "NUL" ] rc, _, err = run(cmd1) if rc != 0: raise RuntimeError(f"Pass1 失败:{inp}\n{err}")
info = extract_loudnorm_json(err)
cmd2 = [ FFMPEG, "-hide_banner", "-nostats", "-y", "-i", str(inp), "-af", ( "loudnorm=I={I}:TP={TP}:LRA={LRA}:" "measured_I={mI}:measured_TP={mTP}:measured_LRA={mLRA}:" "measured_thresh={mTH}:offset={off}:linear=true" ).format( I=TARGET_I, TP=TARGET_TP, LRA=TARGET_LRA, mI=info["input_i"], mTP=info["input_tp"], mLRA=info["input_lra"], mTH=info["input_thresh"], off=info["target_offset"] ), "-c:a", "libmp3lame", "-q:a", "2", str(outp) ] rc, _, err2 = run(cmd2) if rc != 0: raise RuntimeError(f"Pass2 失败:{inp}\n{err2}")
def main(): in_dir = Path("MP3") out_dir = Path("test")
mp3s = sorted(in_dir.rglob("*.mp3")) if not mp3s: print("没找到 mp3:请把文件放到 input_mp3/ 下 🙂") return
for f in mp3s: rel = f.relative_to(in_dir) outp = out_dir / rel print(f"🔧 {rel}") normalize_file(f, outp)
print(f"✅ 完成!输出在:{out_dir}")
if __name__ == "__main__": main()
|