From 02a095af1ed7af338384d39d43906bb47d1591a7 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 14:35:01 +0200 Subject: STT engines now inherit from AbstractSTTEngine --- client/stt.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/client/stt.py b/client/stt.py index a937658..9a8bb61 100644 --- a/client/stt.py +++ b/client/stt.py @@ -5,6 +5,7 @@ import traceback import json import tempfile import logging +from abc import ABCMeta, abstractmethod import requests import yaml @@ -12,8 +13,23 @@ import yaml The default Speech-to-Text implementation which relies on PocketSphinx. """ +class AbstractSTTEngine(object): + """ + Generic parent class for all STT engines + """ -class PocketSphinxSTT(object): + __metaclass__ = ABCMeta + + @classmethod + @abstractmethod + def is_available(cls): + return True + + @abstractmethod + def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False): + pass + +class PocketSphinxSTT(AbstractSTTEngine): def __init__(self, lmd="languagemodel.lm", dictd="dictionary.dic", lmd_persona="languagemodel_persona.lm", dictd_persona="dictionary_persona.dic", @@ -134,7 +150,7 @@ Excerpt from sample profile.yml: """ -class GoogleSTT(object): +class GoogleSTT(AbstractSTTEngine): RATE = 16000 -- cgit v1.3.1 From d4bf59ec1c1fc74cb91a7ed801ee086e622a9db9 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 15:04:41 +0200 Subject: STT engines now use slugs --- client/stt.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/client/stt.py b/client/stt.py index 9a8bb61..e17ccb5 100644 --- a/client/stt.py +++ b/client/stt.py @@ -21,7 +21,6 @@ class AbstractSTTEngine(object): __metaclass__ = ABCMeta @classmethod - @abstractmethod def is_available(cls): return True @@ -31,9 +30,11 @@ class AbstractSTTEngine(object): class PocketSphinxSTT(AbstractSTTEngine): + SLUG = 'sphinx' + def __init__(self, lmd="languagemodel.lm", dictd="dictionary.dic", lmd_persona="languagemodel_persona.lm", dictd_persona="dictionary_persona.dic", - lmd_music=None, dictd_music=None, **kwargs): + lmd_music=None, dictd_music=None, **kwargs): #FIXME: get init args from config """ Initiates the pocketsphinx instance. @@ -152,9 +153,10 @@ Excerpt from sample profile.yml: class GoogleSTT(AbstractSTTEngine): + SLUG = 'google' RATE = 16000 - def __init__(self, api_key, **kwargs): + def __init__(self, api_key=None, **kwargs): #FIXME: get init args from config """ Arguments: api_key - the public api key which allows access to Google APIs @@ -203,13 +205,17 @@ Arguments: engine_type - one of "sphinx" or "google" kwargs - keyword arguments passed to the constructor of the STT engine """ +def get_engines(): + return [stt_engine for stt_engine in AbstractSTTEngine.__subclasses__() if hasattr(stt_engine, 'SLUG') and stt_engine.SLUG] - -def newSTTEngine(engine_type, **kwargs): - t = engine_type.lower() - if t == "sphinx": - return PocketSphinxSTT(**kwargs) - elif t == "google": - return GoogleSTT(**kwargs) +def newSTTEngine(stt_engine, **kwargs): + selected_engines = filter(lambda engine: hasattr(engine, "SLUG") and engine.SLUG == stt_engine, get_engines()) + if len(selected_engines) == 0: + raise ValueError("No STT engine found for slug '%s'" % stt_engine) else: - raise ValueError("Unsupported STT engine type: " + engine_type) + if len(selected_engines) > 1: + print("WARNING: Multiple STT engines found for slug '%s'. This is most certainly a bug." % stt_engine) + 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(**kwargs) -- cgit v1.3.1 From 8eed677c0a05e124883ab216b8fd0d6957dfb4b3 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 15:18:30 +0200 Subject: Implement is_available() in STT engines --- client/stt.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/client/stt.py b/client/stt.py index e17ccb5..a83b6c7 100644 --- a/client/stt.py +++ b/client/stt.py @@ -21,6 +21,7 @@ class AbstractSTTEngine(object): __metaclass__ = ABCMeta @classmethod + @abstractmethod def is_available(cls): return True @@ -54,18 +55,7 @@ class PocketSphinxSTT(AbstractSTTEngine): except: import pocketsphinx as ps - hmm_dir = None - - # Try to get hmm_dir from config - profile_path = os.path.join(os.path.dirname(__file__), 'profile.yml') - if os.path.exists(profile_path): - with open(profile_path, 'r') as f: - profile = yaml.safe_load(f) - if 'pocketsphinx' in profile and 'hmm_dir' in profile['pocketsphinx']: - hmm_dir = profile['pocketsphinx']['hmm_dir'] - - if not hmm_dir: - hmm_dir = "/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k" + hmm_dir = self._get_hmm_dir() with tempfile.NamedTemporaryFile(prefix='psdecoder_music_', suffix='.log', delete=False) as f: self.logfile_music = f.name @@ -85,6 +75,23 @@ class PocketSphinxSTT(AbstractSTTEngine): os.remove(self.logfile_persona) os.remove(self.logfile_default) + @classmethod + def _get_hmm_dir(cls): #FIXME: Replace this as soon as we have a config module + hmm_dir = None + + # Try to get hmm_dir from config + profile_path = os.path.join(os.path.dirname(__file__), 'profile.yml') + if os.path.exists(profile_path): + with open(profile_path, 'r') as f: + profile = yaml.safe_load(f) + if 'pocketsphinx' in profile and 'hmm_dir' in profile['pocketsphinx']: + hmm_dir = profile['pocketsphinx']['hmm_dir'] + + if not hmm_dir: + hmm_dir = "/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k" + + return hmm_dir + def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False): """ Performs STT, transcribing an audio file and returning the result. @@ -126,6 +133,10 @@ class PocketSphinxSTT(AbstractSTTEngine): return result[0] + @classmethod + def is_available(cls): + return (pkgutil.get_loader('pocketsphinx') is not None and os.path.exits(cls._get_hmm_dir())) + """ Speech-To-Text implementation which relies on the Google Speech API. @@ -195,6 +206,10 @@ class GoogleSTT(AbstractSTTEngine): except Exception: traceback.print_exc() + @classmethod + def is_available(cls): + return True + """ Returns a Speech-To-Text engine. -- cgit v1.3.1 From 78f0b5b8344dad427902b84210dc4d217c9d9205 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 15:20:25 +0200 Subject: Use with statement to open file in Google STT engine --- client/stt.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/stt.py b/client/stt.py index a83b6c7..1960a4b 100644 --- a/client/stt.py +++ b/client/stt.py @@ -186,9 +186,8 @@ class GoogleSTT(AbstractSTTEngine): url = "https://www.google.com/speech-api/v2/recognize?output=json&client=chromium&key=%s&lang=%s&maxresults=6&pfilter=2" % ( self.api_key, "en-us") - wav = open(audio_file_path, 'rb') - data = wav.read() - wav.close() + with open(audio_file_path, 'rb') as f: + data = f.read() try: headers = {'Content-type': 'audio/l16; rate=%s' % GoogleSTT.RATE} -- cgit v1.3.1 From 443d31b63f424915ac8e37546aaaaf9c30056b2b Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 15:56:44 +0200 Subject: Use TranscriptionMode enum --- client/mic.py | 7 +++++-- client/stt.py | 61 +++++++++++++++++++++++++++-------------------------------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/client/mic.py b/client/mic.py index 6c428c0..ccc322e 100644 --- a/client/mic.py +++ b/client/mic.py @@ -9,6 +9,7 @@ import audioop import pyaudio import alteration import jasperpath +from stt import TranscriptionMode class Mic: @@ -162,7 +163,7 @@ class Mic: write_frames.close() # check if PERSONA was said - transcribed = self.passive_stt_engine.transcribe(AUDIO_FILE, PERSONA_ONLY=True) + transcribed = self.passive_stt_engine.transcribe(AUDIO_FILE, mode=TranscriptionMode.KEYWORD) if PERSONA in transcribed: return (THRESHOLD, PERSONA) @@ -232,7 +233,9 @@ class Mic: write_frames.writeframes(''.join(frames)) write_frames.close() - return self.active_stt_engine.transcribe(AUDIO_FILE, MUSIC=MUSIC) + mode = TranscriptionMode.MUSIC if MUSIC else TranscriptionMode.NORMAL + + return self.active_stt_engine.transcribe(AUDIO_FILE, mode=mode) def say(self, phrase, OPTIONS=" -vdefault+m3 -p 40 -s 160 --stdout > say.wav"): # alter phrase before speaking diff --git a/client/stt.py b/client/stt.py index 1960a4b..a6532e8 100644 --- a/client/stt.py +++ b/client/stt.py @@ -13,6 +13,9 @@ import yaml The default Speech-to-Text implementation which relies on PocketSphinx. """ +class TranscriptionMode: + NORMAL, KEYWORD, MUSIC = range(3) + class AbstractSTTEngine(object): """ Generic parent class for all STT engines @@ -57,23 +60,23 @@ class PocketSphinxSTT(AbstractSTTEngine): hmm_dir = self._get_hmm_dir() + self._logfiles = {} with tempfile.NamedTemporaryFile(prefix='psdecoder_music_', suffix='.log', delete=False) as f: - self.logfile_music = f.name - with tempfile.NamedTemporaryFile(prefix='psdecoder_persona_', suffix='.log', delete=False) as f: - self.logfile_persona = f.name - with tempfile.NamedTemporaryFile(prefix='psdecoder_default_', suffix='.log', delete=False) as f: - self.logfile_default = f.name + 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_', suffix='.log', delete=False) as f: + self._logfiles[TranscriptionMode.NORMAL] = f.name + self._decoders = {} if lmd_music and dictd_music: - self.speechRec_music = ps.Decoder(hmm=hmm_dir, lm=lmd_music, dict=dictd_music, logfn=self.logfile_music) - self.speechRec_persona = ps.Decoder( - hmm=hmm_dir, lm=lmd_persona, dict=dictd_persona, logfn=self.logfile_persona) - self.speechRec = ps.Decoder(hmm=hmm_dir, lm=lmd, dict=dictd, logfn=self.logfile_default) + self._decoders[TranscriptionMode.MUSIC] = ps.Decoder(hmm=hmm_dir, lm=lmd_music, dict=dictd_music, logfn=self._logfiles[TranscriptionMode.MUSIC]) + self._decoders[TranscriptionMode.KEYWORD] = ps.Decoder(hmm=hmm_dir, lm=lmd_persona, dict=dictd_persona, logfn=self._logfiles[TranscriptionMode.KEYWORD]) + self._decoders[TranscriptionMode.NORMAL] = ps.Decoder(hmm=hmm_dir, lm=lmd, dict=dictd, logfn=self._logfiles[TranscriptionMode.NORMAL]) def __del__(self): - os.remove(self.logfile_music) - os.remove(self.logfile_persona) - os.remove(self.logfile_default) + for filename in self._logfiles.values(): + os.remove(filename) @classmethod def _get_hmm_dir(cls): #FIXME: Replace this as soon as we have a config module @@ -92,7 +95,7 @@ class PocketSphinxSTT(AbstractSTTEngine): return hmm_dir - def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False): + def transcribe(self, audio_file_path, mode=TranscriptionMode.NORMAL): """ Performs STT, transcribing an audio file and returning the result. @@ -105,26 +108,18 @@ class PocketSphinxSTT(AbstractSTTEngine): wavFile = file(audio_file_path, 'rb') wavFile.seek(44) - if MUSIC: - self.speechRec_music.decode_raw(wavFile) - result = self.speechRec_music.get_hyp() - with open(self.logfile_music, 'r+') as f: - for line in f: - self._logger.debug("speechRec_music %s", line.strip()) - f.truncate() - elif PERSONA_ONLY: - self.speechRec_persona.decode_raw(wavFile) - result = self.speechRec_persona.get_hyp() - with open(self.logfile_persona, 'r+') as f: + decoder = self._decoders[TranscriptionMode.NORMAL] + decoder.decode_raw(wavFile) + 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("speechRec_persona %s", line.strip()) - f.truncate() - else: - self.speechRec.decode_raw(wavFile) - result = self.speechRec.get_hyp() - with open(self.logfile_default, 'r+') as f: - for line in f: - self._logger.debug("speechRec_default %s", line.strip()) + self._logger.debug("%s %s", modename, line.strip()) f.truncate() print "===================" @@ -175,7 +170,7 @@ class GoogleSTT(AbstractSTTEngine): self.api_key = api_key self.http = requests.Session() - def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False): + def transcribe(self, audio_fp, mode=TranscriptionMode.NORMAL): """ Performs STT via the Google Speech API, transcribing an audio file and returning an English string. -- cgit v1.3.1 From fa3497cc9619ef54249658fdc0b2f9cfe6f2173f Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 16:03:19 +0200 Subject: Fix mode in AbstractSTTEngine --- client/stt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/stt.py b/client/stt.py index a6532e8..2157053 100644 --- a/client/stt.py +++ b/client/stt.py @@ -29,7 +29,7 @@ class AbstractSTTEngine(object): return True @abstractmethod - def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False): + def transcribe(self, audio_file_path, mode=TranscriptionMode.NORMAL): pass class PocketSphinxSTT(AbstractSTTEngine): -- cgit v1.3.1 From 619ad5ac1184b0940539bbde74c67e0799891fc4 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 16:21:30 +0200 Subject: Make pocketsphinx lm/dic pairs configurable and move config logic into classes --- client/stt.py | 53 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/client/stt.py b/client/stt.py index 2157053..af252c3 100644 --- a/client/stt.py +++ b/client/stt.py @@ -38,7 +38,8 @@ class PocketSphinxSTT(AbstractSTTEngine): def __init__(self, lmd="languagemodel.lm", dictd="dictionary.dic", lmd_persona="languagemodel_persona.lm", dictd_persona="dictionary_persona.dic", - lmd_music=None, dictd_music=None, **kwargs): #FIXME: get init args from config + lmd_music=None, dictd_music=None, + hmm_dir="/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k"): """ Initiates the pocketsphinx instance. @@ -58,8 +59,6 @@ class PocketSphinxSTT(AbstractSTTEngine): except: import pocketsphinx as ps - hmm_dir = self._get_hmm_dir() - self._logfiles = {} with tempfile.NamedTemporaryFile(prefix='psdecoder_music_', suffix='.log', delete=False) as f: self._logfiles[TranscriptionMode.MUSIC] = f.name @@ -79,21 +78,30 @@ class PocketSphinxSTT(AbstractSTTEngine): os.remove(filename) @classmethod - def _get_hmm_dir(cls): #FIXME: Replace this as soon as we have a config module - hmm_dir = None - + 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 = os.path.join(os.path.dirname(__file__), 'profile.yml') if os.path.exists(profile_path): with open(profile_path, 'r') as f: profile = yaml.safe_load(f) - if 'pocketsphinx' in profile and 'hmm_dir' in profile['pocketsphinx']: - hmm_dir = profile['pocketsphinx']['hmm_dir'] - - if not hmm_dir: - hmm_dir = "/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k" - - return hmm_dir + if 'pocketsphinx' in profile: + if 'hmm_dir' in profile['pocketsphinx']: + config['hmm_dir'] = profile['pocketsphinx']['hmm_dir'] + if 'lmd' in profile['pocketsphinx']: + config['lmd'] = profile['pocketsphinx']['lmd'] + if 'dictd' in profile['pocketsphinx']: + config['dictd'] = profile['pocketsphinx']['dictd'] + if 'lmd_persona' in profile['pocketsphinx']: + config['lmd_persona'] = profile['pocketsphinx']['lmd_persona'] + if 'dictd_persona' in profile['pocketsphinx']: + config['dictd_persona'] = profile['pocketsphinx']['dictd_persona'] + if 'lmd_music' in profile['pocketsphinx']: + config['lmd'] = profile['pocketsphinx']['lmd_music'] + if 'dictd_music' in profile['pocketsphinx']: + config['dictd_music'] = profile['pocketsphinx']['dictd_music'] + return config def transcribe(self, audio_file_path, mode=TranscriptionMode.NORMAL): """ @@ -162,14 +170,29 @@ class GoogleSTT(AbstractSTTEngine): SLUG = 'google' RATE = 16000 - def __init__(self, api_key=None, **kwargs): #FIXME: get init args from config + def __init__(self, api_key=None): #FIXME: get init args from config """ Arguments: api_key - the public api key which allows access to Google APIs """ + if not api_key: + raise ValueError("No Google API Key given") self.api_key = api_key self.http = requests.Session() + @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 = os.path.join(os.path.dirname(__file__), 'profile.yml') + if os.path.exists(profile_path): + with open(profile_path, 'r') as f: + profile = yaml.safe_load(f) + if 'keys' in profile and 'GOOGLE_SPEECH' in profile['keys']: + config['api_key'] = profile['keys']['GOOGLE_SPEECH'] + return config + def transcribe(self, audio_fp, mode=TranscriptionMode.NORMAL): """ Performs STT via the Google Speech API, transcribing an audio file and returning an English @@ -227,4 +250,4 @@ def newSTTEngine(stt_engine, **kwargs): 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(**kwargs) + return engine(engine.get_config()) -- cgit v1.3.1 From 39ae3c87f1b6d722cfd844b8c75ae427dc5523d9 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 17:02:40 +0200 Subject: Added get_config() to AbstractSTTEngine --- client/stt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/stt.py b/client/stt.py index af252c3..7ed7403 100644 --- a/client/stt.py +++ b/client/stt.py @@ -23,6 +23,10 @@ class AbstractSTTEngine(object): __metaclass__ = ABCMeta + @classmethod + def get_config(cls): + return {} + @classmethod @abstractmethod def is_available(cls): -- cgit v1.3.1 From 5f3fc0fd372050d85edf6ba4610075051d6c129a Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 18:37:41 +0200 Subject: Fixed STTEngine.get_config() kwargs --- client/stt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/stt.py b/client/stt.py index 7ed7403..29fb79c 100644 --- a/client/stt.py +++ b/client/stt.py @@ -254,4 +254,4 @@ def newSTTEngine(stt_engine, **kwargs): 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()) + return engine(**engine.get_config()) -- cgit v1.3.1 From 36a98507a56630a94fc111ba943b149ec1938a94 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 18:39:44 +0200 Subject: Use tempfile module in mic.py's listen methods --- client/mic.py | 52 ++++++++++++++++++++++++---------------------------- client/stt.py | 23 ++++++++++++++--------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/client/mic.py b/client/mic.py index ccc322e..2bb8c57 100644 --- a/client/mic.py +++ b/client/mic.py @@ -4,7 +4,8 @@ """ import os -from wave import open as open_audio +import tempfile +import wave import audioop import pyaudio import alteration @@ -81,7 +82,6 @@ class Mic: """ THRESHOLD_MULTIPLIER = 1.8 - AUDIO_FILE = "passive.wav" RATE = 16000 CHUNK = 1024 @@ -155,15 +155,17 @@ class Mic: stream.stop_stream() stream.close() audio.terminate() - write_frames = open_audio(AUDIO_FILE, 'wb') - write_frames.setnchannels(1) - write_frames.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) - write_frames.setframerate(RATE) - write_frames.writeframes(''.join(frames)) - write_frames.close() - - # check if PERSONA was said - transcribed = self.passive_stt_engine.transcribe(AUDIO_FILE, mode=TranscriptionMode.KEYWORD) + + with tempfile.NamedTemporaryFile(mode='w+b') as f: + wav_fp = wave.open(f, 'wb') + wav_fp.setnchannels(1) + wav_fp.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) + wav_fp.setframerate(RATE) + wav_fp.writeframes(''.join(frames)) + wav_fp.close() + f.seek(0) + # check if PERSONA was said + transcribed = self.passive_stt_engine.transcribe(f, mode=TranscriptionMode.KEYWORD) if PERSONA in transcribed: return (THRESHOLD, PERSONA) @@ -175,18 +177,10 @@ class Mic: Records until a second of silence or times out after 12 seconds """ - AUDIO_FILE = "active.wav" RATE = 16000 CHUNK = 1024 LISTEN_TIME = 12 - # user can request pre-recorded sound - if not LISTEN: - if not os.path.exists(AUDIO_FILE): - return None - - return self.active_stt_engine.transcribe(AUDIO_FILE) - # check if no threshold provided if THRESHOLD == None: THRESHOLD = self.fetchThreshold() @@ -226,16 +220,18 @@ class Mic: stream.stop_stream() stream.close() audio.terminate() - write_frames = open_audio(AUDIO_FILE, 'wb') - write_frames.setnchannels(1) - write_frames.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) - write_frames.setframerate(RATE) - write_frames.writeframes(''.join(frames)) - write_frames.close() - - mode = TranscriptionMode.MUSIC if MUSIC else TranscriptionMode.NORMAL - return self.active_stt_engine.transcribe(AUDIO_FILE, mode=mode) + with tempfile.SpooledTemporaryFile(mode='w+b') as f: + wav_fp = wave.open(f, 'wb') + wav_fp.setnchannels(1) + wav_fp.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) + wav_fp.setframerate(RATE) + 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 def say(self, phrase, OPTIONS=" -vdefault+m3 -p 40 -s 160 --stdout > say.wav"): # alter phrase before speaking diff --git a/client/stt.py b/client/stt.py index 29fb79c..4a46e71 100644 --- a/client/stt.py +++ b/client/stt.py @@ -33,7 +33,7 @@ class AbstractSTTEngine(object): return True @abstractmethod - def transcribe(self, audio_file_path, mode=TranscriptionMode.NORMAL): + def transcribe(self, fp, mode=TranscriptionMode.NORMAL): pass class PocketSphinxSTT(AbstractSTTEngine): @@ -107,7 +107,7 @@ class PocketSphinxSTT(AbstractSTTEngine): config['dictd_music'] = profile['pocketsphinx']['dictd_music'] return config - def transcribe(self, audio_file_path, mode=TranscriptionMode.NORMAL): + def transcribe(self, fp, mode=TranscriptionMode.NORMAL): """ Performs STT, transcribing an audio file and returning the result. @@ -116,12 +116,17 @@ class PocketSphinxSTT(AbstractSTTEngine): PERSONA_ONLY -- if True, uses the 'Persona' language model and dictionary MUSIC -- if True, uses the 'Music' language model and dictionary """ + decoder = self._decoders[mode] - wavFile = file(audio_file_path, 'rb') - wavFile.seek(44) + fp.seek(44) - decoder = self._decoders[TranscriptionMode.NORMAL] - decoder.decode_raw(wavFile) + # 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() + result = decoder.get_hyp() with open(self._logfiles[mode], 'r+') as f: if mode == TranscriptionMode.KEYWORD: @@ -197,7 +202,7 @@ class GoogleSTT(AbstractSTTEngine): config['api_key'] = profile['keys']['GOOGLE_SPEECH'] return config - def transcribe(self, audio_fp, mode=TranscriptionMode.NORMAL): + def transcribe(self, fp, mode=TranscriptionMode.NORMAL): """ Performs STT via the Google Speech API, transcribing an audio file and returning an English string. @@ -205,11 +210,11 @@ class GoogleSTT(AbstractSTTEngine): Arguments: audio_file_path -- the path to the .wav file to be transcribed """ + url = "https://www.google.com/speech-api/v2/recognize?output=json&client=chromium&key=%s&lang=%s&maxresults=6&pfilter=2" % ( self.api_key, "en-us") - with open(audio_file_path, 'rb') as f: - data = f.read() + data = fp.read() try: headers = {'Content-type': 'audio/l16; rate=%s' % GoogleSTT.RATE} -- cgit v1.3.1 From 8d2e10462a79fc34c446d837cb9af6a5d5171efd Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 15 Sep 2014 18:45:38 +0200 Subject: Read Framerate from wave file in GoogleSTT --- client/stt.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/stt.py b/client/stt.py index 4a46e71..cf32a74 100644 --- a/client/stt.py +++ b/client/stt.py @@ -2,6 +2,7 @@ # -*- coding: utf-8-*- import os import traceback +import wave import json import tempfile import logging @@ -177,7 +178,6 @@ Excerpt from sample profile.yml: class GoogleSTT(AbstractSTTEngine): SLUG = 'google' - RATE = 16000 def __init__(self, api_key=None): #FIXME: get init args from config """ @@ -211,13 +211,17 @@ class GoogleSTT(AbstractSTTEngine): audio_file_path -- the path to the .wav file to be transcribed """ + wav = wave.open(fp, 'rb') + frame_rate = wav.getframerate() + wav.close() + url = "https://www.google.com/speech-api/v2/recognize?output=json&client=chromium&key=%s&lang=%s&maxresults=6&pfilter=2" % ( self.api_key, "en-us") data = fp.read() try: - headers = {'Content-type': 'audio/l16; rate=%s' % GoogleSTT.RATE} + headers = {'Content-type': 'audio/l16; rate=%s' % frame_rate} response = self.http.post(url, data=data, headers=headers) response.encoding = 'utf-8' response_read = response.text -- cgit v1.3.1