core/homeassistant/components/google_cloud/tts.py

156 lines
4.7 KiB
Python

"""Support for the Google Cloud TTS service."""
import logging
import os
from google.api_core.exceptions import GoogleAPIError
from google.cloud import texttospeech
import voluptuous as vol
from homeassistant.components.tts import (
CONF_LANG,
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
Provider,
Voice,
)
from homeassistant.core import HomeAssistant, callback
from .const import (
CONF_ENCODING,
CONF_GAIN,
CONF_GENDER,
CONF_KEY_FILE,
CONF_PITCH,
CONF_PROFILES,
CONF_SPEED,
CONF_TEXT_TYPE,
CONF_VOICE,
DEFAULT_LANG,
)
from .helpers import async_tts_voices, tts_options_schema, tts_platform_schema
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = TTS_PLATFORM_SCHEMA.extend(tts_platform_schema().schema)
async def async_get_engine(hass, config, discovery_info=None):
"""Set up Google Cloud TTS component."""
if key_file := config.get(CONF_KEY_FILE):
key_file = hass.config.path(key_file)
if not os.path.isfile(key_file):
_LOGGER.error("File %s doesn't exist", key_file)
return None
if key_file:
client = texttospeech.TextToSpeechAsyncClient.from_service_account_json(
key_file
)
else:
client = texttospeech.TextToSpeechAsyncClient()
try:
voices = await async_tts_voices(client)
except GoogleAPIError as err:
_LOGGER.error("Error from calling list_voices: %s", err)
return None
return GoogleCloudTTSProvider(
hass,
client,
voices,
config.get(CONF_LANG, DEFAULT_LANG),
tts_options_schema(config, voices),
)
class GoogleCloudTTSProvider(Provider):
"""The Google Cloud TTS API provider."""
def __init__(
self,
hass: HomeAssistant,
client: texttospeech.TextToSpeechAsyncClient,
voices: dict[str, list[str]],
language,
options_schema,
) -> None:
"""Init Google Cloud TTS service."""
self.hass = hass
self.name = "Google Cloud TTS"
self._client = client
self._voices = voices
self._language = language
self._options_schema = options_schema
@property
def supported_languages(self):
"""Return list of supported languages."""
return list(self._voices)
@property
def default_language(self):
"""Return the default language."""
return self._language
@property
def supported_options(self):
"""Return a list of supported options."""
return [option.schema for option in self._options_schema.schema]
@property
def default_options(self):
"""Return a dict including default options."""
return self._options_schema({})
@callback
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
"""Return a list of supported voices for a language."""
if not (voices := self._voices.get(language)):
return None
return [Voice(voice, voice) for voice in voices]
async def async_get_tts_audio(self, message, language, options):
"""Load TTS from google."""
try:
options = self._options_schema(options)
except vol.Invalid as err:
_LOGGER.error("Error: %s when validating options: %s", err, options)
return None, None
encoding = texttospeech.AudioEncoding[options[CONF_ENCODING]]
gender = texttospeech.SsmlVoiceGender[options[CONF_GENDER]]
voice = options[CONF_VOICE]
if voice:
gender = None
if not voice.startswith(language):
language = voice[:5]
request = texttospeech.SynthesizeSpeechRequest(
input=texttospeech.SynthesisInput(**{options[CONF_TEXT_TYPE]: message}),
voice=texttospeech.VoiceSelectionParams(
language_code=language,
ssml_gender=gender,
name=voice,
),
audio_config=texttospeech.AudioConfig(
audio_encoding=encoding,
speaking_rate=options[CONF_SPEED],
pitch=options[CONF_PITCH],
volume_gain_db=options[CONF_GAIN],
effects_profile_id=options[CONF_PROFILES],
),
)
try:
response = await self._client.synthesize_speech(request, timeout=10)
except GoogleAPIError as err:
_LOGGER.error("Error occurred during Google Cloud TTS call: %s", err)
return None, None
if encoding == texttospeech.AudioEncoding.MP3:
extension = "mp3"
elif encoding == texttospeech.AudioEncoding.OGG_OPUS:
extension = "ogg"
else:
extension = "wav"
return extension, response.audio_content