1 """Provide functionality for TTS."""
3 from __future__
import annotations
6 from collections.abc
import Mapping
7 from datetime
import datetime
8 from functools
import partial
10 from http
import HTTPStatus
19 from typing
import Any, Final, TypedDict, final
21 from aiohttp
import web
23 from mutagen.id3
import ID3, TextFrame
as ID3Text
24 from propcache
import cached_property
25 import voluptuous
as vol
31 ATTR_MEDIA_CONTENT_ID,
32 ATTR_MEDIA_CONTENT_TYPE,
70 from .helper
import get_engine_instance
71 from .legacy
import PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, Provider, async_setup_legacy
72 from .media_source
import generate_media_source_id, media_source_id_to_kwargs
73 from .models
import Voice
76 "async_default_engine",
77 "async_get_media_source_audio",
78 "async_support_options",
80 "ATTR_PREFERRED_FORMAT",
81 "ATTR_PREFERRED_SAMPLE_RATE",
82 "ATTR_PREFERRED_SAMPLE_CHANNELS",
83 "ATTR_PREFERRED_SAMPLE_BYTES",
86 "generate_media_source_id",
87 "PLATFORM_SCHEMA_BASE",
95 _LOGGER = logging.getLogger(__name__)
97 ATTR_PLATFORM =
"platform"
98 ATTR_AUDIO_OUTPUT =
"audio_output"
99 ATTR_PREFERRED_FORMAT =
"preferred_format"
100 ATTR_PREFERRED_SAMPLE_RATE =
"preferred_sample_rate"
101 ATTR_PREFERRED_SAMPLE_CHANNELS =
"preferred_sample_channels"
102 ATTR_PREFERRED_SAMPLE_BYTES =
"preferred_sample_bytes"
103 ATTR_MEDIA_PLAYER_ENTITY_ID =
"media_player_entity_id"
106 _DEFAULT_FORMAT =
"mp3"
107 _PREFFERED_FORMAT_OPTIONS: Final[set[str]] = {
108 ATTR_PREFERRED_FORMAT,
109 ATTR_PREFERRED_SAMPLE_RATE,
110 ATTR_PREFERRED_SAMPLE_CHANNELS,
111 ATTR_PREFERRED_SAMPLE_BYTES,
114 CONF_LANG =
"language"
116 SERVICE_CLEAR_CACHE =
"clear_cache"
118 _RE_LEGACY_VOICE_FILE = re.compile(
119 r"([a-f0-9]{40})_([^_]+)_([^_]+)_([a-z_]+)\.[a-z0-9]{3,4}"
121 _RE_VOICE_FILE = re.compile(
122 r"([a-f0-9]{40})_([^_]+)_([^_]+)_(tts\.[a-z0-9_]+)\.[a-z0-9]{3,4}"
124 KEY_PATTERN =
"{0}_{1}_{2}_{3}"
126 SCHEMA_SERVICE_CLEAR_CACHE = vol.Schema({})
130 """Cached TTS file."""
134 pending: asyncio.Task |
None
139 """Return the domain or entity id of the default engine.
141 Returns None if no engines found.
143 default_entity_id: str |
None =
None
145 for entity
in hass.data[DATA_COMPONENT].entities:
146 if entity.platform
and entity.platform.platform_name ==
"cloud":
147 return entity.entity_id
149 if default_entity_id
is None:
150 default_entity_id = entity.entity_id
152 return default_entity_id
or next(iter(hass.data[DATA_TTS_MANAGER].providers),
None)
159 Returns None if no engines found or invalid engine passed in.
161 if engine
is not None:
163 not hass.data[DATA_COMPONENT].
get_entity(engine)
164 and engine
not in hass.data[DATA_TTS_MANAGER].providers
175 language: str |
None =
None,
176 options: dict |
None =
None,
178 """Return if an engine supports options."""
183 hass.data[DATA_TTS_MANAGER].process_options(engine_instance, language, options)
184 except HomeAssistantError:
192 media_source_id: str,
193 ) -> tuple[str, bytes]:
194 """Get TTS audio as extension, data."""
195 return await hass.data[DATA_TTS_MANAGER].async_get_tts_audio(
202 """Return a set with the union of languages supported by tts engines."""
205 for entity
in hass.data[DATA_COMPONENT].entities:
206 for language_tag
in entity.supported_languages:
207 languages.add(language_tag)
209 for tts_engine
in hass.data[DATA_TTS_MANAGER].providers.values():
210 for language_tag
in tts_engine.supported_languages:
211 languages.add(language_tag)
221 to_sample_rate: int |
None =
None,
222 to_sample_channels: int |
None =
None,
223 to_sample_bytes: int |
None =
None,
225 """Convert audio to a preferred format using ffmpeg."""
226 ffmpeg_manager = ffmpeg.get_ffmpeg_manager(hass)
227 return await hass.async_add_executor_job(
229 ffmpeg_manager.binary,
233 to_sample_rate=to_sample_rate,
234 to_sample_channels=to_sample_channels,
235 to_sample_bytes=to_sample_bytes,
245 to_sample_rate: int |
None =
None,
246 to_sample_channels: int |
None =
None,
247 to_sample_bytes: int |
None =
None,
249 """Convert audio to a preferred format using ffmpeg."""
254 with tempfile.NamedTemporaryFile(
255 mode=
"wb+", suffix=f
".{to_extension}"
268 command.extend([
"-f", to_extension])
270 if to_sample_rate
is not None:
271 command.extend([
"-ar",
str(to_sample_rate)])
273 if to_sample_channels
is not None:
274 command.extend([
"-ac",
str(to_sample_channels)])
276 if to_extension ==
"mp3":
278 command.extend([
"-q:a",
"0"])
280 if to_sample_bytes == 2:
282 command.extend([
"-sample_fmt",
"s16"])
284 command.append(output_file.name)
286 with subprocess.Popen(
287 command, stdin=subprocess.PIPE, stderr=subprocess.PIPE
289 _stdout, stderr = proc.communicate(input=audio_bytes)
290 if proc.returncode != 0:
291 _LOGGER.error(stderr.decode())
293 f
"Unexpected error while running ffmpeg with arguments: {command}."
294 "See log for details."
298 return output_file.read()
301 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
303 websocket_api.async_register_command(hass, websocket_list_engines)
304 websocket_api.async_register_command(hass, websocket_get_engine)
305 websocket_api.async_register_command(hass, websocket_list_engine_voices)
308 conf = config[DOMAIN][0]
if config.get(DOMAIN)
else {}
309 use_cache: bool = conf.get(CONF_CACHE, DEFAULT_CACHE)
310 cache_dir: str = conf.get(CONF_CACHE_DIR, DEFAULT_CACHE_DIR)
311 time_memory: int = conf.get(CONF_TIME_MEMORY, DEFAULT_TIME_MEMORY)
316 await tts.async_init_cache()
317 except (HomeAssistantError, KeyError):
318 _LOGGER.exception(
"Error on cache init")
321 hass.data[DATA_TTS_MANAGER] = tts
322 component = hass.data[DATA_COMPONENT] = EntityComponent[TextToSpeechEntity](
323 _LOGGER, DOMAIN, hass
326 component.register_shutdown()
333 component.async_register_entity_service(
336 vol.Required(ATTR_MEDIA_PLAYER_ENTITY_ID): cv.comp_entity_ids,
337 vol.Required(ATTR_MESSAGE): cv.string,
338 vol.Optional(ATTR_CACHE, default=DEFAULT_CACHE): cv.boolean,
339 vol.Optional(ATTR_LANGUAGE): cv.string,
340 vol.Optional(ATTR_OPTIONS): dict,
345 async
def async_clear_cache_handle(service: ServiceCall) ->
None:
346 """Handle clear cache service call."""
347 await tts.async_clear_cache()
349 hass.services.async_register(
352 async_clear_cache_handle,
353 schema=SCHEMA_SERVICE_CLEAR_CACHE,
356 for setup
in platform_setups:
363 hass.async_create_task(setup, eager_start=
True)
369 """Set up a config entry."""
374 """Unload a config entry."""
378 CACHED_PROPERTIES_WITH_ATTR_ = {
381 "supported_languages",
387 """Represent a single TTS engine."""
389 _attr_should_poll =
False
390 __last_tts_loaded: str |
None =
None
392 _attr_default_language: str
393 _attr_default_options: Mapping[str, Any] |
None =
None
394 _attr_supported_languages: list[str]
395 _attr_supported_options: list[str] |
None =
None
400 """Return the state of the entity."""
407 """Return a list of supported languages."""
408 return self._attr_supported_languages
412 """Return the default language."""
413 return self._attr_default_language
417 """Return a list of supported options like voice, emotions."""
418 return self._attr_supported_options
422 """Return a mapping with the default options."""
423 return self._attr_default_options
427 """Return a list of supported voices for a language."""
431 """Call when the entity is added to hass."""
435 except AttributeError
as err:
436 raise AttributeError(
437 "TTS entities must either set the '_attr_default_language' attribute or override the 'default_language' property"
441 except AttributeError
as err:
442 raise AttributeError(
443 "TTS entities must either set the '_attr_supported_languages' attribute or override the 'supported_languages' property"
448 and state.state
is not None
449 and state.state
not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
455 media_player_entity_id: list[str],
458 language: str |
None =
None,
459 options: dict |
None =
None,
461 """Speak via a Media Player."""
462 await self.
hasshass.services.async_call(
466 ATTR_ENTITY_ID: media_player_entity_id,
475 ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
476 ATTR_MEDIA_ANNOUNCE:
True,
484 self, message: str, language: str, options: dict[str, Any]
486 """Process an audio stream to TTS service.
488 Only streaming content is allowed!
493 message=message, language=language, options=options
497 self, message: str, language: str, options: dict[str, Any]
499 """Load tts audio file from the engine."""
500 raise NotImplementedError
503 self, message: str, language: str, options: dict[str, Any]
505 """Load tts audio file from the engine.
507 Return a tuple of file extension and data as bytes.
509 return await self.
hasshass.async_add_executor_job(
510 partial(self.
get_tts_audioget_tts_audio, message, language, options=options)
515 """Hashes an options dictionary."""
516 opts_hash = hashlib.blake2s(digest_size=5)
517 for key, value
in sorted(options.items()):
518 opts_hash.update(
str(key).encode())
519 opts_hash.update(
str(value).encode())
521 return opts_hash.hexdigest()
525 """Representation of a speech store."""
534 """Initialize a speech store."""
536 self.providers: dict[str, Provider] = {}
541 self.
file_cachefile_cache: dict[str, str] = {}
542 self.
mem_cachemem_cache: dict[str, TTSCache] = {}
545 self.filename_to_token: dict[str, str] = {}
546 self.token_to_filename: dict[str, str] = {}
549 """Init cache folder and fetch files."""
552 except OSError
as err:
557 except OSError
as err:
561 """Init config folder and load file cache."""
565 """Read file cache and delete files."""
568 def remove_files() -> None:
569 """Remove files from filesystem."""
570 for filename
in self.
file_cachefile_cache.values():
572 os.remove(os.path.join(self.
cache_dircache_dir, filename))
573 except OSError
as err:
574 _LOGGER.warning(
"Can't remove cache file '%s': %s", filename, err)
576 await self.
hasshass.async_add_executor_job(remove_files)
581 self, engine: str, provider: Provider, config: ConfigType
583 """Register a legacy TTS engine."""
584 provider.hass = self.
hasshass
585 if provider.name
is None:
586 provider.name = engine
587 self.providers[engine] = provider
589 self.
hasshass.config.components.add(
590 PLATFORM_FORMAT.format(domain=DOMAIN, platform=engine)
596 engine_instance: TextToSpeechEntity | Provider,
597 language: str |
None,
598 options: dict |
None,
599 ) -> tuple[str, dict[str, Any]]:
600 """Validate and process options."""
602 language = language
or engine_instance.default_language
605 or engine_instance.supported_languages
is None
606 or language
not in engine_instance.supported_languages
610 options = options
or {}
611 supported_options = engine_instance.supported_options
or []
614 invalid_opts: list[str] = []
615 merged_options =
dict(engine_instance.default_options
or {})
616 for option_name, option_value
in options.items():
620 if (option_name
in supported_options)
or (
621 option_name
in _PREFFERED_FORMAT_OPTIONS
623 merged_options[option_name] = option_value
625 invalid_opts.append(option_name)
630 return language, merged_options
636 cache: bool |
None =
None,
637 language: str |
None =
None,
638 options: dict |
None =
None,
640 """Get URL for play message.
642 This method is a coroutine.
647 language, options = self.
process_optionsprocess_options(engine_instance, language, options)
648 cache_key = self.
_generate_cache_key_generate_cache_key(message, language, options, engine)
649 use_cache = cache
if cache
is not None else self.
use_cacheuse_cache
653 filename = self.
mem_cachemem_cache[cache_key][
"filename"]
655 elif use_cache
and cache_key
in self.
file_cachefile_cache:
656 filename = self.
file_cachefile_cache[cache_key]
661 engine_instance, cache_key, message, use_cache, language, options
665 token = self.filename_to_token.
get(filename)
668 token = secrets.token_urlsafe(16) + os.path.splitext(filename)[1]
671 self.filename_to_token[filename] = token
672 self.token_to_filename[token] = filename
674 return f
"/api/tts_proxy/{token}"
680 cache: bool |
None =
None,
681 language: str |
None =
None,
682 options: dict |
None =
None,
683 ) -> tuple[str, bytes]:
684 """Fetch TTS audio."""
688 language, options = self.
process_optionsprocess_options(engine_instance, language, options)
689 cache_key = self.
_generate_cache_key_generate_cache_key(message, language, options, engine)
690 use_cache = cache
if cache
is not None else self.
use_cacheuse_cache
693 if cache_key
not in self.
mem_cachemem_cache:
694 if use_cache
and cache_key
in self.
file_cachefile_cache:
698 engine_instance, cache_key, message, use_cache, language, options
701 extension = os.path.splitext(self.
mem_cachemem_cache[cache_key][
"filename"])[1][1:]
702 cached = self.
mem_cachemem_cache[cache_key]
703 if pending := cached.get(
"pending"):
705 cached = self.
mem_cachemem_cache[cache_key]
706 return extension, cached[
"voice"]
713 options: dict |
None,
716 """Generate a cache key for a message."""
718 msg_hash = hashlib.sha1(bytes(message,
"utf-8")).hexdigest()
719 return KEY_PATTERN.format(
720 msg_hash, language.replace(
"_",
"-"), options_key, engine
725 engine_instance: TextToSpeechEntity | Provider,
730 options: dict[str, Any],
732 """Receive TTS, store for view in cache and return filename.
734 This method is a coroutine.
736 options =
dict(options
or {})
737 supported_options = engine_instance.supported_options
or []
747 if ATTR_PREFERRED_FORMAT
in supported_options:
748 final_extension = options.get(ATTR_PREFERRED_FORMAT, _DEFAULT_FORMAT)
750 final_extension = options.pop(ATTR_PREFERRED_FORMAT, _DEFAULT_FORMAT)
752 if ATTR_PREFERRED_SAMPLE_RATE
in supported_options:
753 sample_rate = options.get(ATTR_PREFERRED_SAMPLE_RATE)
755 sample_rate = options.pop(ATTR_PREFERRED_SAMPLE_RATE,
None)
757 if sample_rate
is not None:
758 sample_rate =
int(sample_rate)
760 if ATTR_PREFERRED_SAMPLE_CHANNELS
in supported_options:
761 sample_channels = options.get(ATTR_PREFERRED_SAMPLE_CHANNELS)
763 sample_channels = options.pop(ATTR_PREFERRED_SAMPLE_CHANNELS,
None)
765 if sample_channels
is not None:
766 sample_channels =
int(sample_channels)
768 if ATTR_PREFERRED_SAMPLE_BYTES
in supported_options:
769 sample_bytes = options.get(ATTR_PREFERRED_SAMPLE_BYTES)
771 sample_bytes = options.pop(ATTR_PREFERRED_SAMPLE_BYTES,
None)
773 if sample_bytes
is not None:
774 sample_bytes =
int(sample_bytes)
776 async
def get_tts_data() -> str:
777 """Handle data available."""
778 if engine_instance.name
is None or engine_instance.name
is UNDEFINED:
781 if isinstance(engine_instance, Provider):
782 extension, data = await engine_instance.async_get_tts_audio(
783 message, language, options
786 extension, data = await engine_instance.internal_async_get_tts_audio(
787 message, language, options
790 if data
is None or extension
is None:
792 f
"No TTS from {engine_instance.name} for '{message}'"
799 (final_extension != extension)
800 or (sample_rate
is not None)
801 or (sample_channels
is not None)
802 or (sample_bytes
is not None)
810 to_extension=final_extension,
811 to_sample_rate=sample_rate,
812 to_sample_channels=sample_channels,
813 to_sample_bytes=sample_bytes,
817 filename = f
"{cache_key}.{final_extension}".lower()
820 if not _RE_VOICE_FILE.match(filename)
and not _RE_LEGACY_VOICE_FILE.match(
824 f
"TTS filename '{filename}' from {engine_instance.name} is invalid!"
828 if final_extension ==
"mp3":
830 filename, data, engine_instance.name, message, language, options
836 self.
hasshass.async_create_task(
842 audio_task = self.
hasshass.async_create_task(get_tts_data(), eager_start=
False)
846 if audio_task.exception():
847 self.
mem_cachemem_cache.pop(cache_key,
None)
849 audio_task.add_done_callback(handle_error)
851 filename = f
"{cache_key}.{final_extension}".lower()
853 "filename": filename,
855 "pending": audio_task,
860 self, cache_key: str, filename: str, data: bytes
862 """Store voice data to file and file_cache.
864 This method is a coroutine.
866 voice_file = os.path.join(self.
cache_dircache_dir, filename)
868 def save_speech() -> None:
869 """Store speech to filesystem."""
870 with open(voice_file,
"wb")
as speech:
874 await self.
hasshass.async_add_executor_job(save_speech)
875 self.
file_cachefile_cache[cache_key] = filename
876 except OSError
as err:
877 _LOGGER.error(
"Can't write %s: %s", filename, err)
880 """Load voice from file cache into memory.
882 This method is a coroutine.
884 if not (filename := self.
file_cachefile_cache.
get(cache_key)):
887 voice_file = os.path.join(self.
cache_dircache_dir, filename)
889 def load_speech() -> bytes:
890 """Load a speech from filesystem."""
891 with open(voice_file,
"rb")
as speech:
895 data = await self.
hasshass.async_add_executor_job(load_speech)
896 except OSError
as err:
904 self, cache_key: str, filename: str, data: bytes
906 """Store data to memcache and set timer to remove it."""
908 "filename": filename,
914 def async_remove_from_mem(_: datetime) ->
None:
915 """Cleanup memcache."""
916 self.
mem_cachemem_cache.pop(cache_key,
None)
922 async_remove_from_mem,
923 name=
"tts remove_from_mem",
924 cancel_on_shutdown=
True,
929 """Read a voice file and return binary.
931 This method is a coroutine.
933 filename = self.token_to_filename.
get(token)
937 if not (record := _RE_VOICE_FILE.match(filename.lower()))
and not (
938 record := _RE_LEGACY_VOICE_FILE.match(filename.lower())
942 cache_key = KEY_PATTERN.format(
943 record.group(1), record.group(2), record.group(3), record.group(4)
946 if cache_key
not in self.
mem_cachemem_cache:
947 if cache_key
not in self.
file_cachefile_cache:
951 cached = self.
mem_cachemem_cache[cache_key]
952 if pending := cached.get(
"pending"):
954 cached = self.
mem_cachemem_cache[cache_key]
956 content, _ = mimetypes.guess_type(filename)
957 return content, cached[
"voice"]
966 options: dict |
None,
968 """Write ID3 tags to file.
973 data_bytes = io.BytesIO(data)
974 data_bytes.name = filename
980 if options
is not None and (voice := options.get(
"voice"))
is not None:
984 tts_file = mutagen.File(data_bytes)
985 if tts_file
is not None:
986 if not tts_file.tags:
988 if isinstance(tts_file.tags, ID3):
989 tts_file[
"artist"] = ID3Text(
993 tts_file[
"album"] = ID3Text(
997 tts_file[
"title"] = ID3Text(
1002 tts_file[
"artist"] = artist
1003 tts_file[
"album"] = album
1004 tts_file[
"title"] = message
1005 tts_file.save(data_bytes)
1006 except mutagen.MutagenError
as err:
1007 _LOGGER.error(
"ID3 tag error: %s", err)
1009 return data_bytes.getvalue()
1013 """Init cache folder."""
1014 if not os.path.isabs(cache_dir):
1015 cache_dir = hass.config.path(cache_dir)
1016 if not os.path.isdir(cache_dir):
1017 _LOGGER.info(
"Create cache dir %s", cache_dir)
1023 """Return a dict of given engine files."""
1026 folder_data = os.listdir(cache_dir)
1027 for file_data
in folder_data:
1028 if (record := _RE_VOICE_FILE.match(file_data))
or (
1029 record := _RE_LEGACY_VOICE_FILE.match(file_data)
1031 key = KEY_PATTERN.format(
1032 record.group(1), record.group(2), record.group(3), record.group(4)
1034 cache[key.lower()] = file_data.lower()
1039 """TTS view to get a url to a generated speech file."""
1041 requires_auth =
True
1042 url =
"/api/tts_get_url"
1043 name =
"api:tts:geturl"
1046 """Initialize a tts view."""
1049 async
def post(self, request: web.Request) -> web.Response:
1050 """Generate speech and provide url."""
1052 data = await request.json()
1054 return self.json_message(
"Invalid JSON specified", HTTPStatus.BAD_REQUEST)
1056 not data.get(
"engine_id")
1057 and not data.get(ATTR_PLATFORM)
1058 or not data.get(ATTR_MESSAGE)
1060 return self.json_message(
1061 "Must specify platform and message", HTTPStatus.BAD_REQUEST
1064 engine = data.get(
"engine_id")
or data[ATTR_PLATFORM]
1065 message = data[ATTR_MESSAGE]
1066 cache = data.get(ATTR_CACHE)
1067 language = data.get(ATTR_LANGUAGE)
1068 options = data.get(ATTR_OPTIONS)
1071 path = await self.
ttstts.async_get_url_path(
1072 engine, message, cache=cache, language=language, options=options
1074 except HomeAssistantError
as err:
1075 _LOGGER.error(
"Error on init tts: %s", err)
1076 return self.json({
"error": err}, HTTPStatus.BAD_REQUEST)
1081 return self.json({
"url": url,
"path": path})
1085 """TTS view to serve a speech audio."""
1087 requires_auth =
False
1088 url =
"/api/tts_proxy/{filename}"
1089 name =
"api:tts_speech"
1092 """Initialize a tts view."""
1095 async
def get(self, request: web.Request, filename: str) -> web.Response:
1096 """Start a get request."""
1099 content, data = await self.
ttstts.async_read_tts(filename)
1100 except HomeAssistantError
as err:
1101 _LOGGER.error(
"Error on load tts: %s", err)
1102 return web.Response(status=HTTPStatus.NOT_FOUND)
1104 return web.Response(body=data, content_type=content)
1107 @websocket_api.websocket_command(
{
"type": "tts/engine/list",
vol.Optional("country"): str,
1108 vol.Optional(
"language"): str,
1115 """List text to speech engines and, optionally, if they support a given language."""
1116 country = msg.get(
"country")
1117 language = msg.get(
"language")
1119 provider_info: dict[str, Any]
1120 entity_domains: set[str] = set()
1122 for entity
in hass.data[DATA_COMPONENT].entities:
1124 "engine_id": entity.entity_id,
1125 "supported_languages": entity.supported_languages,
1128 provider_info[
"supported_languages"] = language_util.matches(
1129 language, entity.supported_languages, country
1131 providers.append(provider_info)
1133 entity_domains.add(entity.platform.platform_name)
1134 for engine_id, provider
in hass.data[DATA_TTS_MANAGER].providers.items():
1136 "engine_id": engine_id,
1137 "name": provider.name,
1138 "supported_languages": provider.supported_languages,
1141 provider_info[
"supported_languages"] = language_util.matches(
1142 language, provider.supported_languages, country
1144 if engine_id
in entity_domains:
1145 provider_info[
"deprecated"] =
True
1146 providers.append(provider_info)
1148 connection.send_message(
1149 websocket_api.result_message(msg[
"id"], {
"providers": providers})
1153 @websocket_api.websocket_command(
{
"type": "tts/engine/get",
vol.Required("engine_id"): str,
1160 """Get text to speech engine info."""
1161 engine_id = msg[
"engine_id"]
1162 provider_info: dict[str, Any]
1164 provider: TextToSpeechEntity | Provider |
None = next(
1167 for entity
in hass.data[DATA_COMPONENT].entities
1168 if entity.entity_id == engine_id
1173 provider = hass.data[DATA_TTS_MANAGER].providers.get(engine_id)
1176 connection.send_error(
1178 websocket_api.ERR_NOT_FOUND,
1179 f
"tts engine {engine_id} not found",
1184 "engine_id": engine_id,
1185 "supported_languages": provider.supported_languages,
1187 if isinstance(provider, Provider):
1188 provider_info[
"name"] = provider.name
1190 connection.send_message(
1191 websocket_api.result_message(msg[
"id"], {
"provider": provider_info})
1195 @websocket_api.websocket_command(
{
"type": "tts/engine/voices",
vol.Required("engine_id"): str,
1196 vol.Required(
"language"): str,
1203 """List voices for a given language."""
1204 engine_id = msg[
"engine_id"]
1205 language = msg[
"language"]
1209 if not engine_instance:
1210 connection.send_error(
1212 websocket_api.ERR_NOT_FOUND,
1213 f
"tts engine {engine_id} not found",
1217 voices = {
"voices": engine_instance.async_get_supported_voices(language)}
1219 connection.send_message(websocket_api.result_message(msg[
"id"], voices))
1220
None async_register_legacy_engine(self, str engine, Provider provider, ConfigType config)
None __init__(self, HomeAssistant hass, bool use_cache, str cache_dir, int time_memory)
str _async_get_tts_audio(self, TextToSpeechEntity|Provider engine_instance, str cache_key, str message, bool cache, str language, dict[str, Any] options)
dict[str, str] _init_cache(self)
str async_get_url_path(self, str engine, str message, bool|None cache=None, str|None language=None, dict|None options=None)
str _generate_cache_key(self, str message, str language, dict|None options, str engine)
tuple[str, bytes] async_get_tts_audio(self, str engine, str message, bool|None cache=None, str|None language=None, dict|None options=None)
None _async_file_to_mem(self, str cache_key)
None _async_store_to_memcache(self, str cache_key, str filename, bytes data)
None async_init_cache(self)
bytes write_tags(str filename, bytes data, str engine_name, str message, str language, dict|None options)
None async_clear_cache(self)
None _async_save_tts_audio(self, str cache_key, str filename, bytes data)
tuple[str|None, bytes] async_read_tts(self, str token)
tuple[str, dict[str, Any]] process_options(self, TextToSpeechEntity|Provider engine_instance, str|None language, dict|None options)
Mapping[str, Any]|None default_options(self)
list[Voice]|None async_get_supported_voices(self, str language)
TtsAudioType internal_async_get_tts_audio(self, str message, str language, dict[str, Any] options)
None async_internal_added_to_hass(self)
TtsAudioType get_tts_audio(self, str message, str language, dict[str, Any] options)
list[str] supported_languages(self)
str default_language(self)
list[str]|None supported_options(self)
TtsAudioType async_get_tts_audio(self, str message, str language, dict[str, Any] options)
None async_speak(self, list[str] media_player_entity_id, str message, bool cache, str|None language=None, dict|None options=None)
web.Response post(self, web.Request request)
None __init__(self, SpeechManager tts)
None __init__(self, SpeechManager tts)
web.Response get(self, web.Request request, str filename)
None async_write_ha_state(self)
State|None async_get_last_state(self)
CalendarEntity get_entity(HomeAssistant hass, str entity_id)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)
list[Coroutine[Any, Any, None]] async_setup_legacy(HomeAssistant hass, ConfigType config)
None open(self, **Any kwargs)
Callable[[Command|list[Command]], Coroutine[Any, Any, None]] handle_error(Callable[[Command|list[Command]], Any] func)
TextToSpeechEntity|Provider|None get_engine_instance(HomeAssistant hass, str engine)
tuple[str, bytes] async_get_media_source_audio(HomeAssistant hass, str media_source_id)
dict[str, str] _get_cache_files(str cache_dir)
set[str] async_get_text_to_speech_languages(HomeAssistant hass)
bool async_support_options(HomeAssistant hass, str engine, str|None language=None, dict|None options=None)
bytes _convert_audio(str ffmpeg_binary, str from_extension, bytes audio_bytes, str to_extension, int|None to_sample_rate=None, int|None to_sample_channels=None, int|None to_sample_bytes=None)
None websocket_list_engine_voices(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg)
bool async_setup(HomeAssistant hass, ConfigType config)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
str _init_tts_cache_dir(HomeAssistant hass, str cache_dir)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
str|None async_resolve_engine(HomeAssistant hass, str|None engine)
None websocket_get_engine(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg)
str _hash_options(dict options)
None websocket_list_engines(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg)
str|None async_default_engine(HomeAssistant hass)
bytes async_convert_audio(HomeAssistant hass, str from_extension, bytes audio_bytes, str to_extension, int|None to_sample_rate=None, int|None to_sample_channels=None, int|None to_sample_bytes=None)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
str get_url(HomeAssistant hass, *bool require_current_request=False, bool require_ssl=False, bool require_standard_port=False, bool require_cloud=False, bool allow_internal=True, bool allow_external=True, bool allow_cloud=True, bool|None allow_ip=None, bool|None prefer_external=None, bool prefer_cloud=False)