TTSModule.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # coding: utf8
  2. import hashlib
  3. import logging
  4. import os
  5. import sys
  6. import six
  7. from kalliope.core.ConfigurationManager import SettingLoader
  8. from kalliope.core.Players import Mplayer
  9. from kalliope.core.Utils.FileManager import FileManager
  10. logging.basicConfig()
  11. logger = logging.getLogger("kalliope")
  12. class MissingTTSParameter(Exception):
  13. """
  14. Some TTS Parameters are missing in the settings.yml file.
  15. .. seealose:: Settings
  16. """
  17. pass
  18. class TtsGenerateAudioFunctionNotFound(Exception):
  19. """
  20. You must provide a callBack to the TTS
  21. """
  22. pass
  23. class FailToLoadSoundFile(Exception):
  24. """
  25. Fail while truing to load the sound file.
  26. """
  27. pass
  28. class TTSModule(object):
  29. """
  30. Mother class of TTS module. Handle:
  31. - Cache: call cache object to create file, delete file, check if file exist
  32. - Player: call the default player to play the generated file
  33. """
  34. def __init__(self, **kwargs):
  35. # set parameter from what we receive from the settings
  36. self.cache = kwargs.get('cache', False)
  37. self.language = kwargs.get('language', "default")
  38. self.voice = kwargs.get('voice', "default")
  39. # the name of the TSS is the name of the Tss module that have instantiated TTSModule
  40. self.tts_caller_name = self.__class__.__name__
  41. # we don't know yet the words that will be converted to an audio and so we don't have the audio path yet too
  42. self.words = None
  43. self.file_path = None
  44. self.base_cache_path = None
  45. # load settings
  46. sl = SettingLoader()
  47. self.settings = sl.settings
  48. # create the path in the tmp folder
  49. base_path = os.path.join(self.settings.cache_path, self.tts_caller_name, self.language, self.voice)
  50. FileManager.create_directory(base_path)
  51. logger.debug("Class TTSModule called from module %s, cache: %s, language: %s, voice: %s" % (self.tts_caller_name,
  52. self.cache,
  53. self.language,
  54. self.voice))
  55. def play_audio(self):
  56. """
  57. Play the audio file
  58. """
  59. Mplayer.play(self.file_path)
  60. def generate_and_play(self, words, generate_audio_function_from_child=None):
  61. """
  62. Generate an audio file from <words> if not already in cache and call the Player to play it
  63. :param words: Sentence text from which we want to generate an audio file
  64. :type words: String
  65. :param generate_audio_function_from_child: The child function to generate a file if necessary
  66. :type generate_audio_function_from_child; Callback function
  67. .. raises:: TtsGenerateAudioFunctionNotFound
  68. """
  69. if generate_audio_function_from_child is None:
  70. raise TtsGenerateAudioFunctionNotFound
  71. self.words = words
  72. # we can generate the file path from info we have
  73. self.file_path = self._get_path_to_store_audio()
  74. if not self.cache:
  75. # no cache, we need to generate the file
  76. generate_audio_function_from_child()
  77. else:
  78. # we check if the file already exist. If not we generate it with the TTS engine
  79. if not self._is_file_already_in_cache(self.base_cache_path, self.file_path):
  80. generate_audio_function_from_child()
  81. # then play the generated audio file
  82. self.play_audio()
  83. # if the user don't want to keep the cache we remove the file
  84. if not self.cache:
  85. FileManager.remove_file(self.file_path)
  86. def _get_path_to_store_audio(self):
  87. """
  88. Get a sentence (a text) an return the full path of the file
  89. Path syntax:
  90. </path/in/settings>/<tts.name>/tts.parameter["language"]/tts.parameter["voice"]/<md5_of_sentence.tts
  91. E.g:
  92. /tmp/kalliope/acapela/fr/abcd12345.tts
  93. :return: path String
  94. """
  95. md5 = self.generate_md5_from_words(self.words)+".tts"
  96. self.base_cache_path = os.path.join(self.settings.cache_path, self.tts_caller_name, self.language, self.voice)
  97. returned_path = os.path.join(self.base_cache_path, md5)
  98. logger.debug("get_path_to_store_audio return: %s" % returned_path)
  99. return returned_path
  100. @staticmethod
  101. def generate_md5_from_words(words):
  102. """
  103. Generate a md5 hash from received text
  104. :param words: Text to convert into md5 hash
  105. :return: String md5 hash from the received words
  106. """
  107. if isinstance(words, six.text_type):
  108. words = words.encode('utf-8')
  109. return hashlib.md5(words).hexdigest()
  110. @staticmethod
  111. def _is_file_already_in_cache(base_cache_path, file_path):
  112. """
  113. Return true if the file to generate has already been generated before
  114. """
  115. # generate sub folder
  116. FileManager.create_directory(base_cache_path)
  117. # check if the audio file exist
  118. exist_in_cache = os.path.exists(file_path)
  119. if exist_in_cache:
  120. logger.debug("TTSModule, File already in cache: %s" % file_path)
  121. else:
  122. logger.debug("TTSModule, File not yet in cache: %s" % file_path)
  123. return exist_in_cache