Files
hermes-agent/tests/gateway/test_ogg_opus_transcode_routing.py
T
teknium1 3a179fe524 refactor(gateway): every Ogg/Opus voice transcode routes through base.transcode_to_ogg_opus
Matrix, WhatsApp Cloud and the TTS tool each ran their own ffmpeg argv for the same
speech-tuned libopus encode; they predate the shared helper and never migrated, so the
codec flags, timeout handling and error reporting drifted (matrix 48k/30s, whatsapp_cloud
async subprocess with no timeout and no `-ac 1`, tts an in-place sidecar repair).

`transcode_to_ogg_opus` gains `timeout=` and `output_path=` (sibling-file and in-place
writes go through a `.tmp.ogg` sidecar so a failed encode never truncates the source).
Deleted: matrix `_matrix_transcode_voice_to_ogg`, tts `_ffmpeg_transcode_to_opus`;
whatsapp_cloud `_convert_to_opus` keeps only its warn-once ffmpeg install hint and calls
the helper via `asyncio.to_thread`. Matrix and tts keep their 48k bitrate.

Behavior change: whatsapp_cloud transcodes now use mono (`-ac 1`), `-compression_level 10`
and a 60s timeout like every other voice bubble; a failed encode logs at WARNING for all
three sites (matrix previously DEBUG).
2026-09-13 05:32:38 -07:00

77 lines
3.1 KiB
Python

"""Every voice-bubble transcode routes through ``gateway.platforms.base.transcode_to_ogg_opus``.
Matrix, WhatsApp Cloud and the TTS tool each used to run their own ffmpeg argv; the shared
helper is the only place the codec flags, the timeout and the in-place-safe write live.
"""
from __future__ import annotations
import asyncio
import subprocess
from types import SimpleNamespace
def _capture_ffmpeg(monkeypatch):
calls = []
def fake_run(argv, **kwargs):
calls.append((argv, kwargs))
out = argv[-1]
with open(out, "wb") as fh:
fh.write(b"OggS")
return SimpleNamespace(returncode=0, stderr=b"")
monkeypatch.setattr("gateway.platforms.base.subprocess.run", fake_run)
monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/ffmpeg")
return calls
def _bitrate(argv):
return argv[argv.index("-b:a") + 1]
def test_all_sites_share_one_ffmpeg_invocation(monkeypatch, tmp_path):
from gateway.platforms import whatsapp_cloud
from plugins.platforms.matrix import adapter as matrix
from tools import tts_tool_delivery as tts
calls = _capture_ffmpeg(monkeypatch)
src = tmp_path / "speech.mp3"
src.write_bytes(b"ID3")
# Matrix: temp output at 48k.
matrix_out = asyncio.run(asyncio.to_thread(matrix.transcode_to_ogg_opus, str(src), bitrate="48k", timeout=30))
assert matrix_out and matrix_out.endswith(".ogg") and _bitrate(calls[-1][0]) == "48k"
assert calls[-1][1]["timeout"] == 30
# WhatsApp Cloud: sibling .ogg at the 32k default, warn-once on missing ffmpeg untouched.
monkeypatch.setattr(whatsapp_cloud, "_FFMPEG_PATH", "/usr/bin/ffmpeg")
wa = object.__new__(whatsapp_cloud.WhatsAppCloudAdapter)
wa._warned_no_ffmpeg = False
wa_out = asyncio.run(wa._convert_to_opus(str(src)))
assert wa_out == str(tmp_path / "speech.ogg") and _bitrate(calls[-1][0]) == "32k"
# TTS tool: sibling .ogg at 48k, and in-place repair never writes straight over the source.
assert tts._convert_to_opus(str(src)) == str(tmp_path / "speech.ogg") and _bitrate(calls[-1][0]) == "48k"
bad = tmp_path / "bad.ogg"
bad.write_bytes(b"ID3")
assert tts._repair_ogg_container(str(bad)) == str(bad)
assert calls[-1][0][-1] == str(bad) + ".tmp.ogg" and bad.read_bytes() == b"OggS"
# Every argv carries the voice-tuned flag set exactly once.
for argv, kwargs in calls:
assert argv[argv.index("-application") + 1] == "voip" and "-compression_level" in argv
assert kwargs["stdin"] is subprocess.DEVNULL
def test_failed_in_place_repair_keeps_the_source(monkeypatch, tmp_path):
from gateway.platforms.base import transcode_to_ogg_opus
monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/ffmpeg")
monkeypatch.setattr("gateway.platforms.base.subprocess.run",
lambda argv, **kw: SimpleNamespace(returncode=1, stderr=b"boom"))
bad = tmp_path / "bad.ogg"
bad.write_bytes(b"ID3")
assert transcode_to_ogg_opus(str(bad), output_path=str(bad)) is None
assert bad.read_bytes() == b"ID3" and not (tmp_path / "bad.ogg.tmp.ogg").exists()