Преглед на файлове

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

+ 12 - 0
core/TTS/TTSModule.py

@@ -11,10 +11,18 @@ 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):
@@ -75,6 +83,10 @@ class TTSModule(object):
         # 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

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

+ 2 - 2
settings.yml

@@ -48,7 +48,7 @@ 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"
 
@@ -62,7 +62,7 @@ text_to_speech:
       cache: True
   - voxygen:
       language: "fr"
-      voice: "Emma"
+      voice: "Fabienne"
       cache: True
   - acapela:
       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 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

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

+ 37 - 17
tts/voxygen/voxygen.py

@@ -1,31 +1,51 @@
 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):
+        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
     def get_payload(voice, words):