fix: Matrix voice as Ogg/Opus — direct TTS path + non-blocking metadata (completes #54488) - #68063
Closed
malaiwah wants to merge 2 commits into
Closed
fix: Matrix voice as Ogg/Opus — direct TTS path + non-blocking metadata (completes #54488)#68063malaiwah wants to merge 2 commits into
malaiwah wants to merge 2 commits into
Conversation
Builds on NousResearch#54488 (kept as-is) and addresses its two review blockers: 1. Direct text_to_speech coverage — tools/tts_tool.py want_opus now includes Matrix, so model-invoked TTS on a Matrix session emits Ogg/Opus (native-Opus providers directly; MP3 providers via the existing tool-level _convert_to_opus step, which converts to a distinct path — no Edge in/out collision). As a safety net, MatrixAdapter.send_voice now enforces the MSC3245 codec at the adapter boundary: any non-Ogg audio reaching it is transcoded to Ogg/Opus (best-effort; original sent unchanged if ffmpeg is unavailable, preserving previous behaviour). 2. Non-blocking metadata — _matrix_voice_metadata_for_file() is now awaited via asyncio.to_thread from _send_local_file, so its ffprobe/ffmpeg subprocess timeouts can no longer stall the Matrix adapter event loop; the boundary transcode runs off-loop the same way. Tests: Matrix case added to tests/tools/test_tts_opus_routing.py (mirrors the Telegram case); adapter-boundary transcode + skip-when-Ogg cases added to tests/gateway/test_matrix_voice.py. Refs NousResearch#14841 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
malaiwah
force-pushed
the
fix/matrix-voice-opus-complete
branch
from
July 20, 2026 13:52
dd603d9 to
ff0c58a
Compare
Collaborator
Bryntly
reviewed
Jul 20, 2026
Bryntly
left a comment
There was a problem hiding this comment.
The implementation looks great overall!
However, CI is failing on tests/gateway/test_auto_voice_reply_format.py. This is because gateway/run.py was correctly updated to unconditionally generate an .mp3 intermediate path, but the test was still expecting .ogg to be passed to text_to_speech_tool.
Could you please apply the following diff to fix the test?
diff --git a/tests/gateway/test_auto_voice_reply_format.py b/tests/gateway/test_auto_voice_reply_format.py
index eeb39ab60..36a7f626f 100644
--- a/tests/gateway/test_auto_voice_reply_format.py
+++ b/tests/gateway/test_auto_voice_reply_format.py
@@ -15,7 +15,7 @@ from gateway.session import SessionSource
class TestAutoVoiceReplyFormat:
@pytest.mark.asyncio
async def test_telegram_auto_voice_reply_requests_ogg_for_native_voice_bubble(self):
- """Telegram auto-TTS should request OGG/Opus so send_voice sends a voice bubble."""
+ """Telegram auto-TTS should request MP3 then transcode to OGG/Opus so send_voice sends a voice bubble."""
runner = _make_runner()
adapter = _make_adapter(Platform.TELEGRAM)
runner.adapters[Platform.TELEGRAM] = adapter
@@ -24,9 +24,9 @@ class TestAutoVoiceReplyFormat:
def fake_tts(*, text, output_path):
requested_paths.append(output_path)
- assert output_path.endswith(".ogg")
+ assert output_path.endswith(".mp3")
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
- Path(output_path).write_bytes(b"fake ogg opus")
+ Path(output_path).write_bytes(b"fake mp3")
return json.dumps({
"success": True,
"file_path": output_path,
@@ -34,11 +34,17 @@ class TestAutoVoiceReplyFormat:
"voice_compatible": True,
})
- with patch("tools.tts_tool.text_to_speech_tool", side_effect=fake_tts):
+ def fake_convert(path):
+ ogg_path = str(Path(path).with_suffix(".ogg"))
+ Path(ogg_path).write_bytes(b"fake ogg opus")
+ return ogg_path
+
+ with patch("tools.tts_tool.text_to_speech_tool", side_effect=fake_tts), \
+ patch("tools.tts_tool._convert_to_opus", side_effect=fake_convert):
await runner._send_voice_reply(event, "hello from auto tts")
assert requested_paths
- assert requested_paths[0].endswith(".ogg")
+ assert requested_paths[0].endswith(".mp3")
adapter.send_voice.assert_awaited_once()
assert adapter.send_voice.await_args.kwargs["audio_path"].endswith(".ogg")
teknium1
pushed a commit
that referenced
this pull request
Jul 28, 2026
…e sends Salvaged from PR #68063 (base commit, runner-path hunks superseded by the platform-aware OPUS_VOICE_PLATFORMS fix in this branch). Element and other Matrix clients render voice bubbles more reliably when m.audio events carry duration and waveform metadata; probe both best-effort via ffprobe/ffmpeg.
teknium1
pushed a commit
that referenced
this pull request
Jul 28, 2026
…off-loop Salvaged from PR #68063 (@malaiwah). MatrixAdapter.send_voice now transcodes any non-Ogg audio to Ogg/Opus at the adapter boundary (best-effort — the original file is sent unchanged when ffmpeg is unavailable), so MSC3245 voice bubbles render even when a caller hands the adapter MP3/WAV audio. _matrix_voice_metadata_for_file probing now runs via asyncio.to_thread so ffprobe/ffmpeg subprocess timeouts can't stall the adapter event loop. The PR's tools/tts_tool.py want_opus hunk was dropped: main's OPUS_VOICE_PLATFORMS set (PR #73072) already includes matrix; the Matrix opus-routing test is kept. Refs #14841
teknium1
pushed a commit
that referenced
this pull request
Jul 28, 2026
…e sends Salvaged from PR #68063 (base commit, runner-path hunks superseded by the platform-aware OPUS_VOICE_PLATFORMS fix in this branch). Element and other Matrix clients render voice bubbles more reliably when m.audio events carry duration and waveform metadata; probe both best-effort via ffprobe/ffmpeg.
teknium1
pushed a commit
that referenced
this pull request
Jul 28, 2026
…off-loop Salvaged from PR #68063 (@malaiwah). MatrixAdapter.send_voice now transcodes any non-Ogg audio to Ogg/Opus at the adapter boundary (best-effort — the original file is sent unchanged when ffmpeg is unavailable), so MSC3245 voice bubbles render even when a caller hands the adapter MP3/WAV audio. _matrix_voice_metadata_for_file probing now runs via asyncio.to_thread so ffprobe/ffmpeg subprocess timeouts can't stall the adapter event loop. The PR's tools/tts_tool.py want_opus hunk was dropped: main's OPUS_VOICE_PLATFORMS set (PR #73072) already includes matrix; the Matrix opus-routing test is kept. Refs #14841
Contributor
|
Merged into main via consolidated salvage PR #73508 (merge Your contribution is credited to you in git history. Thank you! Closing this PR as merged-via-salvage. |
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…e sends Salvaged from PR NousResearch#68063 (base commit, runner-path hunks superseded by the platform-aware OPUS_VOICE_PLATFORMS fix in this branch). Element and other Matrix clients render voice bubbles more reliably when m.audio events carry duration and waveform metadata; probe both best-effort via ffprobe/ffmpeg.
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…off-loop Salvaged from PR NousResearch#68063 (@malaiwah). MatrixAdapter.send_voice now transcodes any non-Ogg audio to Ogg/Opus at the adapter boundary (best-effort — the original file is sent unchanged when ffmpeg is unavailable), so MSC3245 voice bubbles render even when a caller hands the adapter MP3/WAV audio. _matrix_voice_metadata_for_file probing now runs via asyncio.to_thread so ffprobe/ffmpeg subprocess timeouts can't stall the adapter event loop. The PR's tools/tts_tool.py want_opus hunk was dropped: main's OPUS_VOICE_PLATFORMS set (PR NousResearch#73072) already includes matrix; the Matrix opus-routing test is kept. Refs NousResearch#14841
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds directly on #54488 by @ciabata-git (that commit is included unchanged — please credit it there / land that PR and treat this as the follow-up, whichever is easier to review) and resolves the two blockers from its automated review:
1. Direct
text_to_speechdelivery now coveredtools/tts_tool.py'swant_opusnow includes Matrix, so a model-invoked TTS call on a Matrix session emits Ogg/Opus like Telegram does — native-Opus providers (openai/elevenlabs/mistral/gemini) directly, MP3-native providers via the existing tool-level_convert_to_opusstep (which writes to a distinct.oggpath, so no Edge in/out collision — that only occurs when a.oggoutput path is passed into Edge, which nothing here does).As a defense in depth,
MatrixAdapter.send_voicenow enforces the MSC3245 codec at the adapter boundary: any non-Ogg audio reaching it (any current or future caller) is transcoded to Ogg/Opus before upload. Best-effort — if ffmpeg is unavailable or fails, the original file is sent unchanged, preserving today's behaviour.2. Metadata probing no longer blocks the event loop
_matrix_voice_metadata_for_file()is now awaited viaasyncio.to_thread(...)from_send_local_file, so its ffprobe/ffmpeg subprocess timeouts (10s/15s) can't stall the Matrix adapter. The new boundary transcode runs off-loop the same way.Testing
pytest tests/tools/test_tts_opus_routing.py "tests/gateway/test_matrix_voice.py::TestMatrixSendVoiceMSC3245" "tests/gateway/test_voice_command.py::TestSendVoiceReply" -q -o addopts=→ 13 passed (includes the new Matrix tool-routing case mirroring the Telegram one, adapter-boundary transcode case, and skip-when-already-Ogg case)ruff==0.15.10 checkon the four changed files → All checks passedpython -m py_compileon changed files → passedinfo.duration+org.matrix.msc1767.audioduration/waveform) is running in production on our Matrix homeserver — voice bubbles render and play correctly in Element X iOS, verified on-device against a field-by-field diff with an Element-recorded voice message (see Matrix voice replies sent as mp3 instead of ogg/opus — render as broken attachments #14841).Closes #14841 (together with the #54488 base it includes).
🤖 Generated with Claude Code