瀏覽代碼

Merge branch 'tts_refacto_acapela' into tts_refacto_nico

monf 8 年之前
父節點
當前提交
76eb3228f8

+ 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)

+ 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)

+ 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):
         """
         """

+ 12 - 0
core/TTS/TTSModule.py

@@ -11,10 +11,18 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
 
 
+class MissingTTSParameter(Exception):
+    pass
+
+
 class TtsGenerateAudioFunctionNotFound(Exception):
 class TtsGenerateAudioFunctionNotFound(Exception):
     pass
     pass
 
 
 
 
+class FailToLoadSoundFile(Exception):
+    pass
+
+
 class TTSModule(object):
 class TTSModule(object):
 
 
     def __init__(self, **kwargs):
     def __init__(self, **kwargs):
@@ -75,6 +83,10 @@ class TTSModule(object):
         # then play the generated audio file
         # then play the generated audio file
         self.play_audio()
         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):
     def _get_path_to_store_audio(self):
         """
         """
         Get a sentence (a text) an return the full path of the file
         Get a sentence (a text) an return the full path of the file

+ 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:

+ 2 - 2
settings.yml

@@ -48,7 +48,7 @@ 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
 # where we store generated audio files from TTS engine to reuse them
 cache_path: "/tmp/kalliope_tts_cache"
 cache_path: "/tmp/kalliope_tts_cache"
 
 
@@ -62,7 +62,7 @@ text_to_speech:
       cache: True
       cache: True
   - voxygen:
   - voxygen:
       language: "fr"
       language: "fr"
-      voice: "Emma"
+      voice: "Fabienne"
       cache: True
       cache: True
   - acapela:
   - acapela:
       language: "sonid15"
       language: "sonid15"

+ 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

+ 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"
         }
         }
+

+ 37 - 17
tts/voxygen/voxygen.py

@@ -1,31 +1,51 @@
 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):
+        super(Voxygen, self).__init__(**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)
+        try:
+            if r.status_code == requests.codes.ok and content_type == TTS_CONTENT_TYPE:
+                return FileManager.write_in_file(self.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.")
 
 
     @staticmethod
     @staticmethod
     def get_payload(voice, words):
     def get_payload(voice, words):