Browse Source

watson tts (#393)

* remove acapela TTS

* add watson TTS

* [Exception] Check parameters

* [Exception] check required Parameters

* [Exception] check parameters

* [Documentation] remove espeak
Nicolas Marcq 7 years ago
parent
commit
68143b8837

+ 9 - 8
Docs/tts_list.md

@@ -6,14 +6,15 @@ See the [complete TTS documentation](stt.md) for more information.
 ## Core TTS
 ## Core TTS
 Core TTSs are already packaged with the installation of Kalliope an can be used out of the box.
 Core TTSs are already packaged with the installation of Kalliope an can be used out of the box.
 
 
-| Name      | Description                                      | Type        |
-|-----------|--------------------------------------------------|-------------|
-| Acapela   | [Acapela](../kalliope/tts/acapela/README.md)     | Cloud based |
-| GoogleTTS | [GoogleTTS](../kalliope/tts/googletts/README.md) | Cloud based |
-| VoiceRSS  | [VoiceRSS](../kalliope/tts/voicerss/README.md)   | Cloud based |
-| Pico2wave | [Pico2wave](../kalliope/tts/pico2wave/README.md) | Self hosted |
-| ~~Voxygen~~ | ~~[Voxygen](../kalliope/tts/voxygen/README.md)~~ |~~Cloud based~~|
-| Espeak    | [Espeak](../kalliope/tts/espeak/README.md)       | Self hosted |
+| Name        | Description                                          | Type            |
+|-------------|------------------------------------------------------|-----------------|
+| ~~Acapela~~ | ~~[Acapela](../kalliope/tts/acapela/README.md)~~     | ~~Cloud based~~ |
+| GoogleTTS   | [GoogleTTS](../kalliope/tts/googletts/README.md)     | Cloud based     |
+| VoiceRSS    | [VoiceRSS](../kalliope/tts/voicerss/README.md)       | Cloud based     |
+| Pico2wave   | [Pico2wave](../kalliope/tts/pico2wave/README.md)     | Self hosted     |
+| ~~Voxygen~~ | ~~[Voxygen](../kalliope/tts/voxygen/README.md)~~     | ~~Cloud based~~ |
+| Espeak      | [Espeak](../kalliope/tts/espeak/README.md)           | Self hosted     |
+| Watson      | [watson](../kalliope/tts/watson/README.md)           | Cloud based     |
 
 
 ## Community TTS
 ## Community TTS
 Community TTSs need to be installed manually.
 Community TTSs need to be installed manually.

+ 4 - 4
kalliope/settings.yml

@@ -72,16 +72,16 @@ text_to_speech:
   - pico2wave:
   - pico2wave:
       language: "fr-FR"
       language: "fr-FR"
       cache: True
       cache: True
-  - acapela:
-      language: "sonid15"
-      voice: "Manon"
-      cache: True
   - googletts:
   - googletts:
       language: "fr"
       language: "fr"
       cache: True
       cache: True
   - voicerss:
   - voicerss:
       language: "fr-fr"
       language: "fr-fr"
       cache: True
       cache: True
+  - watson:
+      username: "me"
+      password: "password"
+      voice: "fr-FR_ReneeVoice"
 
 
 # ---------------------------
 # ---------------------------
 # players
 # players

+ 0 - 17
kalliope/tts/acapela/README.md

@@ -1,17 +0,0 @@
-### Acapela
-
-This TTS is based on the [Acapela engine](http://www.acapela-group.com/)
-
-| Parameters | Required | Default | Choices                                                                   | Comment                                                                     |
-|------------|----------|---------|---------------------------------------------------------------------------|-----------------------------------------------------------------------------|
-| voice      | YES      |         | 34 languages (https://acapela-box.com/AcaBox/index.php), example: "manon" | Check available voices on the web site                                      |
-| spd        | NO       | 180     | Min: 120, Max: 240                                                        | Speech rate                                                                 |
-| vct        | NO       | 100     | Min: 85, Max: 115                                                         | Voice shaping                                                               |
-| cache      | No       | TRUE    | True / False                                                              | True if you want to use the cache with this TTS                             |
-
-#### Notes
-
-The voice name is attached to a specific language.
-To test and get the name of a voice, please refer to [this website(https://acapela-box.com/AcaBox/index.php].
-
-The generated file is mp3. 

+ 0 - 1
kalliope/tts/acapela/__init__.py

@@ -1 +0,0 @@
-from .acapela import Acapela

+ 0 - 131
kalliope/tts/acapela/acapela.py

@@ -1,131 +0,0 @@
-import logging
-import re
-
-import requests
-
-from kalliope.core import FileManager
-from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile, MissingTTSParameter
-
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
-
-TTS_URL = "https://acapela-box.com/AcaBox/dovaas.php"
-TTS_CONTENT_TYPE = "audio/mpeg"
-TTS_TIMEOUT_SEC = 30
-
-
-class TCPTimeOutError(Exception):
-    """
-    This error is raised when the TCP connection has been lost. Probably due to a low internet
-    connection while trying to access the remote API.
-    """
-    pass
-
-
-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")
-
-        # speech rate
-        self.spd = kwargs.get('spd', 180)
-        # VOICE SHAPING
-        self.vct = kwargs.get('vct', 100)
-
-        self.words = None
-
-    def say(self, words):
-        """
-        :param words: The sentence to say
-        """
-        self.words = words
-        self.generate_and_play(words, self._generate_audio_file)
-
-    def _generate_audio_file(self):
-        """
-        Generic method used as a Callback in TTSModule
-            - must provided the audio file and write it on the disk
-
-        .. raises:: FailToLoadSoundFile, TCPTimeOutError
-        """
-        # Prepare payload
-        payload = self.get_payload()
-
-        cookie = Acapela._get_cookie()
-
-        # Get the mp3 URL from the page
-        mp3_url = Acapela.get_audio_link(TTS_URL, payload, cookie)
-
-        # getting the mp3
-        headers = {
-            "Cookie": "%s" % cookie
-        }
-        r = requests.get(mp3_url, headers=headers, stream=True, timeout=TTS_TIMEOUT_SEC)
-        content_type = r.headers['Content-Type']
-
-        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):
-        """
-        Generic method used load the payload used to access the remote api
-        :return: Payload to use to access the remote api
-        """
-        return {
-            "text": "%s" % self.words,
-            "voice": "%s22k" % self.voice,
-            "spd": "%s" % self.spd,
-            "vct": "%s" % self.vct,
-            "codecMP3": "1",
-            "format": "WAV 22kHz",
-            "listen": 1
-        }
-
-    @staticmethod
-    def get_audio_link(url, payload, cookie, timeout_expected=TTS_TIMEOUT_SEC):
-        """
-        Return the audio link
-
-        :param url: the url to access
-        :param payload: the payload to use to access the remote api
-        :param timeout_expected: timeout before the post request is cancel
-        :param cookie: cookie used for authentication
-        :return: the audio link
-        :rtype: String
-        """
-        headers = {
-            "Cookie": "%s" % cookie
-        }
-
-        r = requests.post(url, data=payload, headers=headers, timeout=timeout_expected)
-        data = r.json()
-        return data["snd_url"]
-
-    @staticmethod
-    def _get_cookie():
-        """
-        Get a cookie that is used to authenticate post request
-        :return: the str cookie
-        """
-        returned_cookie = ""
-        index_url = "https://acapela-box.com/AcaBox/index.php"
-        r = requests.get(index_url)
-
-        regex = "(acabox=\w+)"
-        cookie_match = re.match(regex, r.headers["Set-Cookie"])
-
-        if cookie_match:
-            returned_cookie = cookie_match.group(1)
-
-        return returned_cookie

+ 0 - 4
kalliope/tts/espeak/README.md

@@ -4,10 +4,6 @@
 
 
 This TTS is based on the eSpeak engine
 This TTS is based on the eSpeak engine
 
 
-## Installation
-
-    kalliope install --git-url "https://github.com/Ultchad/kalliope-espeak.git"
-
 ## Options
 ## Options
 
 
 | Parameters | Required | Default         | Choices                | Comment                                                                                       |
 | Parameters | Required | Default         | Choices                | Comment                                                                                       |

+ 1 - 1
kalliope/tts/espeak/espeak.py

@@ -19,7 +19,7 @@ class Espeak(TTSModule):
         self.pitch = str(kwargs.get('pitch', '50'))
         self.pitch = str(kwargs.get('pitch', '50'))
         self.espeak_exec_path = kwargs.get('path', r'/usr/bin/espeak')
         self.espeak_exec_path = kwargs.get('path', r'/usr/bin/espeak')
 
 
-        if self.voice == 'Default':
+        if self.voice == 'default' or self.voice is None:
             raise MissingTTSParameter("voice parameter is required by the eSpeak TTS")
             raise MissingTTSParameter("voice parameter is required by the eSpeak TTS")
 
 
         # if voice = default, don't add voice option to espeak
         # if voice = default, don't add voice option to espeak

+ 14 - 1
kalliope/tts/googletts/googletts.py

@@ -1,6 +1,6 @@
 import requests
 import requests
 from kalliope.core import FileManager
 from kalliope.core import FileManager
-from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile
+from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile, MissingTTSParameter
 import logging
 import logging
 
 
 logging.basicConfig()
 logging.basicConfig()
@@ -15,6 +15,8 @@ class Googletts(TTSModule):
     def __init__(self, **kwargs):
     def __init__(self, **kwargs):
         super(Googletts, self).__init__(**kwargs)
         super(Googletts, self).__init__(**kwargs)
 
 
+        self._check_parameters()
+
     def say(self, words):
     def say(self, words):
         """
         """
         :param words: The sentence to say
         :param words: The sentence to say
@@ -22,6 +24,17 @@ class Googletts(TTSModule):
 
 
         self.generate_and_play(words, self._generate_audio_file)
         self.generate_and_play(words, self._generate_audio_file)
 
 
+    def _check_parameters(self):
+        """
+        Check parameters are ok, raise MissingTTSParameterException exception otherwise.
+        :return: true if parameters are ok, raise an exception otherwise
+
+               .. raises:: MissingTTSParameterException
+        """
+        if self.language == "default" or self.language is None:
+            raise MissingTTSParameter("[GoogleTTS] Missing parameters, check documentation !")
+        return True
+
     def _generate_audio_file(self):
     def _generate_audio_file(self):
         """
         """
         Generic method used as a Callback in TTSModule
         Generic method used as a Callback in TTSModule

+ 14 - 1
kalliope/tts/pico2wave/pico2wave.py

@@ -1,7 +1,7 @@
 import os
 import os
 import subprocess
 import subprocess
 
 
-from kalliope.core.TTS.TTSModule import TTSModule
+from kalliope.core.TTS.TTSModule import TTSModule, MissingTTSParameter
 import sox
 import sox
 
 
 import logging
 import logging
@@ -18,6 +18,19 @@ class Pico2wave(TTSModule):
         self.samplerate = kwargs.get('samplerate', None)
         self.samplerate = kwargs.get('samplerate', None)
         self.path = kwargs.get('path', None)
         self.path = kwargs.get('path', None)
 
 
+        self._check_parameters()
+
+    def _check_parameters(self):
+        """
+        Check parameters are ok, raise MissingTTSParameters exception otherwise.
+        :return: true if parameters are ok, raise an exception otherwise
+
+               .. raises:: MissingTTSParameterException
+        """
+        if self.language == "default" or self.language is None:
+            raise MissingTTSParameter("[pico2wave] Missing parameters, check documentation !")
+        return True
+
     def say(self, words):
     def say(self, words):
         """
         """
         :param words: The sentence to say
         :param words: The sentence to say

+ 14 - 2
kalliope/tts/voicerss/voicerss.py

@@ -1,6 +1,6 @@
 import requests
 import requests
 from kalliope.core import FileManager
 from kalliope.core import FileManager
-from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile
+from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile, MissingTTSParameter
 import logging
 import logging
 
 
 logging.basicConfig()
 logging.basicConfig()
@@ -14,6 +14,7 @@ TTS_TIMEOUT_SEC = 30
 class Voicerss(TTSModule):
 class Voicerss(TTSModule):
     def __init__(self, **kwargs):
     def __init__(self, **kwargs):
         super(Voicerss, self).__init__(**kwargs)
         super(Voicerss, self).__init__(**kwargs)
+        self._check_parameters()
 
 
     def say(self, words):
     def say(self, words):
         """
         """
@@ -22,6 +23,17 @@ class Voicerss(TTSModule):
 
 
         self.generate_and_play(words, self._generate_audio_file)
         self.generate_and_play(words, self._generate_audio_file)
 
 
+    def _check_parameters(self):
+        """
+        Check parameters are ok, raise MissingTTSParameterException exception otherwise.
+        :return: true if parameters are ok, raise an exception otherwise
+
+               .. raises:: MissingTTSParameterException
+        """
+        if self.language == "default" or self.language is None:
+            raise MissingTTSParameter("[voicerss] Missing parameters, check documentation !")
+        return True
+
     def _generate_audio_file(self):
     def _generate_audio_file(self):
         """
         """
         Generic method used as a Callback in TTSModule
         Generic method used as a Callback in TTSModule
@@ -49,7 +61,7 @@ class Voicerss(TTSModule):
 
 
     def get_payload(self):
     def get_payload(self):
         """
         """
-        Generic method used load the payload used to acces the remote api
+        Generic method used load the payload used to access the remote api
 
 
         :return: Payload to use to access the remote api
         :return: Payload to use to access the remote api
         """
         """

+ 59 - 0
kalliope/tts/watson/README.md

@@ -0,0 +1,59 @@
+# Watson TTS
+
+## Synopsis
+
+This TTS is based on the [Watson engine](https://www.ibm.com/watson/services/text-to-speech/).
+This one is free for less than 10,000 Characters per Month.
+
+You need to create an account and then a project to get a username and password.
+
+Once you project created, you should see your credentials like the following
+```
+{
+  "url": "https://stream.watsonplatform.net/text-to-speech/api",
+  "username": "785dazs-example-98dz-b324-a965478az",
+  "password": "generated_password"
+}
+```
+
+
+## Options
+
+| Parameters | Required | Default | Choices               | Comment                                           |
+|------------|----------|---------|-----------------------|---------------------------------------------------|
+| username   | yes      |         |                       | Username of the created service in IBM cloud      |
+| password   | yes      |         |                       | Password related to the username                  |
+| voice      | yes      |         | See voice table below | Code that define the voice used for the synthesis |
+
+## Voice code
+
+Voice code that can be used in the voice flag of your configuration
+
+| Languages              | Code                             | Gender |
+|------------------------|----------------------------------|--------|
+| German                 | de-DE_BirgitVoice                | Female |
+| German                 | de-DE_DieterVoice                | Male   |
+| UK English             | en-GB_KateVoice                  | Female |
+| US English             | en-US_AllisonVoice               | Female |
+| US English             | en-US_LisaVoice                  | Female |
+| US English             | en-US_MichaelVoice (the default) | Male   |
+| Castilian Spanish      | es-ES_EnriqueVoice               | Male   |
+| Castilian Spanish      | es-ES_LauraVoice                 | Female |
+| Latin American Spanish | es-LA_SofiaVoice                 | Female |
+| North American Spanish | es-US_SofiaVoice                 | Female |
+| French                 | fr-FR_ReneeVoice                 | Female |
+| Italian                | it-IT_FrancescaVoice             | Female |
+| Japanese               | ja-JP_EmiVoice                   | Female |
+| Brazilian Portuguese   | pt-BR_IsabelaVoice               | Female |
+
+## Example configuration in settings.yml
+
+```yml
+default_text_to_speech: "watson"
+cache_path: "/tmp/kalliope_tts_cache"
+text_to_speech:
+  - watson:
+      username: "username_code"
+      password: "generated_password"
+      voice: "fr-FR_ReneeVoice"
+```

+ 0 - 0
kalliope/tts/watson/__init__.py


+ 72 - 0
kalliope/tts/watson/watson.py

@@ -0,0 +1,72 @@
+from kalliope.core.Utils.FileManager import FileManager
+from requests.auth import HTTPBasicAuth
+from kalliope.core.TTS.TTSModule import TTSModule, MissingTTSParameter
+import logging
+import requests
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+TTS_URL = "https://stream.watsonplatform.net/text-to-speech/api/v1"
+TTS_CONTENT_TYPE = "audio/wav"
+
+
+class Watson(TTSModule):
+    def __init__(self, **kwargs):
+        super(Watson, self).__init__(**kwargs)
+
+        # set parameter from what we receive from the settings
+        self.username = kwargs.get('username', None)
+        self.password = kwargs.get('password', None)
+        self.voice = kwargs.get('voice', None)
+
+        self._check_parameters()
+
+    def _check_parameters(self):
+        """
+        Check parameters are ok, raise missingparameters exception otherwise.
+        :return: true if parameters are ok, raise an exception otherwise
+
+               .. raises:: MissingParameterException
+        """
+        if self.username is None or self.password is None or self.voice is None:
+            raise MissingTTSParameter("[Watson] Missing parameters, check documentation !")
+        return True
+
+    def say(self, words):
+        """
+        """
+        self.generate_and_play(words, self._generate_audio_file)
+
+    def _generate_audio_file(self):
+        """
+        Generic method used as a Callback in TTSModule
+        """
+
+        # Prepare payload
+        payload = self.get_payload()
+
+        headers = {
+            "Content-Type": "application/json",
+            "Accept": "audio/wav"
+        }
+
+        url = "%s/synthesize?voice=%s" % (TTS_URL, self.voice)
+
+        response = requests.post(url,
+                                 auth=HTTPBasicAuth(self.username, self.password),
+                                 headers=headers,
+                                 json=payload)
+
+        logger.debug("[Watson TTS] status code: %s" % response.status_code)
+
+        if response.status_code == 200:
+            # OK we get the audio we can write the sound file
+            FileManager.write_in_file(self.file_path, response.content)
+        else:
+            logger.debug("[Watson TTS] Fail to get audio. Header: %s" % response.headers)
+
+    def get_payload(self):
+        return {
+            "text": self.words
+        }