Files

82 lines
2.9 KiB
Python

import os
import numpy as np
import soundfile as sf
import pyloudnorm as pyln
def analyze_game_sfx(wav_dir: str) -> dict:
"""
批量分析游戏音效的听感响度(LUFS)。
对于短音效,优先使用 Momentary Loudness;若文件过短则回退到 K-weighted RMS。
"""
results = {}
if not os.path.exists(wav_dir):
print(f"错误: 目录不存在 -> {wav_dir}")
return results
wav_files = [f for f in os.listdir(wav_dir) if f.lower().endswith('.wav')]
print(f"找到 {len(wav_files)} 个 WAV 文件,开始分析...\n")
for filename in sorted(wav_files):
filepath = os.path.join(wav_dir, filename)
try:
data, sr = sf.read(filepath)
# 确保为二维数组 (samples, channels)
if data.ndim == 1:
data = data.reshape(-1, 1)
duration = len(data) / sr
# --- 核心响度计算 ---
# ITU-R BS.1770 要求至少 ~400ms 才能准确计算 Integrated Loudness
# 游戏音效通常很短,我们采用以下策略:
meter = pyln.Meter(sr, block_size=duration) # 400ms block
# if duration >= 0.4:
# 使用 Momentary Loudness (无门限,适合短音效)
loudness = meter.integrated_loudness(data)
# else:
# # 极短音效:手动 K-weighting 后计算 RMS → 转为 LUFS
# # pyloudnorm 内部有 K-weight filter,这里用简化方式
# filtered = meter._filter_k_weighting(data.T).T
# rms = np.sqrt(np.mean(filtered ** 2))
# # LUFS ≈ -0.691 + 10*log10(rms^2),参考 BS.1770 公式
# loudness = -0.691 + 10 * np.log10(max(rms ** 2, 1e-12))
# method = "K-weighted RMS"
# 静音检测
if np.max(np.abs(data)) < 1e-6:
loudness = -np.inf
results[filename] = {
"loudness_lufs": round(loudness, 2),
"duration_sec": round(duration, 4),
"sample_rate": sr,
"channels": data.shape[1],
}
except Exception as e:
results[filename] = {"error": str(e)}
print(f" ⚠ 跳过 {filename}: {e}")
return results
if __name__ == "__main__":
TARGET_DIR = "vanilla_assets/wav/"
report = analyze_game_sfx(TARGET_DIR)
# 打印结果字典
print("\n===== 分析结果 =====")
print("文件名 | 响度(LUFS) | 时长(s) | 采样率")
for name, info in report.items():
if "error" in info:
print(f"{name}: 错误 - {info['error']}")
else:
print(f"{name}: {info['loudness_lufs']:>7.2f} LUFS | "
f"{info['duration_sec']:.3f}s | {info['sample_rate']}Hz")
# 也输出原始字典方便程序化使用
print("\n===== Raw Dict =====")
print(report)