Skip to content

fix: send voice replies as Opus/OGG on Matrix - #14900

Closed
Kailigithub wants to merge 1 commit into
NousResearch:mainfrom
Kailigithub:fix/issue-14841-matrix-voice-opus
Closed

fix: send voice replies as Opus/OGG on Matrix#14900
Kailigithub wants to merge 1 commit into
NousResearch:mainfrom
Kailigithub:fix/issue-14841-matrix-voice-opus

Conversation

@Kailigithub

Copy link
Copy Markdown
Contributor

Summary

Matrix requires Opus codec in an OGG container for proper voice bubbles per MSC3245. When sent as MP3, Matrix treats voice replies as generic file attachments — on mobile clients they appear as broken or unplayable.

Two code paths were affected:

1. tools/tts_tool.py — model-invoked text_to_speech tool

The want_opus predicate at line 960 only matched "telegram", so when the model calls the TTS tool directly on a Matrix session the output was always MP3 regardless of provider capabilities.

Fix: Extended the check to platform in ("telegram", "matrix").

2. gateway/run.py — auto voice-reply in _send_voice_reply()

The output path was hardcoded to .mp3 regardless of the target platform. For providers that support native Opus output (OpenAI, ElevenLabs, Mistral, Gemini), passing .ogg as the extension allows the tool to request response_format=opus directly — no ffmpeg conversion needed.

Fix: Platform-aware extension selection (.ogg for Telegram/Matrix, .mp3 otherwise).

Testing

  • py_compile passes for both modified files
  • All 7 tests in tests/gateway/test_matrix_voice.py pass
  • All TTS-related tests pass (43 passed, 3 pre-existing failures unrelated to this change)
  • Broader gateway + tools test suite: 312 passed (3 pre-existing failures)

Closes #14841

Matrix requires Opus codec in an OGG container for voice bubbles per
MSC3245. Two code paths were defaulting to MP3 for Matrix:

1. tools/tts_tool.py — want_opus predicate only matched Telegram,
   so model-invoked TTS always produced MP3 on Matrix.
2. gateway/run.py — _send_voice_reply() hardcoded .mp3 extension
   regardless of platform.

Extend the Opus format check to include Matrix alongside Telegram in
both locations.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists platform/matrix Matrix adapter (E2EE) tool/tts Text-to-speech and transcription comp/gateway Gateway runner, session dispatch, delivery labels Apr 24, 2026
@e-shizz

e-shizz commented May 24, 2026

Copy link
Copy Markdown

Hey! I was affected by this exact same issue on my Matrix instance. I can confirm that these two patches (tts_tool.py + gateway/run.py) fix Matrix voice delivery — voice messages now arrive as proper OGG Opus voice bubbles via MSC3245.

The patch has been running stable for me. Anything blocking this from getting merged? Happy to help test or rebase if needed. ★

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating both affected paths. The premise remains valid on current main: Matrix still follows the MP3 auto-reply branch in gateway/run.py:13035, and Matrix is absent from want_opus in tools/tts_tool.py:2202.

Problems

  • gateway/run.py:6308 calls .lower() on event.source.platform, but SessionSource.platform is a Platform enum (gateway/session.py:75), so this path will raise before TTS generation.
  • The .ogg generation path at gateway/run.py:6311 is unsafe for Edge TTS. Edge writes its MP3 stream to the supplied path (tools/tts_tool.py:961-963), while _convert_to_opus() derives its output as the same .ogg path (tools/tts_tool.py:912-916).
  • No Matrix regression test accompanies the change; existing format coverage is Telegram/Slack-only in tests/gateway/test_auto_voice_reply_format.py:17-72.

Suggested changes

  • Compare against Platform.TELEGRAM / Platform.MATRIX directly.
  • Generate to a distinct compatible path, then post-convert to a distinct Ogg/Opus file when required; preserve already-native Opus output.
  • Add Matrix coverage for both auto voice replies and model-invoked TTS. The linked #54488 documents and tests this post-generation conversion direction.

Automated hermes-sweeper review.

Comment thread gateway/run.py
# (Telegram, Matrix); fall back to .mp3 for others.
# The TTS tool may convert to .ogg — use file_path from result.
_platform = event.source.platform or ""
_ext = ".ogg" if _platform.lower() in ("telegram", "matrix") else ".mp3"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

event.source.platform is a Platform enum (gateway/session.py:75), not a string, so .lower() raises here. Compare directly against {Platform.TELEGRAM, Platform.MATRIX} (or normalize through .value) before choosing the extension.

Comment thread gateway/run.py
audio_path = os.path.join(
tempfile.gettempdir(), "hermes_voice",
f"tts_reply_{_uuid.uuid4().hex[:12]}.mp3",
f"tts_reply_{_uuid.uuid4().hex[:12]}{_ext}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generating Edge TTS directly to .ogg does not make it Opus: Edge writes MP3 bytes to the requested path, and _convert_to_opus() derives its output by replacing the suffix with .ogg, producing the same input/output path here. Generate to a distinct path and post-convert to a separate OGG file.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
@malaiwah

malaiwah commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Downstream confirmation the direction is right (Telegram→Matrix for want_opus / audio_ext). Two quick notes on the bot's blockers, both easy:

  • tools/tts_tool.py's platform local is already get_session_env(...).lower() (a str), so platform in ("telegram", "matrix") needs no .lower() on a Platform enum. In gateway/run.py compare the enum directly: event.source.platform in (Platform.TELEGRAM, Platform.MATRIX).
  • The Edge-TTS .ogg in/out collision only bites providers that emit MP3-then-convert; native-Opus providers (OpenAI-compatible / ElevenLabs) write .ogg directly.

That said, for reconcilers: #54488 fixes the same bug more completely — an Edge-safe post-generation transcode (rather than this one-line want_opus extension) plus the MSC3245/MSC1767 duration+waveform metadata that Element needs, plus tests — and the automated review already cites it as the documented/tested direction. Correcting my earlier note: no separate metadata follow-up is needed; #54488 has it. Endorsing that as the one to land.

@Kailigithub

Copy link
Copy Markdown
Contributor Author

Closing as superseded — the same fix landed upstream via the salvage track in commit b2a8a45591. No further action needed on this branch. 🤖

@Kailigithub

Copy link
Copy Markdown
Contributor Author

Closing per supersede comment above. The fix landed via salvage track.

@Kailigithub Kailigithub closed this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/matrix Matrix adapter (E2EE) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matrix voice replies sent as mp3 instead of ogg/opus — render as broken attachments

6 participants