diff options
| author | schneefux <schneefux+commit@schneefux.xyz> | 2014-11-05 14:34:06 +0100 |
|---|---|---|
| committer | schneefux <schneefux+commit@schneefux.xyz> | 2014-11-05 14:34:06 +0100 |
| commit | 533864c8814e9b02eceadd10e1bf20f46dbd1efe (patch) | |
| tree | 1d1e311a6d41cfa281e984a0d6a20cdf7bc6ca7f | |
| parent | 321423887a8aaf639bf0bfe0b34cbe7aaee7b1f6 (diff) | |
| parent | b32e813fc6a002d780e41d8723f6bce90b17fda1 (diff) | |
| download | jasper-client-533864c8814e9b02eceadd10e1bf20f46dbd1efe.tar.gz jasper-client-533864c8814e9b02eceadd10e1bf20f46dbd1efe.zip | |
Merge pull request #229 from Holzhaus/marytts-support
Add config options for TTS engine (+ MaryTTS support)
| -rw-r--r-- | client/tts.py | 159 | ||||
| -rwxr-xr-x | jasper.py | 2 |
2 files changed, 159 insertions, 2 deletions
diff --git a/client/tts.py b/client/tts.py index 55c33b0..ca9a5a0 100644 --- a/client/tts.py +++ b/client/tts.py @@ -15,9 +15,13 @@ import subprocess import pipes import logging import wave +import urllib +import urlparse +import requests from abc import ABCMeta, abstractmethod import argparse +import yaml try: import mad import gtts @@ -25,6 +29,7 @@ except ImportError: pass import diagnose +import jasperpath class AbstractTTSEngine(object): @@ -34,6 +39,16 @@ class AbstractTTSEngine(object): __metaclass__ = ABCMeta @classmethod + def get_config(cls): + return {} + + @classmethod + def get_instance(cls): + config = cls.get_config() + instance = cls(**config) + return instance + + @classmethod @abstractmethod def is_available(cls): return diagnose.check_executable('aplay') @@ -120,6 +135,27 @@ class EspeakTTS(AbstractTTSEngine): self.words_per_minute = words_per_minute @classmethod + def get_config(cls): + # FIXME: Replace this as soon as we have a config module + config = {} + # HMM dir + # Try to get hmm_dir from config + profile_path = jasperpath.config('profile.yml') + if os.path.exists(profile_path): + with open(profile_path, 'r') as f: + profile = yaml.safe_load(f) + if 'espeak-tts' in profile: + if 'voice' in profile['espeak-tts']: + config['voice'] = profile['espeak-tts']['voice'] + if 'pitch_adjustment' in profile['espeak-tts']: + config['pitch_adjustment'] = \ + profile['espeak-tts']['pitch_adjustment'] + if 'words_per_minute' in profile['espeak-tts']: + config['words_per_minute'] = \ + profile['espeak-tts']['words_per_minute'] + return config + + @classmethod def is_available(cls): return (super(cls, cls).is_available() and diagnose.check_executable('espeak')) @@ -249,6 +285,21 @@ class PicoTTS(AbstractTTSEngine): return (super(cls, cls).is_available() and diagnose.check_executable('pico2wave')) + @classmethod + def get_config(cls): + # FIXME: Replace this as soon as we have a config module + config = {} + # HMM dir + # Try to get hmm_dir from config + profile_path = jasperpath.config('profile.yml') + if os.path.exists(profile_path): + with open(profile_path, 'r') as f: + profile = yaml.safe_load(f) + if 'pico-tts' in profile and 'language' in profile['pico-tts']: + config['language'] = profile['pico-tts']['language'] + + return config + @property def languages(self): cmd = ['pico2wave', '-l', 'NULL', @@ -306,6 +357,22 @@ class GoogleTTS(AbstractMp3TTSEngine): diagnose.check_python_import('gtts') and diagnose.check_network_connection()) + @classmethod + def get_config(cls): + # FIXME: Replace this as soon as we have a config module + config = {} + # HMM dir + # Try to get hmm_dir from config + profile_path = jasperpath.config('profile.yml') + if os.path.exists(profile_path): + with open(profile_path, 'r') as f: + profile = yaml.safe_load(f) + if ('google-tts' in profile and + 'language' in profile['google-tts']): + config['language'] = profile['google-tts']['language'] + + return config + @property def languages(self): langs = ['af', 'sq', 'ar', 'hy', 'ca', 'zh-CN', 'zh-TW', 'hr', 'cs', @@ -328,6 +395,96 @@ class GoogleTTS(AbstractMp3TTSEngine): os.remove(tmpfile) +class MaryTTS(AbstractTTSEngine): + """ + Uses the MARY Text-to-Speech System (MaryTTS) + MaryTTS is an open-source, multilingual Text-to-Speech Synthesis platform + written in Java. + Please specify your own server instead of using the demonstration server + (http://mary.dfki.de:59125/) to save bandwidth and to protect your privacy. + """ + + SLUG = "mary-tts" + + def __init__(self, server="mary.dfki.de", port="59125", language="en_GB", + voice="dfki-spike"): + super(self.__class__, self).__init__() + self.server = server + self.port = port + self.netloc = '{server}:{port}'.format(server=self.server, + port=self.port) + self.language = language + self.voice = voice + self.session = requests.Session() + + @property + def languages(self): + r = self.session.get(self._makeurl('/locales')) + r.raise_for_status() + return r.text.splitlines() + + @property + def voices(self): + r = self.session.get(self._makeurl('/voices')) + r.raise_for_status() + return [line.split()[0] for line in r.text.splitlines()] + + @classmethod + def get_config(cls): + # FIXME: Replace this as soon as we have a config module + config = {} + # HMM dir + # Try to get hmm_dir from config + profile_path = jasperpath.config('profile.yml') + if os.path.exists(profile_path): + with open(profile_path, 'r') as f: + profile = yaml.safe_load(f) + if 'mary-tts' in profile: + if 'server' in profile['mary-tts']: + config['server'] = profile['mary-tts']['server'] + if 'port' in profile['mary-tts']: + config['port'] = profile['mary-tts']['port'] + if 'language' in profile['mary-tts']: + config['language'] = profile['mary-tts']['language'] + if 'voice' in profile['mary-tts']: + config['voice'] = profile['mary-tts']['voice'] + + return config + + @classmethod + def is_available(cls): + return (super(cls, cls).is_available() and + diagnose.check_network_connection()) + + def _makeurl(self, path, query={}): + query_s = urllib.urlencode(query) + urlparts = ('http', self.netloc, path, query_s, '') + return urlparse.urlunsplit(urlparts) + + def say(self, phrase): + self._logger.debug("Saying '%s' with '%s'", phrase, self.SLUG) + if self.language not in self.languages: + raise ValueError("Language '%s' not supported by '%s'" + % (self.language, self.SLUG)) + + if self.voice not in self.voices: + raise ValueError("Voice '%s' not supported by '%s'" + % (self.voice, self.SLUG)) + query = {'OUTPUT_TYPE': 'AUDIO', + 'AUDIO': 'WAVE_FILE', + 'INPUT_TYPE': 'TEXT', + 'INPUT_TEXT': phrase, + 'LOCALE': self.language, + 'VOICE': self.voice} + + r = self.session.get(self._makeurl('/process', query=query)) + with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f: + f.write(r.content) + tmpfile = f.name + self.play(tmpfile) + os.remove(tmpfile) + + def get_default_engine_slug(): return 'osx-tts' if platform.system() == 'darwin' else 'espeak-tts' @@ -401,5 +558,5 @@ if __name__ == '__main__': print("") for i, engine in enumerate(available_engines, start=1): print("%d. Testing engine '%s'..." % (i, engine.SLUG)) - engine().say("This is a test.") + engine.get_instance().say("This is a test.") print("Done.") @@ -100,7 +100,7 @@ class Jasper(object): tts_engine_class = tts.get_engine_by_slug(tts_engine_slug) # Initialize Mic - self.mic = Mic(tts_engine_class(), + self.mic = Mic(tts_engine_class.get_instance(), stt.PocketSphinxSTT(**stt.PocketSphinxSTT.get_config()), stt.newSTTEngine(stt_engine_type, api_key=api_key)) |
