-
Notifications
You must be signed in to change notification settings - Fork 52.3k
fix(tts): synthesize mp3 then transcode to opus for .ogg targets #55278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1100,7 +1100,7 @@ def _generate_openai_tts( | |
| speed_default = tts_config.get("speed", 1.0) if isinstance(tts_config, dict) else 1.0 | ||
| speed = float(oai_config.get("speed", speed_default)) | ||
|
|
||
| # The managed OpenAI audio gateway only proxies MANAGED_OPENAI_TTS_MODELS. | ||
| # The managed OpenAI audio gateway only proxies MANAGED_OPENAI_TTS_MODELS. | ||
| # A model set for direct OpenAI (e.g. "tts-1-hd") 400s there with | ||
| # "Unsupported managed OpenAI speech model", so coerce it — unless the user | ||
| # redirected base_url to their own endpoint, in which case respect it. | ||
|
|
@@ -1118,7 +1118,13 @@ def _generate_openai_tts( | |
| ) | ||
| model = DEFAULT_OPENAI_MODEL | ||
|
|
||
| response_format = _tts_response_format_from_path(output_path) | ||
| # Determine response format from extension. | ||
| # Always request mp3 from the API — then transcode to OGG/Opus locally | ||
| # 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" | ||
| synth_path = (output_path[:-4] + ".mp3") if wants_opus else output_path | ||
|
|
||
| OpenAIClient = _import_openai_client() | ||
| client = OpenAIClient(api_key=api_key, base_url=base_url) | ||
|
|
@@ -1134,8 +1140,19 @@ def _generate_openai_tts( | |
| create_kwargs["speed"] = max(0.25, min(4.0, speed)) | ||
| response = client.audio.speech.create(**create_kwargs) | ||
|
|
||
| response.stream_to_file(output_path) | ||
| return output_path | ||
| response.stream_to_file(synth_path) | ||
| if wants_opus: | ||
| converted = _convert_to_opus(synth_path) | ||
| # Clean up the intermediate mp3 if transcoding succeeded | ||
| if converted and converted != synth_path: | ||
| try: | ||
| os.remove(synth_path) | ||
| except OSError: | ||
| pass | ||
| return converted | ||
| # Transcoding failed — return the mp3 as best-effort | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This return path is currently discarded by the caller: |
||
| return synth_path | ||
| return synth_path | ||
| finally: | ||
| close = getattr(client, "close", None) | ||
| if callable(close): | ||
|
|
||
There was a problem hiding this comment.
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.