Browse Source

[Feature] #234 Player modularity

monf 8 years ago
parent
commit
3e5c2b0fa9

+ 3 - 0
Docs/player.md

@@ -0,0 +1,3 @@
+# Players
+
+With Kalliope project, you can set whatever Sound Player you want to use.

+ 73 - 3
kalliope/core/ConfigurationManager/SettingLoader.py

@@ -11,6 +11,7 @@ from kalliope.core.Models.RestAPI import RestAPI
 from kalliope.core.Models.Settings import Settings
 from kalliope.core.Models.Stt import Stt
 from kalliope.core.Models.Trigger import Trigger
+from kalliope.core.Models.Player import Player
 from kalliope.core.Models.Tts import Tts
 from kalliope.core.Utils.FileManager import FileManager
 
@@ -101,9 +102,11 @@ class SettingLoader(with_metaclass(Singleton, object)):
         default_stt_name = self._get_default_speech_to_text(settings)
         default_tts_name = self._get_default_text_to_speech(settings)
         default_trigger_name = self._get_default_trigger(settings)
+        default_player_name = self._get_default_player(settings)
         stts = self._get_stts(settings)
         ttss = self._get_ttss(settings)
         triggers = self._get_triggers(settings)
+        players = self._get_players(settings)
         random_wake_up_answers = self._get_random_wake_up_answers(settings)
         random_wake_up_sound = self._get_random_wake_up_sounds(settings)
         play_on_ready_notification = self._get_play_on_ready_notification(settings)
@@ -120,9 +123,11 @@ class SettingLoader(with_metaclass(Singleton, object)):
         setting_object.default_tts_name = default_tts_name
         setting_object.default_stt_name = default_stt_name
         setting_object.default_trigger_name = default_trigger_name
+        setting_object.default_player_name = default_player_name
         setting_object.stts = stts
         setting_object.ttss = ttss
         setting_object.triggers = triggers
+        setting_object.players = players
         setting_object.random_wake_up_answers = random_wake_up_answers
         setting_object.random_wake_up_sounds = random_wake_up_sound
         setting_object.play_on_ready_notification = play_on_ready_notification
@@ -220,6 +225,33 @@ class SettingLoader(with_metaclass(Singleton, object)):
         except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
+    @staticmethod
+    def _get_default_player(settings):
+        """
+        Get the default player defined in the settings.yml file
+        :param settings: The YAML settings file
+        :type settings: dict
+        :return: the default player
+        :rtype: str
+
+        :Example:
+
+            default_player_name = cls._get_default_player(settings)
+
+        .. seealso:: Player
+        .. raises:: NullSettingException, SettingNotFound
+        .. warnings:: Static and Private
+        """
+
+        try:
+            default_player = settings["default_player"]
+            if default_player is None:
+                raise NullSettingException("Attribute default_player is null")
+            logger.debug("Default Player name: %s" % default_player)
+            return default_player
+        except KeyError as e:
+            raise SettingNotFound("%s setting not found" % e)
+
     @staticmethod
     def _get_stts(settings):
         """
@@ -236,7 +268,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
 
         .. seealso:: Stt
         .. raises:: SettingNotFound
-        .. warnings:: Class Method and Private
+        .. warnings:: Static Method and Private
         """
 
         try:
@@ -275,7 +307,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
 
         .. seealso:: Tts
         .. raises:: SettingNotFound
-        .. warnings:: Class Method and Private
+        .. warnings:: Static Method and Private
         """
 
         try:
@@ -313,7 +345,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
 
         .. seealso:: Trigger
         .. raises:: SettingNotFound
-        .. warnings:: Class Method and Private
+        .. warnings:: Static Method and Private
         """
 
         try:
@@ -335,6 +367,44 @@ class SettingLoader(with_metaclass(Singleton, object)):
                 triggers.append(new_trigger)
         return triggers
 
+    @staticmethod
+    def _get_players(settings):
+        """
+        Return a list of Player object
+
+        :param settings: The YAML settings file
+        :type settings: dict
+        :return: List of Player
+        :rtype: list
+
+        :Example:
+
+            players = cls._get_players(settings)
+
+        .. seealso:: players
+        .. raises:: SettingNotFound
+        .. warnings:: Static Method and Private
+        """
+
+        try:
+            players_list = settings["players"]
+        except KeyError as e:
+            raise SettingNotFound("%s setting not found" % e)
+
+        players = list()
+        for player_el in players_list:
+            if isinstance(player_el, dict):
+                for player_name in player_el:
+                    name = player_name
+                    parameters = player_el[name]
+                    new_player = Player(name=name, parameters=parameters)
+                    players.append(new_player)
+            else:
+                # the player does not have parameters
+                new_player = Player(name=player_el)
+                players.append(new_player)
+        return players
+
     @staticmethod
     def _get_random_wake_up_answers(settings):
         """

+ 15 - 16
kalliope/core/MainController.py

@@ -2,17 +2,22 @@ import logging
 import random
 from time import sleep
 
-from flask import Flask
 from transitions import Machine
-
 from kalliope.core import Utils
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.OrderListener import OrderListener
-from kalliope.core.Players import Mplayer
+
+# API
+from flask import Flask
 from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
+
+# Launchers
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.TriggerLauncher import TriggerLauncher
 from kalliope.core.Utils.RpiUtils import RpiUtils
+from kalliope.core.PlayerLauncher import PlayerLauncher
+
+# Neurons
 from kalliope.neurons.say.say import Say
 
 logging.basicConfig()
@@ -44,6 +49,7 @@ class MainController:
         # Starting the rest API
         self._start_rest_api()
 
+        # rpi setting for led and mute button
         self.rpi_utils = None
         if self.settings.rpi_settings:
             # the useer set GPIO pin, we need to instantiate the RpiUtils class in order to setup GPIO
@@ -58,6 +64,9 @@ class MainController:
                 logger.debug("[MainController] Switching pin_led_started to ON")
                 RpiUtils.switch_pin_to_on(self.settings.rpi_settings.pin_led_started)
 
+        # get the player instance
+        self.player_instance = PlayerLauncher.get_player(settings=self.settings)
+
         # save an instance of the trigger
         self.trigger_instance = None
         self.trigger_callback_called = False
@@ -100,7 +109,7 @@ class MainController:
         This function will start the trigger thread that listen for the hotword
         """
         logger.debug("[MainController] Entering state: %s" % self.state)
-        self.trigger_instance = self._get_default_trigger()
+        self.trigger_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback)
         self.trigger_callback_called = False
         self.trigger_instance.daemon = True
         # Wait that the kalliope trigger is pronounced by the user
@@ -121,7 +130,7 @@ class MainController:
                 Say(message=self.settings.on_ready_answers)
             elif self.settings.on_ready_sounds is not None:
                 random_sound_to_play = self._get_random_sound(self.settings.on_ready_sounds)
-                Mplayer.play(random_sound_to_play)
+                self.player_instance.play(random_sound_to_play)
         self.next_state()
 
     def waiting_for_trigger_callback_thread(self):
@@ -188,8 +197,7 @@ class MainController:
             Say(message=self.settings.random_wake_up_answers)
         else:
             random_sound_to_play = self._get_random_sound(self.settings.random_wake_up_sounds)
-            Mplayer.play(random_sound_to_play)
-
+            self.player_instance.play(random_sound_to_play)
         self.next_state()
 
     def order_listener_callback(self, order):
@@ -215,15 +223,6 @@ class MainController:
         # return to the state "unpausing_trigger"
         self.start_trigger()
 
-    def _get_default_trigger(self):
-        """
-        Return an instance of the default trigger
-        :return: Trigger
-        """
-        for trigger in self.settings.triggers:
-            if trigger.name == self.settings.default_trigger_name:
-                return TriggerLauncher.get_trigger(trigger, callback=self.trigger_callback)
-
     @staticmethod
     def _get_random_sound(random_wake_up_sounds):
         """

+ 27 - 0
kalliope/core/Models/Player.py

@@ -0,0 +1,27 @@
+class Player(object):
+    """
+    This Class is representing a Player with its name and parameters
+
+    .. note:: must be defined in the settings.yml
+    """
+
+    def __init__(self, name=None, parameters=None):
+        self.name = name
+        self.parameters = parameters
+
+    def __str__(self):
+        return str(self.serialize())
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': self.parameters
+        }
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 Player objects
+        :param other: the Player to compare
+        :return: True if both players are similar, False otherwise
+        """
+        return self.__dict__ == other.__dict__

+ 6 - 0
kalliope/core/Models/Settings.py

@@ -12,6 +12,7 @@ class Settings(object):
                  default_tts_name=None,
                  default_stt_name=None,
                  default_trigger_name=None,
+                 default_player_name=None,
                  ttss=None,
                  stts=None,
                  random_wake_up_answers=None,
@@ -20,6 +21,7 @@ class Settings(object):
                  on_ready_answers=None,
                  on_ready_sounds=None,
                  triggers=None,
+                 players=None,
                  rest_api=None,
                  cache_path=None,
                  default_synapse=None,
@@ -30,6 +32,7 @@ class Settings(object):
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
         self.default_trigger_name = default_trigger_name
+        self.default_player_name = default_player_name
         self.ttss = ttss
         self.stts = stts
         self.random_wake_up_answers = random_wake_up_answers
@@ -38,6 +41,7 @@ class Settings(object):
         self.on_ready_answers = on_ready_answers
         self.on_ready_sounds = on_ready_sounds
         self.triggers = triggers
+        self.players = players
         self.rest_api = rest_api
         self.cache_path = cache_path
         self.default_synapse = default_synapse
@@ -59,6 +63,7 @@ class Settings(object):
             'default_tts_name': self.default_tts_name,
             'default_stt_name': self.default_stt_name,
             'default_trigger_name': self.default_trigger_name,
+            'default_player_name': self.default_player_name,
             'ttss': self.ttss,
             'stts': self.stts,
             'random_wake_up_answers': self.random_wake_up_answers,
@@ -67,6 +72,7 @@ class Settings(object):
             'on_ready_answers': self.on_ready_answers,
             'on_ready_sounds': self.on_ready_sounds,
             'triggers': self.triggers,
+            'players': self.players,
             'rest_api': self.rest_api.serialize(),
             'cache_path': self.cache_path,
             'default_synapse': self.default_synapse,

+ 30 - 0
kalliope/core/PlayerLauncher.py

@@ -0,0 +1,30 @@
+import logging
+
+from kalliope.core import Utils
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class PlayerLauncher(object):
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def get_player(settings):
+        """
+        Instantiate a Player
+        :param settings: setting object
+        :type settings: Settings
+        :return: the Player instance
+        :rtype: Player
+        """
+        player_instance = None
+        for player in settings.players:
+            if player.name == settings.default_player_name:
+                logger.debug("PlayerLauncher: Start player %s with parameters: %s" % (player.name, player.parameters))
+                player_instance = Utils.get_dynamic_class_instantiation(package_name="players",
+                                                                        module_name=player.name,
+                                                                        parameters=player.parameters)
+                break
+        return player_instance

+ 0 - 1
kalliope/core/Players/__init__.py

@@ -1 +0,0 @@
-from .Mplayer import Mplayer

+ 27 - 3
kalliope/core/TTS/TTSModule.py

@@ -2,12 +2,14 @@
 import hashlib
 import logging
 import os
-import sys
+import subprocess
+
 import six
 
 from kalliope.core.ConfigurationManager import SettingLoader
-from kalliope.core.Players import Mplayer
+from kalliope.core.PlayerLauncher import PlayerLauncher
 from kalliope.core.Utils.FileManager import FileManager
+from kalliope.core import Utils
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -60,6 +62,7 @@ class TTSModule(object):
         # load settings
         sl = SettingLoader()
         self.settings = sl.settings
+        self.player = PlayerLauncher.get_player(settings=self.settings)
 
         # create the path in the tmp folder
         base_path = os.path.join(self.settings.cache_path, self.tts_caller_name, self.language, self.voice)
@@ -74,7 +77,8 @@ class TTSModule(object):
         """
         Play the audio file
         """
-        Mplayer.play(self.file_path)
+        # Mplayer.play(self.file_path)
+        self.player.play(self.file_path)
 
     def generate_and_play(self, words, generate_audio_function_from_child=None):
         """
@@ -154,3 +158,23 @@ class TTSModule(object):
         else:
             logger.debug("TTSModule, File not yet in cache: %s" % file_path)
         return exist_in_cache
+
+    @staticmethod
+    def convert_mp3_to_wav(file_path_mp3):
+        """ 
+        PyAudio does not support mp3 files 
+        MP3 files must be converted to a wave in order to be played
+        This function assumes ffmpeg is available on the system
+        :param file_path_mp3: the file path to convert from mp3 to wav
+        """
+        logger.debug("Converting mp3 file to wav file: %s" % file_path_mp3)
+        fnull = open(os.devnull, 'w')
+        # temp file
+        tmp_file_wav = file_path_mp3 + ".wav"
+        # Convert mp3 to wave
+        subprocess.call(['ffmpeg', '-y', '-i', file_path_mp3, tmp_file_wav],
+                           stdout=fnull, stderr=fnull)
+        # remove the original file
+        FileManager.remove_file(file_path_mp3)
+        # rename the temp file with the same name as the original file
+        os.rename(tmp_file_wav, file_path_mp3)

+ 17 - 11
kalliope/core/TriggerLauncher.py

@@ -10,19 +10,25 @@ class TriggerLauncher(object):
     def __init__(self):
         pass
 
-    @classmethod
-    def get_trigger(cls, trigger, callback):
+    @staticmethod
+    def get_trigger(settings, callback):
         """
         Start a trigger module
         :param trigger: trigger object to instantiate
         :type trigger: Trigger
-        :param callback: Callback function to call when the trigger
-        catch the magic word
-        :return:
+        :param callback: Callback function to call when the trigger catch the magic word
+        :return: The instance of Trigger 
+        :rtype: Trigger
         """
-        # add the callback method to parameters
-        trigger.parameters["callback"] = callback
-        logger.debug("TriggerLauncher: Start trigger %s with parameters: %s" % (trigger.name, trigger.parameters))
-        return Utils.get_dynamic_class_instantiation(package_name="trigger",
-                                                     module_name=trigger.name,
-                                                     parameters=trigger.parameters)
+        trigger_instance = None
+        for trigger in settings.triggers:
+            if trigger.name == settings.default_trigger_name:
+                # add the callback method to parameters
+                trigger.parameters["callback"] = callback
+                logger.debug(
+                    "TriggerLauncher: Start trigger %s with parameters: %s" % (trigger.name, trigger.parameters))
+                trigger_instance = Utils.get_dynamic_class_instantiation(package_name="trigger",
+                                                                         module_name=trigger.name,
+                                                                         parameters=trigger.parameters)
+                break
+        return trigger_instance

+ 5 - 4
kalliope/core/Utils/Utils.py

@@ -25,7 +25,6 @@ class ModuleNotFoundError(Exception):
 
 
 class Utils(object):
-
     color_list = dict(
         PURPLE='\033[95m',
         BLUE='\033[94m',
@@ -114,6 +113,7 @@ class Utils(object):
         :return:
         """
         package_path = "kalliope." + package_name + "." + module_name.lower() + "." + module_name.lower()
+        logger.debug("[Utils]-> get_dynamic_class_instantiation : package path : %s" % (package_path))
         if resources_dir is not None:
             neuron_resource_path = resources_dir + os.sep + module_name.lower() \
                                    + os.sep + module_name.lower() + ".py"
@@ -121,7 +121,7 @@ class Utils(object):
                 imp.load_source(module_name.capitalize(), neuron_resource_path)
                 package_path = module_name.capitalize()
                 logger.debug("[Utils]-> get_dynamic_class_instantiation : loading path : %s, as package %s" % (
-                                                                                neuron_resource_path, package_path))
+                    neuron_resource_path, package_path))
 
         mod = __import__(package_path, fromlist=[module_name.capitalize()])
 
@@ -129,7 +129,8 @@ class Utils(object):
             klass = getattr(mod, module_name.capitalize())
         except AttributeError:
             logger.debug("Error: No module named %s " % module_name.capitalize())
-            raise ModuleNotFoundError("The module %s does not exist in package %s" % (module_name.capitalize(), package_name))
+            raise ModuleNotFoundError(
+                "The module %s does not exist in package %s" % (module_name.capitalize(), package_name))
 
         if klass is not None:
             # run the plugin
@@ -299,4 +300,4 @@ class Utils(object):
         if sys.version_info[0] < 3:
             if isinstance(text, unicode):
                 text = text.encode("utf-8")
-        return text
+        return text

+ 1 - 0
kalliope/neurons/uri/__init__.py

@@ -0,0 +1 @@
+from .uri import Uri

+ 0 - 0
kalliope/players/__init__.py


+ 1 - 0
kalliope/players/mplayer/__init__.py

@@ -0,0 +1 @@
+from .mplayer import Mplayer

+ 3 - 2
kalliope/core/Players/Mplayer.py → kalliope/players/mplayer/mplayer.py

@@ -13,8 +13,9 @@ class Mplayer(object):
     This Class is representing the MPlayer Object used to play the all sound of the system.
     """
 
-    def __init__(self):
-        pass
+    def __init__(self, **kwargs):
+        logger.debug("[Mplayer.__init__] instance")
+        logger.debug("[Mplayer.__init__] args : %s " % str(kwargs))
 
     @classmethod
     def play(cls, filepath):

+ 1 - 0
kalliope/players/pyalsaaudio/__init__.py

@@ -0,0 +1 @@
+from .pyalsaaudio import Pyalsaaudio

+ 86 - 0
kalliope/players/pyalsaaudio/pyalsaaudio.py

@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+import alsaaudio
+import logging
+import wave
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+CHUNK = 1024
+
+ALSAAUDIO_BIT_MAPPING = {8:  alsaaudio.PCM_FORMAT_S8,
+                         16: alsaaudio.PCM_FORMAT_S16_LE,
+                         24: alsaaudio.PCM_FORMAT_S24_LE,
+                         32: alsaaudio.PCM_FORMAT_S32_LE}
+
+STANDARD_SAMPLE_RATES = (
+    8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 88200,
+    96000, 192000
+)
+
+DEVICE_TYPE_ALL = 'all'
+DEVICE_TYPE_INPUT = 'input'
+DEVICE_TYPE_OUTPUT = 'output'
+
+
+def bits_to_samplefmt(bits):
+    if bits in ALSAAUDIO_BIT_MAPPING.keys():
+        return ALSAAUDIO_BIT_MAPPING[bits]
+    else: 
+        raise ValueError('Unsupported format')
+
+
+class Pyalsaaudio(object):
+    """
+    This Class is representing the Player Object used to play the all sound of the system.
+    """
+
+    def __init__(self, **kwargs):
+        # List devices
+        logger.debug("[pyalsaaudio.__init__] instance")
+        logger.debug("[pyalsaaudio.__init__] devices : %s " % (str(self.get_devices(DEVICE_TYPE_OUTPUT))))
+        logger.debug("[pyalsaaudio.__init__] args : %s " % str(kwargs))
+        self.device = kwargs.get('device', 'default')
+
+    @staticmethod
+    def get_devices(device_type=DEVICE_TYPE_ALL):
+        devices = set()
+        if device_type in (DEVICE_TYPE_ALL,
+                           DEVICE_TYPE_OUTPUT):
+            devices.update(set(alsaaudio.pcms(alsaaudio.PCM_PLAYBACK)))
+        if device_type in (DEVICE_TYPE_ALL,
+                           DEVICE_TYPE_INPUT):
+            devices.update(set(alsaaudio.pcms(alsaaudio.PCM_CAPTURE)))
+        device_names = sorted(list(devices))
+        num_devices = len(device_names)
+        logger.debug('Found %d ALSA devices', num_devices)
+        return device_names
+
+    def play(self, file_path):
+
+        f = wave.open(file_path, 'rb')
+        pcm_type = alsaaudio.PCM_PLAYBACK
+        stream = alsaaudio.PCM(type=pcm_type,
+                               mode=alsaaudio.PCM_NORMAL,
+                               device=self.device)  # this is just for testing
+                               # device='sysdefault:CARD=ALSA')  # this is just for testing
+                                # on RPI3 this is fine; pulse (usually also default) is not working
+                                # device should be configurable; default schould be "default"
+        # Set attributes
+        stream.setchannels(f.getnchannels())
+        stream.setrate(f.getframerate())
+        bits = f.getsampwidth()*8
+        stream.setformat(bits_to_samplefmt(bits))        
+        stream.setperiodsize(CHUNK)
+        
+        logger.debug("[PyAlsaAudioPlayer] %d channels, %d sampling rate, %d bit" % (f.getnchannels(),
+                                                                            f.getframerate(),  bits))
+        
+        data = f.readframes(CHUNK)
+        while data:
+            # Read data from stdin
+            stream.write(data)
+            data = f.readframes(CHUNK)
+     
+        f.close()
+        stream.close()

+ 1 - 0
kalliope/players/pyaudioplayer/__init__.py

@@ -0,0 +1 @@
+from .pyaudioplayer import Pyaudioplayer

+ 57 - 0
kalliope/players/pyaudioplayer/pyaudioplayer.py

@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+import logging
+import wave
+
+import pyaudio
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+CHUNK = 1024
+
+
+class Pyaudioplayer(object):
+    """
+    This Class is representing the Player Object used to play the all sound of the system.
+    """
+
+    def __init__(self, **kwargs):
+        logger.debug("[Pyaudioplayer.__init__] instance")
+        logger.debug("[Pyaudioplayer.__init__] args : %s " % str(kwargs))
+
+    @classmethod
+    def play(cls, file_path):
+        """
+        Play the sound located in the provided file_path
+        :param file_path: The file path of the sound to play. Must be wav format
+        :type file_path: str              
+        """
+        # open the wave file
+        wf = wave.open(file_path, 'rb')
+
+        # instantiate PyAudio
+        p = pyaudio.PyAudio()
+
+        # open stream (2)
+        stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
+                        channels=wf.getnchannels(),
+                        rate=wf.getframerate(),
+                        # frames_per_buffer=CHUNK,
+                        output=True)
+
+        # read data
+        data = wf.readframes(CHUNK)
+
+        logger.debug("Pyplayer file: %s" % str(file_path))
+
+        # play stream (3)
+        while len(data) > 0:
+            stream.write(data)
+            data = wf.readframes(CHUNK)
+
+        # stop stream (4)
+        stream.stop_stream()
+        stream.close()
+
+        # close PyAudio
+        p.terminate()

+ 1 - 0
kalliope/players/sounddeviceplayer/__init__.py

@@ -0,0 +1 @@
+from .sounddeviceplayer import Sounddeviceplayer

+ 26 - 0
kalliope/players/sounddeviceplayer/sounddeviceplayer.py

@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+import sounddevice as sd
+import soundfile as sf
+import logging
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+FS = 48000
+
+
+class Sounddeviceplayer(object):
+    """
+    This Class is representing the Player Object used to play the all sound of the system.
+    """
+
+    def __init__(self, **kwargs):
+        logger.debug("[Sounddeviceplayer.__init__] instance")
+        logger.debug("[Sounddeviceplayer.__init__] args : %s " % str(kwargs))
+
+    @classmethod
+    def play(cls, file_path):
+
+        data, fs = sf.read(file_path)
+        sd.play(data, fs)
+        sd.wait()

+ 26 - 3
kalliope/settings.yml

@@ -25,10 +25,14 @@ triggers:
 # This is the STT that will be used by default
 default_speech_to_text: "google"
 
-# Spreech to Text engines configuration
+# Speech to Text engines configuration
 # Available engine are:
 # - google (via SpeechRecognition)
-# -
+# - wit
+# - bing
+# - apiai
+# - houndify
+# - cmusphinx (must be installed first)
 speech_to_text:
   - google:
       language: "fr-FR"
@@ -53,7 +57,7 @@ default_text_to_speech: "pico2wave"
 # where we store generated audio files from TTS engine to reuse them
 cache_path: "/tmp/kalliope_tts_cache"
 
-# Text to Spreech engines configuration
+# Text to Speech engines configuration
 # Available engine are:
 # - pico2wave
 # - acapela
@@ -75,6 +79,25 @@ text_to_speech:
       language: "fr-fr"
       cache: True
 
+# ---------------------------
+# players
+# ---------------------------
+# This is the sound player that will be used by default
+default_player: "mplayer"
+
+# players configuration
+# Available engine are:
+# - mplayer
+# - pyalsaaudio
+# - pyaudioplayer
+# - sounddeviceplayer
+players:
+  - mplayer: {}
+  - pyalsaaudio:
+     device: "default"
+  - pyaudioplayer: {}
+  - sounddeviceplayer: {}
+
 # ---------------------------
 # Wake up answers
 # ---------------------------

+ 0 - 2
kalliope/trigger/snowboy/snowboy.py

@@ -1,7 +1,5 @@
-import inspect
 import logging
 import os
-import time
 from threading import Thread
 
 from kalliope import Utils

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

@@ -77,6 +77,9 @@ class Acapela(TTSModule):
         # OK we get the audio we can write the sound file
         FileManager.write_in_file(self.file_path, r.content)
 
+        # the received file use MP3 format. It must be converted to wav in order to be played by pyaudio
+        self.convert_mp3_to_wav(self.file_path)
+
     def get_payload(self):
         """
         Generic method used load the payload used to access the remote api

+ 2 - 0
setup.py

@@ -83,6 +83,8 @@ setup(
         'GitPython>=2.1.3',
         'packaging>=16.8',
         'transitions>=0.4.3',
+        'sounddevice>=0.3.7',
+        'SoundFile>=0.9.0'
         'RPi.GPIO>=0.6.3'
     ],