#!/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())