NeuronModule.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # coding: utf8
  2. import logging
  3. import os
  4. import random
  5. import sys
  6. from jinja2 import Template
  7. from core import OrderListener
  8. from core.SynapseLauncher import SynapseLauncher
  9. from core.Utils import Utils
  10. from core.ConfigurationManager import SettingLoader, BrainLoader
  11. logging.basicConfig()
  12. logger = logging.getLogger("kalliope")
  13. class InvalidParameterException(Exception):
  14. """
  15. Some Neuron parameters are invalid.
  16. """
  17. pass
  18. class MissingParameterException(Exception):
  19. """
  20. Some Neuron parameters are missing.
  21. """
  22. pass
  23. class NoTemplateException(Exception):
  24. """
  25. You must specify a say_template or a file_template
  26. """
  27. pass
  28. class TemplateFileNotFoundException(Exception):
  29. """
  30. Template file can not be found. Check the provided path.
  31. """
  32. pass
  33. class TTSModuleNotFound(Exception):
  34. """
  35. TTS module can not be find. It must be configured in the settings file.
  36. """
  37. pass
  38. class NeuronModule(object):
  39. """
  40. This Abstract Class is representing main Class for Neuron.
  41. Each Neuron must implement this Class.
  42. """
  43. def __init__(self, **kwargs):
  44. """
  45. Class used by neuron for talking
  46. :param kwargs: Same parameter as the Child. Can contain info about the tts to use instead of the
  47. default one
  48. """
  49. # get the child who called the class
  50. child_name = self.__class__.__name__
  51. self.neuron_name = child_name
  52. logger.debug("NeuronModule called from class %s with parameters: %s" % (child_name, str(kwargs)))
  53. sl = SettingLoader.Instance()
  54. self.settings = sl.settings
  55. brain_loader = BrainLoader.Instance()
  56. self.brain = brain_loader.brain
  57. # check if the user has overrider the TTS
  58. tts = kwargs.get('tts', None)
  59. if tts is None:
  60. # No tts provided, we load the default one
  61. self.tts = self.settings.default_tts_name
  62. else:
  63. self.tts = tts
  64. # get if the cache settings is present
  65. self.override_cache = kwargs.get('cache', None)
  66. # get templates if provided
  67. # Check if there is a template associate to the output message
  68. self.say_template = kwargs.get('say_template', None)
  69. # check if there is a template file associate to the output message
  70. self.file_template = kwargs.get('file_template', None)
  71. def say(self, message):
  72. """
  73. USe TTS to speak out loud the Message.
  74. A message can be a string, a list or a dict
  75. If it's a string, simply use the TTS with the message
  76. If it's a list, we select randomly a string in the list and give it to the TTS
  77. If it's a dict, we use the template given in parameter to create a string that we give to the TTS
  78. :param message: Can be a String or a dict or a list
  79. .. raises:: TTSModuleNotFound
  80. """
  81. logger.debug("NeuronModule Say() called with message: %s" % message)
  82. tts_message = None
  83. if isinstance(message, str) or isinstance(message, unicode):
  84. logger.debug("message is string")
  85. tts_message = message
  86. if isinstance(message, list):
  87. logger.debug("message is list")
  88. tts_message = random.choice(message)
  89. if isinstance(message, dict):
  90. logger.debug("message is dict")
  91. tts_message = self._get_message_from_dict(message)
  92. if tts_message is not None:
  93. logger.debug("tts_message to say: %s" % tts_message)
  94. # create a tts object from the tts the user want to user
  95. tts_object = next((x for x in self.settings.ttss if x.name == self.tts), None)
  96. if tts_object is None:
  97. raise TTSModuleNotFound("The tts module name %s does not exist in settings file" % self.tts)
  98. # change the cache settings with the one precised for the current neuron
  99. if self.override_cache is not None:
  100. tts_object.parameters = self._update_cache_var(self.override_cache, tts_object.parameters)
  101. logger.debug("NeuroneModule: TTS args: %s" % tts_object)
  102. # get the instance of the TTS module
  103. tts_module_instance = Utils.get_dynamic_class_instantiation("tts", tts_object.name.capitalize(),
  104. tts_object.parameters)
  105. # generate the audio file and play it
  106. tts_module_instance.say(tts_message)
  107. def _get_message_from_dict(self, message_dict):
  108. """
  109. Generate a message that can be played by a TTS engine from a dict of variable and the jinja template
  110. :param message_dict: the dict of message
  111. :return: The message to say
  112. .. raises:: TemplateFileNotFoundException
  113. """
  114. returned_message = None
  115. if (self.say_template is not None and self.file_template is None) or \
  116. (self.say_template is None and self.file_template is not None):
  117. # the user choose a say_template option
  118. if self.say_template is not None:
  119. if isinstance(self.say_template, list):
  120. # then we pick randomly one template
  121. self.say_template = random.choice(self.say_template)
  122. t = Template(self.say_template)
  123. returned_message = t.render(**message_dict)
  124. # trick to remobe unicode problem when loading jinja template with non ascii char
  125. reload(sys)
  126. sys.setdefaultencoding('utf-8')
  127. # the user choose a file_template option
  128. if self.file_template is not None: # the user choose a file_template option
  129. if not os.path.isabs(self.file_template): # os.path.isabs returns True if the path is absolute
  130. # here we are
  131. dir_we_are = os.path.dirname(os.path.realpath(__file__))
  132. # root directory
  133. root_dir = os.path.normpath(dir_we_are + os.sep + os.pardir)
  134. # real path of the template
  135. real_file_template_path = os.path.join(root_dir, self.file_template)
  136. else:
  137. real_file_template_path = self.file_template
  138. if os.path.isfile(real_file_template_path):
  139. # load the content of the file as template
  140. t = Template(self._get_content_of_file(real_file_template_path))
  141. returned_message = t.render(**message_dict)
  142. else:
  143. raise TemplateFileNotFoundException("Template file %s not found in templates folder"
  144. % real_file_template_path)
  145. return returned_message
  146. # we don't force the usage of a template. The user can choose to do nothing with returned value
  147. # else:
  148. # raise NoTemplateException("You must specify a say_template or a file_template")
  149. def run_synapse_by_name(self, name):
  150. SynapseLauncher.start_synapse(name=name, brain=self.brain)
  151. @staticmethod
  152. def _get_content_of_file(real_file_template_path):
  153. """
  154. Return the content of a file in path <real_file_template_path>
  155. :param real_file_template_path: path of the file to return the content
  156. :return: file content str
  157. """
  158. with open(real_file_template_path, 'r') as content_file:
  159. return content_file.read()
  160. @staticmethod
  161. def _update_cache_var(new_override_cache, args_list):
  162. """
  163. update the value for the key "cache" in the dict args_list
  164. :param new_override_cache: cache bolean to set in place of the current one in args_list
  165. :param args_list: arg list that contain "cache" to update
  166. :return:
  167. """
  168. logger.debug("args for TTS plugin before update: %s" % str(args_list))
  169. args_list["cache"] = new_override_cache
  170. logger.debug("args for TTS plugin after update: %s" % str(args_list))
  171. return args_list
  172. @staticmethod
  173. def get_audio_from_stt(callback):
  174. """
  175. Call the default STT to get an audio sample and return it into the callback method
  176. :param callback: A callback function
  177. """
  178. # call the order listener
  179. oa = OrderListener(callback=callback)
  180. oa.start()
  181. def get_neuron_name(self):
  182. """
  183. Return the name of the neuron who call the mother class
  184. :return:
  185. """
  186. return self.neuron_name