files: # Copyright 2017 Mycroft AI Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  from abc import ABCMeta, abstractmethod
  
  
  class AudioBackend(metaclass=ABCMeta):
      """
          Base class for all audio backend implementations.
  
          Args:
              config: configuration dict for the instance
              bus:    Mycroft messagebus emitter
      """
  
      def __init__(self, config, bus):
          self._track_start_callback = None
          self.supports_mime_hints = False
  
      @abstractmethod
      def supported_uris(self):
          """
              Returns: list of supported uri types.
          """
          pass
  
      @abstractmethod
      def clear_list(self):
          """
              Clear playlist
          """
          pass
  
      @abstractmethod
      def add_list(self, tracks):
          """
              Add tracks to backend's playlist.
  
              Args:
                  tracks: list of tracks.
          """
          pass
  
      @abstractmethod
      def play(self, repeat=False):
          """
              Start playback.
  
              Args:
                  repeat: Repeat playlist, defaults to False
          """
          pass
  
      @abstractmethod
      def stop(self):
          """
              Stop playback.
  
              Returns: (bool) True if playback was stopped, otherwise False
          """
          pass
  
      def set_track_start_callback(self, callback_func):
          """
              Register callback on track start, should be called as each track
              in a playlist is started.
          """
          self._track_start_callback = callback_func
  
      def pause(self):
          """
              Pause playback.
          """
          pass
  
      def resume(self):
          """
              Resume paused playback.
          """
          pass
  
      def next(self):
          """
              Skip to next track in playlist.
          """
          pass
  
      def previous(self):
          """
              Skip to previous track in playlist.
          """
          pass
  
      def lower_volume(self):
          """
              Lower volume.
          """
          pass
  
      def restore_volume(self):
          """
              Restore normal volume.
          """
          pass
  
      @abstractmethod
      def seek_forward(self, seconds=1):
          """
              Skip X seconds
  
              Args:
                  seconds (int): number of seconds to seek, if negative rewind
          """
          pass
  
      @abstractmethod
      def seek_backward(self, seconds=1):
          """
              Rewind X seconds
  
              Args:
                  seconds (int): number of seconds to seek, if negative rewind
          """
          pass
  
      def track_info(self):
          """
              Fetch info about current playing track.
  
              Returns:
                  Dict with track info.
          """
          ret = {}
          ret['artist'] = ''
          ret['album'] = ''
          return ret
  
      def shutdown(self):
          """ Perform clean shutdown """
          self.stop()
  
  
  class RemoteAudioBackend(AudioBackend):
      """ Base class for remote audio backends.
  
          These may be things like Chromecasts, mopidy servers, etc.
      """
      pass
  
  
  [... the rest is omitted ...]
files: mycroft/audio/services/__init__.py
files: # Copyright 2017 Mycroft AI Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  from abc import ABCMeta, abstractmethod
  
  
  class AudioBackend:
      """
          Base class for all audio backend implementations.
  
          Args:
              config: configuration dict for the instance
              bus:    Mycroft messagebus emitter
      """
      __metaclass__ = ABCMeta
  
      def __init__(self, config, bus):
          self._track_start_callback = None
          self.supports_mime_hints = False
  
      @abstractmethod
      def supported_uris(self):
          """
              Returns: list of supported uri types.
          """
          pass
  
      @abstractmethod
      def clear_list(self):
          """
              Clear playlist
          """
          pass
  
      @abstractmethod
      def add_list(self, tracks):
          """
              Add tracks to backend's playlist.
  
              Args:
                  tracks: list of tracks.
          """
          pass
  
      @abstractmethod
      def play(self, repeat=False):
          """
              Start playback.
  
              Args:
                  repeat: Repeat playlist, defaults to False
          """
          pass
  
      @abstractmethod
      def stop(self):
          """
              Stop playback.
  
              Returns: (bool) True if playback was stopped, otherwise False
          """
          pass
  
      def set_track_start_callback(self, callback_func):
          """
              Register callback on track start, should be called as each track
              in a playlist is started.
          """
          self._track_start_callback = callback_func
  
      def pause(self):
          """
              Pause playback.
          """
          pass
  
      def resume(self):
          """
              Resume paused playback.
          """
          pass
  
      def next(self):
          """
              Skip to next track in playlist.
          """
          pass
  
      def previous(self):
          """
              Skip to previous track in playlist.
          """
          pass
  
      def lower_volume(self):
          """
              Lower volume.
          """
          pass
  
      def restore_volume(self):
          """
              Restore normal volume.
          """
          pass
  
      @abstractmethod
      def seek_forward(self, seconds=1):
          """
              Skip X seconds
  
              Args:
                  seconds (int): number of seconds to seek, if negative rewind
          """
          pass
  
      @abstractmethod
      def seek_backward(self, seconds=1):
          """
              Rewind X seconds
  
              Args:
                  seconds (int): number of seconds to seek, if negative rewind
          """
          pass
  
      def track_info(self):
          """
              Fetch info about current playing track.
  
              Returns:
                  Dict with track info.
          """
          ret = {}
          ret['artist'] = ''
          ret['album'] = ''
          return ret
  
      def shutdown(self):
          """ Perform clean shutdown """
          self.stop()
  
  
  class RemoteAudioBackend(AudioBackend):
      """ Base class for remote audio backends.
  
          These may be things like Chromecasts, mopidy servers, etc.
      """
      pass
  
  
  [... the rest is omitted ...]
files: mycroft/audio/services/__init__.py
files: # Copyright 2017 Mycroft AI Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  import re
  import json
  from abc import ABCMeta, abstractmethod
  from requests import post, put, exceptions
  from speech_recognition import Recognizer
  from queue import Queue
  from threading import Thread
  
  from mycroft.api import STTApi
  from mycroft.configuration import Configuration
  from mycroft.util.log import LOG
  
  
  class STT(metaclass=ABCMeta):
      """ STT Base class, all  STT backends derives from this one. """
      def __init__(self):
          config_core = Configuration.get()
          self.lang = str(self.init_language(config_core))
          config_stt = config_core.get("stt", {})
          self.config = config_stt.get(config_stt.get("module"), {})
          self.credential = self.config.get("credential", {})
          self.recognizer = Recognizer()
          self.can_stream = False
  
      @staticmethod
      def init_language(config_core):
          lang = config_core.get("lang", "en-US")
          langs = lang.split("-")
          if len(langs) == 2:
              return langs[0].lower() + "-" + langs[1].upper()
          return lang
  
      @abstractmethod
      def execute(self, audio, language=None):
          pass
  
  
  class TokenSTT(STT, metaclass=ABCMeta):
      def __init__(self):
          super(TokenSTT, self).__init__()
          self.token = str(self.credential.get("token"))
  
  
  class GoogleJsonSTT(STT, metaclass=ABCMeta):
      def __init__(self):
          super(GoogleJsonSTT, self).__init__()
          self.json_credentials = json.dumps(self.credential.get("json"))
  
  
  class BasicSTT(STT, metaclass=ABCMeta):
  
      def __init__(self):
          super(BasicSTT, self).__init__()
          self.username = str(self.credential.get("username"))
          self.password = str(self.credential.get("password"))
  
  
  class KeySTT(STT, metaclass=ABCMeta):
  
      def __init__(self):
          super(KeySTT, self).__init__()
          self.id = str(self.credential.get("client_id"))
          self.key = str(self.credential.get("client_key"))
  
  
  class GoogleSTT(TokenSTT):
      def __init__(self):
          super(GoogleSTT, self).__init__()
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          return self.recognizer.recognize_google(audio, self.token, self.lang)
  
  
  class GoogleCloudSTT(GoogleJsonSTT):
      def __init__(self):
          super(GoogleCloudSTT, self).__init__()
          # override language with module specific language selection
          self.lang = self.config.get('lang') or self.lang
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          return self.recognizer.recognize_google_cloud(audio,
                                                        self.json_credentials,
                                                        self.lang)
  
  
  class WITSTT(TokenSTT):
      def __init__(self):
          super(WITSTT, self).__init__()
  
      def execute(self, audio, language=None):
          LOG.warning("WITSTT language should be configured at wit.ai settings.")
          return self.recognizer.recognize_wit(audio, self.token)
  
  
  class IBMSTT(BasicSTT):
      def __init__(self):
          super(IBMSTT, self).__init__()
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          return self.recognizer.recognize_ibm(audio, self.username,
                                               self.password, self.lang)
  
  
  class MycroftSTT(STT):
      def __init__(self):
          super(MycroftSTT, self).__init__()
          self.api = STTApi("stt")
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          try:
              return self.api.stt(audio.get_flac_data(convert_rate=16000),
                                  self.lang, 1)[0]
          except Exception:
              return self.api.stt(audio.get_flac_data(), self.lang, 1)[0]
  
  
  class MycroftDeepSpeechSTT(STT):
      """Mycroft Hosted DeepSpeech"""
      def __init__(self):
          super(MycroftDeepSpeechSTT, self).__init__()
          self.api = STTApi("deepspeech")
  
      def execute(self, audio, language=None):
          language = language or self.lang
          if not language.startswith("en"):
              raise ValueError("Deepspeech is currently english only")
          return self.api.stt(audio.get_wav_data(), self.lang, 1)
  
  
  class DeepSpeechServerSTT(STT):
      """
          STT interface for the deepspeech-server:
          https://github.com/MainRo/deepspeech-server
          use this if you want to host DeepSpeech yourself
      """
      def __init__(self):
          super(DeepSpeechServerSTT, self).__init__()
  
      def execute(self, audio, language=None):
          language = language or self.lang
          if not language.startswith("en"):
              raise ValueError("Deepspeech is currently english only")
          response = post(self.config.get("uri"), data=audio.get_wav_data())
          return response.text
  
  
  class StreamThread(Thread, metaclass=ABCMeta):
      """
          ABC class to be used with Streaming
  
  [... the rest is omitted ...]
files: mycroft/stt/__init__.py
files: # Copyright 2017 Mycroft AI Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  import re
  import json
  from abc import ABCMeta, abstractmethod
  from requests import post, put, exceptions
  from speech_recognition import Recognizer
  from queue import Queue
  from threading import Thread
  
  from mycroft.api import STTApi
  from mycroft.configuration import Configuration
  from mycroft.util.log import LOG
  
  
  class STT:
      __metaclass__ = ABCMeta
  
      def __init__(self):
          config_core = Configuration.get()
          self.lang = str(self.init_language(config_core))
          config_stt = config_core.get("stt", {})
          self.config = config_stt.get(config_stt.get("module"), {})
          self.credential = self.config.get("credential", {})
          self.recognizer = Recognizer()
          self.can_stream = False
  
      @staticmethod
      def init_language(config_core):
          lang = config_core.get("lang", "en-US")
          langs = lang.split("-")
          if len(langs) == 2:
              return langs[0].lower() + "-" + langs[1].upper()
          return lang
  
      @abstractmethod
      def execute(self, audio, language=None):
          pass
  
  
  class TokenSTT(STT):
      __metaclass__ = ABCMeta
  
      def __init__(self):
          super(TokenSTT, self).__init__()
          self.token = str(self.credential.get("token"))
  
  
  class GoogleJsonSTT(STT):
      __metaclass__ = ABCMeta
  
      def __init__(self):
          super(GoogleJsonSTT, self).__init__()
          self.json_credentials = json.dumps(self.credential.get("json"))
  
  
  class BasicSTT(STT):
      __metaclass__ = ABCMeta
  
      def __init__(self):
          super(BasicSTT, self).__init__()
          self.username = str(self.credential.get("username"))
          self.password = str(self.credential.get("password"))
  
  
  class KeySTT(STT):
      __metaclass__ = ABCMeta
  
      def __init__(self):
          super(KeySTT, self).__init__()
          self.id = str(self.credential.get("client_id"))
          self.key = str(self.credential.get("client_key"))
  
  
  class GoogleSTT(TokenSTT):
      def __init__(self):
          super(GoogleSTT, self).__init__()
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          return self.recognizer.recognize_google(audio, self.token, self.lang)
  
  
  class GoogleCloudSTT(GoogleJsonSTT):
      def __init__(self):
          super(GoogleCloudSTT, self).__init__()
          # override language with module specific language selection
          self.lang = self.config.get('lang') or self.lang
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          return self.recognizer.recognize_google_cloud(audio,
                                                        self.json_credentials,
                                                        self.lang)
  
  
  class WITSTT(TokenSTT):
      def __init__(self):
          super(WITSTT, self).__init__()
  
      def execute(self, audio, language=None):
          LOG.warning("WITSTT language should be configured at wit.ai settings.")
          return self.recognizer.recognize_wit(audio, self.token)
  
  
  class IBMSTT(BasicSTT):
      def __init__(self):
          super(IBMSTT, self).__init__()
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          return self.recognizer.recognize_ibm(audio, self.username,
                                               self.password, self.lang)
  
  
  class MycroftSTT(STT):
      def __init__(self):
          super(MycroftSTT, self).__init__()
          self.api = STTApi("stt")
  
      def execute(self, audio, language=None):
          self.lang = language or self.lang
          try:
              return self.api.stt(audio.get_flac_data(convert_rate=16000),
                                  self.lang, 1)[0]
          except Exception:
              return self.api.stt(audio.get_flac_data(), self.lang, 1)[0]
  
  
  class MycroftDeepSpeechSTT(STT):
      """Mycroft Hosted DeepSpeech"""
      def __init__(self):
          super(MycroftDeepSpeechSTT, self).__init__()
          self.api = STTApi("deepspeech")
  
      def execute(self, audio, language=None):
          language = language or self.lang
          if not language.startswith("en"):
              raise ValueError("Deepspeech is currently english only")
          return self.api.stt(audio.get_wav_data(), self.lang, 1)
  
  
  class DeepSpeechServerSTT(STT):
      """
          STT interface for the deepspeech-server:
          https://github.com/MainRo/deepspeech-server
          use this if you want to host DeepSpeech yourself
      """
      def __init__(self):
          super(DeepSpeechServerSTT, self).__init__()
  
      def execute(self, audio, language=None):
          language = language or self.lang
          if not language.startswith("en"):
              raise ValueError("Deepspeech is currently english only")
          response = post(self.config.get("uri"), data=audio.get_wav_data())
          return response.text
  
  
  class StreamThread(Thread):
      """
          ABC class to be used with StreamingSTT class implementations.
      """
      __metaclass
  
  [... the rest is omitted ...]
files: mycroft/stt/__init__.py
files: # Copyright 2017 Mycroft AI Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  import hashlib
  import os
  import random
  import re
  import sys
  from abc import ABCMeta, abstractmethod
  from threading import Thread
  from time import time, sleep
  
  import os.path
  from os.path import dirname, exists, isdir, join
  
  import mycroft.util
  from mycroft.enclosure.api import EnclosureAPI
  from mycroft.configuration import Configuration
  from mycroft.messagebus.message import Message
  from mycroft.metrics import report_timing, Stopwatch
  from mycroft.util import (
      play_wav, play_mp3, check_for_signal, create_signal, resolve_resource_file
  )
  from mycroft.util.log import LOG
  from queue import Queue, Empty
  
  
  def send_playback_metric(stopwatch, ident):
      """
          Send playback metrics in a background thread
      """
  
      def do_send(stopwatch, ident):
          report_timing(ident, 'speech_playback', stopwatch)
  
      t = Thread(target=do_send, args=(stopwatch, ident))
      t.daemon = True
      t.start()
  
  
  class PlaybackThread(Thread):
      """
          Thread class for playing back tts audio and sending
          viseme data to enclosure.
      """
  
      def __init__(self, queue):
          super(PlaybackThread, self).__init__()
          self.queue = queue
          self._terminated = False
          self._processing_queue = False
  
      def init(self, tts):
          self.tts = tts
  
      def clear_queue(self):
          """
              Remove all pending playbacks.
          """
          while not self.queue.empty():
              self.queue.get()
          try:
              self.p.terminate()
          except Exception:
              pass
  
      def run(self):
          """
              Thread main loop. get audio and viseme data from queue
              and play.
          """
          while not self._terminated:
              try:
                  snd_type, data, visemes, ident = self.queue.get(timeout=2)
                  self.blink(0.5)
                  if not self._processing_queue:
                      self._processing_queue = True
                      self.tts.begin_audio()
  
                  stopwatch = Stopwatch()
                  with stopwatch:
                      if snd_type == 'wav':
                          self.p = play_wav(data)
                      elif snd_type == 'mp3':
                          self.p = play_mp3(data)
  
                      if visemes:
                          self.show_visemes(visemes)
                      self.p.communicate()
                      self.p.wait()
                  send_playback_metric(stopwatch, ident)
  
                  if self.queue.empty():
                      self.tts.end_audio()
                      self._processing_queue = False
                  self.blink(0.2)
              except Empty:
                  pass
              except Exception as e:
                  LOG.exception(e)
                  if self._processing_queue:
                      self.tts.end_audio()
                      self._processing_queue = False
  
      def show_visemes(self, pairs):
          """
              Send viseme data to enclosure
  
              Args:
                  pairs(list): Visime and timing pair
  
              Returns:
                  True if button has been pressed.
          """
          if self.enclosure:
              self.enclosure.mouth_viseme(time(), pairs)
  
      def clear(self):
          """ Clear all pending actions for the TTS playback thread. """
          self.clear_queue()
  
      def blink(self, rate=1.0):
          """ Blink mycroft's eyes """
          if self.enclosure and random.random() < rate:
              self.enclosure.eyes_blink("b")
  
      def stop(self):
          """ Stop thread """
          self._terminated = True
          self.clear_queue()
  
  
  class TTS(metaclass=ABCMeta):
      """
      TTS abstract class to be implemented by all TTS engines.
  
      It aggregates the minimum required parameters and exposes
      ``execute(sentence)`` and ``validate_ssml(sentence)`` functions.
  
      Args:
          lang (str):
          config (dict): Configuration for this specific tts engine
          validator (TTSValidator): Used to verify proper installation
          phonetic_spelling (bool): Whether to spell certain words phonetically
          ssml_tags (list): Supported ssml properties. Ex. ['speak', 'prosody']
      """
      def __init__(self, lang, config, validator, audio_ext='wav',
                   phonetic_spelling=True, ssml_tags=None):
          super(TTS, self).__init__()
          self.bus = None  # initalized in "init" step
          self.lang = lang or 'en-us'
          self.config = config
          self.validator = validator
          self.phonetic_spelling = phonetic_spelling
          self.audio_ext = audio_ext
          self.ssml_tags = ssml_tags or []
  
          self.voice = config.get("voice")
          self.filename = '/tmp/tts.wav'
          self.enclosure = None
          random.seed()
          self.queue = Queue()
          self.playback = PlaybackThread(self.queue)
          self.playback.start()
          self.clear_cache()
          self.spellings = self.load_spellings()
          self.tts_name = type(self).__name__
  
      def load_spellings(self):
          """Load phonetic spellings of words as dictionary"""
          path = join('text', self.lang, 'phonetic_spellings.txt')
          spellings_file = resolve_resource_file(path)
          if not spellings_file:
              return {}
          try:
              with open(spellings_file) as f:
                  lines = filter(bool, f.read().split('\n'))
              lines = [i.split(':') for
  
  [... the rest is omitted ...]
files: mycroft/tts/__init__.py
files: # Copyright 2017 Mycroft AI Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  import hashlib
  import os
  import random
  import re
  import sys
  from abc import ABCMeta, abstractmethod
  from threading import Thread
  from time import time, sleep
  
  import os.path
  from os.path import dirname, exists, isdir, join
  
  import mycroft.util
  from mycroft.enclosure.api import EnclosureAPI
  from mycroft.configuration import Configuration
  from mycroft.messagebus.message import Message
  from mycroft.metrics import report_timing, Stopwatch
  from mycroft.util import (
      play_wav, play_mp3, check_for_signal, create_signal, resolve_resource_file
  )
  from mycroft.util.log import LOG
  from queue import Queue, Empty
  
  
  def send_playback_metric(stopwatch, ident):
      """
          Send playback metrics in a background thread
      """
  
      def do_send(stopwatch, ident):
          report_timing(ident, 'speech_playback', stopwatch)
  
      t = Thread(target=do_send, args=(stopwatch, ident))
      t.daemon = True
      t.start()
  
  
  class PlaybackThread(Thread):
      """
          Thread class for playing back tts audio and sending
          viseme data to enclosure.
      """
  
      def __init__(self, queue):
          super(PlaybackThread, self).__init__()
          self.queue = queue
          self._terminated = False
          self._processing_queue = False
  
      def init(self, tts):
          self.tts = tts
  
      def clear_queue(self):
          """
              Remove all pending playbacks.
          """
          while not self.queue.empty():
              self.queue.get()
          try:
              self.p.terminate()
          except Exception:
              pass
  
      def run(self):
          """
              Thread main loop. get audio and viseme data from queue
              and play.
          """
          while not self._terminated:
              try:
                  snd_type, data, visemes, ident = self.queue.get(timeout=2)
                  self.blink(0.5)
                  if not self._processing_queue:
                      self._processing_queue = True
                      self.tts.begin_audio()
  
                  stopwatch = Stopwatch()
                  with stopwatch:
                      if snd_type == 'wav':
                          self.p = play_wav(data)
                      elif snd_type == 'mp3':
                          self.p = play_mp3(data)
  
                      if visemes:
                          self.show_visemes(visemes)
                      self.p.communicate()
                      self.p.wait()
                  send_playback_metric(stopwatch, ident)
  
                  if self.queue.empty():
                      self.tts.end_audio()
                      self._processing_queue = False
                  self.blink(0.2)
              except Empty:
                  pass
              except Exception as e:
                  LOG.exception(e)
                  if self._processing_queue:
                      self.tts.end_audio()
                      self._processing_queue = False
  
      def show_visemes(self, pairs):
          """
              Send viseme data to enclosure
  
              Args:
                  pairs(list): Visime and timing pair
  
              Returns:
                  True if button has been pressed.
          """
          if self.enclosure:
              self.enclosure.mouth_viseme(time(), pairs)
  
      def clear(self):
          """ Clear all pending actions for the TTS playback thread. """
          self.clear_queue()
  
      def blink(self, rate=1.0):
          """ Blink mycroft's eyes """
          if self.enclosure and random.random() < rate:
              self.enclosure.eyes_blink("b")
  
      def stop(self):
          """ Stop thread """
          self._terminated = True
          self.clear_queue()
  
  
  class TTS:
      """
      TTS abstract class to be implemented by all TTS engines.
  
      It aggregates the minimum required parameters and exposes
      ``execute(sentence)`` and ``validate_ssml(sentence)`` functions.
  
      Args:
          lang (str):
          config (dict): Configuration for this specific tts engine
          validator (TTSValidator): Used to verify proper installation
          phonetic_spelling (bool): Whether to spell certain words phonetically
          ssml_tags (list): Supported ssml properties. Ex. ['speak', 'prosody']
      """
      __metaclass__ = ABCMeta
  
      def __init__(self, lang, config, validator, audio_ext='wav',
                   phonetic_spelling=True, ssml_tags=None):
          super(TTS, self).__init__()
          self.bus = None  # initalized in "init" step
          self.lang = lang or 'en-us'
          self.config = config
          self.validator = validator
          self.phonetic_spelling = phonetic_spelling
          self.audio_ext = audio_ext
          self.ssml_tags = ssml_tags or []
  
          self.voice = config.get("voice")
          self.filename = '/tmp/tts.wav'
          self.enclosure = None
          random.seed()
          self.queue = Queue()
          self.playback = PlaybackThread(self.queue)
          self.playback.start()
          self.clear_cache()
          self.spellings = self.load_spellings()
          self.tts_name = type(self).__name__
  
      def load_spellings(self):
          """Load phonetic spellings of words as dictionary"""
          path = join('text', self.lang, 'phonetic_spellings.txt')
          spellings_file = resolve_resource_file(path)
          if not spellings_file:
              return {}
          try:
              with open(spellings_file) as f:
                  lines = filter(bool, f.read().split('\n'))
              lines = [i.split(':
  
  [... the rest is omitted ...]
files: mycroft/tts/__init__.py