#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""纯 Python 音频包络分析（无 numpy）：按 50ms 窗口算 RMS，
输出分贝包络与「显著能量变化点」，用于交叉验证歌曲段落边界。"""
import wave
import array
import sys
import os

WAV = sys.argv[1] if len(sys.argv) > 1 else "05-音频/说书人-音源.wav"
WIN_MS = 50

w = wave.open(WAV, "rb")
nch, sw, sr, nframes = w.getnchannels(), w.getsampwidth(), w.getframerate(), w.getnframes()
print(f"声道={nch} 位深={sw*8} 采样率={sr} 帧数={nframes} 时长={nframes/sr:.3f}s")

step = sr * WIN_MS // 1000          # 每窗口采样点数
w.setpos(0)
raw = w.readframes(nframes)
samples = array.array("h")
samples.frombytes(raw)
if sys.byteorder == "big":
    samples.byteswap()

# 只取左声道，减少一半计算量
mono = samples[0::nch]
del samples

env = []
for i in range(0, len(mono) - step, step):
    chunk = mono[i:i + step]
    acc = 0
    for v in chunk:
        acc += v * v
    rms = (acc / len(chunk)) ** 0.5
    env.append(rms)

import math
db = [20 * math.log10(r / 32768.0 + 1e-9) for r in env]
print(f"窗口数={len(db)}  整体中位={sorted(db)[len(db)//2]:.1f} dB")

# 平滑（3 点滑动）
sm = [sum(db[max(0, i-1):i+2]) / len(db[max(0, i-1):i+2]) for i in range(len(db))]

# 找显著变化点：与前后 2s（40 窗口）均值差 > 4 dB
W = 40
marks = []
for i in range(W, len(sm) - W):
    before = sum(sm[i-W:i]) / W
    after = sum(sm[i:i+W]) / W
    if abs(after - before) > 4.0:
        marks.append((i * WIN_MS / 1000.0, before, after, after - before))

# 合并 2s 内的相邻标记
merged = []
for t, b, a, d in marks:
    if merged and t - merged[-1][0] < 2.0:
        continue
    merged.append((t, b, a, d))

print(f"\n显著能量变化点（合并后 {len(merged)} 处）:")
for t, b, a, d in merged:
    print(f"  {int(t//60)}:{t%60:05.2f}   {b:6.1f} -> {a:6.1f} dB  ({d:+.1f})")

# 每 5 秒打印一次包络，便于看结构
print("\n包络（每 5s）:")
line = []
for i in range(0, len(sm), 100):
    t = i * WIN_MS / 1000.0
    line.append(f"{int(t//60)}:{t%60:04.1f}={sm[i]:6.1f}")
    if len(line) == 6:
        print("  " + "  ".join(line)); line = []
if line:
    print("  " + "  ".join(line))
