fix(tts): convert Edge TTS MP3 to Opus when caller supplies .ogg path - #57069
fix(tts): convert Edge TTS MP3 to Opus when caller supplies .ogg path#57069liuhao1024 wants to merge 2 commits into
Conversation
Edge TTS always writes MP3 bytes regardless of the target filename. When a caller (e.g. gateway/run.py's _send_voice_reply for Telegram) supplies an explicit .ogg output_path, the old code skipped _convert_to_opus() because the file already ended in .ogg — even though the content was MP3. The resulting file failed to render as a native Telegram voice bubble. Fix: for edge/minimax/xai providers, detect .ogg paths that actually contain MP3 data, rename to .mp3, then run the existing opus conversion pipeline. neutts/kittentts/piper are unaffected — they handle .ogg natively in their own generation functions. Regression test included. Fixes NousResearch#57048
Duplicate of #20882 (open, earlier) — that PR already makes the identical |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating the explicit-.ogg Edge TTS path. The premise is confirmed on current main: gateway/run.py:13300-13311 supplies .ogg for Telegram, Edge saves to that supplied path at tools/tts_tool.py:947-971, and tools/tts_tool.py:2562-2570 skips conversion solely because the filename ends in .ogg.
Problems
tools/tts_tool.py:2417renames into a deterministic<stem>.mp3. On POSIX this can replace an existing caller-owned sibling; if rename fails,tools/tts_tool.py:2419-2420silently retains the invalid.ogginstead of converting or failing.- The new intermediate leaks after a successful conversion:
_convert_to_opus()retains its input (tools/tts_tool.py:920-934), while gateway cleanup removes onlyaudio_pathandactual_path(gateway/run.py:13357-13362), both.oggin this route.
Suggested changes
- Use a collision-free intermediate and clean it up in
finally; preserve any pre-existing same-stem MP3 and surface preparation/conversion failure safely. - Add coverage for sibling preservation and intermediate cleanup.
Automated hermes-sweeper review.
| }: | ||
| mp3_sibling = file_str[:-4] + ".mp3" | ||
| try: | ||
| os.rename(file_str, mp3_sibling) |
There was a problem hiding this comment.
This deterministic sibling can overwrite an existing caller-owned <stem>.mp3 on POSIX; on rename failure the except OSError: pass path then returns the mislabeled .ogg without conversion. Please use a collision-free intermediate and clean it up after _convert_to_opus().
|
Resolved at the class level by PR #73072. Rather than a per-provider transcode, |
What does this PR do?
Fixes a bug where Edge TTS (and similar MP3-only providers like minimax/xai) silently writes MP3 bytes under a
.oggfile extension when the caller supplies an explicit.oggoutput path. The resulting file fails to render as a native Telegram voice bubble because it's MP3 data, not Opus.The root cause:
_convert_to_opus()was gated bynot file_str.endswith(".ogg"), but Edge TTS always writes MP3 regardless of the target filename. When a caller (likegateway/run.py's_send_voice_replyfor Telegram) passes an explicit.oggpath, the conversion is skipped because the file already "looks like" .ogg — even though it's MP3 content.Related Issue
Fixes #57048
Type of Change
Changes Made
tools/tts_tool.py: Whenwant_opusis true and the provider is edge/minimax/xai, detect.oggoutput paths that actually contain MP3 data (from caller-supplied paths), rename them to.mp3, and then run_convert_to_opus()as normal. This ensures the conversion pipeline always gets real MP3 input.tests/tools/test_tts_opus_routing.py: Added regression testtest_edge_telegram_explicit_ogg_path_convertsverifying that Edge TTS with an explicit.oggoutput_path correctly converts to real Opus.How to Test
python -m pytest tests/tools/test_tts_opus_routing.py -q— all 3 tests should passpython -m pytest tests/tools/test_tts*.py -q— all 244+ TTS tests should pass (no regressions)HERMES_SESSION_PLATFORM=telegramandoutput_pathends in.ogg, Edge TTS writes MP3 bytes → code renames to.mp3→_convert_to_opusis called → result is real Opus withvoice_compatible: trueChecklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/tools/test_tts*.py -qand all 244 tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
The fix is self-contained in the opus conversion logic. The regression test directly validates the bug scenario (Edge TTS + explicit .ogg path + Telegram platform = real Opus output).