* Added profiles parameter

* Changed supported languages and encodings values
* Added parameters validations
This commit is contained in:
lufton
2019-05-05 13:39:20 +03:00
parent d69de6186e
commit 2a1d81b407
2 changed files with 89 additions and 100 deletions

View File

@@ -1,6 +1,6 @@
{ {
"domain": "google_cloud", "domain": "google_cloud",
"name": "Google Cloud TTS", "name": "Google Cloud Platform",
"documentation": "https://www.home-assistant.io/components/google_cloud", "documentation": "https://www.home-assistant.io/components/google_cloud",
"requirements": [ "requirements": [
"google-cloud-texttospeech==0.4.0" "google-cloud-texttospeech==0.4.0"

View File

@@ -17,32 +17,20 @@ CONF_ENCODING = 'encoding'
CONF_SPEED = 'speed' CONF_SPEED = 'speed'
CONF_PITCH = 'pitch' CONF_PITCH = 'pitch'
CONF_GAIN = 'gain' CONF_GAIN = 'gain'
CONF_PROFILES = 'profiles'
SUPPORTED_LANGUAGES = [ SUPPORTED_LANGUAGES = [
'en', 'da', 'nl', 'fr', 'de', 'it', 'ja', 'ko', 'nb', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-CA', 'fr-FR',
'pl', 'pt', 'ru', 'sk', 'es', 'sv', 'tr', 'uk', '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 = SUPPORTED_LANGUAGES[0] DEFAULT_LANG = 'en-US'
SUPPORTED_GENDERS = [ SUPPORTED_GENDERS = ['NEUTRAL', 'FEMALE', 'MALE']
'Neutral', 'Female', 'Male', DEFAULT_GENDER = 'NEUTRAL'
]
DEFAULT_GENDER = SUPPORTED_GENDERS[0]
GENDERS_DICT = {
'Neutral': texttospeech.enums.SsmlVoiceGender.NEUTRAL,
'Female': texttospeech.enums.SsmlVoiceGender.FEMALE,
'Male': texttospeech.enums.SsmlVoiceGender.MALE,
}
SUPPORTED_ENCODINGS = [ SUPPORTED_ENCODINGS = ['OGG_OPUS', 'MP3', 'LINEAR16']
'ogg', 'mp3', 'wav', DEFAULT_ENCODING = 'OGG_OPUS'
]
DEFAULT_ENCODING = SUPPORTED_ENCODINGS[0]
ENCODINGS_DICT = {
'ogg': texttospeech.enums.AudioEncoding.OGG_OPUS,
'mp3': texttospeech.enums.AudioEncoding.MP3,
'wav': texttospeech.enums.AudioEncoding.LINEAR16,
}
MIN_SPEED = 0.25 MIN_SPEED = 0.25
MAX_SPEED = 4.0 MAX_SPEED = 4.0
@@ -56,75 +44,79 @@ MIN_GAIN = -96.0
MAX_GAIN = 16.0 MAX_GAIN = 16.0
DEFAULT_GAIN = 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 = [ SUPPORTED_OPTIONS = [
CONF_VOICE, CONF_GENDER, CONF_ENCODING, CONF_SPEED, CONF_PITCH, CONF_GAIN, CONF_VOICE, CONF_GENDER, CONF_ENCODING, CONF_SPEED, CONF_PITCH, CONF_GAIN,
CONF_PROFILES,
] ]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES), 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): vol.Optional(CONF_GENDER, default=DEFAULT_GENDER):
vol.In(SUPPORTED_GENDERS), vol.All(vol.Upper, vol.In(SUPPORTED_GENDERS)),
vol.Optional(CONF_VOICE, default=''): cv.string, vol.Optional(CONF_VOICE): cv.string,
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING):
vol.All(vol.Upper, vol.In(SUPPORTED_ENCODINGS)),
vol.Optional(CONF_SPEED, default=DEFAULT_SPEED): vol.Optional(CONF_SPEED, default=DEFAULT_SPEED):
vol.Range(min=MIN_SPEED, max=MAX_SPEED), vol.Range(min=MIN_SPEED, max=MAX_SPEED),
vol.Optional(CONF_PITCH, default=DEFAULT_PITCH): vol.Optional(CONF_PITCH, default=DEFAULT_PITCH):
vol.Range(min=MIN_PITCH, max=MAX_PITCH), vol.Range(min=MIN_PITCH, max=MAX_PITCH),
vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): vol.Optional(CONF_GAIN, default=DEFAULT_GAIN):
vol.Range(min=MIN_GAIN, max=MAX_GAIN), vol.Range(min=MIN_GAIN, max=MAX_GAIN),
vol.Optional(CONF_KEY_FILE, default=''): cv.string, vol.Optional(CONF_PROFILES, default=[]):
vol.All(cv.ensure_list, [vol.In(SUPPORTED_PROFILES)]),
}) })
async def async_get_engine(hass, config): async def async_get_engine(hass, config):
"""Set up Google Cloud TTS component.""" """Set up Google Cloud TTS component."""
key_file = config[CONF_KEY_FILE] key_file = config.get(CONF_KEY_FILE)
if key_file: if key_file:
key_file = hass.config.path(key_file) key_file = hass.config.path(key_file)
if not os.path.isfile(key_file): if not os.path.isfile(key_file):
_LOGGER.error( _LOGGER.error("%s doesn't exist", key_file)
"GOOGLE_APPLICATION_CREDENTIALS file doesn't exist!"
)
return None return None
return GoogleCloudTTSProvider( return GoogleCloudTTSProvider(
hass, hass,
key_file, key_file,
config[CONF_LANG], config.get(CONF_LANG),
config[CONF_GENDER], config.get(CONF_GENDER),
config[CONF_VOICE], config.get(CONF_VOICE),
config[CONF_ENCODING], config.get(CONF_ENCODING),
config[CONF_SPEED], config.get(CONF_SPEED),
config[CONF_PITCH], config.get(CONF_PITCH),
config[CONF_GAIN] config.get(CONF_GAIN),
config.get(CONF_PROFILES)
) )
class GoogleCloudTTSProvider(Provider): class GoogleCloudTTSProvider(Provider):
"""The Google Cloud TTS API provider.""" """The Google Cloud TTS API provider."""
def __init__( def __init__(
self, self, hass, key_file, language, gender, voice, encoding, speed,
hass, pitch, gain, profiles
key_file,
lang,
gender,
voice,
encoding,
speed,
pitch,
gain
): ):
"""Init Google Cloud TTS service.""" """Init Google Cloud TTS service."""
self.hass = hass self.hass = hass
self.name = 'Google Cloud TTS' self.name = 'Google Cloud TTS'
self._lang = lang self._language = language
self._gender = gender self._gender = gender
self._voice = voice self._voice = voice
self._encoding = encoding self._encoding = encoding
self._speed = speed self._speed = speed
self._pitch = pitch self._pitch = pitch
self._gain = gain self._gain = gain
self._profiles = profiles
if key_file: if key_file:
self._client = texttospeech \ self._client = texttospeech \
@@ -132,70 +124,67 @@ class GoogleCloudTTSProvider(Provider):
else: else:
self._client = texttospeech.TextToSpeechClient() self._client = texttospeech.TextToSpeechClient()
@property
def default_language(self):
"""Return the default language."""
return self._lang
@property @property
def supported_languages(self): def supported_languages(self):
"""Return list of supported languages.""" """Return list of supported languages."""
return SUPPORTED_LANGUAGES return SUPPORTED_LANGUAGES
@property
def default_language(self):
"""Return the default language."""
return self._language
@property @property
def supported_options(self): def supported_options(self):
"""Return a list of supported options.""" """Return a list of supported options."""
return 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): async def async_get_tts_audio(self, message, language, options=None):
"""Load TTS from google.""" """Load TTS from google."""
_gender = options.get(CONF_GENDER).upper()
if _gender not in SUPPORTED_GENDERS: _gender = self._gender
_voice = options.get(CONF_VOICE) or self._voice
if _voice and not _voice.startswith(language): language = _voice[:5]
_encoding = options.get(CONF_ENCODING).upper()
if _encoding not in SUPPORTED_ENCODINGS: _encoding = self._encoding
_speed = options.get(CONF_SPEED)
_pitch = options.get(CONF_PITCH)
_gain = options.get(CONF_GAIN)
_profiles = options.get(CONF_PROFILES)
try: try:
synthesis_input = texttospeech.types.SynthesisInput(
text=message
) # pylint: disable=no-member
voice = texttospeech.types.VoiceSelectionParams(
language_code=language,
ssml_gender=texttospeech.enums.SsmlVoiceGender[_gender],
name=_voice
) # pylint: disable=no-member
audio_config = texttospeech.types.AudioConfig(
audio_encoding=texttospeech.enums.AudioEncoding[_encoding],
speaking_rate=max(min(_speed , MAX_SPEED), MIN_SPEED),
pitch=max(min(_pitch, MAX_PITCH), MIN_PITCH),
volume_gain_db=max(min(_gain, MAX_GAIN), MIN_GAIN),
effects_profile_id=_profiles,
) # pylint: disable=no-member
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
_language = language or self._lang
_gender = self._gender
_voice = self._voice
_encoding = self._encoding
_speed = self._speed
_pitch = self._pitch
_gain = self._gain
if options:
if CONF_GENDER in options:
_gender = options[CONF_GENDER].lower().capitalize()
if CONF_VOICE in options:
_voice = options[CONF_VOICE]
if CONF_ENCODING in options:
_encoding = options[CONF_ENCODING].lower()
if CONF_SPEED in options:
_speed = options[CONF_SPEED]
if CONF_PITCH in options:
_pitch = options[CONF_PITCH]
if CONF_GAIN in options:
_gain = options[CONF_GAIN]
synthesis_input = texttospeech.types.SynthesisInput(
text=message
) # pylint: disable=no-member
voice = texttospeech.types.VoiceSelectionParams(
language_code=_language,
ssml_gender=GENDERS_DICT.get(
_gender,
DEFAULT_GENDER
),
name=_voice
) # pylint: disable=no-member
audio_config = texttospeech.types.AudioConfig(
audio_encoding=ENCODINGS_DICT.get(
_encoding,
DEFAULT_ENCODING
),
speaking_rate=_speed,
pitch=_pitch,
volume_gain_db=_gain
) # pylint: disable=no-member
response = await self.hass.async_add_executor_job( response = await self.hass.async_add_executor_job(
self._client.synthesize_speech, self._client.synthesize_speech,
synthesis_input, synthesis_input,