Google Cloud Platform component (TTS) (#23629)
* Added Google Cloud TTS service component feature
* Added Neutral voice gender
* Added line break at the end of files
* Updated CODEOWNERS, reqirements_all.txt and .coveragerc
* Fixed some ci/circleci: static-check errors
* Fixed some ci/circleci: static-check error
* Fixed some ci/circleci: pylint errors
* Fixed some ci/circleci: pylint errors
* * made supported_options const
* fixed direct env variable access
* Fixed import order
* * Component renamed
* Added encoding parameter
* Other fixes
* Changed folder name in .coveragerc
* * Removed whitespaces in blank lines
* Split long line
* Removed whitespaces in blank lines
* ci/circleci: static-check
* Fixed requirements_all.txt
* Added speed, pitch and gain parameters
* Added speed, pitch and gain as supported options
* Split too long line
* * Added profiles parameter
* Changed supported languages and encodings values
* Added parameters validations
* Fixes
* Fixes
* Fixes
* Fixes
* Fixes
* Changed options validation
* Added ToggleEntity save and restore state mechanism
* Revert "Added ToggleEntity save and restore state mechanism"
This reverts commit 0e275014
pull/23128/head
parent
fcfbdd2d89
commit
984d41e334
|
@ -228,6 +228,7 @@ omit =
|
||||||
homeassistant/components/goalfeed/*
|
homeassistant/components/goalfeed/*
|
||||||
homeassistant/components/gogogate2/cover.py
|
homeassistant/components/gogogate2/cover.py
|
||||||
homeassistant/components/google/*
|
homeassistant/components/google/*
|
||||||
|
homeassistant/components/google_cloud/tts.py
|
||||||
homeassistant/components/google_maps/device_tracker.py
|
homeassistant/components/google_maps/device_tracker.py
|
||||||
homeassistant/components/google_travel_time/sensor.py
|
homeassistant/components/google_travel_time/sensor.py
|
||||||
homeassistant/components/googlehome/*
|
homeassistant/components/googlehome/*
|
||||||
|
|
|
@ -93,6 +93,7 @@ homeassistant/components/geniushub/* @zxdavb
|
||||||
homeassistant/components/gitter/* @fabaff
|
homeassistant/components/gitter/* @fabaff
|
||||||
homeassistant/components/glances/* @fabaff
|
homeassistant/components/glances/* @fabaff
|
||||||
homeassistant/components/gntp/* @robbiet480
|
homeassistant/components/gntp/* @robbiet480
|
||||||
|
homeassistant/components/google_cloud/* @lufton
|
||||||
homeassistant/components/google_translate/* @awarecan
|
homeassistant/components/google_translate/* @awarecan
|
||||||
homeassistant/components/google_travel_time/* @robbiet480
|
homeassistant/components/google_travel_time/* @robbiet480
|
||||||
homeassistant/components/googlehome/* @ludeeus
|
homeassistant/components/googlehome/* @ludeeus
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
"""The google_cloud component."""
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"domain": "google_cloud",
|
||||||
|
"name": "Google Cloud Platform",
|
||||||
|
"documentation": "https://www.home-assistant.io/components/google_cloud",
|
||||||
|
"requirements": [
|
||||||
|
"google-cloud-texttospeech==0.4.0"
|
||||||
|
],
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@lufton"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
"""Support for the Google Cloud TTS service."""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import async_timeout
|
||||||
|
import voluptuous as vol
|
||||||
|
from google.cloud import texttospeech
|
||||||
|
|
||||||
|
from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_KEY_FILE = 'key_file'
|
||||||
|
CONF_GENDER = 'gender'
|
||||||
|
CONF_VOICE = 'voice'
|
||||||
|
CONF_ENCODING = 'encoding'
|
||||||
|
CONF_SPEED = 'speed'
|
||||||
|
CONF_PITCH = 'pitch'
|
||||||
|
CONF_GAIN = 'gain'
|
||||||
|
CONF_PROFILES = 'profiles'
|
||||||
|
|
||||||
|
SUPPORTED_LANGUAGES = [
|
||||||
|
'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-CA', 'fr-FR',
|
||||||
|
'it-IT', 'ja-JP', 'ko-KR', 'nb-NO', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT',
|
||||||
|
'ru-RU', 'sk-SK', 'sv-SE', 'tr-TR', 'uk-UA',
|
||||||
|
]
|
||||||
|
DEFAULT_LANG = 'en-US'
|
||||||
|
|
||||||
|
DEFAULT_GENDER = 'NEUTRAL'
|
||||||
|
|
||||||
|
VOICE_REGEX = r'[a-z]{2}-[A-Z]{2}-(Standard|Wavenet)-[A-Z]|'
|
||||||
|
DEFAULT_VOICE = ''
|
||||||
|
|
||||||
|
DEFAULT_ENCODING = 'OGG_OPUS'
|
||||||
|
|
||||||
|
MIN_SPEED = 0.25
|
||||||
|
MAX_SPEED = 4.0
|
||||||
|
DEFAULT_SPEED = 1.0
|
||||||
|
|
||||||
|
MIN_PITCH = -20.0
|
||||||
|
MAX_PITCH = 20.0
|
||||||
|
DEFAULT_PITCH = 0
|
||||||
|
|
||||||
|
MIN_GAIN = -96.0
|
||||||
|
MAX_GAIN = 16.0
|
||||||
|
DEFAULT_GAIN = 0
|
||||||
|
|
||||||
|
SUPPORTED_PROFILES = [
|
||||||
|
"wearable-class-device",
|
||||||
|
"handset-class-device",
|
||||||
|
"headphone-class-device",
|
||||||
|
"small-bluetooth-speaker-class-device",
|
||||||
|
"medium-bluetooth-speaker-class-device",
|
||||||
|
"large-home-entertainment-class-device",
|
||||||
|
"large-automotive-class-device",
|
||||||
|
"telephony-class-application",
|
||||||
|
]
|
||||||
|
|
||||||
|
SUPPORTED_OPTIONS = [
|
||||||
|
CONF_VOICE,
|
||||||
|
CONF_GENDER,
|
||||||
|
CONF_ENCODING,
|
||||||
|
CONF_SPEED,
|
||||||
|
CONF_PITCH,
|
||||||
|
CONF_GAIN,
|
||||||
|
CONF_PROFILES,
|
||||||
|
]
|
||||||
|
|
||||||
|
GENDER_SCHEMA = vol.All(
|
||||||
|
vol.Upper,
|
||||||
|
vol.In(texttospeech.enums.SsmlVoiceGender.__members__)
|
||||||
|
)
|
||||||
|
VOICE_SCHEMA = cv.matches_regex(VOICE_REGEX)
|
||||||
|
SCHEMA_ENCODING = vol.All(
|
||||||
|
vol.Upper,
|
||||||
|
vol.In(texttospeech.enums.AudioEncoding.__members__)
|
||||||
|
)
|
||||||
|
SPEED_SCHEMA = vol.All(
|
||||||
|
vol.Coerce(float),
|
||||||
|
vol.Clamp(min=MIN_SPEED, max=MAX_SPEED)
|
||||||
|
)
|
||||||
|
PITCH_SCHEMA = vol.All(
|
||||||
|
vol.Coerce(float),
|
||||||
|
vol.Clamp(min=MIN_PITCH, max=MAX_PITCH)
|
||||||
|
)
|
||||||
|
GAIN_SCHEMA = vol.All(
|
||||||
|
vol.Coerce(float),
|
||||||
|
vol.Clamp(min=MIN_GAIN, max=MAX_GAIN)
|
||||||
|
)
|
||||||
|
PROFILES_SCHEMA = vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[vol.In(SUPPORTED_PROFILES)]
|
||||||
|
)
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_KEY_FILE): cv.string,
|
||||||
|
vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES),
|
||||||
|
vol.Optional(CONF_GENDER, default=DEFAULT_GENDER): GENDER_SCHEMA,
|
||||||
|
vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): VOICE_SCHEMA,
|
||||||
|
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): SCHEMA_ENCODING,
|
||||||
|
vol.Optional(CONF_SPEED, default=DEFAULT_SPEED): SPEED_SCHEMA,
|
||||||
|
vol.Optional(CONF_PITCH, default=DEFAULT_PITCH): PITCH_SCHEMA,
|
||||||
|
vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA,
|
||||||
|
vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_engine(hass, config):
|
||||||
|
"""Set up Google Cloud TTS component."""
|
||||||
|
key_file = config.get(CONF_KEY_FILE)
|
||||||
|
if 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
|
||||||
|
|
||||||
|
return GoogleCloudTTSProvider(
|
||||||
|
hass,
|
||||||
|
key_file,
|
||||||
|
config.get(CONF_LANG),
|
||||||
|
config.get(CONF_GENDER),
|
||||||
|
config.get(CONF_VOICE),
|
||||||
|
config.get(CONF_ENCODING),
|
||||||
|
config.get(CONF_SPEED),
|
||||||
|
config.get(CONF_PITCH),
|
||||||
|
config.get(CONF_GAIN),
|
||||||
|
config.get(CONF_PROFILES)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleCloudTTSProvider(Provider):
|
||||||
|
"""The Google Cloud TTS API provider."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass,
|
||||||
|
key_file=None,
|
||||||
|
language=DEFAULT_LANG,
|
||||||
|
gender=DEFAULT_GENDER,
|
||||||
|
voice=DEFAULT_VOICE,
|
||||||
|
encoding=DEFAULT_ENCODING,
|
||||||
|
speed=1.0,
|
||||||
|
pitch=0,
|
||||||
|
gain=0,
|
||||||
|
profiles=None
|
||||||
|
):
|
||||||
|
"""Init Google Cloud TTS service."""
|
||||||
|
self.hass = hass
|
||||||
|
self.name = 'Google Cloud TTS'
|
||||||
|
self._language = language
|
||||||
|
self._gender = gender
|
||||||
|
self._voice = voice
|
||||||
|
self._encoding = encoding
|
||||||
|
self._speed = speed
|
||||||
|
self._pitch = pitch
|
||||||
|
self._gain = gain
|
||||||
|
self._profiles = profiles
|
||||||
|
|
||||||
|
if key_file:
|
||||||
|
self._client = texttospeech \
|
||||||
|
.TextToSpeechClient.from_service_account_json(key_file)
|
||||||
|
else:
|
||||||
|
self._client = texttospeech.TextToSpeechClient()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_languages(self):
|
||||||
|
"""Return list of supported languages."""
|
||||||
|
return SUPPORTED_LANGUAGES
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_language(self):
|
||||||
|
"""Return the default language."""
|
||||||
|
return self._language
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_options(self):
|
||||||
|
"""Return a list of supported options."""
|
||||||
|
return SUPPORTED_OPTIONS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_options(self):
|
||||||
|
"""Return a dict including default options."""
|
||||||
|
return {
|
||||||
|
CONF_GENDER: self._gender,
|
||||||
|
CONF_VOICE: self._voice,
|
||||||
|
CONF_ENCODING: self._encoding,
|
||||||
|
CONF_SPEED: self._speed,
|
||||||
|
CONF_PITCH: self._pitch,
|
||||||
|
CONF_GAIN: self._gain,
|
||||||
|
CONF_PROFILES: self._profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_get_tts_audio(self, message, language, options=None):
|
||||||
|
"""Load TTS from google."""
|
||||||
|
options_schema = vol.Schema({
|
||||||
|
vol.Optional(CONF_GENDER, default=self._gender): GENDER_SCHEMA,
|
||||||
|
vol.Optional(CONF_VOICE, default=self._voice): VOICE_SCHEMA,
|
||||||
|
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING):
|
||||||
|
SCHEMA_ENCODING,
|
||||||
|
vol.Optional(CONF_SPEED, default=self._speed): SPEED_SCHEMA,
|
||||||
|
vol.Optional(CONF_PITCH, default=self._speed): SPEED_SCHEMA,
|
||||||
|
vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA,
|
||||||
|
vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA,
|
||||||
|
})
|
||||||
|
options = options_schema(options)
|
||||||
|
|
||||||
|
_encoding = options[CONF_ENCODING]
|
||||||
|
_voice = options[CONF_VOICE]
|
||||||
|
if _voice and not _voice.startswith(language):
|
||||||
|
language = _voice[:5]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-member
|
||||||
|
synthesis_input = texttospeech.types.SynthesisInput(
|
||||||
|
text=message
|
||||||
|
)
|
||||||
|
|
||||||
|
voice = texttospeech.types.VoiceSelectionParams(
|
||||||
|
language_code=language,
|
||||||
|
ssml_gender=texttospeech.enums.SsmlVoiceGender[
|
||||||
|
options[CONF_GENDER]
|
||||||
|
],
|
||||||
|
name=_voice
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_config = texttospeech.types.AudioConfig(
|
||||||
|
audio_encoding=texttospeech.enums.AudioEncoding[_encoding],
|
||||||
|
speaking_rate=options.get(CONF_SPEED),
|
||||||
|
pitch=options.get(CONF_PITCH),
|
||||||
|
volume_gain_db=options.get(CONF_GAIN),
|
||||||
|
effects_profile_id=options.get(CONF_PROFILES),
|
||||||
|
)
|
||||||
|
# pylint: enable=no-member
|
||||||
|
|
||||||
|
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||||
|
response = await self.hass.async_add_executor_job(
|
||||||
|
self._client.synthesize_speech,
|
||||||
|
synthesis_input,
|
||||||
|
voice,
|
||||||
|
audio_config
|
||||||
|
)
|
||||||
|
return _encoding, response.audio_content
|
||||||
|
|
||||||
|
except asyncio.TimeoutError as ex:
|
||||||
|
_LOGGER.error("Timeout for Google Cloud TTS call: %s", ex)
|
||||||
|
except Exception as ex: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Error occured during Google Cloud TTS call: %s", ex
|
||||||
|
)
|
||||||
|
|
||||||
|
return None, None
|
|
@ -519,6 +519,9 @@ google-api-python-client==1.6.4
|
||||||
# homeassistant.components.google_pubsub
|
# homeassistant.components.google_pubsub
|
||||||
google-cloud-pubsub==0.39.1
|
google-cloud-pubsub==0.39.1
|
||||||
|
|
||||||
|
# homeassistant.components.google_cloud
|
||||||
|
google-cloud-texttospeech==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.googlehome
|
# homeassistant.components.googlehome
|
||||||
googledevices==1.0.2
|
googledevices==1.0.2
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue