monf 8 жил өмнө
parent
commit
68290e49d4

+ 0 - 66
core/AudioPlayer.py

@@ -1,66 +0,0 @@
-import logging
-import pygame
-
-from core.FileManager import FileManager
-
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
-
-
-class AudioPlayer:
-    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_MP3_22050_FREQUENCY = 22050
-
-    AUDIO_DEFAULT_VOLUME = 0.8
-
-    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.pre_init(audio_frequency, audio_size, audio_channel, audio_buffer)
-        pygame.mixer.init()
-
-    def play_audio(self, music_file):
-        try:
-            self._init_player_audio(music_file)
-            logger.debug("Music file %s loaded!", music_file)
-        except pygame.error:
-            FileManager.remove_file(music_file)
-            logger.error("File %s not found! (%s)", music_file, pygame.get_error())
-            return
-
-        self._start_player_audio()
-
-    def _init_player_audio(self, music_file):
-        pygame.mixer.music.set_volume(self.volume)
-        pygame.mixer.music.load(music_file)
-
-    @staticmethod
-    def _start_player_audio():
-        clock = pygame.time.Clock()
-        clock.tick(100)
-        logger.debug("Starting pygame audio player")
-        pygame.mixer.music.play()
-        while pygame.mixer.music.get_busy():
-            clock.tick(20)
-        pygame.mixer.quit()
-        return

+ 0 - 48
core/Cache.py

@@ -1,48 +0,0 @@
-import hashlib
-import os
-import logging
-
-from core.FileManager import FileManager
-
-DEFAULT_MODULE_NAME = "default"
-DEFAULT_CACHE_PATH = "/tmp/kalliope/tts"
-DEFAULT_CACHE_EXTENSION = "tts"
-DEFAULT_LANGUAGE = "default"
-DEFAULT_VOICE = "default"
-
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
-
-
-class Cache:
-    def __init__(self, module_name=DEFAULT_MODULE_NAME, cache_path=DEFAULT_CACHE_PATH,
-                 cache_extension=None):
-        if cache_extension is None:
-            cache_extension = DEFAULT_CACHE_EXTENSION
-
-        self._module_name = module_name
-        self._cache_path = cache_path
-        self._cache_extension = cache_extension
-
-    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)
-        file_path = os.path.join(cache_directory, filename)
-        FileManager.create_directory(cache_directory)
-        logger.debug("Cache directory %s exists and File path for audio is: %s", cache_directory, file_path)
-        return file_path
-
-    @staticmethod
-    def generate_md5_from_words(words):
-        if isinstance(words, unicode):
-            words = words.encode('utf-8')
-        return hashlib.md5(words).hexdigest()
-
-    @staticmethod
-    def remove_audio_file(file_path, cache):
-        if not cache:
-            FileManager.remove_file(file_path)

+ 20 - 1
core/ConfigurationManager/SettingLoader.py

@@ -1,6 +1,7 @@
 from YAMLLoader import YAMLLoader
 from YAMLLoader import YAMLLoader
 import logging
 import logging
 
 
+from core.FileManager import FileManager
 from core.Models.RestAPI import RestAPI
 from core.Models.RestAPI import RestAPI
 from core.Models.Settings import Settings
 from core.Models.Settings import Settings
 from core.Models.Stt import Stt
 from core.Models.Stt import Stt
@@ -52,6 +53,7 @@ class SettingLoader(object):
         random_wake_up_answers = cls._get_random_wake_up_answers(settings)
         random_wake_up_answers = cls._get_random_wake_up_answers(settings)
         random_wake_up_sounds = cls._get_random_wake_up_sounds(settings)
         random_wake_up_sounds = cls._get_random_wake_up_sounds(settings)
         rest_api = cls._get_rest_api(settings)
         rest_api = cls._get_rest_api(settings)
+        cache_path = cls._get_cache_path(settings)
 
 
         # create a setting object
         # create a setting object
         setting_object = Settings(default_stt_name=default_stt_name,
         setting_object = Settings(default_stt_name=default_stt_name,
@@ -62,7 +64,8 @@ class SettingLoader(object):
                                   triggers=triggers,
                                   triggers=triggers,
                                   random_wake_up_answers=random_wake_up_answers,
                                   random_wake_up_answers=random_wake_up_answers,
                                   random_wake_up_sounds=random_wake_up_sounds,
                                   random_wake_up_sounds=random_wake_up_sounds,
-                                  rest_api=rest_api)
+                                  rest_api=rest_api,
+                                  cache_path=cache_path)
         return setting_object
         return setting_object
 
 
     @staticmethod
     @staticmethod
@@ -261,3 +264,19 @@ class SettingLoader(object):
             return rest_api_obj
             return rest_api_obj
         else:
         else:
             raise NullSettingException("rest_api settings cannot be null")
             raise NullSettingException("rest_api settings cannot be null")
+
+    @classmethod
+    def _get_cache_path(cls, settings):
+        try:
+            cache_path = settings["cache_path"]
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
+
+        if cache_path is None:
+            raise NullSettingException("cache_path setting cannot be null")
+
+        # test if that path is usable
+        if FileManager.is_path_exists_or_creatable(cache_path):
+            return cache_path
+        else:
+            raise SettingInvalidException("The cache_path seems to be invalid: %s" % cache_path)

+ 0 - 24
core/Cosine.py

@@ -1,24 +0,0 @@
-import re, math
-from collections import Counter
-
-def get_cosine(vec1, vec2):
-    """"
-    :return the cosine of 2 vectors following the math equation from Wikipedia
-    """
-    intersection = set(vec1.keys()) & set(vec2.keys())
-    numerator = sum([vec1[x] * vec2[x] for x in intersection])
-
-    sum1 = sum([vec1[x] ** 2 for x in vec1.keys()])
-    sum2 = sum([vec2[x] ** 2 for x in vec2.keys()])
-    denominator = math.sqrt(sum1) * math.sqrt(sum2)
-
-    if not denominator:
-        return 0.0
-    else:
-        return float(numerator) / denominator
-
-
-def text_to_vector(text):
-    WORD = re.compile(r'\w+')
-    words = WORD.findall(text)
-    return Counter(words)

+ 35 - 4
core/FileManager.py

@@ -1,7 +1,12 @@
+import logging
 import os
 import os
 import shutil
 import shutil
 
 
 
 
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
 class FileManager:
 class FileManager:
     def __init__(self):
     def __init__(self):
         pass
         pass
@@ -13,10 +18,13 @@ class FileManager:
 
 
     @staticmethod
     @staticmethod
     def write_in_file(file_path, content):
     def write_in_file(file_path, content):
-        with open(file_path, "wb") as file_open:
-            file_open.write(content)
-            file_open.close()
-        return not FileManager.file_is_empty(file_path)
+        try:
+            with open(file_path, "wb") as file_open:
+                file_open.write(content)
+                file_open.close()
+            return not FileManager.file_is_empty(file_path)
+        except IOError as e:
+            logger.error("I/O error(%s): %s", e.errno, e.strerror)
 
 
     @staticmethod
     @staticmethod
     def wipe_cache(cache_path):
     def wipe_cache(cache_path):
@@ -30,3 +38,26 @@ class FileManager:
     def remove_file(file_path):
     def remove_file(file_path):
         if os.path.exists(file_path):
         if os.path.exists(file_path):
             return os.remove(file_path)
             return os.remove(file_path)
+
+
+    @staticmethod
+    def is_path_creatable(pathname):
+        """
+        `True` if the current user has sufficient permissions to create the passed
+        pathname; `False` otherwise.
+        """
+        dirname = os.path.dirname(pathname) or os.getcwd()
+        return os.access(dirname, os.W_OK)
+
+    @staticmethod
+    def is_path_exists_or_creatable(pathname):
+        """
+        `True` if the passed pathname is a valid pathname for the current OS _and_
+        either currently exists or is hypothetically creatable; `False` otherwise.
+
+        This function is guaranteed to _never_ raise exceptions.
+        """
+        try:
+            return os.path.exists(pathname) or FileManager.is_path_creatable(pathname)
+        except OSError:
+            return False

+ 2 - 4
core/MainController.py

@@ -2,12 +2,12 @@ import logging
 import os
 import os
 import random
 import random
 
 
-from core import AudioPlayer
 from core import Utils
 from core import Utils
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.OrderAnalyser import OrderAnalyser
 from core.OrderAnalyser import OrderAnalyser
 from core.OrderListener import OrderListener
 from core.OrderListener import OrderListener
+from core.Players import Mplayer
 from core.TriggerLauncher import TriggerLauncher
 from core.TriggerLauncher import TriggerLauncher
 from flask import Flask
 from flask import Flask
 from core.RestAPI.FlaskAPI import FlaskAPI
 from core.RestAPI.FlaskAPI import FlaskAPI
@@ -57,10 +57,8 @@ class MainController:
         if self.settings.random_wake_up_answers is not None:
         if self.settings.random_wake_up_answers is not None:
             Say(message=self.settings.random_wake_up_answers)
             Say(message=self.settings.random_wake_up_answers)
         else:
         else:
-            ap = AudioPlayer()
-            ap.init_play()
             random_sound_to_play = self._get_random_sound(self.settings.random_wake_up_sounds)
             random_sound_to_play = self._get_random_sound(self.settings.random_wake_up_sounds)
-            ap.play_audio(random_sound_to_play)
+            Mplayer.play(random_sound_to_play)
 
 
     def analyse_order(self, order):
     def analyse_order(self, order):
         """
         """

+ 2 - 1
core/Models/Settings.py

@@ -3,7 +3,7 @@
 class Settings(object):
 class Settings(object):
     def __init__(self, default_tts_name=None, default_stt_name=None,
     def __init__(self, default_tts_name=None, default_stt_name=None,
                  default_trigger_name=None, ttss=None, stts=None,
                  default_trigger_name=None, ttss=None, stts=None,
-                 random_wake_up_answers=None, random_wake_up_sounds=None, triggers=None, rest_api=None):
+                 random_wake_up_answers=None, random_wake_up_sounds=None, triggers=None, rest_api=None, cache_path=None):
         self.default_tts_name = default_tts_name
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
         self.default_stt_name = default_stt_name
         self.default_trigger_name = default_trigger_name
         self.default_trigger_name = default_trigger_name
@@ -13,3 +13,4 @@ class Settings(object):
         self.random_wake_up_sounds = random_wake_up_sounds
         self.random_wake_up_sounds = random_wake_up_sounds
         self.triggers = triggers
         self.triggers = triggers
         self.rest_api = rest_api
         self.rest_api = rest_api
+        self.cache_path = cache_path

+ 13 - 11
core/NeuronModule.py

@@ -100,21 +100,23 @@ class NeuronModule(object):
             tts_message = self._get_message_from_dict(message)
             tts_message = self._get_message_from_dict(message)
 
 
         if tts_message is not None:
         if tts_message is not None:
-            # get an instance of the target TTS
-            tts_instance = Utils.get_dynamic_class_instantiation("tts", self.tts.capitalize())
-            tts_args = None
-            for tts_object in self.settings.ttss:
-                if tts_object.name == self.tts:
-                    tts_args = tts_object.parameters
-                    logger.debug("NeuronModule: tts_args: %s" % tts_args)
-
             logger.debug("tts_message to say: %s" % tts_message)
             logger.debug("tts_message to say: %s" % tts_message)
+
+            # create a tts object from the tts the user want to user
+            tts_object = next((x for x in self.settings.ttss if x.name == self.tts), None)
+            if tts_object is None:
+                raise TTSModuleNotFound("The tts module name %s does not exist in settings file" % self.tts)
             # change the cache settings with the one precised for the current neuron
             # change the cache settings with the one precised for the current neuron
             if self.override_cache is not None:
             if self.override_cache is not None:
-                tts_args = self._update_cache_var(self.override_cache, tts_args)
-            logger.debug("NeuroneModule: TTS args: %s" % tts_args)
+                tts_object.parameter = self._update_cache_var(self.override_cache, tts_object.parameter)
+
+            logger.debug("NeuroneModule: TTS args: %s" % tts_object)
 
 
-            tts_instance.say(words=tts_message, **(tts_args if tts_args is not None else {}))
+            # get the instance of the TTS module
+            tts_module_instance = Utils.get_dynamic_class_instantiation("tts", tts_object.name.capitalize(),
+                                                                        tts_object.parameters)
+            # generate the audio file and play it
+            tts_module_instance.say(tts_message)
 
 
     def _get_message_from_dict(self, message_dict):
     def _get_message_from_dict(self, message_dict):
         """
         """

+ 32 - 0
core/Players/Mplayer.py

@@ -0,0 +1,32 @@
+import logging
+import os
+import subprocess
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+MPLAYER_EXEC_PATH = "/usr/bin/mplayer"
+
+
+class Mplayer(object):
+
+    def __init__(self):
+        pass
+
+    @classmethod
+    def play(cls, filepath):
+        mplayer_exec_path = [MPLAYER_EXEC_PATH]
+        mplayer_options = ['-slave', '-quiet']
+        mplayer_command = list()
+        mplayer_command.extend(mplayer_exec_path)
+        mplayer_command.extend(mplayer_options)
+
+        mplayer_command.append(filepath)
+        logger.debug("Mplayer cmd: %s" % str(mplayer_command))
+
+        FNULL = open(os.devnull, 'w')
+
+        subprocess.call(mplayer_command, stdout=FNULL, stderr=FNULL)
+
+

+ 1 - 0
core/Players/__init__.py

@@ -0,0 +1 @@
+from Mplayer import Mplayer

+ 22 - 0
core/TTS/TTSLauncher.py

@@ -0,0 +1,22 @@
+import logging
+
+from core import Utils
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class TTSLauncher(object):
+    def __init__(self):
+        pass
+
+    @classmethod
+    def get_tts(cls, tts):
+        """
+        Return an instance of a TTS module from the name of this module
+        :param tts: TTS model
+        :type tts: Tts
+        :return: TTS module instance
+        """
+        logger.debug("get TTS module \"%s\" with parameters %s" % (tts.name, tts.parameters))
+        return Utils.get_dynamic_class_instantiation("tts", tts.name.capitalize(), tts.parameters)

+ 135 - 0
core/TTS/TTSModule.py

@@ -0,0 +1,135 @@
+# coding: utf8
+import hashlib
+import logging
+import os
+
+from core.FileManager import FileManager
+from core.ConfigurationManager import SettingLoader
+from core.Players import Mplayer
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class MissingTTSParameter(Exception):
+    pass
+
+
+class TtsGenerateAudioFunctionNotFound(Exception):
+    pass
+
+
+class FailToLoadSoundFile(Exception):
+    pass
+
+
+class TTSModule(object):
+
+    def __init__(self, **kwargs):
+        """
+        Mother class of TTS module. Handle:
+        - Cache: call cache object to create file, delete file, check if file exist
+        - Player: call the default player to play the generated file
+        """
+        # set parameter from what we receive from the settings
+        self.cache = kwargs.get('cache', False)
+        self.language = kwargs.get('language', None)
+        self.voice = kwargs.get('voice', "default")
+        # the name of the TSS is the name of the Tss module that have instantiated TTSModule
+        self.tts_caller_name = self.__class__.__name__
+
+        # we don't know yet the words that will be converted to an audio and so we don't have the audio path yet too
+        self.words = None
+        self.file_path = None
+        self.base_cache_path = None
+
+        # load settings
+        self.settings = SettingLoader.get_settings()
+
+        print "Class TTSModule called from module %s, cache: %s, language: %s, voice: %s" % (self.tts_caller_name,
+                                                                                             self.cache,
+                                                                                             self.language,
+                                                                                             self.voice)
+
+    def play_audio(self):
+        """
+        Play the audio file
+        :return:
+        """
+        Mplayer.play(self.file_path)
+
+    def generate_and_play(self, words, generate_audio_function_from_child=None):
+        """
+        Generate an audio file from <words> if not already in cache and call the Player to play it
+        :param words: Sentence text from which we want to generate an audio file
+        :param generate_audio_function_from_child: The child function to generate a file if necessary
+        :return:
+        """
+        if generate_audio_function_from_child is None:
+            raise TtsGenerateAudioFunctionNotFound
+
+        self.words = words
+        # we can generate the file path from info we have
+        self.file_path = self._get_path_to_store_audio()
+
+        if not self.cache:
+            # no cache, we need to generate the file
+            generate_audio_function_from_child()
+        else:
+            # we check if the file already exist. If not we generate it with the TTS engine
+            if not self.is_file_already_in_cache():
+                generate_audio_function_from_child()
+
+        # then play the generated audio file
+        self.play_audio()
+
+        # if the user don't want to keep the cache we remove the file
+        if not self.cache:
+            FileManager.remove_file(self.file_path)
+
+    def _get_path_to_store_audio(self):
+        """
+        Get a sentence (a text) an return the full path of the file
+
+        Path syntax:
+        </path/in/settings>/<tts.name>/tts.parameter["language"]/tts.parameter["voice"]/<md5_of_sentence.tts
+
+        E.g:
+        /tmp/kalliope/voxygene/fr/abcd12345.tts
+
+        :return: path String
+        """
+        md5 = self.generate_md5_from_words(self.words)+".tts"
+        self.base_cache_path = os.path.join(self.settings.cache_path, self.tts_caller_name, self.language, self.voice)
+
+        returned_path = os.path.join(self.base_cache_path, md5)
+        logger.debug("get_path_to_store_audio return: %s" % returned_path)
+        return returned_path
+
+    @staticmethod
+    def generate_md5_from_words(words):
+        """
+        Generate a md5 hash from received text
+        :param words: Text to convert into md5 hash
+        :return: String md5 hash from the received words
+        """
+        if isinstance(words, unicode):
+            words = words.encode('utf-8')
+        return hashlib.md5(words).hexdigest()
+
+    def is_file_already_in_cache(self):
+        """
+        Return true if the file to generate has already been generated before
+        :return:
+        """
+        # generate sub folder
+        FileManager.create_directory(self.base_cache_path)
+
+        # check if the audio file exist
+        exist_in_cache = os.path.exists(self.file_path)
+
+        if exist_in_cache:
+            logger.debug("TTSModule, File already in cache: %s" % self.file_path)
+        else:
+            logger.debug("TTSModule, File not yet in cache: %s" % self.file_path)
+        return exist_in_cache

+ 0 - 0
core/TTS/__init__.py


+ 0 - 2
core/__init__.py

@@ -1,7 +1,5 @@
 from core.OrderAnalyser import OrderAnalyser
 from core.OrderAnalyser import OrderAnalyser
 from core.OrderListener import OrderListener
 from core.OrderListener import OrderListener
-from core.AudioPlayer import AudioPlayer
 from core.ShellGui import ShellGui
 from core.ShellGui import ShellGui
-from core.Cache import Cache
 from core.FileManager import FileManager
 from core.FileManager import FileManager
 from core.Utils import Utils
 from core.Utils import Utils

+ 1 - 0
install/install.yml

@@ -40,6 +40,7 @@
         - libffi-dev
         - libffi-dev
         - sox
         - sox
         - libatlas3-base
         - libatlas3-base
+        - mplayer
 
 
     - name: Copy requirement
     - name: Copy requirement
       copy:
       copy:

+ 4 - 3
settings.yml

@@ -48,7 +48,9 @@ speech_to_text:
 # Text to speech
 # Text to speech
 # ---------------------------
 # ---------------------------
 # This is the default TTS that will be used by Kalliope to talk.
 # This is the default TTS that will be used by Kalliope to talk.
-default_text_to_speech: "pico2wave"
+default_text_to_speech: "voicerss"
+# where we store generated audio files from TTS engine to reuse them
+cache_path: "/tmp/kalliope_tts_cache"
 
 
 # Text to Spreech engines configuration
 # Text to Spreech engines configuration
 # Available engine are:
 # Available engine are:
@@ -59,8 +61,7 @@ text_to_speech:
       language: "fr-FR"
       language: "fr-FR"
       cache: True
       cache: True
   - voxygen:
   - voxygen:
-      language: "fr"
-      voice: "Emma"
+      voice: "Agnes"
       cache: True
       cache: True
   - acapela:
   - acapela:
       language: "sonid15"
       language: "sonid15"

+ 28 - 26
test.py

@@ -1,17 +1,11 @@
 # coding: utf8
 # coding: utf8
 import logging
 import logging
-import re
-from collections import Counter
 
 
-from flask import Flask
-from core.RestAPI.FlaskAPI import FlaskAPI
 from core import OrderAnalyser
 from core import OrderAnalyser
+from core import Utils
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager import SettingLoader
-from core.ConfigurationManager import YAMLLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
-
-from neurons import Systemdate
-from neurons.tasker_autoremote.tasker_autoremote import Tasker_autoremote
+from core.Players import Mplayer
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
@@ -21,33 +15,41 @@ logger.setLevel(logging.DEBUG)
 # oa = OrderAnalyser(order=order)
 # oa = OrderAnalyser(order=order)
 # oa.start()
 # oa.start()
 
 
-# SettingLoader.get_settings()
-#
+
+
 brain = BrainLoader.get_brain()
 brain = BrainLoader.get_brain()
 
 
-order = "est-ce que j'ai des emails"
+order = "bonjour"
 
 
 oa = OrderAnalyser(order=order, brain=brain)
 oa = OrderAnalyser(order=order, brain=brain)
 
 
 oa.start()
 oa.start()
 
 
 
 
-# import wikipedia
+# settings = SettingLoader.get_settings()
 #
 #
-# languages = wikipedia.languages().keys()
-# languages = sorted(languages)
-# for el in languages:
-#     print "- " + el
-
-
-
-
-
-
-
-
-
-
+# tts_name_to_use = "pico2wave"
+# sentence_to_say = "bonjour monsieur, je m'appelle Kalliopé"
+#
+#
+# def _get_tts_object_from_name(tts_name_to_use):
+#     """
+#     Return a Tts object from the nae of the Tss. Get parameters in settings
+#     :param tts_name_to_use:
+#     :return:
+#     """
+#     return next((x for x in settings.ttss if x.name == tts_name_to_use), None)
+#
+#
+# # create a tts object from the tts the user want to user
+# tts_object = _get_tts_object_from_name(tts_name_to_use)
+#
+# if tts_object is None:
+#     print "TTS module name %s not found in settings" % tts_name_to_use
+#
+# else:
+#     tts_module_instance = Utils.get_dynamic_class_instantiation("tts", tts_object.name.capitalize(), tts_object.parameters)
+#     tts_module_instance.say(sentence_to_say)
 
 
 
 
 
 

+ 0 - 121
test_Cosine_Similarity.py

@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re, math
-from collections import Counter
-
-
-
-# user_said = "maman je voudrais ecouter ACDC"
-# order = "je voudrais ecouter {{ artist_name }}"
-
-user_said = "s'il te plait regle le reveil pour dix huit heures et dix neuf  minutes trente trois  secondes cent quatre vingt dix "
-
-user_said_list = [" regle le reveil pour neuf  heures et quinze minutes trente trois secondes ",
-                 "s'il te plait regle le reveil pour dix huit huf  minutes trente trois  secondes cent quatre vingt dix ",
-                 "regle pour dix huit heures et   trente trois  secondes cent quatre vingt dix ",
-                 "s'il te plait regle le reveil poutes trente trois  secondes cent quatre vingt dix ",
-                 "RIEN A VOIR",
-                " minutes neuf trente reveil regle secondes  quinze  le heures et    trois  pour "
-                  ]
-
-order = "{{ politesse }} regle le reveil pour {{ hour}} heures et {{minute }} minutes {{ seconde  }} secondes {{mili}}"
-
-order_list = ["regle le reveil pour  heures et  minutes  secondes ",
-              "{{ politesse }} regle le reveil pour {{ hour}} heures et {{minute }} minutes {{ seconde  }} secondes {{mili}}",
-              "politesse  regle le reveil pour  hour heures et minute  minutes  seconde   secondes mili",
-              " reveil pour {{ hour}}  et {{minute }} minutes  secondes {{mili}}",
-              "{{ politesse }} regle le reveil pour "
-              ]
-
-
-# take a look to each order
-
-WORD = re.compile(r'\w+')
-
-def get_cosine(vec1, vec2):
-     intersection = set(vec1.keys()) & set(vec2.keys())
-     numerator = sum([vec1[x] * vec2[x] for x in intersection])
-
-     sum1 = sum([vec1[x]**2 for x in vec1.keys()])
-     sum2 = sum([vec2[x]**2 for x in vec2.keys()])
-     denominator = math.sqrt(sum1) * math.sqrt(sum2)
-
-     if not denominator:
-        return 0.0
-     else:
-        return float(numerator) / denominator
-
-def text_to_vector(text):
-     words = WORD.findall(text)
-     return Counter(words)
-
-
-def _is_containing_bracket(sentence):
-    # print "sentence to test %s" % sentence
-    pattern = r"{{|}}"
-    # prog = re.compile(pattern)
-    bool = re.search(pattern, sentence)
-    if bool is not None:
-        return True
-    return False
-
-
-def _get_next_value_list(list):
-    ite = list.__iter__()
-    next(ite, None)
-    return next(ite, None)
-
-# check if the order contain bracket
-if _is_containing_bracket(order):
-    # remove white space between {{ and }}
-    # get a table of word said
-    list_word_in_order = re.sub('\s+(?=[^\{\{\}\}]*\}\})', '',order).split()
-    print "order matched: %s" % list_word_in_order
-
-    # get the order, defined by the first words before {{
-    the_order = order[:order.find('{{')]
-    print "the order catched %s" % the_order
-
-
-    # remove sentence before order
-    nb = user_said[user_said.find(the_order):]
-    truncate_list_word_said = nb.split()
-    print "truncate_list_word_said : %s" % truncate_list_word_said
-
-
-    # make dict var:value
-    dictVar = {}
-    for idx, ow in enumerate(list_word_in_order):
-        if _is_containing_bracket(ow):
-            # remove bracket et key dict
-            varname = ow.replace("{{","").replace("}}", "")
-            stopValue = _get_next_value_list(list_word_in_order[idx:])
-            if stopValue is None:
-                dictVar[varname] = " ".join(truncate_list_word_said)
-                break
-            for word_said in truncate_list_word_said:
-                if word_said == stopValue: break
-                if varname in dictVar:
-                    dictVar[varname] += " " + word_said
-                    truncate_list_word_said = truncate_list_word_said[1:]
-                else:
-                    dictVar[varname] = word_said
-        truncate_list_word_said = truncate_list_word_said[1:]
-    print "The dict Var : %s" % dictVar
-
-
-
-for us in user_said_list:
-    for od in order_list:
-        vector1 = text_to_vector(us)
-        vector2 = text_to_vector(od)
-
-        cosine = get_cosine(vector1, vector2)
-
-        print "Cosine -> ", cosine, " for usersaid: ",us, " ,order:", od
-
-
-
-
-
-

+ 0 - 58
tts/TTS.py

@@ -1,58 +0,0 @@
-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("kalliope")
-
-
-class TTS:
-    TTS_CONTENT_TYPE = "audio/mpeg"
-    TTS_TIMEOUT_SEC = 30
-
-    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(volume=volume)
-
-    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 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=language, words=words, file_path=file_path, cache=cache, voice=voice):
-            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=TTS_CONTENT_TYPE, timeout_expected=TTS_TIMEOUT_SEC):
-        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

+ 0 - 1
tts/__init__.py

@@ -1,4 +1,3 @@
-from TTS import TTS
 from voxygen import Voxygen
 from voxygen import Voxygen
 from pico2wave import Pico2wave
 from pico2wave import Pico2wave
 from voicerss import Voicerss
 from voicerss import Voicerss

+ 51 - 0
tts/acapela/README.md

@@ -0,0 +1,51 @@
+### Acapela
+
+This TTS is based on the [Acapela engine](http://www.acapela-group.com/)
+
+| Parameters | Required | Default | Choices                                                                            | Comment                                                                     |
+|------------|----------|---------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|
+| language   | YES      |         | 34 languages (http://www.acapela-group.com/voices/repertoire/), example: "sonid15" | Language are corresponding to an id plz check the note beside to find yours |
+| voice      | YES      |         | multiple and depending of the language                                             | Check available names on the web site                                       |
+| cache      | No       | TRUE    | True / False                                                                       | True if you want to use the cache with this TTS                             |
+
+
+#### Notes
+
+Corresponding languages to id :
+
+Arabic="sonid0"
+Catalan="sonid1"
+Czech="sonid2" 
+Danish="sonid3" 
+Dutch (Belgium)="sonid4" 
+Dutch (Netherlands)="sonid5" 
+English (AU)="sonid6" 
+English (India)="sonid7"
+English (Scottish)="sonid8"
+English (UK)="sonid9" 
+English (USA)="sonid10" 
+Faroese="sonid11"
+Finnish="sonid12"
+French (Belgium)="sonid13" 
+French (Canada) ="sonid14" 
+French (France)="sonid15"
+German="sonid16"
+Greek="sonid17" 
+Italian="sonid18"
+Japanese="sonid19"
+Korean="sonid20"
+Mandarin="sonid21"
+Norwegian="sonid22" 
+Polish="sonid23" 
+Portuguese (Brazil)="sonid24" 
+Portuguese (Portugal)="sonid25" 
+Russian="sonid26" 
+Sami (North)="sonid27"
+Spanish (Spain)="sonid28" 
+Spanish (US)="sonid29" 
+Swedish="sonid30" 
+Swedish (Finland) ="sonid31"
+Swedish (Gothenburg)  ="sonid32"
+Swedish (Scanian) ="sonid33" 
+Turkish ="sonid34"  
+                  

+ 46 - 35
tts/acapela/acapela.py

@@ -1,52 +1,63 @@
+import requests
+import re
+from core import FileManager
+from core.TTS.TTSModule import TTSModule, FailToLoadSoundFile, MissingTTSParameter
 import logging
 import logging
 
 
-import re
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
 
 
-import requests
+TTS_URL = "http://www.acapela-group.com/demo-tts/DemoHTML5Form_V2_fr.php"
+TTS_CONTENT_TYPE = "audio/mpeg"
+TTS_TIMEOUT_SEC = 30
 
 
-from core import AudioPlayer
-from tts import TTS
 
 
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
+class Acapela(TTSModule):
+    def __init__(self, **kwargs):
+        super(Acapela, self).__init__(**kwargs)
 
 
+        self.voice = kwargs.get('voice', None)
+        if self.voice is None:
+            raise MissingTTSParameter("voice parameter is required by the Acapela TTS")
 
 
-class Acapela(TTS):
-    TTS_LANGUAGES_DEFAULT = 'sonid15'
-    TTS_VOICE_DEFAULT = 'Manon'
-    TTS_URL = "http://www.acapela-group.com/demo-tts/DemoHTML5Form_V2_fr.php"
-    TTS_CONTENT_TYPE = "audio/mpeg"
-    TTS_TIMEOUT_SEC = 30
+    def say(self, words):
 
 
-    def __init__(self):
-        TTS.__init__(self)
+        self.generate_and_play(words, self._generate_audio_file)
 
 
-    def say(self, words=None, language=TTS_LANGUAGES_DEFAULT, voice=TTS_VOICE_DEFAULT, cache=True):
-        self.say_generic(cache, language, words, self.get_audio_acapela, AudioPlayer.PLAYER_MP3, AudioPlayer.AUDIO_MP3_22050_FREQUENCY, voice)
+    def _generate_audio_file(self):
 
 
-    def get_audio_acapela(self, **kwargs):
-        language = kwargs.get('language', None)
-        words = kwargs.get('words', None)
-        cache = kwargs.get('cache', None)
-        file_path = kwargs.get('file_path', None)
-        voice = kwargs.get('voice', None)
-        payload = Acapela.get_payload(language, voice, words)
-        url = Acapela.get_audio_link(self.TTS_URL, payload)
+        # Prepare payload
+        payload = self.get_payload()
 
 
-        return TTS.get_audio(file_path, cache, payload, url)
+        # Get the mp3 URL from the page
+        url = Acapela.get_audio_link(TTS_URL, payload)
 
 
-    @staticmethod
-    def get_audio_link(url, payload, timeout_expected=30):
-        r = requests.post(url, payload, timeout=timeout_expected)
-        data = r.content
-        return re.search("(?P<url>https?://[^\s]+).mp3", data).group(0)
+        # getting the mp3
+        r = requests.get(url, params=payload, stream=True, timeout=TTS_TIMEOUT_SEC)
+        content_type = r.headers['Content-Type']
 
 
-    @staticmethod
-    def get_payload(language, voice, words):
+        logger.debug("Acapela : Trying to get url: %s response code: %s and content-type: %s",
+                     r.url,
+                     r.status_code,
+                     content_type)
+        # Verify the response status code and the response content type
+        if r.status_code != requests.codes.ok or content_type != TTS_CONTENT_TYPE:
+            raise FailToLoadSoundFile("Acapela : Fail while trying to remotely access the audio file")
+
+        # OK we get the audio we can write the sound file
+        FileManager.write_in_file(self.file_path, r.content)
+
+    def get_payload(self):
         return {
         return {
-            "MyLanguages": language,
-            "MySelectedVoice": voice,
-            "MyTextForTTS": words,
+            "MyLanguages": self.language,
+            "MySelectedVoice": self.voice,
+            "MyTextForTTS": self.words,
             "t": "1",
             "t": "1",
             "SendToVaaS": ""
             "SendToVaaS": ""
         }
         }
+
+    @staticmethod
+    def get_audio_link(url, payload, timeout_expected=TTS_TIMEOUT_SEC):
+        r = requests.post(url, payload, timeout=timeout_expected)
+        data = r.content
+        return re.search("(?P<url>https?://[^\s]+).mp3", data).group(0)

+ 9 - 0
tts/googletts/README.md

@@ -0,0 +1,9 @@
+### Googletts
+
+This TTS is based on the [Google translate engine](http://translate.google.com/)
+
+
+| Parameters | Required | Default | Choices                                                                                     | Comment                                                                                                    |
+|------------|----------|---------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
+| language   | YES      |         | 103 languages (http://translate.google.com/about/intl/en_ALL/languages.html), example: "fr" | Language are identified with their ISO_639-1 codes (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) |
+| cache      | No       | TRUE    | True / False                                                                                | True if you want to use the cache with this TTS                                                            |

+ 34 - 24
tts/googletts/googletts.py

@@ -1,39 +1,49 @@
+import requests
+from core import FileManager
+from core.TTS.TTSModule import TTSModule, FailToLoadSoundFile
 import logging
 import logging
 
 
-from core import AudioPlayer
-from tts import TTS
-
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
+TTS_URL = "http://translate.google.com/translate_tts"
+TTS_CONTENT_TYPE = "audio/mpeg"
+TTS_TIMEOUT_SEC = 30
+
+
+class Googletts(TTSModule):
+    def __init__(self, **kwargs):
+        super(Googletts, self).__init__(**kwargs)
+
+    def say(self, words):
+
+        self.generate_and_play(words, self._generate_audio_file)
 
 
-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 _generate_audio_file(self):
 
 
-    def __init__(self):
-        TTS.__init__(self)
+        # Prepare payload
+        payload = self.get_payload()
 
 
-    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)
+        # getting the audio
+        r = requests.get(TTS_URL, params=payload, stream=True, timeout=TTS_TIMEOUT_SEC)
+        content_type = r.headers['Content-Type']
 
 
-    def get_audio_googletts(self, **kwargs):
-        words = kwargs.get('words', None)
-        cache = kwargs.get('cache', None)
-        file_path = kwargs.get('file_path', None)
-        language = kwargs.get('language', None)
-        payload = Googletts.get_payload(language,words)
+        logger.debug("Googletts : Trying to get url: %s response code: %s and content-type: %s",
+                     r.url,
+                     r.status_code,
+                     content_type)
+        # Verify the response status code and the response content type
+        if r.status_code != requests.codes.ok or content_type != TTS_CONTENT_TYPE:
+            raise FailToLoadSoundFile("Googletts : Fail while trying to remotely access the audio file")
 
 
-        return TTS.get_audio(file_path, cache, payload, self.TTS_URL)
+        # OK we get the audio we can write the sound file
+        FileManager.write_in_file(self.file_path, r.content)
 
 
-    @staticmethod
-    def get_payload(language, words):
+    def get_payload(self):
         return {
         return {
-            "q": words,
-            "tl": language,
+            "q": self.words,
+            "tl": self.language,
             "ie": "UTF-8",
             "ie": "UTF-8",
             "total": "1",
             "total": "1",
             "client": "tw-ob"
             "client": "tw-ob"
-        }
+        }

+ 20 - 0
tts/pico2wave/README.md

@@ -0,0 +1,20 @@
+### Pico2wave
+
+This TTS is based on the SVOX picoTTS engine
+
+| Parameters | Required | Default | Choices      | Comment                                         |
+|------------|----------|---------|--------------|-------------------------------------------------|
+| language   | YES      |         | 6 languages  | List of supported languages in the Note section |
+| cache      | No       | TRUE    | True / False | True if you want to use the cache with this TTS |
+
+
+#### Notes :
+
+Supported languages : 
+
+Anglais en-US
+Anglais en-GB
+Français fr-FR
+Espagnol es-ES
+Allemand de-DE
+Italien it-IT

+ 28 - 15
tts/pico2wave/pico2wave.py

@@ -1,7 +1,7 @@
+import os
 import subprocess
 import subprocess
 
 
-from core import AudioPlayer
-from tts import TTS
+from core.TTS.TTSModule import TTSModule
 import logging
 import logging
 import sys
 import sys
 
 
@@ -9,20 +9,33 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
 
 
-class Pico2wave(TTS):
-    TTS_LANGUAGES_DEFAULT = 'fr-FR'
+class Pico2wave(TTSModule):
 
 
-    def __init__(self):
-        TTS.__init__(self, AudioPlayer.PLAYER_WAV)
+    def __init__(self, **kwargs):
+        super(Pico2wave, self).__init__(**kwargs)
 
 
-    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)
+    def say(self, words):
+
+        self.generate_and_play(words, self._generate_audio_file)
+
+    def _generate_audio_file(self):
+        pico2wave_exec_path = ["/usr/bin/pico2wave"]
+
+        # pico2wave needs that the file path ends with .wav
+        tmp_path = self.file_path+".wav"
+        pico2wave_options = ["-l=%s" % self.language, "-w=%s" % tmp_path]
+
+        final_command = list()
+        final_command.extend(pico2wave_exec_path)
+        final_command.extend(pico2wave_options)
+        final_command.append(self.words)
+
+        logger.debug("Pico2wave command: %s" % final_command)
+
+        # generate the file with pico2wav
+        subprocess.call(final_command, stderr=sys.stderr)
+
+        # remove the extension .wav
+        os.rename(tmp_path, self.file_path)
 
 
-    @staticmethod
-    def get_audio_pico2wave(**kwargs):
-        language = kwargs.get('language', None)
-        words = kwargs.get('words', None)
-        file_path = kwargs.get('file_path', None)
 
 
-        subprocess.check_output(["/usr/bin/pico2wave", "-l=%s" % language, "-w=%s" % file_path, words], stderr=sys.stderr)
-        return True

+ 8 - 0
tts/voicerss/README.md

@@ -0,0 +1,8 @@
+### Voicerss
+
+This TTS is based on the [VoiceRSS engine](http://www.voicerss.org/)
+
+| Parameters | Required | Default | Choices                                                                          | Comment                                         |
+|------------|----------|---------|----------------------------------------------------------------------------------|-------------------------------------------------|
+| language   | YES      |         | 26 languages (http://www.voicerss.org/api/documentation.aspx), example : "fr-fr" | Languages are identified by the LCID string     |
+| cache      | No       | TRUE    | True / False                                                                     | True if you want to use the cache with this TTS |

+ 34 - 23
tts/voicerss/voicerss.py

@@ -1,37 +1,48 @@
+import requests
+from core import FileManager
+from core.TTS.TTSModule import TTSModule, FailToLoadSoundFile
 import logging
 import logging
 
 
-from core import AudioPlayer
-from tts import TTS
-
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
+TTS_URL = "http://www.voicerss.org/controls/speech.ashx"
+TTS_CONTENT_TYPE = "audio/mpeg"
+TTS_TIMEOUT_SEC = 30
+
+
+class Voicerss(TTSModule):
+    def __init__(self, **kwargs):
+        super(Voicerss, self).__init__(**kwargs)
 
 
-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 say(self, words):
 
 
-    def __init__(self):
-        TTS.__init__(self)
+        self.generate_and_play(words, self._generate_audio_file)
 
 
-    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 _generate_audio_file(self):
 
 
-    def get_audio_voicerss(self, **kwargs):
-        words = kwargs.get('words', None)
-        cache = kwargs.get('cache', None)
-        file_path = kwargs.get('file_path', None)
-        language = kwargs.get('language', None)
-        payload = Voicerss.get_payload(language, words)
+        # Prepare payload
+        payload = self.get_payload()
 
 
-        return TTS.get_audio(file_path, cache, payload, self.TTS_URL)
+        # getting the audio
+        r = requests.get(TTS_URL, params=payload, stream=True, timeout=TTS_TIMEOUT_SEC)
+        content_type = r.headers['Content-Type']
 
 
-    @staticmethod
-    def get_payload(language, words):
+        logger.debug("Voicerss : Trying to get url: %s response code: %s and content-type: %s",
+                     r.url,
+                     r.status_code,
+                     content_type)
+        # Verify the response status code and the response content type
+        if r.status_code != requests.codes.ok or content_type != TTS_CONTENT_TYPE:
+            raise FailToLoadSoundFile("Voicerss : Fail while trying to remotely access the audio file")
+
+        # OK we get the audio we can write the sound file
+        FileManager.write_in_file(self.file_path, r.content)
+
+    def get_payload(self):
         return {
         return {
-            "src": words,
-            "hl": language,
+            "src": self.words,
+            "hl": self.language,
             "c": "mp3"
             "c": "mp3"
         }
         }
+

+ 11 - 0
tts/voxygen/README.md

@@ -0,0 +1,11 @@
+### Voxygen
+
+This TTS is based on the demo of the [Voxygen engine](https://www.voxygen-group.com/)
+
+| Parameters | Required | Default | Choices                                                              | Comment                                                                                       |
+|------------|----------|---------|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
+| voice      | yes      |         | [see the full list](https://www.voxygen-group.com/). Example: "Emma" | Do not set any accent. For Example the voice "Agnès" will be "Agnes" is the settings.yml file |
+| cache      | no       | TRUE    |                                                                      | True if you want to use the cache with this TTS                                               |
+
+
+

+ 33 - 17
tts/voxygen/voxygen.py

@@ -1,31 +1,47 @@
 import logging
 import logging
 
 
-from core import AudioPlayer
-from tts import TTS
+import requests
+
+from core import FileManager
+from core.TTS.TTSModule import TTSModule, MissingTTSParameter, FailToLoadSoundFile
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
+TTS_URL = "https://www.voxygen.fr/sites/all/modules/voxygen_voices/assets/proxy/index.php"
+TTS_TIMEOUT_SEC = 30
+TTS_CONTENT_TYPE = "audio/mpeg"
+
+
+class Voxygen(TTSModule):
+
+    def __init__(self, **kwargs):
+        # voxygen does'nt need a language. The name of the voice correspond to a lang
+        super(Voxygen, self).__init__(language="any", **kwargs)
+
+        self.voice = kwargs.get('voice', None)
+        if self.voice is None:
+            raise MissingTTSParameter("voice parameter is required by the Voxygen TTS")
 
 
-class Voxygen(TTS):
-    TTS_VOICE_DEFAULT = "Michel"
-    TTS_LANGUAGES_DEFAULT = "default"
-    TTS_URL = "https://www.voxygen.fr/sites/all/modules/voxygen_voices/assets/proxy/index.php"
+    def say(self, words):
+        self.generate_and_play(words, self._generate_audio_file)
 
 
-    def __init__(self):
-        TTS.__init__(self)
+    def _generate_audio_file(self):
+        payload = self.get_payload(self.voice, self.words)
 
 
-    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)
+        # getting the mp3
+        r = requests.get(TTS_URL, params=payload, stream=True, timeout=TTS_TIMEOUT_SEC)
+        content_type = r.headers['Content-Type']
 
 
-    def get_audio_voxygen(self, **kwargs):
-        words = kwargs.get('words', None)
-        cache = kwargs.get('cache', None)
-        file_path = kwargs.get('file_path', None)
-        voice = kwargs.get('voice', None)
-        payload = Voxygen.get_payload(voice, words)
+        logger.debug("Voxygen : Trying to get url: %s response code: %s and content-type: %s",
+                     r.url,
+                     r.status_code,
+                     content_type)
 
 
-        return TTS.get_audio(file_path, cache, payload, self.TTS_URL)
+        if r.status_code == requests.codes.ok and content_type == TTS_CONTENT_TYPE:
+            FileManager.write_in_file(self.file_path, r.content)
+        else:
+            logger.debug("Unable to get a valid audio file. Returned code: %s" % r.status_code)
 
 
     @staticmethod
     @staticmethod
     def get_payload(voice, words):
     def get_payload(voice, words):