Skip to content

fix(tts): honor wav/flac output in OpenAI TTS provider instead of forcing mp3 - #37337

Closed
MarvinFS wants to merge 1 commit into
NousResearch:mainfrom
MarvinFS:fix/openai-tts-wav-flac-format
Closed

fix(tts): honor wav/flac output in OpenAI TTS provider instead of forcing mp3#37337
MarvinFS wants to merge 1 commit into
NousResearch:mainfrom
MarvinFS:fix/openai-tts-wav-flac-format

Conversation

@MarvinFS

@MarvinFS MarvinFS commented Jun 2, 2026

Copy link
Copy Markdown

Problem

tools/tts_tool.py::_generate_openai_tts derives the OpenAI response_format solely from a .ogg check, mapping every other extension — including .wav and .flac — to mp3:

if output_path.endswith(".ogg"):
    response_format = "opus"
else:
    response_format = "mp3"

This ignores the user's configured output_format, which Hermes already validates against COMMAND_TTS_OUTPUT_FORMATS = {mp3, wav, ogg, flac} and uses to set the output file's extension (_get_command_tts_output_format).

Impact:

  1. Configured wav/flac output is re-encoded to mp3 (or written as mp3 bytes under a .wav name) even when the backend produces the requested format natively.
  2. On OpenAI-compatible backends without server-side mp3 encoding — e.g. devnen/Chatterbox-TTS-Server, which returns native 24 kHz WAV — the forced mp3 request fails with 500: Failed to encode audio, breaking TTS entirely when wav would have succeeded.

The sibling Mistral provider in the same file already maps .ogg/.wav/.flac/else correctly; the OpenAI provider is simply inconsistent with it.

Fix

Mirror the Mistral provider and map .wav -> wav / .flac -> flac. Both are documented values for OpenAI's audio.speech response_format (mp3, opus, aac, flac, wav, pcm), so this stays within the API contract and adds no new config surface — the resolved output_format already controls the extension.

Testing

  • output_format: wav against an OpenAI-compatible backend returning native WAV (Chatterbox-TTS-Server): now succeeds with a valid WAV instead of 500: Failed to encode audio.
  • .ogg (opus) and default .mp3 paths unchanged.

…cing mp3

_generate_openai_tts derived the OpenAI response_format solely from a
.ogg check, mapping every other extension - including .wav and .flac -
to mp3. This ignored the configured output_format, which Hermes already
validates against COMMAND_TTS_OUTPUT_FORMATS = {mp3, wav, ogg, flac} and
uses to set the output file extension.

Impact:
- Configured wav/flac output was re-encoded to mp3 (or written as mp3
  bytes under a .wav name) even when the backend produced the requested
  format natively.
- On OpenAI-compatible backends without server-side mp3 encoding - e.g.
  devnen/Chatterbox-TTS-Server, which returns native 24 kHz WAV - the
  forced mp3 request failed with "500: Failed to encode audio", breaking
  TTS entirely when wav would have worked.

Mirror the sibling Mistral provider and map .wav -> wav and .flac -> flac.
Both are documented OpenAI audio.speech response_format values, so this
stays within the API contract and adds no new config surface.

@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 a real OpenAI TTS format-selection bug: current main still maps every non-.ogg output path to mp3 in tools/tts_tool.py:1050-1054, while the sibling Mistral implementation maps WAV and FLAC explicitly at tools/tts_tool.py:1450-1457.

Problems

  • The added endswith() branches remain case-sensitive. Because caller-provided paths are preserved at tools/tts_tool.py:2204, speech.WAV and speech.FLAC would still request MP3.
  • The PR adds no regression test. Existing OpenAI tests in tests/tools/test_tts_speed.py:71-114 and tests/tools/test_managed_media_gateways.py:225-302 use .mp3 only.
  • output_format is documented for command providers (website/docs/user-guide/features/tts.md:240-315); this built-in OpenAI path is controlled by output_path.

Suggested changes

  • Normalize Path(output_path).suffix.lower() once before mapping the response format.
  • Add mocked request-construction tests for .ogg, .wav, .flac, and .mp3.
  • Adjust the PR description to describe output-path extension handling.

Automated hermes-sweeper review.

Comment thread tools/tts_tool.py
# where forcing mp3 yields a hard "500: Failed to encode audio".
if output_path.endswith(".ogg"):
response_format = "opus"
elif output_path.endswith(".wav"):

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.

Please normalize the suffix once (for example, Path(output_path).suffix.lower()) before this mapping. text_to_speech_tool() preserves a caller-provided path, so .WAV and .FLAC currently still fall through to mp3.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 13, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Confirmed fixed on main during the #73522 salvage: _tts_response_format_from_path() already honors wav/flac/ogg in the OpenAI provider — no residual delta from this PR. Thanks for the report-and-fix!

(Landed via #73522, merge 237b0f5d5e.) Closing.

@teknium1 teknium1 closed this Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users 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.

3 participants