monf преди 8 години
родител
ревизия
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
 import logging
 
+from core.FileManager import FileManager
 from core.Models.RestAPI import RestAPI
 from core.Models.Settings import Settings
 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_sounds = cls._get_random_wake_up_sounds(settings)
         rest_api = cls._get_rest_api(settings)
+        cache_path = cls._get_cache_path(settings)
 
         # create a setting object
         setting_object = Settings(default_stt_name=default_stt_name,
@@ -62,7 +64,8 @@ class SettingLoader(object):
                                   triggers=triggers,
                                   random_wake_up_answers=random_wake_up_answers,
                                   random_wake_up_sounds=random_wake_up_sounds,
-                                  rest_api=rest_api)
+                                  rest_api=rest_api,
+                                  cache_path=cache_path)
         return setting_object
 
     @staticmethod
@@ -261,3 +264,19 @@ class SettingLoader(object):
             return rest_api_obj
         else:
             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 shutil
 
 
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
 class FileManager:
     def __init__(self):
         pass
@@ -13,10 +18,13 @@ class FileManager:
 
     @staticmethod
     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
     def wipe_cache(cache_path):
@@ -30,3 +38,26 @@ class FileManager:
     def remove_file(file_path):
         if os.path.exists(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 random
 
-from core import AudioPlayer
 from core import Utils
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.OrderAnalyser import OrderAnalyser
 from core.OrderListener import OrderListener
+from core.Players import Mplayer
 from core.TriggerLauncher import TriggerLauncher
 from flask import Flask
 from core.RestAPI.FlaskAPI import FlaskAPI
@@ -57,10 +57,8 @@ class MainController:
         if self.settings.random_wake_up_answers is not None:
             Say(message=self.settings.random_wake_up_answers)
         else:
-            ap = AudioPlayer()
-            ap.init_play()
             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):
         """

+ 2 - 1
core/Models/Settings.py

@@ -3,7 +3,7 @@
 class Settings(object):
     def __init__(self, default_tts_name=None, default_stt_name=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_stt_name = default_stt_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.triggers = triggers
         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)
 
         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)
+
+            # 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
             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):
         """

+ 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.OrderListener import OrderListener
-from core.AudioPlayer import AudioPlayer
 from core.ShellGui import ShellGui
-from core.Cache import Cache
 from core.FileManager import FileManager
 from core.Utils import Utils

+ 1 - 0
install/install.yml

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

+ 4 - 3
settings.yml

@@ -48,7 +48,9 @@ speech_to_text:
 # Text to speech
 # ---------------------------
 # 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
 # Available engine are:
@@ -59,8 +61,7 @@ text_to_speech:
       language: "fr-FR"
       cache: True
   - voxygen:
-      language: "fr"
-      voice: "Emma"
+      voice: "Agnes"
       cache: True
   - acapela:
       language: "sonid15"

+ 28 - 26
test.py

@@ -1,17 +1,11 @@
 # coding: utf8
 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 Utils
 from core.ConfigurationManager import SettingLoader
-from core.ConfigurationManager import YAMLLoader
 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()
 logger = logging.getLogger("kalliope")
@@ -21,33 +15,41 @@ logger.setLevel(logging.DEBUG)
 # oa = OrderAnalyser(order=order)
 # oa.start()
 
-# SettingLoader.get_settings()
-#
+
+
 brain = BrainLoader.get_brain()
 
-order = "est-ce que j'ai des emails"
+order = "bonjour"
 
 oa = OrderAnalyser(order=order, brain=brain)
 
 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 pico2wave import Pico2wave
 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 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 {
-            "MyLanguages": language,
-            "MySelectedVoice": voice,
-            "MyTextForTTS": words,
+            "MyLanguages": self.language,
+            "MySelectedVoice": self.voice,
+            "MyTextForTTS": self.words,
             "t": "1",
             "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
 
-from core import AudioPlayer
-from tts import TTS
-
 logging.basicConfig()
 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 {
-            "q": words,
-            "tl": language,
+            "q": self.words,
+            "tl": self.language,
             "ie": "UTF-8",
             "total": "1",
             "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
 
-from core import AudioPlayer
-from tts import TTS
+from core.TTS.TTSModule import TTSModule
 import logging
 import sys
 
@@ -9,20 +9,33 @@ logging.basicConfig()
 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
 
-from core import AudioPlayer
-from tts import TTS
-
 logging.basicConfig()
 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 {
-            "src": words,
-            "hl": language,
+            "src": self.words,
+            "hl": self.language,
             "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
 
-from core import AudioPlayer
-from tts import TTS
+import requests
+
+from core import FileManager
+from core.TTS.TTSModule import TTSModule, MissingTTSParameter, FailToLoadSoundFile
 
 logging.basicConfig()
 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
     def get_payload(voice, words):