Skip to content

Commit

Permalink
Cleanup language support on TTS (#5255)
Browse files Browse the repository at this point in the history
* Cleanup language support on TTS

* change to default_language & address comments

* Cleanup not needed code / comment from paulus
  • Loading branch information
pvizeli authored and balloob committed Jan 11, 2017
1 parent 467cb18 commit 3f3a3bc
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 55 deletions.
32 changes: 22 additions & 10 deletions homeassistant/components/tts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
https://home-assistant.io/components/tts/
"""
import asyncio
import functools
import hashlib
import logging
import mimetypes
Expand Down Expand Up @@ -247,8 +246,6 @@ def remove_files():
def async_register_engine(self, engine, provider, config):
"""Register a TTS provider."""
provider.hass = self.hass
if CONF_LANG in config:
provider.language = config.get(CONF_LANG)
self.providers[engine] = provider

@asyncio.coroutine
Expand All @@ -257,9 +254,16 @@ def async_get_url(self, engine, message, cache=None, language=None):
This method is a coroutine.
"""
provider = self.providers[engine]

language = language or provider.default_language
if language is None or \
language not in provider.supported_languages:
raise HomeAssistantError("Not supported language {0}".format(
language))

msg_hash = hashlib.sha1(bytes(message, 'utf-8')).hexdigest()
language_key = language or self.providers[engine].language
key = KEY_PATTERN.format(msg_hash, language_key, engine).lower()
key = KEY_PATTERN.format(msg_hash, language, engine).lower()
use_cache = cache if cache is not None else self.use_cache

# is speech allready in memory
Expand Down Expand Up @@ -387,22 +391,30 @@ class Provider(object):
"""Represent a single provider."""

hass = None
language = None

def get_tts_audio(self, message, language=None):
@property
def default_language(self):
"""Default language."""
return None

@property
def supported_languages(self):
"""List of supported languages."""
return None

def get_tts_audio(self, message, language):
"""Load tts audio file from provider."""
raise NotImplementedError()

def async_get_tts_audio(self, message, language=None):
def async_get_tts_audio(self, message, language):
"""Load tts audio file from provider.
Return a tuple of file extension and data as bytes.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None,
functools.partial(self.get_tts_audio, message, language=language))
None, self.get_tts_audio, message, language)


class TextToSpeechView(HomeAssistantView):
Expand Down
36 changes: 29 additions & 7 deletions homeassistant/components/tts/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,50 @@
"""
import os

from homeassistant.components.tts import Provider
import voluptuous as vol

from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG

SUPPORT_LANGUAGES = [
'en', 'de'
]

DEFAULT_LANG = 'en'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES),
})


def get_engine(hass, config):
"""Setup Demo speech component."""
return DemoProvider()
return DemoProvider(config[CONF_LANG])


class DemoProvider(Provider):
"""Demo speech api provider."""

def __init__(self):
"""Initialize demo provider for TTS."""
self.language = 'en'
def __init__(self, lang):
"""Initialize demo provider."""
self._lang = lang

@property
def default_language(self):
"""Default language."""
return self._lang

@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES

def get_tts_audio(self, message, language=None):
def get_tts_audio(self, message, language):
"""Load TTS from demo."""
filename = os.path.join(os.path.dirname(__file__), "demo.mp3")
try:
with open(filename, 'rb') as voice:
data = voice.read()
except OSError:
return
return (None, None)

return ("mp3", data)
22 changes: 14 additions & 8 deletions homeassistant/components/tts/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,36 +42,42 @@
@asyncio.coroutine
def async_get_engine(hass, config):
"""Setup Google speech component."""
return GoogleProvider(hass)
return GoogleProvider(hass, config[CONF_LANG])


class GoogleProvider(Provider):
"""Google speech api provider."""

def __init__(self, hass):
def __init__(self, hass, lang):
"""Init Google TTS service."""
self.hass = hass
self._lang = lang
self.headers = {
'Referer': "http://translate.google.com/",
'User-Agent': ("Mozilla/5.0 (Windows NT 10.0; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/47.0.2526.106 Safari/537.36")
}

@property
def default_language(self):
"""Default language."""
return self._lang

@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES

@asyncio.coroutine
def async_get_tts_audio(self, message, language=None):
def async_get_tts_audio(self, message, language):
"""Load TTS from google."""
from gtts_token import gtts_token

token = gtts_token.Token()
websession = async_get_clientsession(self.hass)
message_parts = self._split_message_to_parts(message)

# If language is not specified or is not supported - use the language
# from the config.
if language not in SUPPORT_LANGUAGES:
language = self.language

data = b''
for idx, part in enumerate(message_parts):
part_token = yield from self.hass.loop.run_in_executor(
Expand Down
22 changes: 18 additions & 4 deletions homeassistant/components/tts/picotts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,31 @@ def get_engine(hass, config):
if shutil.which("pico2wave") is None:
_LOGGER.error("'pico2wave' was not found")
return False
return PicoProvider()
return PicoProvider(config[CONF_LANG])


class PicoProvider(Provider):
"""pico speech api provider."""

def get_tts_audio(self, message, language=None):
def __init__(self, lang):
"""Initialize pico provider."""
self._lang = lang

@property
def default_language(self):
"""Default language."""
return self._lang

@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES

def get_tts_audio(self, message, language):
"""Load TTS using pico2wave."""
if language not in SUPPORT_LANGUAGES:
language = self.language
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmpf:
fname = tmpf.name

cmd = ['pico2wave', '--wave', fname, '-l', language, message]
subprocess.call(cmd)
data = None
Expand All @@ -52,6 +65,7 @@ def get_tts_audio(self, message, language=None):
return (None, None)
finally:
os.remove(fname)

if data:
return ("wav", data)
return (None, None)
37 changes: 22 additions & 15 deletions homeassistant/components/tts/voicerss.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,27 +93,34 @@ class VoiceRSSProvider(Provider):
def __init__(self, hass, conf):
"""Init VoiceRSS TTS service."""
self.hass = hass
self.extension = conf.get(CONF_CODEC)

self.form_data = {
'key': conf.get(CONF_API_KEY),
'hl': conf.get(CONF_LANG),
'c': (conf.get(CONF_CODEC)).upper(),
'f': conf.get(CONF_FORMAT),
self._extension = conf[CONF_CODEC]
self._lang = conf[CONF_LANG]

self._form_data = {
'key': conf[CONF_API_KEY],
'hl': conf[CONF_LANG],
'c': (conf[CONF_CODEC]).upper(),
'f': conf[CONF_FORMAT],
}

@property
def default_language(self):
"""Default language."""
return self._lang

@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES

@asyncio.coroutine
def async_get_tts_audio(self, message, language=None):
def async_get_tts_audio(self, message, language):
"""Load TTS from voicerss."""
websession = async_get_clientsession(self.hass)
form_data = self.form_data.copy()
form_data = self._form_data.copy()

form_data['src'] = message

# If language is specified and supported - use it instead of the
# language in the config.
if language in SUPPORT_LANGUAGES:
form_data['hl'] = language
form_data['hl'] = language

request = None
try:
Expand Down Expand Up @@ -141,4 +148,4 @@ def async_get_tts_audio(self, message, language=None):
if request is not None:
yield from request.release()

return (self.extension, data)
return (self._extension, data)
Loading

0 comments on commit 3f3a3bc

Please sign in to comment.