NeuronModule.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # coding: utf8
  2. import logging
  3. import random
  4. import sys
  5. from jinja2 import Template
  6. from kalliope.core import OrderListener
  7. from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
  8. from kalliope.core.Models import Order
  9. from kalliope.core.NeuronLauncher import NeuronLauncher
  10. from kalliope.core.NeuronParameterLoader import NeuronParameterLoader
  11. from kalliope.core.OrderAnalyser2 import OrderAnalyser2
  12. from kalliope.core.SynapseLauncher import SynapseLauncher
  13. from kalliope.core.Utils.Utils import Utils
  14. logging.basicConfig()
  15. logger = logging.getLogger("kalliope")
  16. class InvalidParameterException(Exception):
  17. """
  18. Some Neuron parameters are invalid.
  19. """
  20. pass
  21. class MissingParameterException(Exception):
  22. """
  23. Some Neuron parameters are missing.
  24. """
  25. pass
  26. class NoTemplateException(Exception):
  27. """
  28. You must specify a say_template or a file_template
  29. """
  30. pass
  31. class TemplateFileNotFoundException(Exception):
  32. """
  33. Template file can not be found. Check the provided path.
  34. """
  35. pass
  36. class TTSModuleNotFound(Exception):
  37. """
  38. TTS module can not be find. It must be configured in the settings file.
  39. """
  40. pass
  41. class NeuronModule(object):
  42. """
  43. This Abstract Class is representing main Class for Neuron.
  44. Each Neuron must implement this Class.
  45. """
  46. def __init__(self, **kwargs):
  47. """
  48. Class used by neuron for talking
  49. :param kwargs: Same parameter as the Child. Can contain info about the tts to use instead of the
  50. default one
  51. """
  52. # get the child who called the class
  53. child_name = self.__class__.__name__
  54. self.neuron_name = child_name
  55. sl = SettingLoader()
  56. self.settings = sl.settings
  57. brain_loader = BrainLoader()
  58. self.brain = brain_loader.brain
  59. # check if the user has overrider the TTS
  60. tts = kwargs.get('tts', None)
  61. if tts is None:
  62. # No tts provided, we load the default one
  63. self.tts = self.settings.default_tts_name
  64. else:
  65. self.tts = tts
  66. # get if the cache settings is present
  67. self.override_cache = kwargs.get('cache', None)
  68. # get templates if provided
  69. # Check if there is a template associate to the output message
  70. self.say_template = kwargs.get('say_template', None)
  71. # check if there is a template file associate to the output message
  72. self.file_template = kwargs.get('file_template', None)
  73. def say(self, message):
  74. """
  75. USe TTS to speak out loud the Message.
  76. A message can be a string, a list or a dict
  77. If it's a string, simply use the TTS with the message
  78. If it's a list, we select randomly a string in the list and give it to the TTS
  79. If it's a dict, we use the template given in parameter to create a string that we give to the TTS
  80. :param message: Can be a String or a dict or a list
  81. .. raises:: TTSModuleNotFound
  82. """
  83. logger.debug("NeuronModule Say() called with message: %s" % message)
  84. tts_message = None
  85. if isinstance(message, str) or isinstance(message, unicode):
  86. logger.debug("message is string")
  87. tts_message = message
  88. if isinstance(message, list):
  89. logger.debug("message is list")
  90. tts_message = random.choice(message)
  91. if isinstance(message, dict):
  92. logger.debug("message is dict")
  93. tts_message = self._get_message_from_dict(message)
  94. if tts_message is not None:
  95. logger.debug("tts_message to say: %s" % tts_message)
  96. # create a tts object from the tts the user want to use
  97. tts_object = next((x for x in self.settings.ttss if x.name == self.tts), None)
  98. if tts_object is None:
  99. raise TTSModuleNotFound("The tts module name %s does not exist in settings file" % self.tts)
  100. # change the cache settings with the one precised for the current neuron
  101. if self.override_cache is not None:
  102. tts_object.parameters = self._update_cache_var(self.override_cache, tts_object.parameters)
  103. logger.debug("NeuroneModule: TTS args: %s" % tts_object)
  104. # get the instance of the TTS module
  105. tts_folder = None
  106. if self.settings.resources:
  107. tts_folder = self.settings.resources.tts_folder
  108. tts_module_instance = Utils.get_dynamic_class_instantiation(package_name="tts",
  109. module_name=tts_object.name,
  110. parameters=tts_object.parameters,
  111. resources_dir=tts_folder)
  112. # generate the audio file and play it
  113. tts_module_instance.say(tts_message)
  114. def _get_message_from_dict(self, message_dict):
  115. """
  116. Generate a message that can be played by a TTS engine from a dict of variable and the jinja template
  117. :param message_dict: the dict of message
  118. :return: The message to say
  119. .. raises:: TemplateFileNotFoundException
  120. """
  121. returned_message = None
  122. # the user chooses a say_template option
  123. if self.say_template is not None:
  124. returned_message = self._get_say_template(self.say_template, message_dict)
  125. # trick to remove unicode problem when loading jinja template with non ascii char
  126. reload(sys)
  127. sys.setdefaultencoding('utf-8')
  128. # the user chooses a file_template option
  129. if self.file_template is not None: # the user choose a file_template option
  130. returned_message = self._get_file_template(self.file_template, message_dict)
  131. return returned_message
  132. @staticmethod
  133. def _get_say_template(list_say_template, message_dict):
  134. if isinstance(list_say_template, list):
  135. # then we pick randomly one template
  136. list_say_template = random.choice(list_say_template)
  137. t = Template(list_say_template)
  138. return t.render(**message_dict)
  139. @classmethod
  140. def _get_file_template(cls, file_template, message_dict):
  141. real_file_template_path = Utils.get_real_file_path(file_template)
  142. if real_file_template_path is None:
  143. raise TemplateFileNotFoundException("Template file %s not found in templates folder"
  144. % real_file_template_path)
  145. # load the content of the file as template
  146. t = Template(cls._get_content_of_file(real_file_template_path))
  147. returned_message = t.render(**message_dict)
  148. return returned_message
  149. def run_synapse_by_name(self, name):
  150. SynapseLauncher.start_synapse(name=name, brain=self.brain)
  151. def is_order_matching(self, order_said, order_match):
  152. return OrderAnalyser2().spelt_order_match_brain_order_via_table(order_to_analyse=order_match,
  153. user_said=order_said)
  154. def run_synapse_by_name_with_order(self, order, synapse_name, order_template):
  155. """
  156. Run a synapse using its name, and giving an order so it can retrieve its params.
  157. Useful for neurotransmitters.
  158. :param order: the order to match
  159. :param synapse_name: the name of the synapse
  160. :param order_template: order_template coming from the neurotransmitter
  161. :return: True if a synapse as been found and started using its params
  162. """
  163. synapse_to_run = self.brain.get_synapse_by_name(synapse_name=synapse_name)
  164. if synapse_to_run:
  165. # Make a list with the synapse
  166. logger.debug("[run_synapse_by_name_with_order]-> a synapse has been found %s" % synapse_to_run.name)
  167. list_to_run = list()
  168. list_to_run.append(synapse_to_run)
  169. parameters = None
  170. for signal in synapse_to_run.signals:
  171. if isinstance(signal, Order):
  172. parameters = NeuronParameterLoader.get_parameters(synapse_order=order_template,
  173. user_order=order)
  174. logger.debug("[NeuronModule]-> parameter load from user answer: %s" % parameters)
  175. if parameters is not None:
  176. break
  177. # start the neuron list
  178. NeuronLauncher.start_neuron_list(neuron_list=synapse_to_run.neurons, parameters_dict=parameters)
  179. else:
  180. logger.debug("[NeuronModule]-> run_synapse_by_name_with_order, the synapse has not been found : %s"
  181. % synapse_name)
  182. return synapse_to_run is not None
  183. @staticmethod
  184. def _get_content_of_file(real_file_template_path):
  185. """
  186. Return the content of a file in path <real_file_template_path>
  187. :param real_file_template_path: path of the file to return the content
  188. :return: file content str
  189. """
  190. with open(real_file_template_path, 'r') as content_file:
  191. return content_file.read()
  192. @staticmethod
  193. def _update_cache_var(new_override_cache, args_dict):
  194. """
  195. update the value for the key "cache" in the dict args_list
  196. :param new_override_cache: cache boolean to set in place of the current one in args_list
  197. :param args_dict: arg list that contain "cache" to update
  198. :return:
  199. """
  200. logger.debug("args for TTS plugin before update: %s" % str(args_dict))
  201. args_dict["cache"] = new_override_cache
  202. logger.debug("args for TTS plugin after update: %s" % str(args_dict))
  203. return args_dict
  204. @staticmethod
  205. def get_audio_from_stt(callback):
  206. """
  207. Call the default STT to get an audio sample and return it into the callback method
  208. :param callback: A callback function
  209. """
  210. # call the order listener
  211. ol = OrderListener(callback=callback)
  212. ol.start()
  213. ol.join()
  214. # wait that the STT engine has finish his job (or the neurotransmitter neuron will be killed)
  215. if ol.stt_instance is not None:
  216. ol.stt_instance.join()
  217. def get_neuron_name(self):
  218. """
  219. Return the name of the neuron who call the mother class
  220. :return:
  221. """
  222. return self.neuron_name