summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2014-11-05 14:34:06 +0100
committerschneefux <schneefux+commit@schneefux.xyz>2014-11-05 14:34:06 +0100
commit533864c8814e9b02eceadd10e1bf20f46dbd1efe (patch)
tree1d1e311a6d41cfa281e984a0d6a20cdf7bc6ca7f /client
parent321423887a8aaf639bf0bfe0b34cbe7aaee7b1f6 (diff)
parentb32e813fc6a002d780e41d8723f6bce90b17fda1 (diff)
downloadjasper-client-533864c8814e9b02eceadd10e1bf20f46dbd1efe.tar.gz
jasper-client-533864c8814e9b02eceadd10e1bf20f46dbd1efe.zip
Merge pull request #229 from Holzhaus/marytts-support
Add config options for TTS engine (+ MaryTTS support)
Diffstat (limited to 'client')
-rw-r--r--client/tts.py159
1 files changed, 158 insertions, 1 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.")