TTSModule.py 5.2 KB

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