2019-11-05 21:39:15 +00:00
|
|
|
"""Support for the cloud for text to speech service."""
|
|
|
|
|
|
|
|
from hass_nabucasa import Cloud
|
2023-04-18 12:42:03 +00:00
|
|
|
from hass_nabucasa.voice import MAP_VOICE, TTS_VOICES, AudioOutput, VoiceError
|
2019-11-05 21:39:15 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2023-04-06 15:42:55 +00:00
|
|
|
from homeassistant.components.tts import (
|
|
|
|
ATTR_AUDIO_OUTPUT,
|
2023-04-23 02:01:32 +00:00
|
|
|
ATTR_VOICE,
|
2023-04-06 15:42:55 +00:00
|
|
|
CONF_LANG,
|
|
|
|
PLATFORM_SCHEMA,
|
|
|
|
Provider,
|
2023-04-22 00:41:14 +00:00
|
|
|
Voice,
|
2023-04-06 15:42:55 +00:00
|
|
|
)
|
2023-04-19 11:47:49 +00:00
|
|
|
from homeassistant.core import callback
|
2019-11-05 21:39:15 +00:00
|
|
|
|
|
|
|
from .const import DOMAIN
|
|
|
|
|
2023-04-06 15:42:55 +00:00
|
|
|
ATTR_GENDER = "gender"
|
2019-11-05 21:39:15 +00:00
|
|
|
|
2023-04-18 12:42:03 +00:00
|
|
|
SUPPORT_LANGUAGES = list(TTS_VOICES)
|
2019-11-05 21:39:15 +00:00
|
|
|
|
2020-11-17 15:31:59 +00:00
|
|
|
|
|
|
|
def validate_lang(value):
|
|
|
|
"""Validate chosen gender or language."""
|
2021-10-17 18:19:56 +00:00
|
|
|
if (lang := value.get(CONF_LANG)) is None:
|
2021-01-12 23:05:30 +00:00
|
|
|
return value
|
|
|
|
|
2023-04-06 15:42:55 +00:00
|
|
|
if (gender := value.get(ATTR_GENDER)) is None:
|
|
|
|
gender = value[ATTR_GENDER] = next(
|
2020-11-17 15:31:59 +00:00
|
|
|
(chk_gender for chk_lang, chk_gender in MAP_VOICE if chk_lang == lang), None
|
|
|
|
)
|
|
|
|
|
|
|
|
if (lang, gender) not in MAP_VOICE:
|
|
|
|
raise vol.Invalid("Unsupported language and gender specified.")
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
PLATFORM_SCHEMA = vol.All(
|
|
|
|
PLATFORM_SCHEMA.extend(
|
|
|
|
{
|
2021-01-12 23:05:30 +00:00
|
|
|
vol.Optional(CONF_LANG): str,
|
2023-04-06 15:42:55 +00:00
|
|
|
vol.Optional(ATTR_GENDER): str,
|
2020-11-17 15:31:59 +00:00
|
|
|
}
|
|
|
|
),
|
|
|
|
validate_lang,
|
2019-11-05 21:39:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def async_get_engine(hass, config, discovery_info=None):
|
|
|
|
"""Set up Cloud speech component."""
|
|
|
|
cloud: Cloud = hass.data[DOMAIN]
|
|
|
|
|
|
|
|
if discovery_info is not None:
|
2021-01-12 23:05:30 +00:00
|
|
|
language = None
|
|
|
|
gender = None
|
2019-11-05 21:39:15 +00:00
|
|
|
else:
|
|
|
|
language = config[CONF_LANG]
|
2023-04-06 15:42:55 +00:00
|
|
|
gender = config[ATTR_GENDER]
|
2019-11-05 21:39:15 +00:00
|
|
|
|
|
|
|
return CloudProvider(cloud, language, gender)
|
|
|
|
|
|
|
|
|
|
|
|
class CloudProvider(Provider):
|
|
|
|
"""NabuCasa Cloud speech API provider."""
|
|
|
|
|
2021-05-20 15:51:39 +00:00
|
|
|
def __init__(self, cloud: Cloud, language: str, gender: str) -> None:
|
2019-11-05 21:39:15 +00:00
|
|
|
"""Initialize cloud provider."""
|
|
|
|
self.cloud = cloud
|
|
|
|
self.name = "Cloud"
|
|
|
|
self._language = language
|
|
|
|
self._gender = gender
|
|
|
|
|
2021-01-12 23:05:30 +00:00
|
|
|
if self._language is not None:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._language, self._gender = cloud.client.prefs.tts_default_voice
|
|
|
|
cloud.client.prefs.async_listen_updates(self._sync_prefs)
|
|
|
|
|
|
|
|
async def _sync_prefs(self, prefs):
|
|
|
|
"""Sync preferences."""
|
|
|
|
self._language, self._gender = prefs.tts_default_voice
|
|
|
|
|
2019-11-05 21:39:15 +00:00
|
|
|
@property
|
|
|
|
def default_language(self):
|
|
|
|
"""Return the default language."""
|
|
|
|
return self._language
|
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_languages(self):
|
|
|
|
"""Return list of supported languages."""
|
|
|
|
return SUPPORT_LANGUAGES
|
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_options(self):
|
|
|
|
"""Return list of supported options like voice, emotion."""
|
2023-04-18 12:42:03 +00:00
|
|
|
return [ATTR_GENDER, ATTR_VOICE, ATTR_AUDIO_OUTPUT]
|
2019-11-05 21:39:15 +00:00
|
|
|
|
2023-04-19 11:47:49 +00:00
|
|
|
@callback
|
2023-04-22 00:41:14 +00:00
|
|
|
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
|
2023-04-19 11:47:49 +00:00
|
|
|
"""Return a list of supported voices for a language."""
|
2023-04-22 00:41:14 +00:00
|
|
|
if not (voices := TTS_VOICES.get(language)):
|
|
|
|
return None
|
|
|
|
return [Voice(voice, voice) for voice in voices]
|
2023-04-19 11:47:49 +00:00
|
|
|
|
2019-11-05 21:39:15 +00:00
|
|
|
@property
|
|
|
|
def default_options(self):
|
|
|
|
"""Return a dict include default options."""
|
2023-04-06 15:42:55 +00:00
|
|
|
return {
|
|
|
|
ATTR_GENDER: self._gender,
|
|
|
|
ATTR_AUDIO_OUTPUT: AudioOutput.MP3,
|
|
|
|
}
|
2019-11-05 21:39:15 +00:00
|
|
|
|
|
|
|
async def async_get_tts_audio(self, message, language, options=None):
|
|
|
|
"""Load TTS from NabuCasa Cloud."""
|
|
|
|
# Process TTS
|
|
|
|
try:
|
|
|
|
data = await self.cloud.voice.process_tts(
|
2023-04-18 12:42:03 +00:00
|
|
|
text=message,
|
|
|
|
language=language,
|
|
|
|
gender=options.get(ATTR_GENDER),
|
|
|
|
voice=options.get(ATTR_VOICE),
|
2023-04-06 15:42:55 +00:00
|
|
|
output=options[ATTR_AUDIO_OUTPUT],
|
2019-11-05 21:39:15 +00:00
|
|
|
)
|
|
|
|
except VoiceError:
|
|
|
|
return (None, None)
|
|
|
|
|
2023-04-06 15:42:55 +00:00
|
|
|
return (str(options[ATTR_AUDIO_OUTPUT]), data)
|