Home Assistant Unofficial Reference 2024.12.1
legacy.py
Go to the documentation of this file.
1 """Provide the legacy TTS service provider interface."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 from collections.abc import Coroutine, Mapping
7 from functools import partial
8 import logging
9 from pathlib import Path
10 from typing import TYPE_CHECKING, Any
11 
12 import voluptuous as vol
13 
15  ATTR_MEDIA_ANNOUNCE,
16  ATTR_MEDIA_CONTENT_ID,
17  ATTR_MEDIA_CONTENT_TYPE,
18  DOMAIN as DOMAIN_MP,
19  SERVICE_PLAY_MEDIA,
20  MediaType,
21 )
22 from homeassistant.config import config_per_platform
23 from homeassistant.const import (
24  ATTR_ENTITY_ID,
25  CONF_DESCRIPTION,
26  CONF_NAME,
27  CONF_PLATFORM,
28 )
29 from homeassistant.core import HomeAssistant, ServiceCall, callback
30 from homeassistant.helpers import discovery
32 from homeassistant.helpers.service import async_set_service_schema
33 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
34 from homeassistant.setup import (
35  SetupPhases,
36  async_prepare_setup_platform,
37  async_start_setup,
38 )
39 from homeassistant.util.yaml import load_yaml_dict
40 
41 from .const import (
42  ATTR_CACHE,
43  ATTR_LANGUAGE,
44  ATTR_MESSAGE,
45  ATTR_OPTIONS,
46  CONF_CACHE,
47  CONF_CACHE_DIR,
48  CONF_FIELDS,
49  CONF_TIME_MEMORY,
50  DATA_TTS_MANAGER,
51  DEFAULT_CACHE,
52  DEFAULT_CACHE_DIR,
53  DEFAULT_TIME_MEMORY,
54  DOMAIN,
55  TtsAudioType,
56 )
57 from .media_source import generate_media_source_id
58 from .models import Voice
59 
60 _LOGGER = logging.getLogger(__name__)
61 
62 CONF_SERVICE_NAME = "service_name"
63 
64 
65 def _deprecated_platform(value: str) -> str:
66  """Validate if platform is deprecated."""
67  if value == "google":
68  raise vol.Invalid(
69  "google tts service has been renamed to google_translate,"
70  " please update your configuration."
71  )
72  return value
73 
74 
75 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
76  {
77  vol.Required(CONF_PLATFORM): vol.All(cv.string, _deprecated_platform),
78  vol.Optional(CONF_CACHE, default=DEFAULT_CACHE): cv.boolean,
79  vol.Optional(CONF_CACHE_DIR, default=DEFAULT_CACHE_DIR): cv.string,
80  vol.Optional(CONF_TIME_MEMORY, default=DEFAULT_TIME_MEMORY): vol.All(
81  vol.Coerce(int), vol.Range(min=60, max=57600)
82  ),
83  vol.Optional(CONF_SERVICE_NAME): cv.string,
84  }
85 )
86 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
87 
88 SERVICE_SAY = "say"
89 
90 SCHEMA_SERVICE_SAY = vol.Schema(
91  {
92  vol.Required(ATTR_MESSAGE): cv.string,
93  vol.Optional(ATTR_CACHE): cv.boolean,
94  vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
95  vol.Optional(ATTR_LANGUAGE): cv.string,
96  vol.Optional(ATTR_OPTIONS): dict,
97  }
98 )
99 
100 
102  hass: HomeAssistant, config: ConfigType
103 ) -> list[Coroutine[Any, Any, None]]:
104  """Set up legacy text-to-speech providers."""
105  # Load service descriptions from tts/services.yaml
106  services_yaml = Path(__file__).parent / "services.yaml"
107  services_dict = await hass.async_add_executor_job(
108  load_yaml_dict, str(services_yaml)
109  )
110 
111  async def async_setup_platform(
112  p_type: str,
113  p_config: ConfigType | None = None,
114  discovery_info: DiscoveryInfoType | None = None,
115  ) -> None:
116  """Set up a TTS platform."""
117  if p_config is None:
118  p_config = {}
119 
120  platform = await async_prepare_setup_platform(hass, config, DOMAIN, p_type)
121  if platform is None:
122  _LOGGER.error("Unknown text-to-speech platform specified")
123  return
124 
125  try:
126  with async_start_setup(
127  hass,
128  integration=p_type,
129  group=str(id(p_config)),
130  phase=SetupPhases.PLATFORM_SETUP,
131  ):
132  if hasattr(platform, "async_get_engine"):
133  provider = await platform.async_get_engine(
134  hass, p_config, discovery_info
135  )
136  else:
137  provider = await hass.async_add_executor_job(
138  platform.get_engine, hass, p_config, discovery_info
139  )
140 
141  if provider is None:
142  _LOGGER.error("Error setting up platform: %s", p_type)
143  return
144 
145  hass.data[DATA_TTS_MANAGER].async_register_legacy_engine(
146  p_type, provider, p_config
147  )
148  except Exception:
149  _LOGGER.exception("Error setting up platform: %s", p_type)
150  return
151 
152  async def async_say_handle(service: ServiceCall) -> None:
153  """Service handle for say."""
154  entity_ids = service.data[ATTR_ENTITY_ID]
155 
156  await hass.services.async_call(
157  DOMAIN_MP,
158  SERVICE_PLAY_MEDIA,
159  {
160  ATTR_ENTITY_ID: entity_ids,
161  ATTR_MEDIA_CONTENT_ID: generate_media_source_id(
162  hass,
163  engine=p_type,
164  message=service.data[ATTR_MESSAGE],
165  language=service.data.get(ATTR_LANGUAGE),
166  options=service.data.get(ATTR_OPTIONS),
167  cache=service.data.get(ATTR_CACHE),
168  ),
169  ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
170  ATTR_MEDIA_ANNOUNCE: True,
171  },
172  blocking=True,
173  context=service.context,
174  )
175 
176  service_name = p_config.get(CONF_SERVICE_NAME, f"{p_type}_{SERVICE_SAY}")
177  hass.services.async_register(
178  DOMAIN, service_name, async_say_handle, schema=SCHEMA_SERVICE_SAY
179  )
180 
181  # Register the service description
182  service_desc = {
183  CONF_NAME: f"Say a TTS message with {p_type}",
184  CONF_DESCRIPTION: (
185  f"Say something using text-to-speech on a media player with {p_type}."
186  ),
187  CONF_FIELDS: services_dict[SERVICE_SAY][CONF_FIELDS],
188  }
189  async_set_service_schema(hass, DOMAIN, service_name, service_desc)
190 
191  async def async_platform_discovered(
192  platform: str, info: dict[str, Any] | None
193  ) -> None:
194  """Handle for discovered platform."""
195  await async_setup_platform(platform, discovery_info=info)
196 
197  discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
198 
199  return [
200  async_setup_platform(p_type, p_config)
201  for p_type, p_config in config_per_platform(config, DOMAIN)
202  if p_type is not None
203  ]
204 
205 
206 class Provider:
207  """Represent a single TTS provider."""
208 
209  hass: HomeAssistant | None = None
210  name: str | None = None
211 
212  @property
213  def default_language(self) -> str | None:
214  """Return the default language."""
215  return None
216 
217  @property
218  @abstractmethod
219  def supported_languages(self) -> list[str]:
220  """Return a list of supported languages."""
221 
222  @property
223  def supported_options(self) -> list[str] | None:
224  """Return a list of supported options like voice, emotions."""
225  return None
226 
227  @callback
228  def async_get_supported_voices(self, language: str) -> list[Voice] | None:
229  """Return a list of supported voices for a language."""
230  return None
231 
232  @property
233  def default_options(self) -> Mapping[str, Any] | None:
234  """Return a mapping with the default options."""
235  return None
236 
238  self, message: str, language: str, options: dict[str, Any]
239  ) -> TtsAudioType:
240  """Load tts audio file from provider."""
241  raise NotImplementedError
242 
244  self, message: str, language: str, options: dict[str, Any]
245  ) -> TtsAudioType:
246  """Load tts audio file from provider.
247 
248  Return a tuple of file extension and data as bytes.
249  """
250  if TYPE_CHECKING:
251  assert self.hass
252  return await self.hass.async_add_executor_job(
253  partial(self.get_tts_audioget_tts_audio, message, language, options=options)
254  )
list[Voice]|None async_get_supported_voices(self, str language)
Definition: legacy.py:228
list[str]|None supported_options(self)
Definition: legacy.py:223
Mapping[str, Any]|None default_options(self)
Definition: legacy.py:233
TtsAudioType async_get_tts_audio(self, str message, str language, dict[str, Any] options)
Definition: legacy.py:245
TtsAudioType get_tts_audio(self, str message, str language, dict[str, Any] options)
Definition: legacy.py:239
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
str generate_media_source_id(str domain, str identifier)
Definition: __init__.py:71
str _deprecated_platform(str value)
Definition: legacy.py:65
list[Coroutine[Any, Any, None]] async_setup_legacy(HomeAssistant hass, ConfigType config)
Definition: legacy.py:103
Iterable[tuple[str|None, ConfigType]] config_per_platform(ConfigType config, str domain)
Definition: config.py:969
None async_set_service_schema(HomeAssistant hass, str domain, str service, dict[str, Any] schema)
Definition: service.py:844
ModuleType|None async_prepare_setup_platform(core.HomeAssistant hass, ConfigType hass_config, str domain, str platform_name)
Definition: setup.py:487
Generator[None] async_start_setup(core.HomeAssistant hass, str integration, SetupPhases phase, str|None group=None)
Definition: setup.py:739