mirror of
https://github.com/howelljiang/baidu-voice.git
synced 2026-07-21 22:53:57 +08:00
250 lines
8.4 KiB
Python
250 lines
8.4 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 homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
|
|
|
from .const import (
|
|
CONF_API_KEY,
|
|
CONF_APP_ID,
|
|
CONF_SECRET_KEY,
|
|
DOMAIN,
|
|
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.hass = hass
|
|
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 = "百度语音合成"
|
|
|
|
# Add device info
|
|
self._attr_device_info = DeviceInfo(
|
|
identifiers={(DOMAIN, app_id)},
|
|
name="百度语音",
|
|
manufacturer="百度",
|
|
model="语音合成服务",
|
|
entry_type=DeviceEntryType.SERVICE,
|
|
)
|
|
|
|
_LOGGER.debug(
|
|
"Initializing Baidu TTS entity with app_id: %s, unique_id: %s, name: %s",
|
|
app_id,
|
|
self._attr_unique_id,
|
|
self._attr_name,
|
|
)
|
|
|
|
# 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."""
|
|
_LOGGER.debug("Getting default language: %s", TTS_DEFAULT_LANGUAGE)
|
|
return TTS_DEFAULT_LANGUAGE
|
|
|
|
@property
|
|
def supported_languages(self) -> list[str]:
|
|
"""Return a list of supported languages."""
|
|
languages = list(TTS_LANGUAGES.keys())
|
|
_LOGGER.debug("Getting supported languages: %s", languages)
|
|
return languages
|
|
|
|
@property
|
|
def supported_options(self) -> list[str]:
|
|
"""Return list of supported options."""
|
|
options = ["speed", "pitch", "volume", "voice", "fileformat"]
|
|
_LOGGER.debug("Getting supported options: %s", options)
|
|
return options
|
|
|
|
@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
|
|
options = {
|
|
"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),
|
|
),
|
|
}
|
|
_LOGGER.debug("Getting default options: %s", options)
|
|
return options
|
|
|
|
@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))
|
|
_LOGGER.debug("Getting supported voices for language %s: %s", language, voices)
|
|
return voices
|
|
|
|
def get_tts_audio(
|
|
self, message: str, language: str, options: dict[str, Any]
|
|
) -> TtsAudioType:
|
|
"""Get TTS audio from Baidu."""
|
|
_LOGGER.debug("=================== TTS Debug Info Start ===================")
|
|
_LOGGER.debug("Message: %s", message)
|
|
_LOGGER.debug("Language: %s", language)
|
|
_LOGGER.debug("Raw options dict: %s", options)
|
|
_LOGGER.debug("Config entry data: %s", self._config_entry.data)
|
|
_LOGGER.debug("Config entry options: %s", self._config_entry.options)
|
|
|
|
# 获取配置值,优先级:options > config_entry.options > config_entry.data > 默认值
|
|
try:
|
|
speed = int(
|
|
options.get("speed")
|
|
or self._config_entry.options.get("speed")
|
|
or self._config_entry.data.get("speed")
|
|
or TTS_DEFAULT_SPEED
|
|
)
|
|
pitch = int(
|
|
options.get("pitch")
|
|
or self._config_entry.options.get("pitch")
|
|
or self._config_entry.data.get("pitch")
|
|
or TTS_DEFAULT_PITCH
|
|
)
|
|
volume = int(
|
|
options.get("volume")
|
|
or self._config_entry.options.get("volume")
|
|
or self._config_entry.data.get("volume")
|
|
or TTS_DEFAULT_VOLUME
|
|
)
|
|
voice = int(
|
|
options.get("voice")
|
|
or self._config_entry.options.get("voice")
|
|
or self._config_entry.data.get("voice")
|
|
or TTS_DEFAULT_VOICE
|
|
)
|
|
fileformat = int(
|
|
options.get("fileformat")
|
|
or self._config_entry.options.get("fileformat")
|
|
or self._config_entry.data.get("fileformat")
|
|
or TTS_DEFAULT_FILEFORMAT
|
|
)
|
|
|
|
_LOGGER.debug(
|
|
"Parsed values - speed: %s(%s), pitch: %s(%s), volume: %s(%s), "
|
|
"voice: %s(%s), fileformat: %s(%s)",
|
|
speed,
|
|
type(speed),
|
|
pitch,
|
|
type(pitch),
|
|
volume,
|
|
type(volume),
|
|
voice,
|
|
type(voice),
|
|
fileformat,
|
|
type(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参数
|
|
api_params = {
|
|
"spd": speed,
|
|
"pit": pitch,
|
|
"vol": volume,
|
|
"per": voice,
|
|
"aue": fileformat,
|
|
}
|
|
_LOGGER.debug("API parameters: %s", api_params)
|
|
|
|
try:
|
|
_LOGGER.debug("Calling Baidu TTS API")
|
|
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
|
|
|
|
_LOGGER.debug("=================== TTS Debug Info End ===================")
|
|
return format_config, result
|