Return the requested format for OpenAI TTS (#169839)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Denis Shulyaka
2026-05-05 10:29:30 -04:00
committed by Paulus Schoutsen
co-authored by Copilot Autofix powered by AI
parent c12e1b5f4a
commit c5e08b2409
2 changed files with 84 additions and 10 deletions
@@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal
from openai import OpenAIError
from propcache.api import cached_property
@@ -166,14 +166,15 @@ class OpenAITTSEntity(TextToSpeechEntity, OpenAIBaseLLMEntity):
client = self.entry.runtime_data
response_format = options[ATTR_PREFERRED_FORMAT]
if response_format not in self._supported_formats:
# common aliases
if response_format == "ogg":
response_format = "opus"
elif response_format == "raw":
response_format = "pcm"
else:
response_format = self.default_options[ATTR_PREFERRED_FORMAT]
if response_format in ("ogg", "oga"):
codec: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] = "opus"
elif response_format == "raw":
response_format = codec = "pcm"
elif response_format not in self._supported_formats:
response_format = self.default_options[ATTR_PREFERRED_FORMAT]
codec = response_format
else:
codec = response_format
try:
async with client.audio.speech.with_streaming_response.create(
@@ -182,7 +183,7 @@ class OpenAITTSEntity(TextToSpeechEntity, OpenAIBaseLLMEntity):
input=message,
instructions=str(options.get(CONF_PROMPT)),
speed=options.get(CONF_TTS_SPEED, RECOMMENDED_TTS_SPEED),
response_format=response_format,
response_format=codec,
) as response:
response_data = bytearray()
async for chunk in response.iter_bytes():
@@ -118,6 +118,79 @@ async def test_tts(
)
@pytest.mark.parametrize(
("preferred_format", "expected_response_format"),
[
("ogg", "opus"),
("oga", "opus"),
("mp3", "mp3"),
],
)
@pytest.mark.usefixtures("mock_init_component")
async def test_tts_preferred_format(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_create_speech: MagicMock,
calls: list[ServiceCall],
preferred_format: str,
expected_response_format: str,
) -> None:
"""Test text to speech preferred format handling."""
mock_create_speech.return_value = [b"mock audio data"]
await hass.services.async_call(
tts.DOMAIN,
"speak",
{
ATTR_ENTITY_ID: "tts.openai_tts",
tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something",
tts.ATTR_MESSAGE: "There is a person at the front door.",
tts.ATTR_OPTIONS: {tts.ATTR_PREFERRED_FORMAT: preferred_format},
},
blocking=True,
)
assert len(calls) == 1
assert (
await retrieve_media(hass, hass_client, calls[0].data[ATTR_MEDIA_CONTENT_ID])
== HTTPStatus.OK
)
mock_create_speech.assert_called_once_with(
model="gpt-4o-mini-tts",
voice="marin",
input="There is a person at the front door.",
instructions="",
speed=1.0,
response_format=expected_response_format,
)
@pytest.mark.usefixtures("mock_init_component")
async def test_tts_raw_preferred_format_returns_pcm(
hass: HomeAssistant,
mock_create_speech: MagicMock,
) -> None:
"""Test raw preferred format is returned as pcm."""
tts_entity = hass.data[tts.DOMAIN].get_entity("tts.openai_tts")
mock_create_speech.return_value = [b"mock audio data"]
result = await tts_entity.async_get_tts_audio(
"There is a person at the front door.",
"en-US",
{tts.ATTR_PREFERRED_FORMAT: "raw", tts.ATTR_VOICE: "marin"},
)
assert result == ("pcm", b"mock audio data")
mock_create_speech.assert_called_once_with(
model="gpt-4o-mini-tts",
voice="marin",
input="There is a person at the front door.",
instructions="",
speed=1.0,
response_format="pcm",
)
@pytest.mark.usefixtures("mock_init_component")
async def test_tts_error(
hass: HomeAssistant,