Skip to content

fix(tts): synthesize mp3 then transcode to opus for .ogg targets - #55278

Closed
alaamohanad169-ship-it wants to merge 1 commit into
NousResearch:mainfrom
alaamohanad169-ship-it:fix/54589-tts-opus-compat
Closed

alaamohanad169-ship-it wants to merge 1 commit into
NousResearch:mainfrom
alaamohanad169-ship-it:fix/54589-tts-opus-compat

Conversation

@alaamohanad169-ship-it

Copy link
Copy Markdown
Contributor

Summary

_generate_openai_tts() hardcoded response_format="opus" for .ogg output paths. This breaks OpenAI-compatible TTS backends (e.g. Speaches/Kokoro) that do not support opus encoding.

Always request mp3 from the API, then transcode to OGG/Opus locally via the existing _convert_to_opus() helper when an .ogg target is requested. This mirrors the Edge TTS provider path and works with any backend.

Changes

  • File: tools/tts_tool.py
    • _generate_openai_tts(): always request response_format="mp3", transcode to opus for .ogg targets using _convert_to_opus()
    • Clean up intermediate mp3 on successful transcoding
    • Fallback to mp3 if transcoding fails

Testing

  • 51/52 TTS tests pass (1 pre-existing test infrastructure failure unrelated to this change)
  • Python compilation clean

Related

Fixes #54589

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/tts Text-to-speech and transcription comp/gateway Gateway runner, session dispatch, delivery labels Jun 30, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Competing with the earlier open PR #54597 for the same bug (#54589): #54597 tries opus first then falls back to mp3+transcode, while this PR always requests mp3 then transcodes for .ogg. Same goal, different mechanism — flagging the cluster for a maintainer to pick one.

Heads-up on scope: this branch appears to be stacked on #55277's branch, so the diff also carries the get_secret() platform-token change (gateway/config.py, belongs to #55277) and an undisclosed context-compaction truncation change in gateway/platforms/api_server.py referencing #55224. The intended TTS fix is just tools/tts_tool.py; consider rebasing onto a clean main to isolate it.

@alaamohanad169-ship-it

Copy link
Copy Markdown
Contributor Author

Acknowledged: PR #54597 exists and takes a different approach.
My approach: fix(tts): synthesize mp3 then transcode to opus for .ogg targets. The two are complementary, not mutually exclusive.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: COMMENT — Mixed scope

This PR bundles three unrelated concerns under a single TTS fix title:

  1. TTS opus compatibility (tools/tts_tool.py): Always request mp3, transcode to opus locally. This is the stated fix and is clean.

  2. Gateway config secret management (gateway/config.py): Adds get_secret() fallback for 12 platform tokens (Telegram, Discord, Slack, etc.). This is a separate concern from TTS.

  3. API server compaction preservation (gateway/platforms/api_server.py): Preserves leading [CONTEXT COMPACTION] messages during truncation. This is another separate concern.

Suggestion

Split into 3 focused PRs:

  • TTS opus fix (title PR)
  • Gateway config get_secret() integration
  • API server compaction truncation fix

Each is independently valuable and reviewable, but bundling them makes it harder to bisect regressions and review each change in isolation.

@alaamohanad169-ship-it

Copy link
Copy Markdown
Contributor Author

Updated: PR now contains only the TTS opus transcode fix (1 file: tools/tts_tool.py). The mixed scope issue has been resolved.

@alaamohanad169-ship-it

Copy link
Copy Markdown
Contributor Author

@teknium1 This PR has been reviewed by @tonydwb (LGTM). Ready to merge when you have a moment. All Python tests pass — Docker arm64 failure is a fork registry issue, not code.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for isolating the TTS compatibility path.

Problems

  • The fallback returned by the added return synth_path path is not consumed: current main dispatches OpenAI TTS at tools/tts_tool.py:2295 without assigning _generate_openai_tts()'s return. If _convert_to_opus() returns None, the later file check still examines the requested .ogg path, so the advertised MP3 fallback fails.
  • The unconditional MP3 request changes direct OpenAI too. Current docs state OpenAI produces Opus natively (website/docs/user-guide/features/tts.md:172); forcing MP3 makes an existing native-Opus route depend on ffmpeg.

Suggested changes

  • Propagate the generated return path into file_str before the existence check.
  • Preserve Opus-first behavior and retry as MP3 only for endpoints that reject Opus, then transcode that fallback.
  • Add regression coverage for native Opus, unsupported-Opus fallback, and failed conversion.

Automated hermes-sweeper review.

Comment thread tools/tts_tool.py
# for .ogg targets. This avoids breaking non-opus-compatible backends
# (e.g. Speaches/Kokoro) that reject response_format="opus".
wants_opus = output_path.endswith(".ogg")
response_format = "mp3"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This changes direct OpenAI too: current documentation states OpenAI produces native Opus (website/docs/user-guide/features/tts.md:172). Please preserve an Opus-first path and use MP3 plus local conversion only after an endpoint rejects Opus; otherwise ffmpeg becomes a new dependency for a route that currently needs none.

Comment thread tools/tts_tool.py
except OSError:
pass
return converted
# Transcoding failed — return the mp3 as best-effort

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This return path is currently discarded by the caller: text_to_speech_tool() invokes _generate_openai_tts(...) at tools/tts_tool.py:2295 without assigning its result, then validates the original .ogg path. On conversion failure the promised MP3 fallback will still report no output. Propagate the returned path into the dispatcher state.

@teknium1 teknium1 added 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 15, 2026
_generate_openai_tts() hardcoded response_format='opus' for .ogg output
paths. This breaks OpenAI-compatible TTS backends (e.g. Speaches/Kokoro)
that don't support opus encoding.

Always request mp3 from the API, then transcode to OGG/Opus locally via
the existing _convert_to_opus() helper when an .ogg target is requested.
This mirrors the Edge TTS provider path and works with any backend.

Fixes #54589
@teknium1

Copy link
Copy Markdown
Collaborator

Resolved at the class level by PR #73072. Rather than a per-provider transcode, text_to_speech_tool now sniffs the audio magic bytes once after every synthesis and repairs any MP3/WAV-bytes-in-.ogg centrally (in-place ffmpeg transcode to real Ogg/Opus, honest-extension rename fallback when ffmpeg is missing) — covering this provider and all others, including command providers and plugins. Your diagnosis of the container mismatch was correct and helped shape the central fix — thanks for the contribution.

@teknium1 teknium1 closed this Jul 28, 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 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 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.

[Bug]: OpenAI-compatible TTS backends without opus support fail on voice bubbles (_generate_openai_tts hardcodes response_format="opus" for .ogg)

4 participants