patristic-be / tests /test_audiobook_encode.py
Mario33333's picture
deploy: reader-access security hardening (feature/audiobook@06a5ed8)
7c2d250 verified
Raw
History Blame Contribute Delete
6.56 kB
"""Regression tests for encode_waveform int16 fix.
The bug that shipped: passing an un-normalized int16 waveform to soundfile
caused values at Β±32768 β†’ full-scale clipping noise. The fix in encode.py
detects integer dtype and divides by ``np.iinfo(dtype).max`` before encoding.
These tests verify:
1. ``int16_sine`` β€” a known 1-second sine at 1 kHz normalizes correctly: the
decoded ogg waveform stays within [-1, 1] and has < 5 % clipped samples.
2. ``float_passthrough`` β€” a float32 waveform already in [-1, 1] encodes and
decodes without corruption (max amplitude ≀ 1.01, clipped% < 5 %).
"""
from __future__ import annotations
import io
import math
import numpy as np
import pytest
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _sine_int16(freq_hz: int, duration_s: float, sample_rate: int) -> np.ndarray:
"""Pure sine at *freq_hz*, scaled to Β±30 000 in int16 (typical TTS output)."""
t = np.linspace(0, duration_s, int(sample_rate * duration_s), endpoint=False)
wave = np.sin(2 * math.pi * freq_hz * t) * 30_000
return wave.astype(np.int16)
def _sine_float(freq_hz: int, duration_s: float, sample_rate: int) -> np.ndarray:
"""Pure sine at *freq_hz* as float32 in [-0.9, 0.9]."""
t = np.linspace(0, duration_s, int(sample_rate * duration_s), endpoint=False)
return (np.sin(2 * math.pi * freq_hz * t) * 0.9).astype(np.float32)
def _decode_ogg(audio_bytes: bytes) -> tuple[np.ndarray, int]:
"""Decode an Ogg file and return (waveform_float32, sample_rate)."""
import soundfile as sf
buf = io.BytesIO(audio_bytes)
data, sr = sf.read(buf, dtype="float32")
return data, sr
def _clipped_pct(data: np.ndarray, threshold: float = 0.99) -> float:
"""Fraction of samples whose absolute value exceeds *threshold*."""
if data.size == 0:
return 0.0
return float(np.sum(np.abs(data) > threshold)) / data.size
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestEncodeWaveformInt16Regression:
"""int16 PCM input must be normalized before encoding (the shipped bug)."""
def test_int16_sine_max_amplitude_within_range(self):
"""Decoded ogg amplitude ≀ 1.01 β€” proves normalization happened."""
from src.lib.audiobook.encode import encode_waveform
pcm = _sine_int16(freq_hz=1_000, duration_s=1.0, sample_rate=16_000)
assert pcm.dtype == np.int16
audio_bytes, mime, duration_ms = encode_waveform(pcm, 16_000)
# Must produce an ogg (Opus or Vorbis); a WAV fallback still passes
# but is unexpected on a modern libsndfile.
assert mime in ("audio/ogg", "audio/wav"), f"unexpected mime: {mime}"
assert len(audio_bytes) > 0
assert duration_ms > 0
decoded, _ = _decode_ogg(audio_bytes)
max_amp = float(np.max(np.abs(decoded)))
assert max_amp <= 1.01, (
f"max amplitude {max_amp:.4f} > 1.01 β€” int16 normalization missing "
f"(pre-fix: raw int16 cast to float gives Β±32768 β†’ full-scale clip)"
)
def test_int16_sine_clipped_samples_below_5_pct(self):
"""< 5 % of samples may be clipped β€” rules out the full-scale-noise failure."""
from src.lib.audiobook.encode import encode_waveform
pcm = _sine_int16(freq_hz=1_000, duration_s=1.0, sample_rate=16_000)
audio_bytes, _mime, _dur = encode_waveform(pcm, 16_000)
decoded, _ = _decode_ogg(audio_bytes)
pct = _clipped_pct(decoded)
assert pct < 0.05, (
f"{pct*100:.1f}% of samples clipped (β‰₯ 5 %) β€” "
f"this is the shipped 'random noise' bug: un-normalized int16 "
f"fills every sample at full scale."
)
def test_int16_duration_is_reasonable(self):
"""duration_ms returned by encode_waveform is within 10 % of the true
value β€” ensures the frame count / sample_rate math is correct."""
from src.lib.audiobook.encode import encode_waveform
sample_rate = 22_050
duration_s = 2.0
pcm = _sine_int16(1_000, duration_s, sample_rate)
_audio, _mime, duration_ms = encode_waveform(pcm, sample_rate)
expected_ms = int(round(duration_s * 1000))
assert abs(duration_ms - expected_ms) <= expected_ms * 0.10, (
f"duration_ms={duration_ms} is more than 10% off from expected {expected_ms}"
)
class TestEncodeWaveformFloatPassthrough:
"""Float32 waveform already in [-1, 1] must encode cleanly (no corruption)."""
def test_float_sine_max_amplitude_within_range(self):
"""Float path: decoded amplitude ≀ 1.01."""
from src.lib.audiobook.encode import encode_waveform
wave = _sine_float(freq_hz=440, duration_s=0.5, sample_rate=24_000)
assert wave.dtype == np.float32
audio_bytes, mime, duration_ms = encode_waveform(wave, 24_000)
assert mime in ("audio/ogg", "audio/wav")
assert len(audio_bytes) > 0
decoded, _ = _decode_ogg(audio_bytes)
max_amp = float(np.max(np.abs(decoded)))
assert max_amp <= 1.01, f"float passthrough corrupted amplitude: {max_amp:.4f}"
def test_float_sine_clipped_samples_below_5_pct(self):
"""Float path: < 5 % clipped."""
from src.lib.audiobook.encode import encode_waveform
wave = _sine_float(freq_hz=440, duration_s=0.5, sample_rate=24_000)
audio_bytes, _mime, _dur = encode_waveform(wave, 24_000)
decoded, _ = _decode_ogg(audio_bytes)
pct = _clipped_pct(decoded)
assert pct < 0.05, f"{pct*100:.1f}% of float-path samples clipped"
def test_float_zero_signal_stays_silent(self):
"""All-zeros float input β†’ all-zeros output (silence, not noise)."""
from src.lib.audiobook.encode import encode_waveform
silence = np.zeros(16_000, dtype=np.float32)
audio_bytes, _mime, _dur = encode_waveform(silence, 16_000)
decoded, _ = _decode_ogg(audio_bytes)
max_amp = float(np.max(np.abs(decoded)))
# Codec quantization noise is tiny; well below 1 % of full scale.
assert max_amp < 0.01, f"silence encoded to noise: max_amp={max_amp:.6f}"