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