1 """Support for the Amazon Polly text to speech service."""
3 from __future__
import annotations
5 from collections
import defaultdict
7 from typing
import Any, Final
11 import voluptuous
as vol
14 PLATFORM_SCHEMA
as TTS_PLATFORM_SCHEMA,
29 AWS_CONF_CONNECT_TIMEOUT,
30 AWS_CONF_MAX_POOL_CONNECTIONS,
31 AWS_CONF_READ_TIMEOUT,
37 CONF_SECRET_ACCESS_KEY,
40 CONTENT_TYPE_EXTENSIONS,
42 DEFAULT_OUTPUT_FORMAT,
47 SUPPORTED_OUTPUT_FORMATS,
48 SUPPORTED_SAMPLE_RATES,
49 SUPPORTED_SAMPLE_RATES_MAP,
53 _LOGGER: Final = logging.getLogger(__name__)
55 PLATFORM_SCHEMA: Final = TTS_PLATFORM_SCHEMA.extend(
57 vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(SUPPORTED_REGIONS),
58 vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string,
59 vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string,
60 vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string,
61 vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): vol.In(SUPPORTED_VOICES),
62 vol.Optional(CONF_ENGINE, default=DEFAULT_ENGINE): vol.In(SUPPORTED_ENGINES),
63 vol.Optional(CONF_OUTPUT_FORMAT, default=DEFAULT_OUTPUT_FORMAT): vol.In(
64 SUPPORTED_OUTPUT_FORMATS
66 vol.Optional(CONF_SAMPLE_RATE): vol.All(
67 cv.string, vol.In(SUPPORTED_SAMPLE_RATES)
69 vol.Optional(CONF_TEXT_TYPE, default=DEFAULT_TEXT_TYPE): vol.In(
79 discovery_info: DiscoveryInfoType |
None =
None,
81 """Set up Amazon Polly speech component."""
82 output_format = config[CONF_OUTPUT_FORMAT]
83 sample_rate = config.get(CONF_SAMPLE_RATE, DEFAULT_SAMPLE_RATES[output_format])
84 if sample_rate
not in SUPPORTED_SAMPLE_RATES_MAP[output_format]:
86 "%s is not a valid sample rate for %s", sample_rate, output_format
90 config[CONF_SAMPLE_RATE] = sample_rate
92 profile: str |
None = config.get(CONF_PROFILE_NAME)
94 if profile
is not None:
95 boto3.setup_default_session(profile_name=profile)
98 CONF_REGION: config[CONF_REGION],
99 CONF_ACCESS_KEY_ID: config.get(CONF_ACCESS_KEY_ID),
100 CONF_SECRET_ACCESS_KEY: config.get(CONF_SECRET_ACCESS_KEY),
101 "config": botocore.config.Config(
102 connect_timeout=AWS_CONF_CONNECT_TIMEOUT,
103 read_timeout=AWS_CONF_READ_TIMEOUT,
104 max_pool_connections=AWS_CONF_MAX_POOL_CONNECTIONS,
108 del config[CONF_REGION]
109 del config[CONF_ACCESS_KEY_ID]
110 del config[CONF_SECRET_ACCESS_KEY]
112 polly_client = boto3.client(
"polly", **aws_config)
114 supported_languages: list[str] = []
116 all_voices: dict[str, dict[str, str]] = {}
118 all_engines: dict[str, set[str]] = defaultdict(set)
120 all_voices_req = polly_client.describe_voices()
122 for voice
in all_voices_req.get(
"Voices", []):
123 voice_id: str |
None = voice.get(
"Id")
126 all_voices[voice_id] = voice
127 language_code: str |
None = voice.get(
"LanguageCode")
128 if language_code
is not None and language_code
not in supported_languages:
129 supported_languages.append(language_code)
130 for engine
in voice.get(
"SupportedEngines"):
131 all_engines[engine].
add(voice_id)
134 polly_client, config, supported_languages, all_voices, all_engines
139 """Amazon Polly speech api provider."""
143 polly_client: boto3.client,
145 supported_languages: list[str],
146 all_voices: dict[str, dict[str, str]],
147 all_engines: dict[str, set[str]],
149 """Initialize Amazon Polly provider for TTS."""
155 self.default_voice: str = self.
configconfig[CONF_VOICE]
156 self.default_engine: str = self.
configconfig[CONF_ENGINE]
161 """Return a list of supported languages."""
166 """Return the default language."""
167 return self.
all_voicesall_voices.
get(self.default_voice, {}).
get(
"LanguageCode")
171 """Return dict include default options."""
172 return {CONF_VOICE: self.default_voice, CONF_ENGINE: self.default_engine}
176 """Return a list of supported options."""
177 return [CONF_VOICE, CONF_ENGINE]
183 options: dict[str, Any],
185 """Request TTS file from Polly."""
186 voice_id = options.get(CONF_VOICE, self.default_voice)
187 voice_in_dict = self.
all_voicesall_voices[voice_id]
188 if language != voice_in_dict.get(
"LanguageCode"):
189 _LOGGER.error(
"%s does not support the %s language", voice_id, language)
192 engine = options.get(CONF_ENGINE, self.default_engine)
193 if voice_id
not in self.
all_enginesall_engines[engine]:
194 _LOGGER.error(
"%s does not support the %s engine", voice_id, engine)
197 _LOGGER.debug(
"Requesting TTS file for text: %s", message)
198 resp = self.
clientclient.synthesize_speech(
200 OutputFormat=self.
configconfig[CONF_OUTPUT_FORMAT],
201 SampleRate=self.
configconfig[CONF_SAMPLE_RATE],
203 TextType=self.
configconfig[CONF_TEXT_TYPE],
207 _LOGGER.debug(
"Reply received for TTS: %s", message)
209 CONTENT_TYPE_EXTENSIONS[resp.get(
"ContentType")],
210 resp.get(
"AudioStream").read(),
TtsAudioType get_tts_audio(self, str message, str language, dict[str, Any] options)
str|None default_language(self)
None __init__(self, boto3.client polly_client, ConfigType config, list[str] supported_languages, dict[str, dict[str, str]] all_voices, dict[str, set[str]] all_engines)
list[str] supported_options(self)
list[str] supported_languages(self)
dict[str, str] default_options(self)
Provider|None get_engine(HomeAssistant hass, ConfigType config, DiscoveryInfoType|None discovery_info=None)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)