"""Actual AAC/H.264 remux and audio codec metadata, without a camera.""" from __future__ import annotations import io import math import os import shutil import struct import subprocess import tempfile import unittest from pathlib import Path import av import imageio_ffmpeg import blink_audio class AudioRemuxTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.temp = tempfile.TemporaryDirectory(prefix="blink-audio-test-") cls.root = Path(cls.temp.name) cls.ffmpeg = shutil.which("ffmpeg") or imageio_ffmpeg.get_ffmpeg_exe() cls.source = cls.root / "source.ts" cls.run_ffmpeg([ "-f", "lavfi", "-i", "testsrc2=size=320x180:rate=10:duration=4", "-f", "lavfi", "-i", "sine=frequency=1000:sample_rate=16000:duration=4", "-f", "lavfi", "-i", "sine=frequency=500:sample_rate=16000:duration=4", "-map", "0:v:0", "-map", "1:a:0", "-map", "2:a:0", "-c:v", "libx264", "-g", "5", "-preset", "ultrafast", "-c:a", "aac", "-b:a", "32k", "-ac", "1", "-f", "mpegts", str(cls.source)]) cls.video_only = cls.root / "video.ts" cls.run_ffmpeg(["-i", str(cls.source), "-map", "0:v:0", "-c", "copy", "-f", "mpegts", str(cls.video_only)]) @classmethod def tearDownClass(cls): cls.temp.cleanup() @classmethod def run_ffmpeg(cls, args): env = dict(os.environ, GCONV_PATH=os.environ.get("GCONV_PATH", "/dev/null")) result = subprocess.run([cls.ffmpeg, "-hide_banner", "-loglevel", "error", *args], capture_output=True, timeout=20, env=env) if result.returncode: detail = result.stderr.decode("utf-8", "replace")[-2000:].strip() raise AssertionError(f"FFmpeg exit code {result.returncode}: {detail}" if detail else f"FFmpeg failed with exit code {result.returncode}") def remux(self, enabled, track=0, source=None): target = self.root / f"output-{enabled}-{track}-{source is not None}.mp4" self.run_ffmpeg(["-y", "-i", str(source or self.source), "-map", "0:v:0", "-c:v", "copy", *blink_audio.stream_options({ "camera_audio_enabled": enabled, "camera_audio_track": track}), "-f", "mp4", "-movflags", blink_audio.fragment_flags({"camera_audio_enabled": enabled}), str(target)]) return target def test_selected_primary_and_alternate_aac_channels_are_saved(self): for track, frequency in ((0, 1000), (1, 500)): with self.subTest(track=track): target = self.remux(True, track) self.assertEqual(blink_audio.aac_mime_codec_from_moov(target.read_bytes()), "mp4a.40.2") with av.open(str(target)) as container: self.assertEqual([s.type for s in container.streams], ["video", "audio"]) frames = list(container.decode(audio=0)) samples = [sample for frame in frames[3:8] for (sample,) in struct.iter_unpack( "