Browse Source

Refactor TTS and add 2 more TTS (google + voicerss)

Unknown 8 years ago
parent
commit
1b6705c433

+ 20 - 14
core/AudioPlayer.py

@@ -8,28 +8,33 @@ logger = logging.getLogger("jarvis")
 
 
 class AudioPlayer:
-    PLAYER_MP3 = "MP3"
-    PLAYER_WAV = "WAV"
+    PLAYER_MP3 = "mp3"
+    PLAYER_WAV = "wav"
 
     AUDIO_MP3_FREQUENCY = 16000
     AUDIO_MP3_SIZE = -16
     AUDIO_MP3_CHANNEL = 1
     AUDIO_MP3_BUFFER = 2048
 
+    AUDIO_MP3_44100_FREQUENCY = 44100
+
     AUDIO_DEFAULT_VOLUME = 0.8
 
-    def __init__(self, default_type=None, audio_frequency=AUDIO_MP3_FREQUENCY, audio_size=AUDIO_MP3_SIZE, audio_channel=AUDIO_MP3_CHANNEL, audio_buffer=AUDIO_MP3_BUFFER, volume=AUDIO_DEFAULT_VOLUME):
-        if default_type == self.PLAYER_MP3:
-            self.audio_frequency = self.AUDIO_MP3_FREQUENCY
-            self.audio_size = self.AUDIO_MP3_SIZE
-            self.audio_channel = self.AUDIO_MP3_CHANNEL
-            self.audio_buffer = self.AUDIO_MP3_BUFFER
-        else:
-            self.audio_frequency = audio_frequency
-            self.audio_size = audio_size
-            self.audio_channel = audio_channel
-            self.audio_buffer = audio_buffer
+    def __init__(self, volume=AUDIO_DEFAULT_VOLUME):
         self.volume = volume
+
+    def init_play(self, default_type=None, audio_frequency=AUDIO_MP3_FREQUENCY, audio_size=AUDIO_MP3_SIZE, audio_channel=AUDIO_MP3_CHANNEL,
+                  audio_buffer=AUDIO_MP3_BUFFER):
+        if default_type == self.PLAYER_MP3 or default_type == self.PLAYER_MP3:
+            audio_size = self.AUDIO_MP3_SIZE
+            audio_channel = self.AUDIO_MP3_CHANNEL
+            audio_buffer = self.AUDIO_MP3_BUFFER
+        else:
+            audio_size = audio_size
+            audio_channel = audio_channel
+            audio_buffer = audio_buffer
+
+        audio_frequency = audio_frequency
         pygame.mixer.init(audio_frequency, audio_size, audio_channel, audio_buffer)
 
     def play_audio(self, music_file):
@@ -49,9 +54,10 @@ class AudioPlayer:
 
     @staticmethod
     def _start_player_audio():
+        clock = pygame.time.Clock()
+        clock.tick(100)
         logger.debug("Starting pygame audio player")
         pygame.mixer.music.play()
-        clock = pygame.time.Clock()
         while pygame.mixer.music.get_busy():
             clock.tick(20)
         return

+ 2 - 0
core/Cache.py

@@ -26,6 +26,8 @@ class Cache:
 
     def get_audio_file_cache_path(self, words, language=DEFAULT_LANGUAGE, voice=DEFAULT_VOICE):
         # fix UnicodeEncodeError: 'ascii' codec can't encode character X in position Y
+        if voice is None:
+            voice = DEFAULT_VOICE
         md5 = self.generate_md5_from_words(words)
         filename = voice + "." + md5 + "." + self._cache_extension
         cache_directory = os.path.join(self._cache_path, self._module_name, language)

+ 2 - 2
settings.yml

@@ -47,11 +47,11 @@ speech_to_text:
 # - voxygen
 text_to_speech:
   - pico2wave:
-      language: "us"
+      language: "en-US"
       cache: True
   - voxygen:
       language: "fr"
-      voice: "michel"
+      voice: "Emma"
       cache: True
 
 # Trigger engine configuration

+ 45 - 5
tts/TTS.py

@@ -1,15 +1,55 @@
 from core import AudioPlayer
 from core import Cache
 
+import logging
+import os
+import requests
+import sys
+from core import FileManager
+
+logging.basicConfig()
+logger = logging.getLogger("jarvis")
+
 
 class TTS:
-    def __init__(self, audio_player_type=None, cache_extension=None, volume=0.8):
+    def __init__(self, cache_extension=None, volume=0.8):
         self.cache = Cache(module_name=self.__class__.__name__, cache_extension=cache_extension)
-        self.audio_player = AudioPlayer(audio_player_type, volume=volume)
+        self.audio_player = AudioPlayer(volume=volume)
 
-    def play_audio(self, music_file, cache=False):
+    def play_audio(self, music_file, music_type, audio_frequency, cache=False):
+        self.audio_player.init_play(music_type, audio_frequency)
         self.audio_player.play_audio(music_file)
         self.cache.remove_audio_file(music_file, cache)
 
-    def unify_key(self, key):
-        return key.lower()
+    def say_generic(self, cache, language, words, get_audio_specific, audio_type, audio_frequency, voice=None):
+        file_path = self.cache.get_audio_file_cache_path(words, language, voice)
+
+        if get_audio_specific(language, words, file_path, cache):
+            self.play_audio(file_path, audio_type, audio_frequency, cache)
+
+    @staticmethod
+    def unify_key(key):
+        return key.lower()
+
+    @staticmethod
+    def get_audio(file_path, cache, payload, url, content_type_expected, timeout_expected=30):
+        if not cache or not os.path.exists(file_path) or FileManager.file_is_empty(file_path):
+
+            r = requests.get(url, params=payload, stream=True, timeout=timeout_expected)
+
+            content_type = r.headers['Content-Type']
+            logger.debug("Trying to get url: %s response code: %s and content-type: %s", r.url, r.status_code, content_type)
+
+            try:
+                if r.status_code == requests.codes.ok and content_type == content_type_expected:
+                    return FileManager.write_in_file(file_path, r.content)
+                else:
+                    return False
+            except IOError as e:
+                logger.error("I/O error(%s): %s", e.errno, e.strerror)
+            except ValueError:
+                logger.error("Could not convert data to an integer.")
+            except:
+                logger.error("Unexpected error: %s", sys.exc_info()[0])
+        else:
+            return True

+ 2 - 0
tts/__init__.py

@@ -1,3 +1,5 @@
 from TTS import TTS
 from voxygen import Voxygen
 from pico2wave import Pico2wave
+from voicerss import Voicerss
+from googletts import Googletts

+ 0 - 1
tts/acapela/__init__.py

@@ -1 +0,0 @@
-from voxygen import *

+ 0 - 86
tts/acapela/acapela.py

@@ -1,86 +0,0 @@
-import sha
-import os
-import shutil
-import sys
-import pygame
-import requests
-import logging
-
-logging.basicConfig()
-logger = logging.getLogger("jarvis")
-
-VOXYGEN_LANGUAGES = {
-    "fr" : {"electra":"Electra","emma":"Emma","becool":"Becool","agnes":"Agnes","loic":"Loic","fabienne":"Fabienne","helene":"Helene","marion":"Marion","matteo":"Matteo","melodine":"Melodine","mendoo":"Mendoo","michel":"Michel","moussa":"Moussa","philippe":"Philippe","sorciere":"Sorciere"},
-    "ar" : {"adel":"Adel"},
-    "de" : {"matthias":"Matthias","jylvia":"Sylvia"},
-    "uk" : {"bronwen":"Bronwen","elizabeth":"elizabeth","judith":"Judith","paul":"Paul","witch":"Witch"},
-    "us" : {"bruce":"Bruce","jenny":"Jenny"},
-    "es" : {"martha":"Martha"},
-    "it" : {"sonia":"Sonia"}
-}
-
-CACHE_PATH = "/tmp/jarvis/tts/acapela/"
-
-
-def say(words=None, voice=None, language=None, cache=None):
-
-    if not os.path.exists(CACHE_PATH):
-        os.makedirs(CACHE_PATH)
-
-    sha1 = sha.new(words).hexdigest()
-
-    tempfile = CACHE_PATH+voice+"."+sha1+".tts"
-
-    get_audio(voice,words,tempfile,cache)
-
-    play_audio(tempfile)
-
-    if not cache:
-        os.remove(tempfile)
-
-
-def get_audio(voice, text, filepath,cache):
-    if not cache or not os.path.exists(filepath):
-        payload ={
-            "method" : "redirect",
-            "text" : text.encode('utf8'),
-            "voice" : voice
-        }
-
-        r = requests.get("http://www.acapela-group.com/demo-tts/DemoHTML5Form_V2_fr.php", params=payload, stream=True)
-        logger.debug("Trying to get url: %s response code: %s",r.url,r.status_code)
-
-        if r.status_code==200:
-            with open(os.path.abspath(filepath), "wb") as sound_file:
-                sound_file.write(r.content)
-
-
-def play_audio(music_file, volume=0.8):
-    pygame.mixer.init(16000, -16, 1, 2048)
-    pygame.mixer.music.set_volume(volume)
-    clock = pygame.time.Clock()
-    try:
-        pygame.mixer.music.load(music_file)
-        logger.debug("Music file {} loaded!".format(music_file))
-    except pygame.error:
-        os.remove(music_file)
-        logger.debug("File {} not found! ({})".format(music_file, pygame.get_error()))
-        return
-    pygame.mixer.music.play()
-    while pygame.mixer.music.get_busy():
-        clock.tick(10)
-
-
-def get_voice(voice=None, language=None):
-    if language in VOXYGEN_LANGUAGES:
-        if voice in VOXYGEN_LANGUAGES[language]:
-            return VOXYGEN_LANGUAGES[language][voice]
-
-    logger.debug("Cannot find language maching language: %s voice: %s",language,voice)
-    return ""
-
-
-def wipe_cache():
-    shutil.rmtree(CACHE_PATH)
-
-

+ 1 - 0
tts/googletts/__init__.py

@@ -0,0 +1 @@
+from googletts import Googletts

+ 30 - 0
tts/googletts/googletts.py

@@ -0,0 +1,30 @@
+import logging
+
+from core import AudioPlayer
+from tts import TTS
+
+logging.basicConfig()
+logger = logging.getLogger("jarvis")
+
+
+class Googletts(TTS):
+    TTS_LANGUAGES_DEFAULT = 'fr'
+    TTS_URL = "http://translate.google.com/translate_tts"
+    TTS_CONTENT_TYPE = "audio/mpeg"
+    TTS_TIMEOUT_SEC = 30
+
+    def __init__(self):
+        TTS.__init__(self)
+
+    def say(self, words=None, language=TTS_LANGUAGES_DEFAULT, cache=True):
+        self.say_generic(cache, language, words, self.get_audio_googletts, AudioPlayer.PLAYER_MP3, 25000)
+
+    def get_audio_googletts(self, language, words, file_path, cache):
+        payload = {
+            "q": words,
+            "tl": language,
+            "ie": "UTF-8",
+            "total": "1",
+            "client": "tw-ob"
+        }
+        return self.get_audio(file_path, cache, payload, self.TTS_URL, self.TTS_CONTENT_TYPE, self.TTS_TIMEOUT_SEC)

+ 9 - 26
tts/pico2wave/pico2wave.py

@@ -1,4 +1,6 @@
 import subprocess
+
+from core import AudioPlayer
 from tts import TTS
 import logging
 import sys
@@ -8,34 +10,15 @@ logger = logging.getLogger("jarvis")
 
 
 class Pico2wave(TTS):
-    PICO2WAVE_LANGUAGES = dict(fr="fr-FR", us="en-US", uk="en-GB", de="de-DE", es="es-ES", it="it-IT")
-    PICO2WAVE_LANGUAGES_DEFAULT = 'fr'
-
-    def __init__(self, audio_player_type=None):
-        """
-
-        :param audio_player_type: MP3 or WAV
-        """
-        TTS.__init__(self, audio_player_type, "wav")
-
-    def say(self, words=None, language=PICO2WAVE_LANGUAGES_DEFAULT, cache=False):
-        file_path = self.cache.get_audio_file_cache_path(words, language)
-        language = self.get_voice(language)
+    TTS_LANGUAGES_DEFAULT = 'fr-FR'
 
-        self.get_audio(words, language, file_path)
-        self.play_audio(file_path, cache=cache)
+    def __init__(self):
+        TTS.__init__(self, AudioPlayer.PLAYER_WAV)
 
-    def get_voice(self, language):
-        logger.debug("Pico2wave asked lang: %s" % language)
-        language = self.unify_key(language)
-
-        if language in self.PICO2WAVE_LANGUAGES:
-            return self.PICO2WAVE_LANGUAGES[language]
-
-        logger.debug("Cannot find language matching language:  %s replace by default voice: %s", language, self.PICO2WAVE_LANGUAGES_DEFAULT)
-        return self.PICO2WAVE_LANGUAGES_DEFAULT
+    def say(self, words=None, language=TTS_LANGUAGES_DEFAULT, cache=False):
+        self.say_generic(cache, language, words, self.get_audio_pico2wave, AudioPlayer.PLAYER_WAV, AudioPlayer.AUDIO_MP3_FREQUENCY)
 
     @staticmethod
-    def get_audio(words, language, file_path):
+    def get_audio_pico2wave(language, words, file_path, cache):
         subprocess.check_output(["/usr/bin/pico2wave", "-l=%s" % language, "-w=%s" % file_path, words], stderr=sys.stderr)
-
+        return True

+ 1 - 0
tts/voicerss/__init__.py

@@ -0,0 +1 @@
+from voicerss import Voicerss

+ 28 - 0
tts/voicerss/voicerss.py

@@ -0,0 +1,28 @@
+import logging
+
+from core import AudioPlayer
+from tts import TTS
+
+logging.basicConfig()
+logger = logging.getLogger("jarvis")
+
+
+class Voicerss(TTS):
+    TTS_LANGUAGES_DEFAULT = 'fr-fr'
+    TTS_URL = "http://www.voicerss.org/controls/speech.ashx"
+    TTS_CONTENT_TYPE = "audio/mpeg"
+    TTS_TIMEOUT_SEC = 30
+
+    def __init__(self):
+        TTS.__init__(self)
+
+    def say(self, words=None, language=TTS_LANGUAGES_DEFAULT, cache=True):
+        self.say_generic(cache, language, words, self.get_audio_voicerss, AudioPlayer.PLAYER_MP3, AudioPlayer.AUDIO_MP3_44100_FREQUENCY)
+
+    def get_audio_voicerss(self, language, words, file_path, cache):
+        payload = {
+            "src": words,
+            "hl": language,
+            "c": "mp3"
+        }
+        return self.get_audio(file_path, cache, payload, self.TTS_URL, self.TTS_CONTENT_TYPE, self.TTS_TIMEOUT_SEC)

+ 17 - 67
tts/voxygen/voxygen.py

@@ -1,11 +1,6 @@
-import os
-
-import requests
 import logging
-import sys
 
 from core import AudioPlayer
-from core import FileManager
 from tts import TTS
 
 logging.basicConfig()
@@ -13,67 +8,22 @@ logger = logging.getLogger("jarvis")
 
 
 class Voxygen(TTS):
-    VOXYGEN_LANGUAGES = dict(
-        fr=dict(electra="Electra", emma="Emma", becool="Becool", agnes="Agnes", loic="Loic", fabienne="Fabienne", helene="Helene", marion="Marion",
-                matteo="Matteo",
-                melodine="Melodine", mendoo="Mendoo", michel="Michel", moussa="Moussa", philippe="Philippe", sorciere="Sorciere"),
-        ar=dict(adel="Adel"),
-        de=dict(matthias="Matthias", jylvia="Sylvia"),
-        uk=dict(bronwen="Bronwen", elizabeth="elizabeth", judith="Judith", paul="Paul", witch="Witch"),
-        us=dict(bruce="Bruce", jenny="Jenny"),
-        es=dict(martha="Martha"),
-        it=dict(sonia="Sonia"))
-
-    VOXYGEN_VOICE_DEFAULT = "Michel"
-    VOXYGEN_LANGUAGES_DEFAULT = "default"
-    VOXYGEN_URL = "https://www.voxygen.fr/sites/all/modules/voxygen_voices/assets/proxy/index.php"
-    VOXYGEN_CONTENT_TYPE = "audio/mpeg"
-    VOXYGEN_TIMEOUT_SEC = 30
+    TTS_VOICE_DEFAULT = "Michel"
+    TTS_LANGUAGES_DEFAULT = "default"
+    TTS_URL = "https://www.voxygen.fr/sites/all/modules/voxygen_voices/assets/proxy/index.php"
+    TTS_CONTENT_TYPE = "audio/mpeg"
+    TTS_TIMEOUT_SEC = 30
 
     def __init__(self):
-        TTS.__init__(self, AudioPlayer.PLAYER_MP3)
-
-    def say(self, words=None, voice=None, language=VOXYGEN_LANGUAGES_DEFAULT, cache=True):
-        voice = self.get_voice(voice, language)
-
-        file_path = self.cache.get_audio_file_cache_path(words, language, voice)
-
-        if self.get_audio(voice, words, file_path, cache):
-            self.play_audio(file_path,cache)
-
-    def get_voice(self, voice, language):
-        language = self.unify_key(language)
-        voice = self.unify_key(voice)
-        if language in self.VOXYGEN_LANGUAGES and voice in self.VOXYGEN_LANGUAGES[language]:
-            return self.VOXYGEN_LANGUAGES[language][voice]
-
-        logger.warn("Cannot find language matching language: %s voice: %s replace by default voice: %s", language, voice, self.VOXYGEN_VOICE_DEFAULT)
-        return self.VOXYGEN_VOICE_DEFAULT
-
-    def get_audio(self, voice, words, file_path, cache):
-        if not cache or not os.path.exists(file_path) or FileManager.file_is_empty(file_path):
-            payload = {
-                "method": "redirect",
-                # "text": words.encode('utf8'),
-                "text": words,
-                "voice": voice
-            }
-
-            r = requests.get(self.VOXYGEN_URL, params=payload, stream=True, timeout=self.VOXYGEN_TIMEOUT_SEC)
-
-            content_type = r.headers['Content-Type']
-            logger.debug("Trying to get url: %s response code: %s and content-type: %s", r.url, r.status_code, content_type)
-
-            try:
-                if r.status_code == requests.codes.ok and content_type == self.VOXYGEN_CONTENT_TYPE:
-                    return FileManager.write_in_file(file_path, r.content)
-                else:
-                    return False
-            except IOError as e:
-                logger.error("I/O error(%s): %s", e.errno, e.strerror)
-            except ValueError:
-                logger.error("Could not convert data to an integer.")
-            except:
-                logger.error("Unexpected error: %s", sys.exc_info()[0])
-        else:
-            return True
+        TTS.__init__(self)
+
+    def say(self, words=None, voice=TTS_VOICE_DEFAULT, language=TTS_LANGUAGES_DEFAULT, cache=True):
+        self.say_generic(cache, language, words, self.get_audio_voxygen, AudioPlayer.PLAYER_MP3, AudioPlayer.AUDIO_MP3_FREQUENCY, voice)
+
+    def get_audio_voxygen(self, voice, words, file_path, cache):
+        payload = {
+            "method": "redirect",
+            "text": words,
+            "voice": voice
+        }
+        return self.get_audio(file_path, cache, payload, self.TTS_URL, self.TTS_CONTENT_TYPE, self.TTS_TIMEOUT_SEC)