diff --git a/client/conversation.py b/client/conversation.py index 6b2fab1a0..87383d744 100644 --- a/client/conversation.py +++ b/client/conversation.py @@ -26,22 +26,18 @@ def handleForever(self): for notif in notifications: self._logger.info("Received notification: '%s'", str(notif)) - self._logger.debug("Started listening for keyword '%s'", - self.persona) + self._logger.debug("Started listening for keyword '%s'", self.persona) threshold, transcribed = self.mic.passiveListen(self.persona) - self._logger.debug("Stopped listening for keyword '%s'", - self.persona) + self._logger.debug("Stopped listening for keyword '%s'", self.persona) if not transcribed or not threshold: self._logger.info("Nothing has been said or transcribed.") continue - self._logger.info("Keyword '%s' has been said!", self.persona) - self._logger.debug("Started to listen actively with threshold: %r", - threshold) + self._logger.info("Keyword '%s' has been said!", self.persona) + self._logger.debug("Started to listen actively with threshold: %r", threshold) input = self.mic.activeListenToAllOptions(threshold) - self._logger.debug("Stopped to listen actively with threshold: %r", - threshold) + self._logger.debug("Stopped to listen actively with threshold: %r", threshold) if input: self.brain.query(input) diff --git a/client/modules/Unclear.py b/client/modules/Unclear.py index 071eea384..04c700d5b 100644 --- a/client/modules/Unclear.py +++ b/client/modules/Unclear.py @@ -18,9 +18,12 @@ def handle(text, mic, profile): number) """ - messages = ["I'm sorry, could you repeat that?", - "My apologies, could you try saying that again?", - "Say that again?", "I beg your pardon?"] + messages = [ + "I'm sorry, could you repeat that?", + "My apologies, could you try saying that again?", + "Say that again?", + "I beg your pardon?" + ] message = random.choice(messages) diff --git a/client/stt.py b/client/stt.py index a48696099..c18fc1418 100644 --- a/client/stt.py +++ b/client/stt.py @@ -427,6 +427,143 @@ def is_available(cls): return diagnose.check_network_connection() +class WatsonSTT(AbstractSTTEngine): + """ + Speech-To-Text implementation which relies on the IBM Watson Speech-To-Text + API. This requires an IBM Bluemix account, but the first 1000 minutes of + transcribing per month are free. + + To obtain a login: + 1. Register for IBM Bluemix here: + https://console.ng.bluemix.net/registration/ + 2. Once you've logged in, click the "Use Services & APIs" link on the + dashboard + 3. Click the "Speech To Text" icon + 4. In the form on the right, leave all options as defaults and click Create + 5. You'll now have a new service listed on your dashboard. If you click + that service there will be a navigation option for "Service Credentials" + in the left hand nav. Find your username and password there. + + Excerpt from sample profile.yml: + + ... + timezone: US/Pacific + stt_engine: watson + watson: + username: $YOUR_USERNAME_HERE + password: $YOUR_PASSWORD_HERE + + """ + + SLUG = 'watson' + + def __init__(self, username=None, password=None, language='en-us'): + # FIXME: get init args from config + """ + Arguments: + username - the watson api username credential + password - the watson api password credential + """ + self._logger = logging.getLogger(__name__) + self._username = None + self._password = None + self._http = requests.Session() + self.username = username + self.password = password + + @property + def request_url(self): + return self._request_url + + @property + def username(self): + return self._username + + @username.setter + def username(self, value): + self._username = value + + @property + def password(self): + return self._password + + @password.setter + def password(self, value): + self._password = value + + @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) + if 'watson' in profile: + if 'username' in profile['watson']: + config['username'] = profile['watson']['username'] + if 'password' in profile['watson']: + config['password'] = profile['watson']['password'] + return config + + def transcribe(self, fp): + """ + Performs STT via the Watson Speech-to-Text API, transcribing an audio + file and returning an English string. + + Arguments: + fp -- the path to the .wav file to be transcribed + """ + + if not self.username: + self._logger.critical('Username missing, transcription request aborted.') + return [] + elif not self.password: + self._logger.critical('Password missing, transcription request aborted.') + return [] + + wav = wave.open(fp, 'rb') + frame_rate = wav.getframerate() + wav.close() + data = fp.read() + + headers = { + 'content-type': 'audio/l16; rate={0}; channels=1'.format(frame_rate) + } + r = self._http.post( + 'https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?continuous=true', + data=data, headers=headers, auth=(self.username, self.password) + ) + try: + r.raise_for_status() + except requests.exceptions.HTTPError as e: + self._logger.critical('Request failed with http status %d', + r.status_code) + if r.status_code == requests.codes['forbidden']: + self._logger.warning('Status 403 is probably caused by invalid credentials.') + return [] + r.encoding = 'utf-8' + results = [] + try: + response = r.json() + if not response['results']: + raise ValueError('Nothing has been transcribed.') + results = tuple([alt['transcript'].strip().upper() for alt in response['results'][0]['alternatives']]) + self._logger.info('Transcribed: %r', results) + except ValueError as e: + self._logger.critical('Empty response: %s', e.args[0]) + except (KeyError, IndexError): + self._logger.critical('Cannot parse response.', exc_info=True) + + return results + + @classmethod + def is_available(cls): + return diagnose.check_network_connection() + + class AttSTT(AbstractSTTEngine): """ Speech-To-Text implementation which relies on the AT&T Speech API. @@ -587,7 +724,7 @@ def headers(self): def transcribe(self, fp): data = fp.read() - r = requests.post('https://api.wit.ai/speech?v=20150101', + r = requests.post('https://api.wit.ai/speech?v=20160526', data=data, headers=self.headers) try: diff --git a/jasper.py b/jasper.py index 5056e3eeb..b34f2ecba 100755 --- a/jasper.py +++ b/jasper.py @@ -111,14 +111,13 @@ def __init__(self): def run(self): if 'first_name' in self.config: - salutation = ("How can I be of service, %s?" - % self.config["first_name"]) + salutation = "Hey, {0}. Glad to see you. How can I help you?".format(self.config["first_name"]) else: - salutation = "How can I be of service?" + salutation = "How can I help you?" self.mic.say(salutation) - conversation = Conversation("JASPER", self.mic, self.config) - conversation.handleForever() + Conversation(self.config.get('persona', 'jasper').upper(), + self.mic, self.config).handleForever() if __name__ == "__main__": diff --git a/jessy-notes.md b/jessy-notes.md new file mode 100644 index 000000000..3fcd9a9aa --- /dev/null +++ b/jessy-notes.md @@ -0,0 +1,34 @@ +# Updated URLs + +OpenFST: +https://github.com/AdolfVonKleist/Phonetisaurus/tree/openfst-1.5.3 + +MITLM: +https://github.com/mitlm/mitlm + +sudo wget https://m2m-aligner.googlecode.com/files/m2m-aligner-1.2.tar.gz +sudo wget https://www.dropbox.com/s/154q9yt3xenj2gr/phonetisaurus-0.8a.tgz +sudo wget https://phonetisaurus.googlecode.com/files/is2013-conversion.tgz + + +Installing stuff: +----------------------------------------- +1. sudo su -c "echo 'deb http://cognomen.co.uk/apt/debian jessie main' > /etc/apt/sources.list.d/cognomen.list" + +2. gpg --keyserver keyserver.ubuntu.com --recv FC88E181D61C9391C4A49682CF36B219807AA92B && gpg --export --armor keymaster@cognomen.co.uk | sudo apt-key add - + +3. sudo apt-get update + +4. sudo apt-get install phonetisaurus m2m-aligner mitlm libfst-tools libfst1-plugins-base libfst-dev + +5. sudo apt-get install pocketsphinx-hmm-en-hub4wsj + + + +Configure: .jasper/profile.yml: + +stt_passive_engine: sphinx +pocketsphinx: + hmm_dir: /usr/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k + +-------------------------- diff --git a/static/audio/rd2d/begin/R2D2-do.wav b/static/audio/rd2d/begin/R2D2-do.wav new file mode 100644 index 000000000..2fdee2784 Binary files /dev/null and b/static/audio/rd2d/begin/R2D2-do.wav differ diff --git a/static/audio/rd2d/begin/qr2_d2w2.wav b/static/audio/rd2d/begin/qr2_d2w2.wav new file mode 100644 index 000000000..605e864dc Binary files /dev/null and b/static/audio/rd2d/begin/qr2_d2w2.wav differ diff --git a/static/audio/rd2d/begin/qsntnc1.wav b/static/audio/rd2d/begin/qsntnc1.wav new file mode 100644 index 000000000..8a4997d30 Binary files /dev/null and b/static/audio/rd2d/begin/qsntnc1.wav differ diff --git a/static/audio/rd2d/begin/qword1.wav b/static/audio/rd2d/begin/qword1.wav new file mode 100644 index 000000000..8b6ecc287 Binary files /dev/null and b/static/audio/rd2d/begin/qword1.wav differ diff --git a/static/audio/rd2d/begin/qword4.wav b/static/audio/rd2d/begin/qword4.wav new file mode 100644 index 000000000..26fe7b793 Binary files /dev/null and b/static/audio/rd2d/begin/qword4.wav differ diff --git a/static/audio/rd2d/end/qr2_d2w1.wav b/static/audio/rd2d/end/qr2_d2w1.wav new file mode 100644 index 000000000..1ad9b4c66 Binary files /dev/null and b/static/audio/rd2d/end/qr2_d2w1.wav differ diff --git a/static/audio/rd2d/end/qr2_d2w3.wav b/static/audio/rd2d/end/qr2_d2w3.wav new file mode 100644 index 000000000..643ff734a Binary files /dev/null and b/static/audio/rd2d/end/qr2_d2w3.wav differ diff --git a/static/audio/rd2d/end/qsntnc10.wav b/static/audio/rd2d/end/qsntnc10.wav new file mode 100644 index 000000000..82bde35b7 Binary files /dev/null and b/static/audio/rd2d/end/qsntnc10.wav differ diff --git a/static/audio/rd2d/end/qsntnc18.wav b/static/audio/rd2d/end/qsntnc18.wav new file mode 100644 index 000000000..b821e8530 Binary files /dev/null and b/static/audio/rd2d/end/qsntnc18.wav differ diff --git a/static/audio/rd2d/end/qsntnc6.wav b/static/audio/rd2d/end/qsntnc6.wav new file mode 100644 index 000000000..dc0ca795b Binary files /dev/null and b/static/audio/rd2d/end/qsntnc6.wav differ diff --git a/static/audio/rd2d/end/qword16.wav b/static/audio/rd2d/end/qword16.wav new file mode 100644 index 000000000..f487f9f99 Binary files /dev/null and b/static/audio/rd2d/end/qword16.wav differ diff --git a/static/audio/rd2d/end/qword8.wav b/static/audio/rd2d/end/qword8.wav new file mode 100644 index 000000000..3f4e87c95 Binary files /dev/null and b/static/audio/rd2d/end/qword8.wav differ diff --git a/static/audio/rd2d/end/qword9.wav b/static/audio/rd2d/end/qword9.wav new file mode 100644 index 000000000..c755b4cea Binary files /dev/null and b/static/audio/rd2d/end/qword9.wav differ diff --git a/static/audio/rd2d/error/qsntnc13.wav b/static/audio/rd2d/error/qsntnc13.wav new file mode 100644 index 000000000..d5ba63f42 Binary files /dev/null and b/static/audio/rd2d/error/qsntnc13.wav differ diff --git a/static/audio/rd2d/error/qsntnc20.wav b/static/audio/rd2d/error/qsntnc20.wav new file mode 100644 index 000000000..42193f60b Binary files /dev/null and b/static/audio/rd2d/error/qsntnc20.wav differ diff --git a/static/audio/rd2d/error/qsntnc4.wav b/static/audio/rd2d/error/qsntnc4.wav new file mode 100644 index 000000000..9d85b6a5e Binary files /dev/null and b/static/audio/rd2d/error/qsntnc4.wav differ diff --git a/static/audio/rd2d/error/qsntnc7.wav b/static/audio/rd2d/error/qsntnc7.wav new file mode 100644 index 000000000..50645513d Binary files /dev/null and b/static/audio/rd2d/error/qsntnc7.wav differ diff --git a/static/audio/rd2d/error/qsntnc9.wav b/static/audio/rd2d/error/qsntnc9.wav new file mode 100644 index 000000000..a845fb028 Binary files /dev/null and b/static/audio/rd2d/error/qsntnc9.wav differ diff --git a/static/audio/rd2d/idle/R2D2-yeah.wav b/static/audio/rd2d/idle/R2D2-yeah.wav new file mode 100644 index 000000000..8d78f4757 Binary files /dev/null and b/static/audio/rd2d/idle/R2D2-yeah.wav differ diff --git a/static/audio/rd2d/idle/qr2_d2s1.wav b/static/audio/rd2d/idle/qr2_d2s1.wav new file mode 100644 index 000000000..28f916c96 Binary files /dev/null and b/static/audio/rd2d/idle/qr2_d2s1.wav differ diff --git a/static/audio/rd2d/idle/qr2_d2s2.wav b/static/audio/rd2d/idle/qr2_d2s2.wav new file mode 100644 index 000000000..ec27b312a Binary files /dev/null and b/static/audio/rd2d/idle/qr2_d2s2.wav differ diff --git a/static/audio/rd2d/idle/qr2_d2s3.wav b/static/audio/rd2d/idle/qr2_d2s3.wav new file mode 100644 index 000000000..eb38049dd Binary files /dev/null and b/static/audio/rd2d/idle/qr2_d2s3.wav differ diff --git a/static/audio/rd2d/idle/qscaning.wav b/static/audio/rd2d/idle/qscaning.wav new file mode 100644 index 000000000..bf9341c24 Binary files /dev/null and b/static/audio/rd2d/idle/qscaning.wav differ diff --git a/static/audio/rd2d/idle/qsntnc16.wav b/static/audio/rd2d/idle/qsntnc16.wav new file mode 100644 index 000000000..e2d559e9e Binary files /dev/null and b/static/audio/rd2d/idle/qsntnc16.wav differ diff --git a/static/audio/rd2d/idle/qsntnc2.wav b/static/audio/rd2d/idle/qsntnc2.wav new file mode 100644 index 000000000..758f8d6ca Binary files /dev/null and b/static/audio/rd2d/idle/qsntnc2.wav differ diff --git a/static/audio/rd2d/idle/qsntnc8.wav b/static/audio/rd2d/idle/qsntnc8.wav new file mode 100644 index 000000000..852109a2f Binary files /dev/null and b/static/audio/rd2d/idle/qsntnc8.wav differ diff --git a/static/audio/rd2d/idle/qword22.wav b/static/audio/rd2d/idle/qword22.wav new file mode 100644 index 000000000..f21a1e418 Binary files /dev/null and b/static/audio/rd2d/idle/qword22.wav differ