Files

85 lines
2.8 KiB
Python
Raw Permalink Normal View History

2026-09-14 01:57:00 +09:00
#!/usr/bin/env python3
"""Generate the audio fixtures `webapi_smoke.py` expects.
Synthesised rather than downloaded so the smoke test has no network dependency
and no external tools — `soundfile` and `numpy` come in with `librosa`, so this
runs in the container image as-is (no ffmpeg needed).
sine440.wav 12 s, 440 Hz, stereo, -12 dBFS — a known-tone reference
pinknoise.wav 8 s, pink noise, stereo — broadband, exercises the STFT
tagged.mp3 a tagged file — only if ffmpeg is available
Usage: python3 scripts/make_fixtures.py [output_dir] (default /tmp/ujm-test)
"""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
import numpy as np
import soundfile as sf
SR = 44100
def sine(path: Path, seconds: float = 12.0, hz: float = 440.0,
dbfs: float = -12.0) -> None:
"""A tone at a known level. Peak is exactly 10**(dbfs/20) before encoding."""
t = np.arange(int(seconds * SR)) / SR
mono = (10 ** (dbfs / 20)) * np.sin(2 * np.pi * hz * t)
sf.write(path, np.stack([mono, mono], axis=1), SR, subtype="PCM_16")
def pink(path: Path, seconds: float = 8.0, peak: float = 0.25) -> None:
"""Pink noise via 1/sqrt(f) shaping, normalised to a known peak."""
rng = np.random.default_rng(7)
n = int(seconds * SR)
spectrum = np.fft.rfft(rng.standard_normal(n))
freqs = np.fft.rfftfreq(n, 1 / SR)
freqs[0] = freqs[1] # avoid dividing by zero at DC
shaped = np.fft.irfft(spectrum / np.sqrt(freqs), n)
mono = peak * shaped / np.max(np.abs(shaped))
sf.write(path, np.stack([mono, mono], axis=1), SR, subtype="PCM_16")
def tagged_mp3(path: Path) -> bool:
"""An MP3 with ID3 tags, so the tag-reading path is exercised.
Genuinely optional: MP3 encoding is the one thing numpy cannot do, so this
needs ffmpeg and is skipped when it is absent. The suite's tag assertions
adapt — see `webapi_smoke.py`.
"""
if shutil.which("ffmpeg") is None:
return False
subprocess.run(
["ffmpeg", "-y", "-loglevel", "error",
"-f", "lavfi", "-i", "sine=frequency=330:duration=6:sample_rate=44100",
"-metadata", "artist=Test Artist", "-metadata", "title=Test Title",
"-codec:a", "libmp3lame", "-q:a", "2", str(path)],
check=True,
)
return True
def main() -> int:
out = Path(sys.argv[1] if len(sys.argv) > 1 else "/tmp/ujm-test")
out.mkdir(parents=True, exist_ok=True)
sine(out / "sine440.wav")
pink(out / "pinknoise.wav")
mp3 = tagged_mp3(out / "テスト.mp3")
for f in sorted(out.iterdir()):
print(f" {f.name} {f.stat().st_size / 1024:.0f} KiB")
if not mp3:
print("\nnote: no ffmpeg, so no tagged MP3 fixture was written "
"(tag assertions will be skipped)")
return 0
if __name__ == "__main__":
raise SystemExit(main())