20 from abc
import ABCMeta, abstractmethod
21 from threading
import Thread
22 from time
import time, sleep
25 from os.path
import dirname, exists, isdir, join
33 play_wav, play_mp3, check_for_signal, create_signal, resolve_resource_file
36 from queue
import Queue, Empty
41 Send playback metrics in a background thread 44 def do_send(stopwatch, ident):
47 t = Thread(target=do_send, args=(stopwatch, ident))
54 Thread class for playing back tts audio and sending 55 viseme data to enclosure. 59 super(PlaybackThread, self).
__init__()
69 Remove all pending playbacks. 71 while not self.queue.empty():
80 Thread main loop. get audio and viseme data from queue 85 snd_type, data, visemes, ident = self.queue.get(timeout=2)
89 self.tts.begin_audio()
95 elif snd_type ==
'mp3':
104 if self.queue.empty():
110 except Exception
as e:
118 Send viseme data to enclosure 121 pairs(list): Visime and timing pair 124 True if button has been pressed. 127 self.enclosure.mouth_viseme(time(), pairs)
130 """ Clear all pending actions for the TTS playback thread. """ 134 """ Blink mycroft's eyes """ 135 if self.enclosure
and random.random() < rate:
136 self.enclosure.eyes_blink(
"b")
146 TTS abstract class to be implemented by all TTS engines. 148 It aggregates the minimum required parameters and exposes 149 ``execute(sentence)`` and ``validate_ssml(sentence)`` functions. 153 config (dict): Configuration for this specific tts engine 154 validator (TTSValidator): Used to verify proper installation 155 phonetic_spelling (bool): Whether to spell certain words phonetically 156 ssml_tags (list): Supported ssml properties. Ex. ['speak', 'prosody'] 158 __metaclass__ = ABCMeta
160 def __init__(self, lang, config, validator, audio_ext='wav',
161 phonetic_spelling=
True, ssml_tags=
None):
177 self.playback.start()
183 """Load phonetic spellings of words as dictionary""" 184 path = join(
'text', self.
lang,
'phonetic_spellings.txt')
186 if not spellings_file:
189 with open(spellings_file)
as f:
190 lines = filter(bool, f.read().split(
'\n'))
191 lines = [i.split(
':')
for i
in lines]
192 return {key.strip(): value.strip()
for key, value
in lines}
194 LOG.exception(
'Failed to load phonetic spellings.')
198 """Helper function for child classes to call in execute()""" 200 self.bus.emit(
Message(
"recognizer_loop:audio_output_start"))
204 Helper function for child classes to call in execute(). 206 Sends the recognizer_loop:audio_output_end message, indicating 207 that speaking is done for the moment. It also checks if cache 208 directory needs cleaning to free up disk space. 211 self.bus.emit(
Message(
"recognizer_loop:audio_output_end"))
220 """ Performs intial setup of TTS object. 223 bus: Mycroft messagebus connection 226 self.playback.init(self)
232 Abstract method that a tts implementation needs to implement. 233 Should get data from tts. 236 sentence(str): Sentence to synthesize 237 wav_file(str): output file 240 tuple: (wav_file, phoneme) 245 """Override to modify each supported ssml tag""" 250 return re.sub(
'<[^>]*>',
'', text).replace(
' ',
' ')
254 Check if engine supports ssml, if not remove all tags 255 Remove unsupported / invalid tags 258 utterance(str): Sentence to validate 260 Returns: validated_sentence (str) 267 tags = re.findall(
'<[^>]*>', utterance)
270 if any(supported
in tag
for supported
in self.
ssml_tags):
271 utterance = utterance.replace(tag, self.
modify_tag(tag))
274 utterance = utterance.replace(tag,
"")
277 return utterance.replace(
" ",
" ")
280 """ Default preprocessing is no preprocessing. 282 This method can be overridden to create chunks suitable to the 283 TTS engine in question. 286 sentence (str): sentence to preprocess 289 list: list of sentence parts 295 Convert sentence to speech, preprocessing out unsupported ssml 297 The method caches results if possible using the hash of the 301 sentence: Sentence to be spoken 302 ident: Id reference to current interaction 308 for word
in re.findall(
r"[\w']+", sentence):
310 sentence = sentence.replace(word,
314 for sentence
in chunks:
315 key = str(hashlib.md5(
316 sentence.encode(
'utf-8',
'ignore')).hexdigest())
317 wav_file = os.path.join(
321 if os.path.exists(wav_file):
322 LOG.debug(
"TTS cache hit")
325 wav_file, phonemes = self.
get_tts(sentence, wav_file)
329 vis = self.
viseme(phonemes)
330 self.queue.put((self.
audio_ext, wav_file, vis, ident))
334 Create visemes from phonemes. Needs to be implemented for all 338 phonemes(str): String with phoneme data 343 """ Remove all cached files. """ 348 if os.path.isdir(dir_path):
349 for f
in os.listdir(dir_path):
350 file_path = os.path.join(dir_path, f)
351 if os.path.isfile(file_path):
354 elif os.path.isfile(dir_path):
362 key: Hash key for the sentence 363 phonemes: phoneme string to save 366 pho_file = os.path.join(cache_dir, key +
".pho")
368 with open(pho_file,
"w")
as cachefile:
369 cachefile.write(phonemes)
371 LOG.exception(
"Failed to write {} to cache".format(pho_file))
376 Load phonemes from cache file. 379 Key: Key identifying phoneme cache 381 pho_file = os.path.join(
384 if os.path.exists(pho_file):
386 with open(pho_file,
"r") as cachefile: 387 phonemes = cachefile.read().strip() 390 LOG.debug(
"Failed to read .PHO from cache")
400 TTS Validator abstract class to be implemented by all TTS engines. 402 It exposes and implements ``validate(tts)`` function as a template to 403 validate the TTS engines. 405 __metaclass__ = ABCMeta
421 clazz = self.get_tts_class()
422 if not isinstance(self.tts, clazz):
423 raise AttributeError(
'tts must be instance of ' + clazz.__name__)
426 filename = self.tts.filename
427 if not (filename
and filename.endswith(
'.wav')):
428 raise AttributeError(
'file: %s must be in .wav format!' % filename)
430 dir_path = dirname(filename)
431 if not (exists(dir_path)
and isdir(dir_path)):
432 raise AttributeError(
'filename: %s is not valid!' % filename)
469 "responsive_voice": ResponsiveVoice
475 Factory method to create a TTS engine based on configuration. 477 The configuration file ``mycroft.conf`` contains a ``tts`` section with 478 the name of a TTS module to be read by this method. 481 "module": <engine_name> 484 config = Configuration.get()
485 lang = config.get(
"lang",
"en-us")
486 tts_module = config.get(
'tts', {}).
get(
'module',
'mimic')
487 tts_config = config.get(
'tts', {}).
get(tts_module, {})
488 tts_lang = tts_config.get(
'lang', lang)
489 clazz = TTSFactory.CLASSES.get(tts_module)
490 tts = clazz(tts_lang, tts_config)
491 tts.validator.validate()
def report_timing(ident, system, timing, additional_data=None)
def blink(self, rate=1.0)
def load_phonemes(self, key)
def __init__(self, queue)
def validate_instance(self)
def execute(self, sentence, ident=None)
def resolve_resource_file(res_name)
def check_for_signal(signal_name, sec_lifetime=0)
def validate_connection(self)
def validate_ssml(self, utterance)
def viseme(self, phonemes)
def get_tts(self, sentence, wav_file)
def curate_cache(directory, min_free_percent=5.0, min_free_disk=50)
def create_signal(signal_name)
def modify_tag(self, tag)
def _preprocess_sentence(self, sentence)
def validate_filename(self)
def send_playback_metric(stopwatch, ident)
def show_visemes(self, pairs)
def validate_dependencies(self)
def save_phonemes(self, key, phonemes)
def __init__(self, lang, config, validator, audio_ext='wav', phonetic_spelling=True, ssml_tags=None)
def get_cache_directory(domain=None)
def get(phrase, lang=None, context=None)