summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/mic.py9
-rw-r--r--client/modules/MPDControl.py13
-rw-r--r--client/stt.py185
-rw-r--r--client/test.py13
4 files changed, 85 insertions, 135 deletions
diff --git a/client/mic.py b/client/mic.py
index 2a6fe4a..f75539b 100644
--- a/client/mic.py
+++ b/client/mic.py
@@ -9,7 +9,6 @@ import audioop
import pyaudio
import alteration
import jasperpath
-from stt import TranscriptionMode
class Mic:
@@ -172,8 +171,7 @@ class Mic:
wav_fp.close()
f.seek(0)
# check if PERSONA was said
- transcribed = self.passive_stt_engine.transcribe(
- f, mode=TranscriptionMode.KEYWORD)
+ transcribed = self.passive_stt_engine.transcribe(f)
if any(PERSONA in phrase for phrase in transcribed):
return (THRESHOLD, PERSONA)
@@ -250,10 +248,7 @@ class Mic:
wav_fp.writeframes(''.join(frames))
wav_fp.close()
f.seek(0)
- mode = (TranscriptionMode.MUSIC if MUSIC
- else TranscriptionMode.NORMAL)
- transcribed = self.active_stt_engine.transcribe(f, mode=mode)
- return transcribed
+ return self.active_stt_engine.transcribe(f)
def say(self, phrase,
OPTIONS=" -vdefault+m3 -p 40 -s 160 --stdout > say.wav"):
diff --git a/client/modules/MPDControl.py b/client/modules/MPDControl.py
index f23fc7d..2548237 100644
--- a/client/modules/MPDControl.py
+++ b/client/modules/MPDControl.py
@@ -3,9 +3,6 @@ import re
import logging
import difflib
import mpd
-import stt
-import vocabcompiler
-import jasperpath
from mic import Mic
# Standard module stuff
@@ -78,17 +75,11 @@ class MusicMode(object):
"PLAYLIST"]
phrases.extend(self.music.get_soup_playlist())
- vocabulary_music = vocabcompiler.PocketsphinxVocabulary(
- name='music', path=jasperpath.config('vocabularies'))
- vocabulary_music.compile(phrases)
-
- # create a new mic with the new music models
- config = stt.PocketSphinxSTT.get_config()
+ music_stt_engine = mic.active_stt_engine.get_instance('music', phrases)
self.mic = Mic(mic.speaker,
mic.passive_stt_engine,
- stt.PocketSphinxSTT(vocabulary_music=vocabulary_music,
- **config))
+ music_stt_engine)
def delegateInput(self, input):
diff --git a/client/stt.py b/client/stt.py
index f5254ca..0ae7871 100644
--- a/client/stt.py
+++ b/client/stt.py
@@ -14,28 +14,48 @@ import diagnose
import vocabcompiler
-class TranscriptionMode:
- NORMAL, KEYWORD, MUSIC = range(3)
-
-
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, mode=TranscriptionMode.NORMAL):
+ def transcribe(self, fp):
pass
@@ -45,9 +65,9 @@ class PocketSphinxSTT(AbstractSTTEngine):
"""
SLUG = 'sphinx'
+ VOCABULARY_TYPE = vocabcompiler.PocketsphinxVocabulary
- def __init__(self, vocabulary=None, vocabulary_keyword=None,
- vocabulary_music=None, hmm_dir="/usr/local/share/" +
+ def __init__(self, vocabulary, hmm_dir="/usr/local/share/" +
"pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k"):
"""
@@ -55,9 +75,6 @@ class PocketSphinxSTT(AbstractSTTEngine):
Arguments:
vocabulary -- a PocketsphinxVocabulary instance
- vocabulary_keyword -- a PocketsphinxVocabulary instance
- (containing, e.g., 'Jasper')
- vocabulary_music -- (optional) a PocketsphinxVocabulary instance
hmm_dir -- the path of the Hidden Markov Model (HMM)
"""
@@ -69,35 +86,15 @@ class PocketSphinxSTT(AbstractSTTEngine):
except:
import pocketsphinx as ps
- self._logfiles = {}
- with tempfile.NamedTemporaryFile(prefix='psdecoder_music_',
- suffix='.log', delete=False) as f:
- self._logfiles[TranscriptionMode.MUSIC] = f.name
- with tempfile.NamedTemporaryFile(prefix='psdecoder_keyword_',
- suffix='.log', delete=False) as f:
- self._logfiles[TranscriptionMode.KEYWORD] = f.name
- with tempfile.NamedTemporaryFile(prefix='psdecoder_normal_',
+ with tempfile.NamedTemporaryFile(prefix='psdecoder_',
suffix='.log', delete=False) as f:
- self._logfiles[TranscriptionMode.NORMAL] = f.name
+ self._logfile = f.name
- self._decoders = {}
- if vocabulary_music is not None:
- self._decoders[TranscriptionMode.MUSIC] = \
- ps.Decoder(hmm=hmm_dir,
- logfn=self._logfiles[TranscriptionMode.MUSIC],
- **vocabulary_music.decoder_kwargs)
- self._decoders[TranscriptionMode.KEYWORD] = \
- ps.Decoder(hmm=hmm_dir,
- logfn=self._logfiles[TranscriptionMode.KEYWORD],
- **vocabulary_keyword.decoder_kwargs)
- self._decoders[TranscriptionMode.NORMAL] = \
- ps.Decoder(hmm=hmm_dir,
- logfn=self._logfiles[TranscriptionMode.NORMAL],
- **vocabulary.decoder_kwargs)
+ self._decoder = ps.Decoder(hmm=hmm_dir, logfn=self._logfile,
+ **vocabulary.decoder_kwargs)
def __del__(self):
- for filename in self._logfiles.values():
- os.remove(filename)
+ os.remove(self._logfile)
@classmethod
def get_config(cls):
@@ -107,78 +104,38 @@ class PocketSphinxSTT(AbstractSTTEngine):
# Try to get hmm_dir from config
profile_path = jasperpath.config('profile.yml')
- name_default = 'default'
- path_default = jasperpath.config('vocabularies')
-
- name_keyword = 'keyword'
- path_keyword = jasperpath.config('vocabularies')
-
if os.path.exists(profile_path):
with open(profile_path, 'r') as f:
profile = yaml.safe_load(f)
- if 'pocketsphinx' in profile:
- if 'hmm_dir' in profile['pocketsphinx']:
- config['hmm_dir'] = profile['pocketsphinx']['hmm_dir']
-
- if 'vocabulary_default_name' in profile['pocketsphinx']:
- name_default = \
- profile['pocketsphinx']['vocabulary_default_name']
-
- if 'vocabulary_default_path' in profile['pocketsphinx']:
- path_default = \
- profile['pocketsphinx']['vocabulary_default_path']
-
- if 'vocabulary_keyword_name' in profile['pocketsphinx']:
- name_keyword = \
- profile['pocketsphinx']['vocabulary_keyword_name']
-
- if 'vocabulary_keyword_path' in profile['pocketsphinx']:
- path_keyword = \
- profile['pocketsphinx']['vocabulary_keyword_path']
-
- config['vocabulary'] = vocabcompiler.PocketsphinxVocabulary(
- name_default, path=path_default)
- config['vocabulary_keyword'] = vocabcompiler.PocketsphinxVocabulary(
- name_keyword, path=path_keyword)
-
- config['vocabulary'].compile(vocabcompiler.get_all_phrases())
- config['vocabulary_keyword'].compile(
- vocabcompiler.get_keyword_phrases())
+ try:
+ config['hmm_dir'] = profile['pocketsphinx']['hmm_dir']
+ except KeyError:
+ pass
return config
- def transcribe(self, fp, mode=TranscriptionMode.NORMAL):
+ def transcribe(self, fp):
"""
Performs STT, transcribing an audio file and returning the result.
Arguments:
- audio_file_path -- the path to the audio file to-be transcribed
- PERSONA_ONLY -- if True, uses the 'Persona' language model and
- dictionary
- MUSIC -- if True, uses the 'Music' language model and dictionary
+ fp -- a file object containing audio data
"""
- decoder = self._decoders[mode]
fp.seek(44)
# FIXME: Can't use the Decoder.decode_raw() here, because
# pocketsphinx segfaults with tempfile.SpooledTemporaryFile()
data = fp.read()
- decoder.start_utt()
- decoder.process_raw(data, False, True)
- decoder.end_utt()
+ self._decoder.start_utt()
+ self._decoder.process_raw(data, False, True)
+ self._decoder.end_utt()
- result = decoder.get_hyp()
- with open(self._logfiles[mode], 'r+') as f:
- if mode == TranscriptionMode.KEYWORD:
- modename = "[KEYWORD]"
- elif mode == TranscriptionMode.MUSIC:
- modename = "[MUSIC]"
- else:
- modename = "[NORMAL]"
- for line in f:
- self._logger.debug("%s %s", modename, line.strip())
- f.truncate()
+ result = self._decoder.get_hyp()
+ with open(self._logfile, 'r+') as f:
+ for line in f:
+ self._logger.debug(line.strip())
+ f.truncate()
print "==================="
print "JASPER: " + result[0]
@@ -248,7 +205,7 @@ class GoogleSTT(AbstractSTTEngine):
config['api_key'] = profile['keys']['GOOGLE_SPEECH']
return config
- def transcribe(self, fp, mode=TranscriptionMode.NORMAL):
+ def transcribe(self, fp):
"""
Performs STT via the Google Speech API, transcribing an audio file and
returning an English string.
@@ -293,33 +250,41 @@ class GoogleSTT(AbstractSTTEngine):
return diagnose.check_network_connection()
-def get_engines():
- return [stt_engine for stt_engine in AbstractSTTEngine.__subclasses__()
- if hasattr(stt_engine, 'SLUG') and stt_engine.SLUG]
-
+def get_engine_by_slug(slug=None):
+ """
+ Returns:
+ An STT Engine implementation available on the current platform
-def newSTTEngine(stt_engine, **kwargs):
+ Raises:
+ ValueError if no speaker implementation is supported on this platform
"""
- Returns a Speech-To-Text engine.
- Currently, the supported implementations are the default Pocket Sphinx and
- the Google Speech API
+ if not slug or type(slug) is not str:
+ raise TypeError("Invalid slug '%s'", slug)
- Arguments:
- engine_type -- one of "sphinx" or "google"
- kwargs -- keyword arguments passed to the constructor of the STT engine
- """
selected_engines = filter(lambda engine: hasattr(engine, "SLUG") and
- engine.SLUG == stt_engine, get_engines())
+ engine.SLUG == slug, get_engines())
if len(selected_engines) == 0:
- raise ValueError("No STT engine found for slug '%s'" % stt_engine)
+ raise ValueError("No TTS 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.") % stt_engine)
+ print("WARNING: Multiple TTS 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.)") % stt_engine)
- return engine(**engine.get_config())
+ raise ValueError("TTS 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 [tts_engine for tts_engine in
+ list(get_subclasses(AbstractSTTEngine))
+ if hasattr(tts_engine, 'SLUG') and tts_engine.SLUG]
diff --git a/client/test.py b/client/test.py
index 0a414f4..5a5dc6b 100644
--- a/client/test.py
+++ b/client/test.py
@@ -17,7 +17,6 @@ import brain
import jasperpath
import tts
import diagnose
-from stt import TranscriptionMode
DEFAULT_PROFILE = {
'prefers_email': False,
@@ -154,22 +153,22 @@ class TestPatchedPocketsphinxVocabulary(TestPocketsphinxVocabulary):
self).testVocabulary()
-class TestMic(unittest.TestCase):
+class TestSTT(unittest.TestCase):
def setUp(self):
self.jasper_clip = jasperpath.data('audio', 'jasper.wav')
self.time_clip = jasperpath.data('audio', 'time.wav')
from stt import PocketSphinxSTT
- self.stt = PocketSphinxSTT(**PocketSphinxSTT.get_config())
+ self.passive_stt_engine = PocketSphinxSTT.get_passive_instance()
+ self.active_stt_engine = PocketSphinxSTT.get_active_instance()
def testTranscribeJasper(self):
"""
Does Jasper recognize his name (i.e., passive listen)?
"""
with open(self.jasper_clip, mode="rb") as f:
- transcription = self.stt.transcribe(f,
- mode=TranscriptionMode.KEYWORD)
+ transcription = self.passive_stt_engine.transcribe(f)
self.assertIn("JASPER", transcription)
def testTranscribe(self):
@@ -177,7 +176,7 @@ class TestMic(unittest.TestCase):
Does Jasper recognize 'time' (i.e., active listen)?
"""
with open(self.time_clip, mode="rb") as f:
- transcription = self.stt.transcribe(f)
+ transcription = self.active_stt_engine.transcribe(f)
self.assertIn("TIME", transcription)
@@ -411,7 +410,7 @@ if __name__ == '__main__':
else:
test_cases.append(TestG2P)
test_cases.append(TestPocketsphinxVocabulary)
- test_cases.append(TestMic)
+ test_cases.append(TestSTT)
suite = unittest.TestSuite()