From 273b986fd9979c0ae8438da32b743a22318cabf2 Mon Sep 17 00:00:00 2001 From: Sherman <58446328+sherman-yang@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:32:29 -0600 Subject: [PATCH] feat(tools): expand command TTS output_format allowlist (m4a/aac/amr/opus) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command-type TTS providers validated output_format against a hardcoded {mp3,wav,ogg,flac} set; any other value was silently coerced back to mp3, which then mismatched the output path the post-run check expects. This blocked common ffmpeg-producible containers/codecs — notably m4a (AAC), the portable choice for WeChat/iOS/mobile voice files — with no config-only path (only a local source patch, lost on every update). Widen COMMAND_TTS_OUTPUT_FORMATS to add m4a, aac, amr, opus. This only permits a command provider to declare these; the user's command still produces the file (e.g. via ffmpeg). No built-in provider behavior changes and no new required config. Update the two tests that pinned the old set, and add a positive case covering the new formats. Document the supported output_format values. --- tests/tools/test_tts_command_providers.py | 13 +++++++++++-- tools/tts_tool.py | 4 +++- website/docs/user-guide/features/tts.md | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/tools/test_tts_command_providers.py b/tests/tools/test_tts_command_providers.py index 24c1f25f830be..c096487bc9a76 100644 --- a/tests/tools/test_tts_command_providers.py +++ b/tests/tools/test_tts_command_providers.py @@ -243,10 +243,19 @@ class TestConfigGetters: assert _get_command_tts_output_format({"format": "ogg"}, "/tmp/clip.xyz") == "ogg" def test_output_format_rejects_unknown(self): - assert _get_command_tts_output_format({"format": "m4a"}) == DEFAULT_COMMAND_TTS_OUTPUT_FORMAT + assert _get_command_tts_output_format({"format": "midi"}) == DEFAULT_COMMAND_TTS_OUTPUT_FORMAT def test_output_format_supported_set(self): - assert COMMAND_TTS_OUTPUT_FORMATS == frozenset({"mp3", "wav", "ogg", "flac"}) + assert COMMAND_TTS_OUTPUT_FORMATS == frozenset( + {"mp3", "wav", "ogg", "flac", "m4a", "aac", "amr", "opus"} + ) + + def test_output_format_accepts_extended_formats(self): + # m4a/aac/amr/opus are common ffmpeg-producible containers/codecs; + # honored both via explicit config and via the output path suffix. + for fmt in ("m4a", "aac", "amr", "opus"): + assert _get_command_tts_output_format({"format": fmt}) == fmt + assert _get_command_tts_output_format({}, f"/tmp/clip.{fmt}") == fmt def test_voice_compatible_boolean(self): assert _is_command_tts_voice_compatible({"voice_compatible": True}) is True diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 92a177fd9bf91..2b9b33109a7c0 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -623,7 +623,9 @@ BUILTIN_TTS_PROVIDERS = frozenset({ DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS = 120 DEFAULT_COMMAND_TTS_OUTPUT_FORMAT = "mp3" -COMMAND_TTS_OUTPUT_FORMATS = frozenset({"mp3", "wav", "ogg", "flac"}) +COMMAND_TTS_OUTPUT_FORMATS = frozenset( + {"mp3", "wav", "ogg", "flac", "m4a", "aac", "amr", "opus"} +) DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH = 5000 # Platforms whose native voice-bubble delivery requires Ogg/Opus audio. diff --git a/website/docs/user-guide/features/tts.md b/website/docs/user-guide/features/tts.md index 8c15602e6d5fc..6713f77f14a05 100644 --- a/website/docs/user-guide/features/tts.md +++ b/website/docs/user-guide/features/tts.md @@ -284,6 +284,8 @@ tts: output_format: wav ``` +**Supported `output_format` values:** `mp3` (default), `wav`, `ogg`, `flac`, `m4a`, `aac`, `amr`, `opus`. Your command must actually produce that format (e.g. via `ffmpeg`); Hermes only validates the declared value and names the output file accordingly. An unknown value falls back to `mp3`. The chosen format is also exposed to the command as the `{format}` placeholder. + #### Example: Doubao (Chinese seed-tts-2.0) For high-quality Chinese TTS via ByteDance's [seed-tts-2.0](https://www.volcengine.com/docs/6561/1257544) bidirectional-streaming API, install the [`doubao-speech`](https://pypi.org/project/doubao-speech/) PyPI package and wire it in as a command provider: