diff --git a/__init__.py b/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/client/automated_mic.py b/client/automated_mic.py new file mode 100644 index 000000000..b1610b666 --- /dev/null +++ b/client/automated_mic.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8-*- +""" +A drop-in replacement for the Mic class that allows for input I/O to +happen via provided audio file and output via string return. This is +intended to drive automated testing. + +Unlike test_mic.py this does include both active and passive listening +in order to support testing and evaluation of wake words and related +configuration. +""" + +import logging +import pyaudio + + +class Mic: + prev = None + + def __init__(self, speaker, passive_stt_engine, active_stt_engine): + """ + Initiates the pocketsphinx instance. + + Arguments: + speaker -- handles platform-independent audio output + passive_stt_engine -- performs STT while Jasper is in passive listen + mode + acive_stt_engine -- performs STT while Jasper is in active listen mode + """ + self._logger = logging.getLogger(__name__) + self.speaker = speaker + self.passive_stt_engine = passive_stt_engine + self.active_stt_engine = active_stt_engine + self._logger.info("Initializing PyAudio. ALSA/Jack error messages " + + "that pop up during this process are normal and " + + "can usually be safely ignored.") + self._audio = pyaudio.PyAudio() + self._logger.info("Initialization of PyAudio completed.") + + def listen(self, stt_engine): + with open(self.audio_input, 'rb') as f: + return stt_engine.transcribe(f) + + def passiveListen(self, PERSONA): + # check if PERSONA was said + transcribed = self.listen(self.passive_stt_engine) + + if any(PERSONA in phrase for phrase in transcribed): + return (False, PERSONA) + + return (False, transcribed) + + def activeListenToAllOptions(self, THRESHOLD=None, LISTEN=True, + MUSIC=False): + return [self.activeListen(THRESHOLD=THRESHOLD, LISTEN=LISTEN, + MUSIC=MUSIC)] + + def activeListen(self, THRESHOLD=None, LISTEN=True, MUSIC=False): + if not LISTEN: + return self.prev + + input = self.listen(self.active_stt_engine) + self.prev = input + return input + + def say(self, phrase, OPTIONS=None): + self.text_output = phrase diff --git a/client/jasperpath.py b/client/jasperpath.py index 787e2e0ae..097e730ee 100644 --- a/client/jasperpath.py +++ b/client/jasperpath.py @@ -8,6 +8,8 @@ DATA_PATH = os.path.join(APP_PATH, "static") LIB_PATH = os.path.join(APP_PATH, "client") PLUGIN_PATH = os.path.join(LIB_PATH, "modules") +TEST_PATH = os.path.join(APP_PATH, "tests") +TEST_AUDIO_PATH = os.path.join(TEST_PATH, "audio") CONFIG_PATH = os.path.expanduser(os.getenv('JASPER_CONFIG', '~/.jasper')) @@ -18,3 +20,8 @@ def config(*fname): def data(*fname): return os.path.join(DATA_PATH, *fname) + + +# Convenience method to join a filename to a path +def join(path, *fname): + return os.path.join(path, *fname) diff --git a/jasper.py b/jasper.py index 5056e3eeb..3bf6757ca 100755 --- a/jasper.py +++ b/jasper.py @@ -18,20 +18,29 @@ # Add jasperpath.LIB_PATH to sys.path sys.path.append(jasperpath.LIB_PATH) -parser = argparse.ArgumentParser(description='Jasper Voice Control Center') -parser.add_argument('--local', action='store_true', - help='Use text input instead of a real microphone') -parser.add_argument('--no-network-check', action='store_true', - help='Disable the network connection check') -parser.add_argument('--diagnose', action='store_true', - help='Run diagnose and exit') -parser.add_argument('--debug', action='store_true', help='Show debug messages') -args = parser.parse_args() - -if args.local: - from client.local_mic import Mic +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Jasper Voice Control Center') + parser.add_argument('--local', action='store_true', + help='Use text input instead of a real microphone') + parser.add_argument('--no-network-check', action='store_true', + help='Disable the network connection check') + parser.add_argument('--automated', action='store_true', + help='Use audio input from files and text output') + parser.add_argument('--diagnose', action='store_true', + help='Run diagnose and exit') + parser.add_argument('--debug', action='store_true', + help='Show debug messages') + args = parser.parse_args() + + if args.local: + from client.local_mic import Mic + elif args.automated: + from client.automated_mic import Mic + else: + from client.mic import Mic else: - from client.mic import Mic + # Running this as a module, so assume an automated test scenario + from client.automated_mic import Mic class Jasper(object): diff --git a/tests/audio/jasper.wav b/tests/audio/jasper.wav new file mode 100644 index 000000000..f951f8256 Binary files /dev/null and b/tests/audio/jasper.wav differ diff --git a/tests/audio/meaning_of_life.wav b/tests/audio/meaning_of_life.wav new file mode 100644 index 000000000..288f7e136 Binary files /dev/null and b/tests/audio/meaning_of_life.wav differ diff --git a/tests/test_audio.py b/tests/test_audio.py new file mode 100644 index 000000000..dd889fa17 --- /dev/null +++ b/tests/test_audio.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8-*- +import unittest +from client import jasperpath +from client.modules import Life +from jasper import Jasper + +DEFAULT_PROFILE = { + 'prefers_email': False, + 'location': 'Cape Town', + 'timezone': 'US/Eastern', + 'phone_number': '012344321' +} + +WAKE_AUDIO_FILE = jasperpath.join(jasperpath.TEST_AUDIO_PATH, 'jasper.wav') + +# set input file path to mic.audio_input +# read output text from mic.text_output + + +class TestAudio(unittest.TestCase): + + def setUp(self): + self.profile = DEFAULT_PROFILE + self.send = False + self.persona = "Jasper" + self.jasper = Jasper() + self.mic = self.jasper.mic + + def testLife(self): + # Wake up + self.mic.audio_input = WAKE_AUDIO_FILE + threshold, transcribed = self.mic.passiveListen(self.persona) + print(transcribed) + + # Run the meaning of life module + self.mic.audio_input = jasperpath.join(jasperpath.TEST_AUDIO_PATH, + 'meaning_of_life.wav') + transcribed = self.mic.activeListen() + print(transcribed) + Life.handle(transcribed, self.mic, self.profile) + + print(self.mic.text_output) + self.assertTrue("42" in self.mic.text_output)