basyx commited on
Commit
5da5be4
·
verified ·
1 Parent(s): 440715a

Test additive video audio mixing

Browse files
Files changed (1) hide show
  1. tests/test_mix_audio.py +89 -0
tests/test_mix_audio.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from app.core.exceptions import InputError
8
+ from app.models.media import InputMedia, MediaSource
9
+ from app.operations.mix_audio import add_audio
10
+
11
+
12
+ def _media(path: Path, filename: str, mime: str) -> InputMedia:
13
+ path.write_bytes(b"input")
14
+ return InputMedia(
15
+ source=MediaSource.MULTIPART,
16
+ filename=filename,
17
+ mime_type=mime,
18
+ temp_path=path,
19
+ size=path.stat().st_size,
20
+ )
21
+
22
+
23
+ class FakeFFmpeg:
24
+ def __init__(self) -> None:
25
+ self.args: list[str | Path] = []
26
+
27
+ async def run(self, args, operation: str) -> None:
28
+ self.args = list(args)
29
+ output = Path(args[-1])
30
+ output.parent.mkdir(parents=True, exist_ok=True)
31
+ output.write_bytes(b"output")
32
+
33
+
34
+ @pytest.mark.asyncio
35
+ async def test_add_audio_preserves_video_and_mixes_sfx(tmp_path: Path) -> None:
36
+ video = _media(tmp_path / "video.mp4", "video.mp4", "video/mp4")
37
+ sfx = _media(tmp_path / "impact.wav", "impact.wav", "audio/wav")
38
+ ffmpeg = FakeFFmpeg()
39
+
40
+ result = await add_audio(
41
+ ffmpeg,
42
+ [video, sfx],
43
+ {"start": 2.5, "volume": 0.4},
44
+ tmp_path / "out",
45
+ )
46
+
47
+ assert result.path == tmp_path / "out" / "audio-mix.mp4"
48
+ command = [str(value) for value in ffmpeg.args]
49
+ assert "-map" in command
50
+ assert "0:v:0" in command
51
+ assert "[mixed]" in command
52
+ assert "-c:v" in command
53
+ assert command[command.index("-c:v") + 1] == "copy"
54
+ filter_complex = command[command.index("-filter_complex") + 1]
55
+ assert "[1:a]volume=0.4,adelay=2500:all=1[a1]" in filter_complex
56
+ assert "amix=inputs=2:duration=first:dropout_transition=0:normalize=0[mixed]" in filter_complex
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_add_audio_supports_multiple_sfx_tracks(tmp_path: Path) -> None:
61
+ video = _media(tmp_path / "video.mp4", "video.mp4", "video/mp4")
62
+ sfx1 = _media(tmp_path / "one.wav", "one.wav", "audio/wav")
63
+ sfx2 = _media(tmp_path / "two.wav", "two.wav", "audio/wav")
64
+ ffmpeg = FakeFFmpeg()
65
+
66
+ await add_audio(
67
+ ffmpeg,
68
+ [video, sfx1, sfx2],
69
+ {"audio_tracks": '[{"start": 1, "volume": 0.5}, {"start": 4.25, "volume": 0.25}]'},
70
+ tmp_path / "out",
71
+ )
72
+
73
+ filter_complex = str(ffmpeg.args[ffmpeg.args.index("-filter_complex") + 1])
74
+ assert "[1:a]volume=0.5,adelay=1000:all=1[a1]" in filter_complex
75
+ assert "[2:a]volume=0.25,adelay=4250:all=1[a2]" in filter_complex
76
+ assert "amix=inputs=3:duration=first:dropout_transition=0:normalize=0[mixed]" in filter_complex
77
+
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_add_audio_rejects_track_count_mismatch(tmp_path: Path) -> None:
81
+ video = _media(tmp_path / "video.mp4", "video.mp4", "video/mp4")
82
+ sfx = _media(tmp_path / "one.wav", "one.wav", "audio/wav")
83
+ with pytest.raises(InputError, match="one object for each"):
84
+ await add_audio(
85
+ FakeFFmpeg(),
86
+ [video, sfx],
87
+ {"audio_tracks": '[{"start": 1}, {"start": 2}]'},
88
+ tmp_path / "out",
89
+ )