text: #!/usr/bin/env python2
  # -*- coding: utf-8-*-
  import os
  import wave
  import json
  import tempfile
  import logging
  import urllib
  from abc import ABCMeta, abstractmethod
  import requests
  import yaml
  import client.jasperpath as jasperpath
  import client.diagnose as diagnose
  import client.vocabcompiler as vocabcompiler
  
  import base64  # Baidu TTS
  from uuid import getnode as get_mac
  
  
  class AbstractSTTEngine(object):
      """
      Generic parent class for all STT engines
      """
  
      __metaclass__ = ABCMeta
      VOCABULARY_TYPE = None
  
      @classmethod
      def get_config(cls):
          return {}
  
      @classmethod
      def get_instance(cls, vocabulary_name, phrases):
          config = cls.get_config()
          if cls.VOCABULARY_TYPE:
              vocabulary = cls.VOCABULARY_TYPE(vocabulary_name,
                                               path=jasperpath.config(
                                                   'vocabularies'))
              if not vocabulary.matches_phrases(phrases):
                  vocabulary.compile(phrases)
              config['vocabulary'] = vocabulary
          instance = cls(**config)
          return instance
  
      @classmethod
      def get_passive_instance(cls):
          phrases = vocabcompiler.get_keyword_phrases()
          return cls.get_instance('keyword', phrases)
  
      @classmethod
      def get_active_instance(cls):
          phrases = vocabcompiler.get_all_phrases()
          return cls.get_instance('default', phrases)
  
      @classmethod
      @abstractmethod
      def is_available(cls):
          return True
  
      @abstractmethod
      def transcribe(self, fp):
          pass
  
      @abstractmethod
      def utt_start(self):
          pass
  
      @abstractmethod
      def utt_end(self):
          pass
  
      @abstractmethod
      def utt_transcribe(self, data):
          pass
  
  
  class BaiduSTT(AbstractSTTEngine):
      """
      Baidu's STT engine
      To leverage this engile, please register a developer account at
      yuyin.baidu.com.  Then new an application, get API Key and Secret
      Key in management console.  Fill them in profile.yml.
      ...
          baidu_yuyin:
              api_key: 'xxx............................................'
              secret_key: 'xxx.........................................'
          ...
      """
  
      SLUG = "baidu-stt"
  
      def __init__(self, api_key='', secret_key=''):
          self._logger = logging.getLogger(__name__)
          self.api_key = api_key
          self.secret_key = secret_key
          self.token = ''
  
      @classmethod
      def get_config(cls):
          # FIXME: Replace this as soon as we have a config module
          config = {}
          # Try to get baidu_yuyin config from config
          profile_path = jasperpath.config('profile.yml')
          if os.path.exists(profile_path):
              with open(profile_path, 'r') as f:
                  profile = yaml.safe_load(f)
                  if 'baidu_yuyin' in profile:
                      if 'api_key' in profile['baidu_yuyin']:
                          config['api_key'] = \
                              profile['baidu_yuyin']['api_key']
                      if 'secret_key' in profile['baidu_yuyin']:
                          config['secret_key'] = \
                              profile['baidu_yuyin']['secret_key']
          return config
  
      def get_token(self):
          URL = 'http://openapi.baidu.com/oauth/2.0/token'
          params = urllib.parse.urlencode({'grant_type': 'client_credentials',
                                           'client_id': self.api_key,
                                           'client_secret': self.secret_key})
          r = requests.get(URL, params=params)
          try:
              r.raise_for_status()
              token = r.json()['access_token']
              return token
          except requests.exceptions.HTTPError:
              self._logger.critical('Token request failed with response: %r',
                                    r.text,
                                    exc_info=True)
              return ''
  
      def transcribe(self, fp):
          try:
              wav_file = wave.open(fp, 'rb')
          except IOError:
              self._logger.critical('wav file not found: %s',
                                    fp,
                                    exc_info=True)
              return []
          n_frames = wav_file.getnframes()
          frame_rate = wav_file.getframerate()
          audio = wav_file.readframes(n_frames)
          base_data = base64.b64encode(audio)
          if self.token == '':
              self.token = self.get_token()
          data = {"format": "wav",
                  "token": self.token,
                  "len": len(audio),
                  "rate": frame_rate,
                  "speech": base_data.decode(encoding="utf-8"),
                  "cuid": str(get_mac())[:32],
                  "channel": 1}
          data = json.dumps(data)
          r = requests.post('http://vop.baidu.com/server_api',
                            data=data,
                            headers={'content-type': 'application/json'})
          try:
              r.raise_for_status()
              text = ''
              if 'result' in r.json():
                  text = r.json()['result'][0]
          except requests.exceptions.HTTPError:
              self._logger.critical('Request failed with response: %r',
                                    r.text,
                                    exc_info=True)
              return []
          except requests.exceptions.RequestException:
              self._logger.critical('Request failed.', exc_info=True)
              return []
          except ValueError as e:
              self._logger.critical('Cannot parse response: %s',
                                    e.args[0])
              return []
          except KeyError:
              self._logger.critical('Cannot parse response.',
                                    exc_info=True)
              return []
          else:
              transcribed = []
              if text:
                  transcribed.append(text.upper())
              self._logger.info(u'Baidu STT outputs: %s' % text)
              return transcribed
  
      @classmethod
      def is_available(cls):
          return diagnose.check_network_connection()
  
      def utt_start(self):
          return True
  
      def utt_end(self):
          return True
  
      def utt_transcribe(self, data):
          return ''
  
  
  class PocketSphinxSTT(AbstractSTTEngine):
      """
      The default Speech-to-Text implementation which relies on PocketSphinx.
      """
  
      SLUG = 'sphinx'
      VOCABULARY_TYPE = vocabcompiler.PocketsphinxVocabulary
  
      _pocketsphinx_v5 = False
      _previous_decoding_output = ''
  
      def __init__(self, vocabulary, hmm_dir="/usr/local/share/" +
                   "pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k"):
  
          """
          Initiates the pocketsphinx instance.
  
          Arguments:
              vocabulary -- a PocketsphinxVocabulary instance
              hmm_dir -- the path of the Hidden Markov Model (HMM)
          """
  
          self._logger = logging.getLogger(__name__)
  
          # quirky bug where first import doesn't work
          try:
              import pocketsphinx as ps
          except Exception:
              import pocketsphinx as ps
  
          with tempfile.NamedTemporaryFile(prefix='psdecoder_',
                                           suffix='.log', delete=False) as f:
              self._logfile = f.name
  
          self._logger.debug("Initializing PocketSphinx Decoder with hmm_dir " +
                             "'%s'", hmm_dir)
  
          # Perform some checks on the hmm_dir so that we can display more
          # meaningful error messages if neccessary
          if not os.path.exists(hmm_dir):
              msg = ("hmm_dir '%s' does not exist! Please make sure that you " +
                     "have set the correct hmm_dir in your profile.") % hmm_dir
              self._logger.error(msg)
              raise RuntimeError(msg)
          # Lets check if all required files are there. Refer to:
          # http://cmusphinx.sourceforge.net/wiki/acousticmodelformat
          # for details
          missing_hmm_files = []
          for fname in ('mdef', 'feat.params', 'means', 'noisedict',
                        'transition_matrices', 'variances'):
              if not os.path.exists(os.path.join(hmm_dir, fname)):
                  missing_hmm_files.append(fname)
          mixweights = os.path.exists(os.path.join(hmm_dir, 'mixture_weights'))
          sendump = os.path.exists(os.path.join(hmm_dir, 'sendump'))
          if not mixweights and not sendump:
              # We only need mixture_weights OR sendump
              missing_hmm_files.append('mixture_weights or sendump')
          if missing_hmm_files:
              self._logger.warning("hmm_dir '%s' is missing files: %s. Please " +
                                   "make sure that you have set the correct " +
                                   "hmm_dir in your profile.",
                                   hmm_dir, ', '.join(missing_hmm_files))
          self._pocketsphinx_v5 = hasattr(ps.Decoder, 'default_config')
          if self._pocketsphinx_v5:
              # Pocketsphinx v5
              config = ps.Decoder.default_config()
              config.set_string('-hmm', hmm_dir)
              config.set_string('-lm', vocabulary.languagemodel_file)
              config.set_string('-dict', vocabulary.dictionary_file)
              config.set_string('-logfn', self._logfile)
              self._decoder = ps.Decoder(config)
          else:
              self._decoder = ps.Decoder(hmm=hmm_dir, logfn=self._logfile,
                                         lm=vocabulary.languagemodel_file,
                                         dict=vocabulary.dictionary_file)
  
      def __del__(self):
          os.remove(self._logfile)
  
      @classmethod
      def get_config(cls):
          # FIXME: Replace this as soon as we have a config module
          config = {}
          # HMM dir
          # Try to get hmm_dir from config
          profile_path = jasperpath.config('profile.yml')
  
          if os.path.exists(profile_path):
              with open(profile_path, 'r') as f:
                  profile = yaml.safe_load(f)
                  try:
                      config['hmm_dir'] = profile['pocketsphinx']['hmm_dir']
                  except KeyError:
                      pass
  
          return config
  
      def transcribe(self, fp):
          """
          Performs STT, transcribing an audio file and returning the result.
  
          Arguments:
              fp -- a file object containing audio data
          """
  
          fp.seek(44)
  
          # FIXME: Can't use the Decoder.decode_raw() here, because
          # pocketsphinx segfaults with tempfile.SpooledTemporaryFile()
          data = fp.read()
          self._decoder.start_utt()
          self._decoder.process_raw(data, False, True)
          self._decoder.end_utt()
  
          if self._pocketsphinx_v5:
              hyp = self._decoder.hyp()
              result = hyp.hypstr if hyp is not None else ''
          else:
              result = self._decoder.get_hyp()[0]
          if self._logfile is not None:
              with open(self._logfile, 'r+') as f:
                  for line in f:
                      self._logger.debug(line.strip())
                  f.truncate()
  
          transcribed = [result] if result != '' else []
          self._logger.info('Transcribed: %r', transcribed)
          return transcribed
  
      @classmethod
      def is_available(cls):
          return diagnose.check_python_import('pocketsphinx')
  
      def utt_start(self):
          self._decoder.start_utt()
          return True
  
      def utt_end(self):
          self._decoder.end_utt()
          return True
  
      def utt_transcribe(self, data):
          self._decoder.process_raw(data, False, False)
          if self._pocketsphinx_v5:
              hyp = self._decoder.hyp()
              result = hyp.hypstr if hyp is not None else ''
          else:
              result = self._decoder.get_hyp()[0]
          transcribed = [result] if result != '' else []
          if self._previous_decoding_output != transcribed:
              self._logger.info('Partial: %r', transcribed)
          self._previous_decoding_output = transcribed
          return transcribed
  
  
  def get_engine_by_slug(slug=None):
      """
      Returns:
          An STT Engine implementation available on the current platform
  
      Raises:
          ValueError if no speaker implementation is supported on this platform
      """
  
      if not slug or type(slug) is not str:
          raise TypeError("Invalid slug '%s'", slug)
  
      selected_filter = filter(lambda engine: hasattr(engine, "SLUG") and
                               engine.SLUG == slug, get_engines())
      selected_engines = [engine for engine in selected_filter]
      if len(selected_engines) == 0:
          raise ValueError("No STT engine found for slug '%s'" % slug)
      else:
          if len(selected_engines) > 1:
              print(("WARNING: Multiple STT engines found for slug '%s'. " +
                     "This is most certainly a bug.") % slug)
          engine = selected_engines[0]
          if not engine.is_available():
              raise ValueError(("STT engine '%s' is not available (due to " +
                                "missing dependencies, missing " +
                                "dependencies, etc.)") % slug)
          return engine
  
  
  def get_engines():
      def get_subclasses(cls):
          subclasses = set()
          for subclass in cls.__subclasses__():
              subclasses.add(subclass)
              subclasses.update(get_subclasses(subclass))
          return subclasses
      return [stt_engine for stt_engine in
              list(get_subclasses(AbstractSTTEngine))
              if hasattr(stt_engine, 'SLUG') and stt_engine.SLUG]
  