Parcourir la 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 il y a 7 ans
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 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 TTSs need to be installed manually.

+ 4 - 4
kalliope/settings.yml

@@ -72,16 +72,16 @@ text_to_speech:
   - pico2wave:
       language: "fr-FR"
       cache: True
-  - acapela:
-      language: "sonid15"
-      voice: "Manon"
-      cache: True
   - googletts:
       language: "fr"
       cache: True
   - voicerss:
       language: "fr-fr"
       cache: True
+  - watson:
+      username: "me"
+      password: "password"
+      voice: "fr-FR_ReneeVoice"
 
 # ---------------------------
 # 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
 
-## Installation
-
-    kalliope install --git-url "https://github.com/Ultchad/kalliope-espeak.git"
-
 ## Options
 
 | 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.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")
 
         # if voice = default, don't add voice option to espeak

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

@@ -1,6 +1,6 @@
 import requests
 from kalliope.core import FileManager
-from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile
+from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile, MissingTTSParameter
 import logging
 
 logging.basicConfig()
@@ -15,6 +15,8 @@ class Googletts(TTSModule):
     def __init__(self, **kwargs):
         super(Googletts, self).__init__(**kwargs)
 
+        self._check_parameters()
+
     def say(self, words):
         """
         :param words: The sentence to say
@@ -22,6 +24,17 @@ class Googletts(TTSModule):
 
         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):
         """
         Generic method used as a Callback in TTSModule

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

@@ -1,7 +1,7 @@
 import os
 import subprocess
 
-from kalliope.core.TTS.TTSModule import TTSModule
+from kalliope.core.TTS.TTSModule import TTSModule, MissingTTSParameter
 import sox
 
 import logging
@@ -18,6 +18,19 @@ class Pico2wave(TTSModule):
         self.samplerate = kwargs.get('samplerate', 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):
         """
         :param words: The sentence to say

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

@@ -1,6 +1,6 @@
 import requests
 from kalliope.core import FileManager
-from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile
+from kalliope.core.TTS.TTSModule import TTSModule, FailToLoadSoundFile, MissingTTSParameter
 import logging
 
 logging.basicConfig()
@@ -14,6 +14,7 @@ TTS_TIMEOUT_SEC = 30
 class Voicerss(TTSModule):
     def __init__(self, **kwargs):
         super(Voicerss, self).__init__(**kwargs)
+        self._check_parameters()
 
     def say(self, words):
         """
@@ -22,6 +23,17 @@ class Voicerss(TTSModule):
 
         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):
         """
         Generic method used as a Callback in TTSModule
@@ -49,7 +61,7 @@ class Voicerss(TTSModule):
 
     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
         """

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