feat(audio): ffmpeg production pass — loop/loudnorm builders, runner, build script
Pure arg builders (audio_ops) + thin runner (audio_run) + build_audio_media.py. Produces the 5 normalized soundtrack loops + a synthesized pink-noise bed under the gitignored sample_media/audio tree. 6 tests pass (white-noise integration runs against bundled imageio_ffmpeg). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
"""Unit tests for the pure audio ffmpeg arg builders (no ffmpeg run), plus an
|
||||
opt-in integration test that actually synthesizes a noise bed when ffmpeg is
|
||||
present."""
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from tools.pipeline.audio_ops import audio_loop_args, loudnorm_args, white_noise_args
|
||||
|
||||
|
||||
def test_loop_args_mirror_the_video_crossfade_recipe():
|
||||
args = audio_loop_args("in.mp3", "out.mp3", duration=30.0, overlap=2.0, ff="FF")
|
||||
assert args[0] == "FF"
|
||||
joined = " ".join(args)
|
||||
# tail crossfades over head, then concats the middle — the audio analogue of
|
||||
# crossfade_loop_args. Output length = duration - overlap.
|
||||
assert "acrossfade=d=2.0" in joined
|
||||
assert "atrim=0:2.0" in joined # head
|
||||
assert "atrim=28.0:30.0" in joined # tail (d-overlap : d)
|
||||
assert "concat=n=2:v=0:a=1" in joined
|
||||
assert "-map" in args and "[out]" in args
|
||||
assert "-q:a" in args and "4" in args
|
||||
|
||||
|
||||
def test_loop_args_reject_overlap_past_half():
|
||||
with pytest.raises(ValueError):
|
||||
audio_loop_args("in.mp3", "out.mp3", duration=10.0, overlap=5.0)
|
||||
|
||||
|
||||
def test_loudnorm_args_carry_the_locked_targets():
|
||||
args = loudnorm_args("in.mp3", "out.mp3", ff="FF")
|
||||
joined = " ".join(args)
|
||||
assert "loudnorm=I=-18.0:TP=-1.5:LRA=11.0" in joined
|
||||
assert args[0] == "FF" and args[-1] == "out.mp3"
|
||||
|
||||
|
||||
def test_white_noise_args_are_pink_and_clean():
|
||||
args = white_noise_args("noise.mp3", duration=60.0, color="pink", ff="FF")
|
||||
joined = " ".join(args)
|
||||
assert "anoisesrc=color=pink" in joined
|
||||
assert "-t" in args and "60.0" in args
|
||||
assert args[-1] == "noise.mp3"
|
||||
|
||||
|
||||
def test_white_noise_rejects_unknown_color():
|
||||
with pytest.raises(ValueError):
|
||||
white_noise_args("noise.mp3", color="ultraviolet")
|
||||
|
||||
|
||||
def _have_ffmpeg() -> bool:
|
||||
if shutil.which("ffmpeg"):
|
||||
return True
|
||||
try:
|
||||
import imageio_ffmpeg # noqa: F401
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _have_ffmpeg(), reason="no ffmpeg available")
|
||||
def test_generate_white_noise_writes_a_playable_loop(tmp_path):
|
||||
from tools.pipeline.audio_run import generate_white_noise
|
||||
out = generate_white_noise(tmp_path / "pink.mp3", duration=2.0)
|
||||
assert out.exists() and out.stat().st_size > 0
|
||||
Reference in New Issue
Block a user