Files
SoundGame/tools/scan_clicks.py
T

77 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""爆音资产排查(R6):扫描 6 游戏全部 .ogg。
检测项:
1. 峰值 > -1dBFS(有损编码过冲/归一不足)
2. 首尾 5ms 内电平突变(无淡入淡出 → 爆豆声)。loop 类(bgm/ 与 sfx/amb_*)按
首尾交叉淡化工艺制造,边界连续是特性,只做 DC/峰值检查,不报爆豆、不修。
3. 直流偏置(|DC| > 0.001)
输出 JSON 行到 stdout 供汇总;--fix 模式见 fix_clicks.py 逻辑(本文件只扫)。
"""
import os
import sys
import numpy as np
import soundfile as sf
ROOT = "/home/xsl/SoundGame/web"
GAMES = ["storyline", "escape", "simon", "rhythm", "space", "rpg"]
EDGE_MS = 5
DC_THRESH = 0.001
PEAK_THRESH = 10 ** (-1.0 / 20) # -1dBFS
def is_loop(relpath):
parts = relpath.split(os.sep)
return "/bgm/" in "/" + relpath or os.path.basename(relpath).startswith("amb_")
def scan(path):
y, sr = sf.read(path, always_2d=True)
mono = y.mean(axis=1)
peak = float(np.max(np.abs(mono)))
dc = float(np.mean(mono))
n5 = max(1, int(sr * EDGE_MS / 1000))
n1 = max(1, int(sr * 1 / 1000))
n50 = max(1, int(sr * 50 / 1000))
head = float(np.max(np.abs(mono[:n5])))
tail = float(np.max(np.abs(mono[-n5:])))
# 爆豆判定(突变而非渐变):首/末 1ms 内的最大幅值 与 相邻 50ms 窗口峰值之比。
# 正常淡入(8ms cos²)首 1ms 幅值 < 局部峰值 15%;硬边/步进则趋近 1。
loc_h = float(np.max(np.abs(mono[:n50])))
loc_t = float(np.max(np.abs(mono[-n50:])))
FLOOR = 0.01 # 局部窗口低于 -40dB 视为静音区,不判爆豆(编解码底噪残余)
step_head = loc_h > FLOOR and float(np.max(np.abs(mono[:n1]))) > 0.15 * loc_h
step_tail = loc_t > FLOOR and float(np.max(np.abs(mono[-n1:]))) > 0.15 * loc_t
return {
"path": os.path.relpath(path, ROOT),
"loop": is_loop(os.path.relpath(path, ROOT)),
"peak_db": round(20 * np.log10(max(peak, 1e-9)), 2),
"dc": round(dc, 5),
"head5ms_db": round(20 * np.log10(max(head, 1e-9)), 2),
"tail5ms_db": round(20 * np.log10(max(tail, 1e-9)), 2),
"hot_peak": peak > PEAK_THRESH,
"dc_bad": abs(dc) > DC_THRESH,
"click_head": step_head,
"click_tail": step_tail,
}
def main():
results = []
for g in GAMES:
base = os.path.join(ROOT, g, "audio")
for dirpath, _d, files in os.walk(base):
for f in sorted(files):
if f.endswith(".ogg"):
results.append(scan(os.path.join(dirpath, f)))
import json
for r in results:
print(json.dumps(r, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main())