Files
baidu-voice/custom_components/baidu_voice/tts.py
T

192 lines
6.0 KiB
Python

"""Support for Baidu TTS."""
from __future__ import annotations
import logging
from typing import Any
from aip import AipSpeech
from homeassistant.components.tts import (
TextToSpeechEntity,
TtsAudioType,
Voice,
callback,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .const import (
CONF_API_KEY,
CONF_APP_ID,
CONF_SECRET_KEY,
TTS_DEFAULT_FILEFORMAT,
TTS_DEFAULT_LANGUAGE,
TTS_DEFAULT_PITCH,
TTS_DEFAULT_SPEED,
TTS_DEFAULT_VOICE,
TTS_DEFAULT_VOLUME,
TTS_FILEFORMAT_MAP,
TTS_LANGUAGES,
TTS_SUPPORTED_VOICES,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities,
) -> None:
"""Set up Baidu TTS from a config entry."""
_LOGGER.debug("Setting up Baidu TTS")
entity = BaiduTTSEntity(hass, config_entry)
async_add_entities([entity])
class BaiduTTSEntity(TextToSpeechEntity):
"""Represent a Baidu TTS entity."""
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize the Baidu TTS entity."""
self._config_entry = config_entry
# Generate unique ID and set name
app_id = config_entry.data[CONF_APP_ID]
self._attr_unique_id = f"baidu_tts_{app_id}"
self._attr_name = "Baidu TTS"
# Initialize the TTS client
self._client = AipSpeech(
app_id,
config_entry.data[CONF_API_KEY],
config_entry.data[CONF_SECRET_KEY],
)
@property
def default_language(self) -> str:
"""Return the default language."""
return TTS_DEFAULT_LANGUAGE
@property
def supported_languages(self) -> list[str]:
"""Return a list of supported languages."""
return list(TTS_LANGUAGES.keys())
@property
def supported_options(self) -> list[str]:
"""Return list of supported options."""
return ["speed", "pitch", "volume", "voice", "fileformat"]
@property
def default_options(self) -> dict[str, Any]:
"""Return a dict of default options."""
# Get current values from config_entry.options first, then data, then fall back to defaults
return {
"speed": self._config_entry.options.get(
"speed", self._config_entry.data.get("speed", TTS_DEFAULT_SPEED)
),
"pitch": self._config_entry.options.get(
"pitch", self._config_entry.data.get("pitch", TTS_DEFAULT_PITCH)
),
"volume": self._config_entry.options.get(
"volume", self._config_entry.data.get("volume", TTS_DEFAULT_VOLUME)
),
"voice": self._config_entry.options.get(
"voice", self._config_entry.data.get("voice", TTS_DEFAULT_VOICE)
),
"fileformat": self._config_entry.options.get(
"fileformat",
self._config_entry.data.get("fileformat", TTS_DEFAULT_FILEFORMAT),
),
}
@callback
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
"""Return a list of supported voices for a language."""
voices = []
for voice_id, voice_name in TTS_SUPPORTED_VOICES.items():
voices.append(Voice(str(voice_id), voice_name))
return voices
def get_tts_audio(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Get TTS audio from Baidu."""
# options > config_entry.data > default
try:
speed = int(
options.get("speed")
or self._config_entry.data.get("speed")
or TTS_DEFAULT_SPEED
)
pitch = int(
options.get("pitch")
or self._config_entry.data.get("pitch")
or TTS_DEFAULT_PITCH
)
volume = int(
options.get("volume")
or self._config_entry.data.get("volume")
or TTS_DEFAULT_VOLUME
)
voice = int(
options.get("voice")
or self._config_entry.data.get("voice")
or TTS_DEFAULT_VOICE
)
fileformat = int(
options.get("fileformat")
or self._config_entry.data.get("fileformat")
or TTS_DEFAULT_FILEFORMAT
)
except ValueError as ex:
_LOGGER.error("Error parsing options: %s", ex)
raise HomeAssistantError(f"Invalid option value: {ex}") from ex
format_config = TTS_FILEFORMAT_MAP.get(fileformat, TTS_FILEFORMAT_MAP[6])
_LOGGER.debug("Using audio format: %s", format_config)
api_params = {
"spd": speed,
"pit": pitch,
"vol": volume,
"per": voice,
"aue": fileformat,
}
_LOGGER.debug("API parameters: %s", api_params)
try:
result = self._client.synthesis(
message,
language,
1, # Use standard voice synthesis
api_params,
)
if isinstance(result, dict):
error_msg = result.get("err_msg", "Unknown error")
error_no = result.get("err_no", "Unknown")
_LOGGER.error(
"Baidu TTS API error - error_no: %s, error_msg: %s, full_result: %s",
error_no,
error_msg,
result,
)
return None, None
_LOGGER.debug("Successfully generated audio, size: %d bytes", len(result))
except Exception as ex:
_LOGGER.error(
"Error during TTS generation: %s, type: %s",
str(ex),
type(ex).__name__,
)
raise HomeAssistantError("Failed to generate TTS audio") from ex
return format_config, result