diff --git a/gateway/platforms/signal.py b/gateway/platforms/signal.py index df9d07b4f71..7b81b2a957a 100644 --- a/gateway/platforms/signal.py +++ b/gateway/platforms/signal.py @@ -801,7 +801,8 @@ class SignalAdapter(BasePlatformAdapter): # require AAC to be muxed into an MP4 container. Remux losslessly # with ``ffmpeg -c:a copy`` so the cached file is a normal .m4a. # No re-encode, sub-100ms on a Pi 5. Graceful no-op if ffmpeg is - # absent; the STT layer has its own sniff-and-remux fallback. + # absent: the raw ADTS file is cached as-is and STT may reject it + # (there is no downstream sniff-and-remux fallback). if ext == ".aac": remuxed: Optional[Tuple[bytes, str]] = await asyncio.to_thread(_remux_aac_to_m4a, raw_data) if remuxed is not None: @@ -904,7 +905,7 @@ class SignalAdapter(BasePlatformAdapter): # ------------------------------------------------------------------ @staticmethod - def _markdown_to_signal(text: str) -> tuple: + def _markdown_to_signal(text: str) -> tuple[str, list[str]]: """Backward-compatible wrapper around shared Signal formatting helper.""" return markdown_to_signal(text) diff --git a/tests/gateway/test_signal.py b/tests/gateway/test_signal.py index b55c4215ecb..e79ee7a8591 100644 --- a/tests/gateway/test_signal.py +++ b/tests/gateway/test_signal.py @@ -201,20 +201,45 @@ class TestSignalHelpers: assert _is_audio_ext(ext) is True def test_remux_aac_to_m4a_round_trip(self): - """Real ADTS file from the audio cache remuxes to a valid MP4 container. + """A real ADTS AAC stream remuxes to a valid MP4 (.m4a) container. - Round-trips the actual Android voice note that triggered the - bug report — proves the end-to-end fix. + Generates a short ADTS AAC sample with ffmpeg at runtime so the + end-to-end remux path actually exercises in CI (skipped only when + ffmpeg is unavailable), rather than depending on a machine-specific + file. """ - import os import shutil + import subprocess + import tempfile from gateway.platforms.signal import _remux_aac_to_m4a - src = "/home/pi/.hermes/audio_cache/audio_fcfc38390b47.mp3" - if not os.path.exists(src) or not shutil.which("ffmpeg"): + + ffmpeg = shutil.which("ffmpeg") + if not ffmpeg: import pytest - pytest.skip("ffmpeg or source file not available in this env") - with open(src, "rb") as f: - aac_data = f.read() + pytest.skip("ffmpeg not available in this env") + + # Synthesize 0.5s of silence encoded as raw ADTS AAC. + with tempfile.NamedTemporaryFile(suffix=".aac", delete=False) as tmp: + adts_path = tmp.name + try: + gen = subprocess.run( + [ffmpeg, "-y", "-loglevel", "error", "-f", "lavfi", + "-i", "anullsrc=r=44100:cl=mono", "-t", "0.5", + "-c:a", "aac", "-f", "adts", adts_path], + capture_output=True, timeout=30, + ) + if gen.returncode != 0: + import pytest + pytest.skip("ffmpeg could not produce an ADTS AAC sample") + with open(adts_path, "rb") as f: + aac_data = f.read() + finally: + try: + import os + os.unlink(adts_path) + except OSError: + pass + result = _remux_aac_to_m4a(aac_data) assert result is not None m4a_bytes, ext = result