import numpy as np import soundfile as sf from conftest import speechish from producer import io as pio from producer.cli import _apply_args, build_parser, process_one from producer.config import Options def _mk_wav(tmp_path, name="in.wav", stereo=False): x = speechish(3.0, level_dbfs=-30.0) if stereo: data = np.stack([x, x * 0.5], axis=1) sf.write(str(tmp_path / name), data, 44100, subtype="PCM_16") else: sf.write(str(tmp_path / name), x, 44100, subtype="PCM_16") return tmp_path / name def _opts(**kw): opts = Options() opts.denoise = "off" opts.enhance = "off" for k, v in kw.items(): setattr(opts, k, v) return opts def test_decode_stereo_mixdown(tmp_path): p = _mk_wav(tmp_path, "st.wav", stereo=True) x, sr = pio.decode(p) assert sr == 44100 assert x.dtype.name == "float32" assert x.ndim == 1 def test_encode_wav_bitdepths(tmp_path): x = speechish(2.0, level_dbfs=-20.0) for depth in (16, 24, 32): out = tmp_path / f"o{depth}.wav" pio.encode(x, 44100, out, "wav", depth) y, sr = pio.decode(out) assert sr == 44100 assert ( abs( float(np.sqrt(np.mean(y.astype(np.float64) ** 2))) - float(np.sqrt(np.mean(x.astype(np.float64) ** 2))) ) < 1e-3 ) def test_encode_flac(tmp_path): x = speechish(2.0, level_dbfs=-20.0) out = tmp_path / "o.flac" pio.encode(x, 44100, out, "flac", 24) y, sr = pio.decode(out) assert sr == 44100 assert np.corrcoef(x, y)[0, 1] > 0.999 def test_mp3_roundtrip(tmp_path): import pytest as _pt if not pio.ffmpeg_available(): _pt.skip("ffmpeg missing") x = speechish(4.0, level_dbfs=-20.0) out = tmp_path / "o.mp3" pio.encode(x, 44100, out, "mp3", 16) y, sr = pio.decode(out) assert sr == 44100 from producer import meters assert abs(meters.rms_db(x) - meters.rms_db(y)) < 0.7 def test_decode_via_ffmpeg_fallback(tmp_path): import pytest as _pt if not pio.ffmpeg_available(): _pt.skip("ffmpeg missing") x = speechish(4.0, level_dbfs=-20.0) out = tmp_path / "o.mp3" pio.encode(x, 44100, out, "mp3", 16) y, sr = pio.decode(out) assert sr == 44100 assert y.size > 0 def test_process_one_end_to_end(tmp_path, capsys): import json from producer import meters inp = _mk_wav(tmp_path, "e2e.wav") out = tmp_path / "e2e_master.wav" rc = process_one(inp, _opts(report=True), single=True) assert rc == out assert out.exists() rep_path = out.with_name("e2e_master.report.json") assert rep_path.exists() rep = json.loads(rep_path.read_text()) y, sr = pio.decode(out) assert abs(meters.rms_db(y) + 20.0) < 0.6 assert meters.true_peak_db(y, sr) <= -2.9 assert rep["after"]["rms_db"] != 0 def test_dry_run_listing(tmp_path, capsys): from producer.cli import main inp = _mk_wav(tmp_path, "dry.wav") rc = main([str(inp), "--dry-run", "--denoise", "off"]) assert rc == 0 out = capsys.readouterr().out assert "denoise" in out and "dsp" in out and "levelling" in out def test_arg_parsing_precedence(): parser = build_parser() args = parser.parse_args( ["in.wav", "--profile", "podcast", "--warmth", "0.1", "--ceiling", "-2.0"] ) opts = Options() _apply_args(opts, args) assert opts.profile == "podcast" assert opts.loudness_mode() == "lufs" assert opts.strengths["warmth"] == 0.1 assert opts.ceiling() == -2.0 assert opts.strengths["air"] is None def test_radio_profile_has_tuned_defaults(): from producer import pipeline opts = Options(profile="radio") assert opts.eff("tape") > 0 and opts.eff("soothe") > 0 assert opts.loudness_mode() == "lufs" stages = pipeline.build_stages(opts) assert [st.name for st in stages] == ["denoise", "enhance", "dsp", "levelling"] assert opts.denoise_strength is not None def test_tape_and_soothe_flags_override(): args = build_parser().parse_args( ["in.wav", "--profile", "radio", "--tape", "0.4", "--soothe", "0.7"] ) opts = Options() _apply_args(opts, args) assert opts.profile == "radio" assert opts.strengths["tape"] == 0.4 assert opts.strengths["soothe"] == 0.7