From 2b1e8411ab5eaa7e1349b78d03270ab02ae2950d Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 6 Oct 2014 16:52:53 +0200 Subject: Fix flake8 style nits --- client/app_utils.py | 30 ++++++----- client/brain.py | 41 +++++++++------ client/conversation.py | 22 +++++--- client/diagnose.py | 24 +++++---- client/g2p.py | 9 ++-- client/jasperpath.py | 3 +- client/local_mic.py | 6 ++- client/mic.py | 52 ++++++++++-------- client/modules/Birthday.py | 18 ++++--- client/modules/Gmail.py | 7 +-- client/modules/HN.py | 28 ++++++---- client/modules/Joke.py | 5 +- client/modules/Life.py | 3 +- client/modules/MPDControl.py | 73 ++++++++++++++----------- client/modules/News.py | 24 ++++++--- client/modules/Notifications.py | 14 ++--- client/modules/Time.py | 3 +- client/modules/Unclear.py | 3 +- client/modules/Weather.py | 37 +++++++------ client/notifier.py | 7 ++- client/populate.py | 53 ++++++++++++------- client/stt.py | 104 ++++++++++++++++++++++++------------ client/test.py | 34 ++++++++---- client/test_mic.py | 6 ++- client/tts.py | 114 ++++++++++++++++++++++++++++------------ client/vocabcompiler.py | 7 +-- jasper.py | 57 +++++++++++++------- 27 files changed, 501 insertions(+), 283 deletions(-) diff --git a/client/app_utils.py b/client/app_utils.py index cea6881..edc8467 100644 --- a/client/app_utils.py +++ b/client/app_utils.py @@ -3,7 +3,6 @@ import smtplib from email.MIMEText import MIMEText import urllib2 import re -import requests from pytz import timezone @@ -31,15 +30,18 @@ def sendEmail(SUBJECT, BODY, TO, FROM, SENDER, PASSWORD, SMTP_SERVER): def emailUser(profile, SUBJECT="", BODY=""): """ - Sends an email. + sends an email. - Arguments: - profile -- contains information related to the user (e.g., email address) + Arguments: + profile -- contains information related to the user (e.g., email + address) SUBJECT -- subject line of the email BODY -- body text of the email """ def generateSMSEmail(profile): - """Generates an email from a user's phone number based on their carrier.""" + """ + Generates an email from a user's phone number based on their carrier. + """ if profile['carrier'] is None or not profile['phone_number']: return None @@ -81,10 +83,11 @@ def emailUser(profile, SUBJECT="", BODY=""): def getTimezone(profile): """ - Returns the pytz timezone for a given profile. + Returns the pytz timezone for a given profile. - Arguments: - profile -- contains information related to the user (e.g., email address) + Arguments: + profile -- contains information related to the user (e.g., email + address) """ try: return timezone(profile['timezone']) @@ -94,9 +97,9 @@ def getTimezone(profile): def generateTinyURL(URL): """ - Generates a compressed URL. + Generates a compressed URL. - Arguments: + Arguments: URL -- the original URL to-be compressed """ target = "http://tinyurl.com/api-create.php?url=" + URL @@ -106,12 +109,13 @@ def generateTinyURL(URL): def isNegative(phrase): """ - Returns True if the input phrase has a negative sentiment. + Returns True if the input phrase has a negative sentiment. - Arguments: + Arguments: phrase -- the input phrase to-be evaluated """ - return bool(re.search(r'\b(no(t)?|don\'t|stop|end)\b', phrase, re.IGNORECASE)) + return bool(re.search(r'\b(no(t)?|don\'t|stop|end)\b', phrase, + re.IGNORECASE)) def isPositive(phrase): diff --git a/client/brain.py b/client/brain.py index d905853..593ca39 100644 --- a/client/brain.py +++ b/client/brain.py @@ -1,10 +1,9 @@ # -*- coding: utf-8-*- import logging -import os import pkgutil -import importlib import jasperpath + class Brain(object): def __init__(self, mic, profile): @@ -16,7 +15,8 @@ class Brain(object): Arguments: mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ self.mic = mic @@ -33,22 +33,27 @@ class Brain(object): """ logger = logging.getLogger(__name__) - module_locations = [jasperpath.PLUGIN_PATH] - logger.debug("Looking for modules in: %s", ', '.join(["'%s'" % location for location in module_locations])) + locations = [jasperpath.PLUGIN_PATH] + logger.debug("Looking for modules in: %s", + ', '.join(["'%s'" % location for location in locations])) modules = [] - for finder, name, ispkg in pkgutil.walk_packages(module_locations): + for finder, name, ispkg in pkgutil.walk_packages(locations): try: loader = finder.find_module(name) mod = loader.load_module(name) except: - logger.warning("Skipped module '%s' due to an error.", name, exc_info=True) + logger.warning("Skipped module '%s' due to an error.", name, + exc_info=True) else: if hasattr(mod, 'WORDS'): - logger.debug("Found module '%s' with words: %r", name, mod.WORDS) + logger.debug("Found module '%s' with words: %r", name, + mod.WORDS) modules.append(mod) else: - logger.warning("Skipped module '%s' because it misses the WORDS constant.", name) - modules.sort(key=lambda mod: mod.PRIORITY if hasattr(mod, 'PRIORITY') else 0, reverse=True) + logger.warning("Skipped module '%s' because it misses " + + "the WORDS constant.", name) + modules.sort(key=lambda mod: mod.PRIORITY if hasattr(mod, 'PRIORITY') + else 0, reverse=True) return modules def query(self, texts): @@ -62,14 +67,20 @@ class Brain(object): for module in self.modules: for text in texts: if module.isValid(text): - self._logger.debug("'%s' is a valid phrase for module '%s'", text, module.__name__) + self._logger.debug("'%s' is a valid phrase for module " + + "'%s'", text, module.__name__) try: module.handle(text, self.mic, self.profile) except: - self._logger.error('Failed to execute module', exc_info=True) - self.mic.say("I'm sorry. I had some trouble with that operation. Please try again later.") + self._logger.error('Failed to execute module', + exc_info=True) + self.mic.say("I'm sorry. I had some trouble with " + + "that operation. Please try again later.") else: - self._logger.debug("Handling of phrase '%s' by module '%s' completed", text, module.__name__) + self._logger.debug("Handling of phrase '%s' by " + + "module '%s' completed", text, + module.__name__) finally: return - self._logger.debug("No module was able to handle any of these phrases: %r", texts) + self._logger.debug("No module was able to handle any of these " + + "phrases: %r", texts) diff --git a/client/conversation.py b/client/conversation.py index 9b86295..6b2fab1 100644 --- a/client/conversation.py +++ b/client/conversation.py @@ -3,6 +3,7 @@ import logging from notifier import Notifier from brain import Brain + class Conversation(object): def __init__(self, persona, mic, profile): @@ -14,27 +15,34 @@ class Conversation(object): self.notifier = Notifier(profile) def handleForever(self): - """Delegates user input to the handling function when activated.""" - self._logger.info("Starting to handle conversation with keyword '%s'.", self.persona) + """ + Delegates user input to the handling function when activated. + """ + self._logger.info("Starting to handle conversation with keyword '%s'.", + self.persona) while True: # Print notifications until empty notifications = self.notifier.getAllNotifications() 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.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) else: diff --git a/client/diagnose.py b/client/diagnose.py index 9e2c68f..fb19294 100755 --- a/client/diagnose.py +++ b/client/diagnose.py @@ -8,11 +8,11 @@ import subprocess import logging import sys from distutils.spawn import find_executable -from pip.req import parse_requirements import pip.util logger = logging.getLogger(__name__) + class Diagnostics: """ @@ -39,7 +39,8 @@ class Diagnostics: @classmethod def check_phonetisaurus_dictionary_file(cls): - return os.path.isfile(os.path.join(jasperpath.APP_PATH, "..", "phonetisaurus/g014b2b.fst")) + return os.path.isfile(os.path.join(jasperpath.APP_PATH, "..", + "phonetisaurus/g014b2b.fst")) @classmethod def check_phonetisaurus_program(cls): @@ -60,10 +61,13 @@ class Diagnostics: @classmethod def check_all_pip_requirements_installed(cls): distributions = pip.util.get_installed_distributions() - requirements_lines = [line.strip() for line in open('requirements.txt').readlines()] - requirements = [ name.split('==')[0] for name in list(filter(None, requirements_lines))] - installed_packages = [ pkg.project_name for pkg in list(distributions)] - missing_packages = [ pkg for pkg in requirements if pkg not in installed_packages ] + requirements_lines = [line.strip() for line in + open('requirements.txt').readlines()] + requirements = [name.split('==')[0] for name in + list(filter(None, requirements_lines))] + installed_packages = [pkg.project_name for pkg in list(distributions)] + missing_packages = [pkg for pkg in requirements + if pkg not in installed_packages] if missing_packages: logger.info("Missing packages: "+', '.join(missing_packages)) return False @@ -102,9 +106,11 @@ class DiagnosticRunner: def select_methods(self, prefix): def is_match(method_name): - return callable(getattr(self.diagnostics, method_name)) and re.match(r"\A" + prefix + "_", method_name) + return (callable(getattr(self.diagnostics, method_name)) and + re.match(r"\A" + prefix + "_", method_name)) - return [method_name for method_name in dir(self.diagnostics) if is_match(method_name)] + return [method_name for method_name in dir(self.diagnostics) + if is_match(method_name)] def initialize_log(self): logger.info("Starting jasper diagnostic at %s" % time.strftime("%c")) @@ -131,5 +137,3 @@ if __name__ == '__main__': logging.basicConfig(stream=sys.stdout, level=logging.INFO) DiagnosticRunner(Diagnostics).run() - - diff --git a/client/g2p.py b/client/g2p.py index 6e308ef..4e64a4a 100644 --- a/client/g2p.py +++ b/client/g2p.py @@ -16,11 +16,13 @@ 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 'fst_model' in profile['pocketsphinx']: + if ('pocketsphinx' in profile and + 'fst_model' in profile['pocketsphinx']): FST_MODEL = profile['pocketsphinx']['fst_model'] if not FST_MODEL: - FST_MODEL = os.path.join(jasperpath.APP_PATH, os.pardir, 'phonetisaurus', 'g014b2b.fst') + FST_MODEL = os.path.join(jasperpath.APP_PATH, os.pardir, 'phonetisaurus', + 'g014b2b.fst') def parseLine(line): @@ -52,7 +54,8 @@ def translateWords(words): def translateFile(input_filename, output_filename=None): out = subprocess.check_output( - ['phonetisaurus-g2p', '--model=%s' % FST_MODEL, '--input=%s' % input_filename, '--words', '--isfile']) + ['phonetisaurus-g2p', '--model=%s' % FST_MODEL, + '--input=%s' % input_filename, '--words', '--isfile']) out = parseOutput(out) if output_filename: diff --git a/client/jasperpath.py b/client/jasperpath.py index 53534ff..787e2e0 100644 --- a/client/jasperpath.py +++ b/client/jasperpath.py @@ -2,7 +2,8 @@ import os # Jasper main directory -APP_PATH = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)) +APP_PATH = os.path.normpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), os.pardir)) DATA_PATH = os.path.join(APP_PATH, "static") LIB_PATH = os.path.join(APP_PATH, "client") diff --git a/client/local_mic.py b/client/local_mic.py index 3707439..a58370d 100644 --- a/client/local_mic.py +++ b/client/local_mic.py @@ -15,8 +15,10 @@ class Mic: def passiveListen(self, PERSONA): return True, "JASPER" - def activeListenToAllOptions(self, THRESHOLD=None, LISTEN=True, MUSIC=False): - return [self.activeListen(THRESHOLD=THRESHOLD, LISTEN=LISTEN, MUSIC=MUSIC)] + 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: diff --git a/client/mic.py b/client/mic.py index af5919c..ae5642e 100644 --- a/client/mic.py +++ b/client/mic.py @@ -3,7 +3,6 @@ The Mic class handles all interactions with the microphone and speaker. """ -import os import tempfile import wave import audioop @@ -12,6 +11,7 @@ import alteration import jasperpath from stt import TranscriptionMode + class Mic: speechRec = None @@ -23,7 +23,8 @@ class Mic: Arguments: speaker -- handles platform-independent audio output - passive_stt_engine -- performs STT while Jasper is in passive listen mode + 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.speaker = speaker @@ -51,10 +52,10 @@ class Mic: # prepare recording stream stream = self._audio.open(format=pyaudio.paInt16, - channels=1, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) + channels=1, + rate=RATE, + input=True, + frames_per_buffer=CHUNK) # stores the audio data frames = [] @@ -83,8 +84,8 @@ class Mic: def passiveListen(self, PERSONA): """ - Listens for PERSONA in everyday sound. Times out after LISTEN_TIME, so needs to be - restarted. + Listens for PERSONA in everyday sound. Times out after LISTEN_TIME, so + needs to be restarted. """ THRESHOLD_MULTIPLIER = 1.8 @@ -99,10 +100,10 @@ class Mic: # prepare recording stream stream = self._audio.open(format=pyaudio.paInt16, - channels=1, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) + channels=1, + rate=RATE, + input=True, + frames_per_buffer=CHUNK) # stores the audio data frames = [] @@ -161,7 +162,7 @@ class Mic: # save the audio data stream.stop_stream() stream.close() - + with tempfile.NamedTemporaryFile(mode='w+b') as f: wav_fp = wave.open(f, 'wb') wav_fp.setnchannels(1) @@ -171,7 +172,8 @@ 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, mode=TranscriptionMode.KEYWORD) if PERSONA in transcribed: return (THRESHOLD, PERSONA) @@ -189,7 +191,8 @@ class Mic: if options: return options[0] - def activeListenToAllOptions(self, THRESHOLD=None, LISTEN=True, MUSIC=False): + def activeListenToAllOptions(self, THRESHOLD=None, LISTEN=True, + MUSIC=False): """ Records until a second of silence or times out after 12 seconds @@ -201,20 +204,21 @@ class Mic: LISTEN_TIME = 12 # check if no threshold provided - if THRESHOLD == None: + if THRESHOLD is None: THRESHOLD = self.fetchThreshold() self.speaker.play(jasperpath.data('audio', 'beep_hi.wav')) # prepare recording stream stream = self._audio.open(format=pyaudio.paInt16, - channels=1, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) + channels=1, + rate=RATE, + input=True, + frames_per_buffer=CHUNK) frames = [] - # increasing the range # results in longer pause after command generation + # increasing the range # results in longer pause after command + # generation lastN = [THRESHOLD * 1.2 for i in range(30)] for i in range(0, RATE / CHUNK * LISTEN_TIME): @@ -246,11 +250,13 @@ class Mic: wav_fp.writeframes(''.join(frames)) wav_fp.close() f.seek(0) - mode = TranscriptionMode.MUSIC if MUSIC else TranscriptionMode.NORMAL + 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"): + def say(self, phrase, + OPTIONS=" -vdefault+m3 -p 40 -s 160 --stdout > say.wav"): # alter phrase before speaking phrase = alteration.clean(phrase) self.speaker.say(phrase) diff --git a/client/modules/Birthday.py b/client/modules/Birthday.py index b484a4f..200af94 100644 --- a/client/modules/Birthday.py +++ b/client/modules/Birthday.py @@ -1,7 +1,7 @@ # -*- coding: utf-8-*- import datetime import re -from facebook import * +import facebook from app_utils import getTimezone WORDS = ["BIRTHDAY"] @@ -15,18 +15,20 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ oauth_access_token = profile['keys']["FB_TOKEN"] - graph = GraphAPI(oauth_access_token) + graph = facebook.GraphAPI(oauth_access_token) try: - results = graph.request( - "me/friends", args={'fields': 'id,name,birthday'}) - except GraphAPIError: - mic.say( - "I have not been authorized to query your Facebook. If you would like to check birthdays in the future, please visit the Jasper dashboard.") + results = graph.request("me/friends", + args={'fields': 'id,name,birthday'}) + except facebook.GraphAPIError: + mic.say("I have not been authorized to query your Facebook. If you " + + "would like to check birthdays in the future, please visit " + + "the Jasper dashboard.") return except: mic.say( diff --git a/client/modules/Gmail.py b/client/modules/Gmail.py index 001cf9c..83ed3bd 100644 --- a/client/modules/Gmail.py +++ b/client/modules/Gmail.py @@ -3,7 +3,6 @@ import imaplib import email import re from dateutil import parser -from app_utils import * WORDS = ["EMAIL", "INBOX"] @@ -51,7 +50,8 @@ def fetchUnreadEmails(profile, since=None, markRead=False, limit=None): Fetches a list of unread email objects from a user's Gmail inbox. Arguments: - profile -- contains information related to the user (e.g., Gmail address) + profile -- contains information related to the user (e.g., Gmail + address) since -- if provided, no emails before this date will be returned markRead -- if True, marks all returned emails as read in target inbox @@ -93,7 +93,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., Gmail address) + profile -- contains information related to the user (e.g., Gmail + address) """ try: msgs = fetchUnreadEmails(profile, limit=5) diff --git a/client/modules/HN.py b/client/modules/HN.py index ec7ecef..d205829 100644 --- a/client/modules/HN.py +++ b/client/modules/HN.py @@ -51,7 +51,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ mic.say("Pulling up some stories.") stories = getTopStories(maxResults=3) @@ -93,17 +94,24 @@ def handle(text, mic, profile): if profile['prefers_email']: body += article_link else: - if not app_utils.emailUser(profile, SUBJECT="", BODY=article_link): - mic.say( - "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.") + if not app_utils.emailUser(profile, SUBJECT="", + BODY=article_link): + mic.say("I'm having trouble sending you these " + + "articles. Please make sure that your " + + "phone number and carrier are correct " + + "on the dashboard.") return # if prefers email, we send once, at the end if profile['prefers_email']: body += "" - if not app_utils.emailUser(profile, SUBJECT="From the Front Page of Hacker News", BODY=body): - mic.say( - "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.") + if not app_utils.emailUser(profile, + SUBJECT="From the Front Page of " + + "Hacker News", + BODY=body): + mic.say("I'm having trouble sending you these articles. " + + "Please make sure that your phone number and " + + "carrier are correct on the dashboard.") return mic.say("All done.") @@ -113,12 +121,12 @@ def handle(text, mic, profile): if not profile['prefers_email'] and profile['phone_number']: mic.say("Here are some front-page articles. " + - all_titles + ". Would you like me to send you these? If so, which?") + all_titles + ". Would you like me to send you these? " + + "If so, which?") handleResponse(mic.activeListen()) else: - mic.say( - "Here are some front-page articles. " + all_titles) + mic.say("Here are some front-page articles. " + all_titles) def isValid(text): diff --git a/client/modules/Joke.py b/client/modules/Joke.py index c560f4e..7192b03 100644 --- a/client/modules/Joke.py +++ b/client/modules/Joke.py @@ -6,7 +6,7 @@ import jasperpath WORDS = ["JOKE", "KNOCK KNOCK"] -def getRandomJoke(filename=jasperpath.data('text','JOKES.txt')): +def getRandomJoke(filename=jasperpath.data('text', 'JOKES.txt')): jokeFile = open(filename, "r") jokes = [] start = "" @@ -38,7 +38,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ joke = getRandomJoke() diff --git a/client/modules/Life.py b/client/modules/Life.py index 4cafd75..658e5cf 100644 --- a/client/modules/Life.py +++ b/client/modules/Life.py @@ -13,7 +13,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ messages = ["It's 42, you idiot.", "It's 42. How many times do I have to tell you?"] diff --git a/client/modules/MPDControl.py b/client/modules/MPDControl.py index 6e8e438..eefdcbd 100644 --- a/client/modules/MPDControl.py +++ b/client/modules/MPDControl.py @@ -11,14 +11,16 @@ from mic import Mic # Standard module stuff WORDS = ["MUSIC", "SPOTIFY"] + def handle(text, mic, profile): """ - Responds to user-input, typically speech text, by telling a joke. + Responds to user-input, typically speech text, by telling a joke. - Arguments: + Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ logger = logging.getLogger(__name__) @@ -34,7 +36,8 @@ def handle(text, mic, profile): mpdwrapper = MPDWrapper(**kwargs) except: logger.error("Couldn't connect to MPD server", exc_info=True) - mic.say("I'm sorry. It seems that Spotify is not enabled. Please read the documentation to learn how to configure Spotify.") + mic.say("I'm sorry. It seems that Spotify is not enabled. Please " + + "read the documentation to learn how to configure Spotify.") return mic.say("Please give me a moment, I'm loading your Spotify playlists.") @@ -49,6 +52,7 @@ def handle(text, mic, profile): return + def isValid(text): """ Returns True if the input is related to jokes/humor. @@ -58,6 +62,7 @@ def isValid(text): """ return any(word in text.upper() for word in WORDS) + # The interesting part class MusicMode(object): @@ -68,9 +73,9 @@ class MusicMode(object): self.music = mpdwrapper # index spotify playlists into new dictionary and language models - original = self.music.get_soup_playlist( - ) + ["STOP", "CLOSE", "PLAY", "PAUSE", - "NEXT", "PREVIOUS", "LOUDER", "SOFTER", "LOWER", "HIGHER", "VOLUME", "PLAYLIST"] + original = ["STOP", "CLOSE", "PLAY", "PAUSE", "NEXT", "PREVIOUS", + "LOUDER", "SOFTER", "LOWER", "HIGHER", "VOLUME", + "PLAYLIST"] + self.music.get_soup_playlist() pronounced = g2p.translateWords(original) zipped = zip(original, pronounced) lines = ["%s %s" % (x, y) for x, y in zipped] @@ -84,16 +89,17 @@ class MusicMode(object): f.close() # make language model - os.system( - "text2idngram -vocab sentences_spotify.txt < sentences_spotify.txt -idngram spotify.idngram") - os.system( - "idngram2lm -idngram spotify.idngram -vocab sentences_spotify.txt -arpa languagemodel_spotify.lm") + os.system("text2idngram -vocab sentences_spotify.txt < " + + "sentences_spotify.txt -idngram spotify.idngram") + os.system("idngram2lm -idngram spotify.idngram -vocab " + + "sentences_spotify.txt -arpa languagemodel_spotify.lm") # create a new mic with the new music models self.mic = Mic( mic.speaker, mic.passive_stt_engine, - stt.PocketSphinxSTT(lmd_music="languagemodel_spotify.lm", dictd_music="dictionary_spotify.dic") + stt.PocketSphinxSTT(lmd_music="languagemodel_spotify.lm", + dictd_music="dictionary_spotify.dic") ) def delegateInput(self, input): @@ -195,6 +201,7 @@ class MusicMode(object): self.mic.say("Pardon?") self.music.play() + def reconnect(func, *default_args, **default_kwargs): """ Reconnects before running @@ -228,6 +235,7 @@ class Song(object): self.artist = artist self.album = album + class MPDWrapper(object): def __init__(self, server="localhost", port=6600): """ @@ -329,7 +337,7 @@ class MPDWrapper(object): def get_soup(self): """ - returns the list of unique words that comprise song and artist titles + Returns the list of unique words that comprise song and artist titles """ soup = [] @@ -340,17 +348,17 @@ class MPDWrapper(object): soup.extend(song_words) soup.extend(artist_words) - title_trans = ''.join( - chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256)) - soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate(title_trans).replace("_", "") - for x in soup] + title_trans = ''.join(chr(c) if chr(c).isupper() or chr(c).islower() + else '_' for c in range(256)) + soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate( + title_trans).replace("_", "") for x in soup] soup = [x for x in soup if x != ""] return list(set(soup)) def get_soup_playlist(self): """ - returns the list of unique words that comprise playlist names + Returns the list of unique words that comprise playlist names """ soup = [] @@ -358,17 +366,17 @@ class MPDWrapper(object): for name in self.playlists: soup.extend(name.split(" ")) - title_trans = ''.join( - chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256)) - soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate(title_trans).replace("_", "") - for x in soup] + title_trans = ''.join(chr(c) if chr(c).isupper() or chr(c).islower() + else '_' for c in range(256)) + soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate( + title_trans).replace("_", "") for x in soup] soup = [x for x in soup if x != ""] return list(set(soup)) def get_soup_separated(self): """ - returns the list of PHRASES that comprise song and artist titles + Returns the list of PHRASES that comprise song and artist titles """ title_soup = [song.title for song in self.songs] @@ -376,23 +384,26 @@ class MPDWrapper(object): soup = list(set(title_soup + artist_soup)) - title_trans = ''.join( - chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256)) - soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate(title_trans).replace("_", " ") - for x in soup] + title_trans = ''.join(chr(c) if chr(c).isupper() or chr(c).islower() + else '_' for c in range(256)) + soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate( + title_trans).replace("_", " ") for x in soup] soup = [re.sub(' +', ' ', x) for x in soup if x != ""] return soup def fuzzy_songs(self, query): """ - Returns songs matching a query best as possible on either artist field, etc + Returns songs matching a query best as possible on either artist + field, etc """ query = query.upper() - matched_song_titles = difflib.get_close_matches(query, self.song_titles) - matched_song_artists = difflib.get_close_matches(query, self.song_artists) + matched_song_titles = difflib.get_close_matches(query, + self.song_titles) + matched_song_artists = difflib.get_close_matches(query, + self.song_artists) # if query is beautifully matched, then forget about everything else strict_priority_title = [x for x in matched_song_titles if x == query] @@ -420,4 +431,4 @@ class MPDWrapper(object): query = query.upper() lookup = {n.upper(): n for n in self.playlists} results = [lookup[r] for r in difflib.get_close_matches(query, lookup)] - return results \ No newline at end of file + return results diff --git a/client/modules/News.py b/client/modules/News.py index d80b791..433ea05 100644 --- a/client/modules/News.py +++ b/client/modules/News.py @@ -41,7 +41,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ mic.say("Pulling up the news") articles = getTopArticles(maxResults=3) @@ -84,17 +85,23 @@ def handle(text, mic, profile): if profile['prefers_email']: body += article_link else: - if not app_utils.emailUser(profile, SUBJECT="", BODY=article_link): - mic.say( - "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.") + if not app_utils.emailUser(profile, SUBJECT="", + BODY=article_link): + mic.say("I'm having trouble sending you these " + + "articles. Please make sure that your " + + "phone number and carrier are correct " + + "on the dashboard.") return # if prefers email, we send once, at the end if profile['prefers_email']: body += "" - if not app_utils.emailUser(profile, SUBJECT="Your Top Headlines", BODY=body): - mic.say( - "I'm having trouble sending you these articles. Please make sure that your phone number and carrier are correct on the dashboard.") + if not app_utils.emailUser(profile, + SUBJECT="Your Top Headlines", + BODY=body): + mic.say("I'm having trouble sending you these articles. " + + "Please make sure that your phone number and " + + "carrier are correct on the dashboard.") return mic.say("All set") @@ -105,7 +112,8 @@ def handle(text, mic, profile): if 'phone_number' in profile: mic.say("Here are the current top headlines. " + all_titles + - ". Would you like me to send you these articles? If so, which?") + ". Would you like me to send you these articles? " + + "If so, which?") handleResponse(mic.activeListen()) else: diff --git a/client/modules/Notifications.py b/client/modules/Notifications.py index 9ee004c..2d413b6 100644 --- a/client/modules/Notifications.py +++ b/client/modules/Notifications.py @@ -1,6 +1,6 @@ # -*- coding: utf-8-*- import re -from facebook import * +import facebook WORDS = ["FACEBOOK", "NOTIFICATION"] @@ -15,17 +15,19 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ oauth_access_token = profile['keys']['FB_TOKEN'] - graph = GraphAPI(oauth_access_token) + graph = facebook.GraphAPI(oauth_access_token) try: results = graph.request("me/notifications") - except GraphAPIError: - mic.say( - "I have not been authorized to query your Facebook. If you would like to check your notifications in the future, please visit the Jasper dashboard.") + except facebook.GraphAPIError: + mic.say("I have not been authorized to query your Facebook. If you " + + "would like to check your notifications in the future, " + + "please visit the Jasper dashboard.") return except: mic.say( diff --git a/client/modules/Time.py b/client/modules/Time.py index 90ffdf9..8bc5c8e 100644 --- a/client/modules/Time.py +++ b/client/modules/Time.py @@ -14,7 +14,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ tz = getTimezone(profile) diff --git a/client/modules/Unclear.py b/client/modules/Unclear.py index 3900300..071eea3 100644 --- a/client/modules/Unclear.py +++ b/client/modules/Unclear.py @@ -14,7 +14,8 @@ def handle(text, mic, profile): Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ messages = ["I'm sorry, could you repeat that?", diff --git a/client/modules/Weather.py b/client/modules/Weather.py index 5922d33..7076f50 100644 --- a/client/modules/Weather.py +++ b/client/modules/Weather.py @@ -9,7 +9,9 @@ WORDS = ["WEATHER", "TODAY", "TOMORROW"] def replaceAcronyms(text): - """Replaces some commonly-used acronyms for an improved verbal weather report.""" + """ + Replaces some commonly-used acronyms for an improved verbal weather report. + """ def parseDirections(text): words = { @@ -39,19 +41,21 @@ def getForecast(profile): def handle(text, mic, profile): """ - Responds to user-input, typically speech text, with a summary of - the relevant weather for the requested date (typically, weather - information will not be available for days beyond tomorrow). + Responds to user-input, typically speech text, with a summary of + the relevant weather for the requested date (typically, weather + information will not be available for days beyond tomorrow). - Arguments: + Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) - profile -- contains information related to the user (e.g., phone number) + profile -- contains information related to the user (e.g., phone + number) """ if not profile['location']: mic.say( - "I'm sorry, I can't seem to access that information. Please make sure that you've set your location on the dashboard.") + "I'm sorry, I can't seem to access that information. Please make" + + "sure that you've set your location on the dashboard.") return tz = getTimezone(profile) @@ -77,14 +81,16 @@ def handle(text, mic, profile): for entry in forecast: try: date_desc = entry['title'].split()[0].strip().lower() - if date_desc == 'forecast': #For global forecasts - date_desc = entry['title'].split()[2].strip().lower() - weather_desc = entry['summary'] - - elif date_desc == 'current': #For first item of global forecasts - continue + if date_desc == 'forecast': + # For global forecasts + date_desc = entry['title'].split()[2].strip().lower() + weather_desc = entry['summary'] + elif date_desc == 'current': + # For first item of global forecasts + continue else: - weather_desc = entry['summary'].split('-')[1] #US forecasts + # US forecasts + weather_desc = entry['summary'].split('-')[1] if weekday == date_desc: output = date_keyword + \ @@ -108,4 +114,5 @@ def isValid(text): Arguments: text -- user-input, typically transcribed speech """ - return bool(re.search(r'\b(weathers?|temperature|forecast|outside|hot|cold|jacket|coat|rain)\b', text, re.IGNORECASE)) + return bool(re.search(r'\b(weathers?|temperature|forecast|outside|hot|' + + r'cold|jacket|coat|rain)\b', text, re.IGNORECASE)) diff --git a/client/notifier.py b/client/notifier.py index 2ae66f8..b081a66 100644 --- a/client/notifier.py +++ b/client/notifier.py @@ -4,6 +4,7 @@ from modules import Gmail from apscheduler.scheduler import Scheduler import logging + class Notifier(object): class NotificationClient(object): @@ -22,9 +23,11 @@ class Notifier(object): self.notifiers = [] if 'gmail_address' in profile and 'gmail_password' in profile: - self.notifiers.append(self.NotificationClient(self.handleEmailNotifications, None)) + self.notifiers.append(self.NotificationClient( + self.handleEmailNotifications, None)) else: - self._logger.warning('gmail_address or gmail_password not set in profile, Gmail notifier will not be used') + self._logger.warning('gmail_address or gmail_password not set ' + + 'in profile, Gmail notifier will not be used') sched = Scheduler() sched.start() diff --git a/client/populate.py b/client/populate.py index e871f26..c309500 100644 --- a/client/populate.py +++ b/client/populate.py @@ -7,10 +7,13 @@ from pytz import timezone import feedparser import jasperpath + def run(): profile = {} - print "Welcome to the profile populator. If, at any step, you'd prefer not to enter the requested information, just hit 'Enter' with a blank field to continue." + print("Welcome to the profile populator. If, at any step, you'd prefer " + + "not to enter the requested information, just hit 'Enter' with a " + + "blank field to continue.") def simple_request(var, cleanVar, cleanInput=None): input = raw_input(cleanVar + ": ") @@ -24,20 +27,29 @@ def run(): simple_request('last_name', 'Last name') # gmail - print "\nJasper uses your Gmail to send notifications. Alternatively, you can skip this step (or just fill in the email address if you want to receive email notifications) and setup a Mailgun account, as at http://jasperproject.github.io/documentation/software/#mailgun.\n" + print("\nJasper uses your Gmail to send notifications. Alternatively, " + + "you can skip this step (or just fill in the email address if you " + + "want to receive email notifications) and setup a Mailgun " + + "account, as at http://jasperproject.github.io/documentation/" + + "software/#mailgun.\n") simple_request('gmail_address', 'Gmail address') profile['gmail_password'] = getpass() # phone number clean_number = lambda s: re.sub(r'[^0-9]', '', s) - phone_number = clean_number(raw_input( - "\nPhone number (no country code). Any dashes or spaces will be removed for you: ")) + phone_number = clean_number(raw_input("\nPhone number (no country " + + "code). Any dashes or spaces will " + + "be removed for you: ")) profile['phone_number'] = phone_number # carrier print("\nPhone carrier (for sending text notifications).") - print( - "If you have a US phone number, you can enter one of the following: 'AT&T', 'Verizon', 'T-Mobile' (without the quotes). If your carrier isn't listed or you have an international number, go to http://www.emailtextmessages.com and enter the email suffix for your carrier (e.g., for Virgin Mobile, enter 'vmobl.com'; for T-Mobile Germany, enter 't-d1-sms.de').") + print("If you have a US phone number, you can enter one of the " + + "following: 'AT&T', 'Verizon', 'T-Mobile' (without the quotes). " + + "If your carrier isn't listed or you have an international " + + "number, go to http://www.emailtextmessages.com and enter the " + + "email suffix for your carrier (e.g., for Virgin Mobile, enter " + + "'vmobl.com'; for T-Mobile Germany, enter 't-d1-sms.de').") carrier = raw_input('Carrier: ') if carrier == 'AT&T': profile['carrier'] = 'txt.att.net' @@ -50,7 +62,8 @@ def run(): # location def verifyLocation(place): - feed = feedparser.parse('http://rss.wunderground.com/auto/rss_full/' + place) + feed = feedparser.parse('http://rss.wunderground.com/auto/rss_full/' + + place) numEntries = len(feed['entries']) if numEntries == 0: return False @@ -58,18 +71,20 @@ def run(): print("Location saved as " + feed['feed']['description'][33:]) return True - print( - "\nLocation should be a 5-digit US zipcode (e.g., 08544). If you are outside the US, insert the name of your nearest big town/city. For weather requests.") + print("\nLocation should be a 5-digit US zipcode (e.g., 08544). If you " + + "are outside the US, insert the name of your nearest big " + + "town/city. For weather requests.") location = raw_input("Location: ") - while location and (verifyLocation(location) == False): + while location and not verifyLocation(location): print("Weather not found. Please try another location.") location = raw_input("Location: ") if location: profile['location'] = location # timezone - print( - "\nPlease enter a timezone from the list located in the TZ* column at http://en.wikipedia.org/wiki/List_of_tz_database_time_zones, or none at all.") + print("\nPlease enter a timezone from the list located in the TZ* " + + "column at http://en.wikipedia.org/wiki/" + + "List_of_tz_database_time_zones, or none at all.") tz = raw_input("Timezone: ") while tz: try: @@ -80,8 +95,8 @@ def run(): print("Not a valid timezone. Try again.") tz = raw_input("Timezone: ") - response = raw_input( - "\nWould you prefer to have notifications sent by email (E) or text message (T)? ") + response = raw_input("\nWould you prefer to have notifications sent by " + + "email (E) or text message (T)? ") while not response or (response != 'E' and response != 'T'): response = raw_input("Please choose email (E) or text message (T): ") profile['prefers_email'] = (response == 'E') @@ -91,9 +106,10 @@ def run(): "google": "GOOGLE_SPEECH" } - response = raw_input( - "\nIf you would like to choose a specific STT engine, please specify which." + - "\nAvailable implementations: %s. (Press Enter to default to PocketSphinx): " % stt_engines.keys()) + response = raw_input("\nIf you would like to choose a specific STT " + + "engine, please specify which.\nAvailable " + + "implementations: %s. (Press Enter to default " + + "to PocketSphinx): " % stt_engines.keys()) if (response in stt_engines): profile["stt_engine"] = response api_key_name = stt_engines[response] @@ -101,7 +117,8 @@ def run(): key = raw_input("\nPlease enter your API key: ") profile["keys"] = {api_key_name: key} else: - print("Unrecognized STT engine. Available implementations: %s" % stt_engines.keys()) + print("Unrecognized STT engine. Available implementations: %s" + % stt_engines.keys()) profile["stt_engine"] = "sphinx" # write to profile diff --git a/client/stt.py b/client/stt.py index c5caf99..cfaced6 100644 --- a/client/stt.py +++ b/client/stt.py @@ -16,16 +16,18 @@ import jasperpath 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 """ __metaclass__ = ABCMeta - + @classmethod def get_config(cls): return {} @@ -39,14 +41,18 @@ class AbstractSTTEngine(object): def transcribe(self, fp, mode=TranscriptionMode.NORMAL): pass + class PocketSphinxSTT(AbstractSTTEngine): SLUG = 'sphinx' - def __init__(self, lmd=jasperpath.config("languagemodel.lm"), dictd=jasperpath.config("dictionary.dic"), - lmd_persona=jasperpath.data("languagemodel_persona.lm"), dictd_persona=jasperpath.data("dictionary_persona.dic"), + def __init__(self, lmd=jasperpath.config("languagemodel.lm"), + dictd=jasperpath.config("dictionary.dic"), + lmd_persona=jasperpath.data("languagemodel_persona.lm"), + dictd_persona=jasperpath.data("dictionary_persona.dic"), lmd_music=None, dictd_music=None, - hmm_dir="/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k"): + hmm_dir="/usr/local/share/pocketsphinx/model/hmm/en_US/" + + "hub4wsj_sc_8k"): """ Initiates the pocketsphinx instance. @@ -54,7 +60,8 @@ class PocketSphinxSTT(AbstractSTTEngine): speaker -- handles platform-independent audio output lmd -- filename of the full language model dictd -- filename of the full dictionary (.dic) - lmd_persona -- filename of the 'Persona' language model (containing, e.g., 'Jasper') + lmd_persona -- filename of the 'Persona' language model (containing, + e.g., 'Jasper') dictd_persona -- filename of the 'Persona' dictionary (.dic) """ @@ -67,25 +74,35 @@ class PocketSphinxSTT(AbstractSTTEngine): import pocketsphinx as ps self._logfiles = {} - with tempfile.NamedTemporaryFile(prefix='psdecoder_music_', suffix='.log', delete=False) as f: + 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: + 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: + 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._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]) + 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): for filename in self._logfiles.values(): os.remove(filename) @classmethod - def get_config(cls): #FIXME: Replace this as soon as we have a config module + 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 @@ -101,13 +118,16 @@ class PocketSphinxSTT(AbstractSTTEngine): if 'dictd' in profile['pocketsphinx']: config['dictd'] = profile['pocketsphinx']['dictd'] if 'lmd_persona' in profile['pocketsphinx']: - config['lmd_persona'] = profile['pocketsphinx']['lmd_persona'] + config['lmd_persona'] = \ + profile['pocketsphinx']['lmd_persona'] if 'dictd_persona' in profile['pocketsphinx']: - config['dictd_persona'] = profile['pocketsphinx']['dictd_persona'] + 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'] + config['dictd_music'] = \ + profile['pocketsphinx']['dictd_music'] return config def transcribe(self, fp, mode=TranscriptionMode.NORMAL): @@ -116,7 +136,8 @@ class PocketSphinxSTT(AbstractSTTEngine): Arguments: audio_file_path -- the path to the audio file to-be transcribed - PERSONA_ONLY -- if True, uses the 'Persona' language model and dictionary + 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] @@ -129,7 +150,7 @@ class PocketSphinxSTT(AbstractSTTEngine): 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: @@ -158,11 +179,16 @@ Speech-To-Text implementation which relies on the Google Speech API. This implementation requires a Google API key to be present in profile.yml To obtain an API key: -1. Join the Chromium Dev group: https://groups.google.com/a/chromium.org/forum/?fromgroups#!forum/chromium-dev -2. Create a project through the Google Developers console: https://console.developers.google.com/project -3. Select your project. In the sidebar, navigate to "APIs & Auth." Activate the Speech API. -4. Under "APIs & Auth," navigate to "Credentials." Create a new key for public API access. -5. Add your credentials to your profile.yml. Add an entry to the 'keys' section using the key name 'GOOGLE_SPEECH.' Sample configuration: +1. Join the Chromium Dev group: + https://groups.google.com/a/chromium.org/forum/?fromgroups#!forum/chromium-dev +2. Create a project through the Google Developers console: + https://console.developers.google.com/project +3. Select your project. In the sidebar, navigate to "APIs & Auth." Activate + the Speech API. +4. Under "APIs & Auth," navigate to "Credentials." Create a new key for public + API access. +5. Add your credentials to your profile.yml. Add an entry to the 'keys' section + using the key name 'GOOGLE_SPEECH.' Sample configuration: 6. Set the value of the 'stt_engine' key in your profile.yml to 'google' @@ -181,7 +207,8 @@ class GoogleSTT(AbstractSTTEngine): SLUG = 'google' - def __init__(self, api_key=None): #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 @@ -192,7 +219,8 @@ class GoogleSTT(AbstractSTTEngine): self.http = requests.Session() @classmethod - def get_config(cls): #FIXME: Replace this as soon as we have a config module + 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 @@ -206,8 +234,8 @@ class GoogleSTT(AbstractSTTEngine): def transcribe(self, fp, mode=TranscriptionMode.NORMAL): """ - Performs STT via the Google Speech API, transcribing an audio file and returning an English - string. + Performs STT via the Google Speech API, transcribing an audio file and + returning an English string. Arguments: audio_file_path -- the path to the .wav file to be transcribed @@ -217,8 +245,9 @@ class GoogleSTT(AbstractSTTEngine): 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") + 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() @@ -231,7 +260,8 @@ class GoogleSTT(AbstractSTTEngine): response_parts = response_read.strip().split("\n") decoded = json.loads(response_parts[-1]) if decoded['result']: - texts = [alt['transcript'] for alt in decoded['result'][0]['alternative']] + texts = [alt['transcript'] for alt in + decoded['result'][0]['alternative']] if texts: print "===================" print "JASPER: " + ', '.join(texts) @@ -256,17 +286,25 @@ 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] + return [stt_engine for stt_engine in AbstractSTTEngine.__subclasses__() + if hasattr(stt_engine, 'SLUG') and stt_engine.SLUG] + def newSTTEngine(stt_engine, **kwargs): - selected_engines = filter(lambda engine: hasattr(engine, "SLUG") and engine.SLUG == stt_engine, get_engines()) + 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: 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 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) + raise ValueError(("STT engine '%s' is not available (due to " + + "missing dependencies, missing dependencies, " + + "etc.)") % stt_engine) return engine(**engine.get_config()) diff --git a/client/test.py b/client/test.py index fd74571..7366ef2 100644 --- a/client/test.py +++ b/client/test.py @@ -6,7 +6,6 @@ import unittest import logging import argparse from mock import patch, Mock -from urllib2 import URLError, urlopen import test_mic import vocabcompiler @@ -23,11 +22,13 @@ DEFAULT_PROFILE = { 'phone_number': '012344321' } + class UnorderedList(list): def __eq__(self, other): return sorted(self) == sorted(other) + class TestVocabCompiler(unittest.TestCase): def testWordExtraction(self): @@ -46,14 +47,17 @@ class TestVocabCompiler(unittest.TestCase): with patch.object(g2p, 'translateWords') as translateWords: with patch.object(vocabcompiler, 'text2lm') as text2lm: - with patch.object(brain.Brain, 'get_modules', classmethod(lambda cls: [mock_module])) as modules: + with patch.object(brain.Brain, 'get_modules', + classmethod(lambda cls: [mock_module])): vocabcompiler.compile(sentences, dictionary, languagemodel) - translateWords.assert_called_once_with(UnorderedList(words)) + translateWords.assert_called_once_with( + UnorderedList(words)) self.assertTrue(text2lm.called) os.remove(sentences) os.remove(dictionary) + class TestMic(unittest.TestCase): def setUp(self): @@ -65,7 +69,8 @@ class TestMic(unittest.TestCase): def testTranscribeJasper(self): """Does Jasper recognize his name (i.e., passive listen)?""" - transcription = self.stt.transcribe(self.jasper_clip, PERSONA_ONLY=True) + transcription = self.stt.transcribe(self.jasper_clip, + PERSONA_ONLY=True) self.assertTrue("JASPER" in transcription) def testTranscribe(self): @@ -132,7 +137,7 @@ class TestModules(unittest.TestCase): inputs = ["Who's there?", "Random response"] outputs = self.runConversation(query, inputs, Joke) self.assertEqual(len(outputs), 3) - allJokes = open(jasperpath.data('text','JOKES.txt'), 'r').read() + allJokes = open(jasperpath.data('text', 'JOKES.txt'), 'r').read() self.assertTrue(outputs[2] in allJokes) def testTime(self): @@ -142,10 +147,11 @@ class TestModules(unittest.TestCase): inputs = [] self.runConversation(query, inputs, Time) - @unittest.skipIf(not Diagnostics.check_network_connection(), "No internet connection") + @unittest.skipIf(not Diagnostics.check_network_connection(), + "No internet connection") def testGmail(self): key = 'gmail_password' - if not key in self.profile or not self.profile[key]: + if key not in self.profile or not self.profile[key]: return from modules import Gmail @@ -154,7 +160,8 @@ class TestModules(unittest.TestCase): inputs = [] self.runConversation(query, inputs, Gmail) - @unittest.skipIf(not Diagnostics.check_network_connection(), "No internet connection") + @unittest.skipIf(not Diagnostics.check_network_connection(), + "No internet connection") def testHN(self): from modules import HN @@ -166,7 +173,8 @@ class TestModules(unittest.TestCase): outputs = self.runConversation(query, inputs, HN) self.assertTrue("front-page articles" in outputs[1]) - @unittest.skipIf(not Diagnostics.check_network_connection(), "No internet connection") + @unittest.skipIf(not Diagnostics.check_network_connection(), + "No internet connection") def testNews(self): from modules import News @@ -178,7 +186,8 @@ class TestModules(unittest.TestCase): outputs = self.runConversation(query, inputs, News) self.assertTrue("top headlines" in outputs[1]) - @unittest.skipIf(not Diagnostics.check_network_connection(), "No internet connection") + @unittest.skipIf(not Diagnostics.check_network_connection(), + "No internet connection") def testWeather(self): from modules import Weather @@ -189,12 +198,14 @@ class TestModules(unittest.TestCase): "can't see that far ahead" in outputs[0] or "Tomorrow" in outputs[0]) + class TestTTS(unittest.TestCase): def testTTS(self): tts_engine = tts.get_engine_by_slug('dummy-tts') tts_instance = tts_engine() tts_instance.say('This is a test.') + class TestBrain(unittest.TestCase): @staticmethod @@ -235,7 +246,8 @@ if __name__ == '__main__': parser = argparse.ArgumentParser( description='Test suite for the Jasper client code.') parser.add_argument('--light', action='store_true', - help='runs a subset of the tests (only requires Python dependencies)') + help='runs a subset of the tests (only requires ' + + 'Python dependencies)') parser.add_argument('--debug', action='store_true', help='show debug messages') args = parser.parse_args() diff --git a/client/test_mic.py b/client/test_mic.py index 472bf62..2448d8d 100644 --- a/client/test_mic.py +++ b/client/test_mic.py @@ -16,8 +16,10 @@ class Mic: def passiveListen(self, PERSONA): return True, "JASPER" - def activeListenToAllOptions(self, THRESHOLD=None, LISTEN=True, MUSIC=False): - return [self.activeListen(THRESHOLD=THRESHOLD, LISTEN=LISTEN, MUSIC=MUSIC)] + 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: diff --git a/client/tts.py b/client/tts.py index 8fef776..244c0f4 100644 --- a/client/tts.py +++ b/client/tts.py @@ -18,7 +18,6 @@ import logging from abc import ABCMeta, abstractmethod from distutils.spawn import find_executable -import yaml import argparse import wave @@ -28,12 +27,13 @@ try: except ImportError: pass + class AbstractTTSEngine(object): """ Generic parent class for all speakers """ __metaclass__ = ABCMeta - + @classmethod @abstractmethod def is_available(cls): @@ -50,7 +50,8 @@ class AbstractTTSEngine(object): # FIXME: Use platform-independent audio-output here # See issue jasperproject/jasper-client#188 cmd = ['aplay', '-D', 'hw:1,0', str(filename)] - self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) + self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) + for arg in cmd])) with tempfile.TemporaryFile() as f: subprocess.call(cmd, stdout=f, stderr=f) f.seek(0) @@ -58,13 +59,15 @@ class AbstractTTSEngine(object): if output: self._logger.debug("Output was: '%s'", output) + class AbstractMp3TTSEngine(AbstractTTSEngine): """ Generic class that implements the 'play' method for mp3 files """ @classmethod def is_available(cls): - return (super(AbstractMp3TTSEngine, cls).is_available() and 'mad' in sys.modules.keys()) + return (super(AbstractMp3TTSEngine, cls).is_available() and + 'mad' in sys.modules.keys()) def play_mp3(self, filename): mf = mad.MadFile(filename) @@ -72,7 +75,8 @@ class AbstractMp3TTSEngine(AbstractTTSEngine): wav = wave.open(f, mode='wb') wav.setframerate(mf.samplerate()) wav.setnchannels(1 if mf.mode() == mad.MODE_SINGLE_CHANNEL else 2) - wav.setsampwidth(4L) # width of 32 bit audio + # 4L is the sample width of 32 bit audio + wav.setsampwidth(4L) frame = mf.read() while frame is not None: wav.writeframes(frame) @@ -80,6 +84,7 @@ class AbstractMp3TTSEngine(AbstractTTSEngine): wav.close() self.play(f.name) + class DummyTTS(AbstractTTSEngine): """ Dummy TTS engine that logs phrases with INFO level instead of synthesizing @@ -94,11 +99,12 @@ class DummyTTS(AbstractTTSEngine): def say(self, phrase): self._logger.info(phrase) - + def play(self, filename): self._logger.debug("Playback of file '%s' requested") pass + class EspeakTTS(AbstractTTSEngine): """ Uses the eSpeak speech synthesizer included in the Jasper disk image @@ -107,7 +113,8 @@ class EspeakTTS(AbstractTTSEngine): SLUG = "espeak-tts" - def __init__(self, voice='default+m3', pitch_adjustment=40, words_per_minute=160): + def __init__(self, voice='default+m3', pitch_adjustment=40, + words_per_minute=160): super(self.__class__, self).__init__() self.voice = voice self.pitch_adjustment = pitch_adjustment @@ -115,7 +122,8 @@ class EspeakTTS(AbstractTTSEngine): @classmethod def is_available(cls): - return (super(cls, cls).is_available() and find_executable('espeak') is not None) + return (super(cls, cls).is_available() and + find_executable('espeak') is not None) def say(self, phrase): self._logger.debug("Saying '%s' with '%s'", phrase, self.SLUG) @@ -127,7 +135,8 @@ class EspeakTTS(AbstractTTSEngine): '-w', fname, phrase] cmd = [str(x) for x in cmd] - self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) + self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) + for arg in cmd])) with tempfile.TemporaryFile() as f: subprocess.call(cmd, stdout=f, stderr=f) f.seek(0) @@ -137,6 +146,7 @@ class EspeakTTS(AbstractTTSEngine): self.play(fname) os.remove(fname) + class FestivalTTS(AbstractTTSEngine): """ Uses the festival speech synthesizer @@ -147,13 +157,17 @@ class FestivalTTS(AbstractTTSEngine): @classmethod def is_available(cls): - if super(cls, cls).is_available() and find_executable('text2wave') is not None and find_executable('festival') is not None: + if (super(cls, cls).is_available() and + find_executable('text2wave') is not None and + find_executable('festival') is not None): logger = logging.getLogger(__name__) cmd = ['festival', '--pipe'] with tempfile.SpooledTemporaryFile() as out_f: with tempfile.SpooledTemporaryFile() as in_f: - logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) - subprocess.call(cmd, stdin=in_f, stdout=out_f, stderr=out_f) + logger.debug('Executing %s', ' '.join([pipes.quote(arg) + for arg in cmd])) + subprocess.call(cmd, stdin=in_f, stdout=out_f, + stderr=out_f) out_f.seek(0) output = out_f.read().strip() if output: @@ -169,14 +183,18 @@ class FestivalTTS(AbstractTTSEngine): in_f.write(phrase) in_f.seek(0) with tempfile.SpooledTemporaryFile() as err_f: - self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) - subprocess.call(cmd, stdin=in_f, stdout=out_f, stderr=err_f) + self._logger.debug('Executing %s', + ' '.join([pipes.quote(arg) + for arg in cmd])) + subprocess.call(cmd, stdin=in_f, stdout=out_f, + stderr=err_f) err_f.seek(0) output = err_f.read() if output: self._logger.debug("Output was: '%s'", output) self.play(out_f.name) + class MacOSXTTS(AbstractTTSEngine): """ Uses the OS X built-in 'say' command @@ -186,12 +204,15 @@ class MacOSXTTS(AbstractTTSEngine): @classmethod def is_available(cls): - return (platform.system() == 'darwin' and find_executable('say') is not None and find_executable('afplay') is not None) + return (platform.system() == 'darwin' and + find_executable('say') is not None and + find_executable('afplay') is not None) def say(self, phrase): self._logger.debug("Saying '%s' with '%s'", phrase, self.SLUG) cmd = ['say', str(phrase)] - self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) + self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) + for arg in cmd])) with tempfile.TemporaryFile() as f: subprocess.call(cmd, stdout=f, stderr=f) f.seek(0) @@ -201,7 +222,8 @@ class MacOSXTTS(AbstractTTSEngine): def play(self, filename): cmd = ['afplay', str(filename)] - self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) + self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) + for arg in cmd])) with tempfile.TemporaryFile() as f: subprocess.call(cmd, stdout=f, stderr=f) f.seek(0) @@ -209,6 +231,7 @@ class MacOSXTTS(AbstractTTSEngine): if output: self._logger.debug("Output was: '%s'", output) + class PicoTTS(AbstractTTSEngine): """ Uses the svox-pico-tts speech synthesizer @@ -223,7 +246,8 @@ class PicoTTS(AbstractTTSEngine): @classmethod def is_available(cls): - return (super(cls, cls).is_available() and find_executable('pico2wave') is not None) + return (super(cls, cls).is_available() and + find_executable('pico2wave') is not None) @property def languages(self): @@ -234,7 +258,8 @@ class PicoTTS(AbstractTTSEngine): subprocess.call(cmd, stderr=f) f.seek(0) output = f.read() - pattern = re.compile(r'Unknown language: NULL\nValid languages:\n((?:[a-z]{2}-[A-Z]{2}\n)+)') + pattern = re.compile(r'Unknown language: NULL\nValid languages:\n' + + r'((?:[a-z]{2}-[A-Z]{2}\n)+)') matchobj = pattern.match(output) if not matchobj: raise RuntimeError("pico2wave: valid languages not detected") @@ -247,10 +272,12 @@ class PicoTTS(AbstractTTSEngine): fname = f.name cmd = ['pico2wave', '--wave', fname] if self.language not in self.languages: - raise ValueError("Language '%s' not supported by '%s'", self.language, self.SLUG) + raise ValueError("Language '%s' not supported by '%s'", + self.language, self.SLUG) cmd.extend(['-l', self.language]) cmd.append(phrase) - self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) for arg in cmd])) + self._logger.debug('Executing %s', ' '.join([pipes.quote(arg) + for arg in cmd])) with tempfile.TemporaryFile() as f: subprocess.call(cmd, stdout=f, stderr=f) f.seek(0) @@ -260,6 +287,7 @@ class PicoTTS(AbstractTTSEngine): self.play(fname) os.remove(fname) + class GoogleTTS(AbstractMp3TTSEngine): """ Uses the Google TTS online translator @@ -274,19 +302,23 @@ class GoogleTTS(AbstractMp3TTSEngine): @classmethod def is_available(cls): - return (super(cls, cls).is_available() and 'gtts' in sys.modules.keys()) + return (super(cls, cls).is_available() and + 'gtts' in sys.modules.keys()) @property def languages(self): - langs = ['af', 'sq', 'ar', 'hy', 'ca', 'zh-CN', 'zh-TW', 'hr', 'cs', 'da', 'nl', 'en', 'eo', 'fi', 'fr', 'de', - 'el', 'ht', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'ko', 'la', 'lv', 'mk', 'no', 'pl', 'pt', 'ro', 'ru', - 'sr', 'sk', 'es', 'sw', 'sv', 'ta', 'th', 'tr', 'vi', 'cy'] + langs = ['af', 'sq', 'ar', 'hy', 'ca', 'zh-CN', 'zh-TW', 'hr', 'cs', + 'da', 'nl', 'en', 'eo', 'fi', 'fr', 'de', 'el', 'ht', 'hi', + 'hu', 'is', 'id', 'it', 'ja', 'ko', 'la', 'lv', 'mk', 'no', + 'pl', 'pt', 'ro', 'ru', 'sr', 'sk', 'es', 'sw', 'sv', 'ta', + 'th', 'tr', 'vi', 'cy'] return langs def say(self, phrase): self._logger.debug("Saying '%s' with '%s'", phrase, self.SLUG) if self.language not in self.languages: - raise ValueError("Language '%s' not supported by '%s'", self.language, self.SLUG) + raise ValueError("Language '%s' not supported by '%s'", + self.language, self.SLUG) tts = gtts.gTTS(text=phrase, lang=self.language) with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f: tmpfile = f.name @@ -294,9 +326,11 @@ class GoogleTTS(AbstractMp3TTSEngine): self.play_mp3(tmpfile) os.remove(tmpfile) + def get_default_engine_slug(): return 'osx-tts' if platform.system() == 'darwin' else 'espeak-tts' + def get_engine_by_slug(slug=None): """ Returns: @@ -305,21 +339,26 @@ def get_engine_by_slug(slug=None): 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_engines = filter(lambda engine: hasattr(engine, "SLUG") and engine.SLUG == slug, get_engines()) + selected_engines = filter(lambda engine: hasattr(engine, "SLUG") and + engine.SLUG == slug, get_engines()) if len(selected_engines) == 0: raise ValueError("No TTS engine found for slug '%s'" % slug) else: if len(selected_engines) > 1: - print("WARNING: Multiple TTS engines found for slug '%s'. This is most certainly a bug." % slug) + 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("TTS engine '%s' is not available (due to missing dependencies, missing dependencies, etc.)" % slug) + 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() @@ -327,30 +366,35 @@ def get_engines(): subclasses.add(subclass) subclasses.update(get_subclasses(subclass)) return subclasses - return [tts_engine for tts_engine in list(get_subclasses(AbstractTTSEngine)) if hasattr(tts_engine, 'SLUG') and tts_engine.SLUG] + return [tts_engine for tts_engine in + list(get_subclasses(AbstractTTSEngine)) + if hasattr(tts_engine, 'SLUG') and tts_engine.SLUG] if __name__ == '__main__': parser = argparse.ArgumentParser(description='Jasper TTS module') - parser.add_argument('--debug', action='store_true', help='Show debug messages') + parser.add_argument('--debug', action='store_true', + help='Show debug messages') args = parser.parse_args() logging.basicConfig() if args.debug: logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) - + engines = get_engines() available_engines = [] for engine in get_engines(): if engine.is_available(): available_engines.append(engine) + disabled_engines = list(set(engines).difference(set(available_engines))) print("Available TTS engines:") for i, engine in enumerate(available_engines, start=1): print("%d. %s" % (i, engine.SLUG)) - + print("") print("Disabled TTS engines:") - for i, engine in enumerate(list(set(engines).difference(set(available_engines))), start=1): + + for i, engine in enumerate(disabled_engines, start=1): print("%d. %s" % (i, engine.SLUG)) print("") diff --git a/client/vocabcompiler.py b/client/vocabcompiler.py index 1ca0d26..52b1f0a 100644 --- a/client/vocabcompiler.py +++ b/client/vocabcompiler.py @@ -1,6 +1,7 @@ # -*- coding: utf-8-*- """ - Iterates over all the WORDS variables in the modules and creates a dictionary for the client. +Iterates over all the WORDS variables in the modules and creates a +dictionary for the client. """ import os @@ -11,8 +12,8 @@ from brain import Brain def text2lm(in_filename, out_filename): """Wrapper around the language model compilation tools""" def text2idngram(in_filename, out_filename): - cmd = "text2idngram -vocab %s < %s -idngram temp.idngram" % (out_filename, - in_filename) + cmd = "text2idngram -vocab %s < %s -idngram temp.idngram" % ( + out_filename, in_filename) os.system(cmd) def idngram2lm(in_filename, out_filename): diff --git a/jasper.py b/jasper.py index 689f6ee..27a8de8 100755 --- a/jasper.py +++ b/jasper.py @@ -2,7 +2,6 @@ # -*- coding: utf-8-*- import os import sys -import traceback import shutil import logging @@ -18,8 +17,10 @@ sys.path.append(jasperpath.LIB_PATH) from client.conversation import Conversation 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('--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('--debug', action='store_true', help='Show debug messages') args = parser.parse_args() @@ -28,34 +29,44 @@ if args.local: else: from client.mic import Mic + class Jasper(object): def __init__(self): self._logger = logging.getLogger(__name__) - + # Create config dir if it does not exist yet if not os.path.exists(jasperpath.CONFIG_PATH): try: os.makedirs(jasperpath.CONFIG_PATH) except OSError: - self._logger.error("Could not create config dir: '%s'", jasperpath.CONFIG_PATH, exc_info=True) + self._logger.error("Could not create config dir: '%s'", + jasperpath.CONFIG_PATH, exc_info=True) raise # Check if config dir is writable if not os.access(jasperpath.CONFIG_PATH, os.W_OK): - self._logger.critical("Config dir %s is not writable. Jasper won't work correctly.") + self._logger.critical("Config dir %s is not writable. Jasper " + + "won't work correctly.", + jasperpath.CONFIG_PATH) - # FIXME: For backwards compatibility, move old config file to newly created config dir + # FIXME: For backwards compatibility, move old config file to newly + # created config dir old_configfile = os.path.join(jasperpath.LIB_PATH, 'profile.yml') new_configfile = jasperpath.config('profile.yml') if os.path.exists(old_configfile): if os.path.exists(new_configfile): - self._logger.warning("Deprecated profile file found: '%s'. Please remove it.", old_configfile) + self._logger.warning("Deprecated profile file found: '%s'. " + + "Please remove it.", old_configfile) else: - self._logger.warning("Deprecated profile file found: '%s'. Trying to copy it to new location '%s'.", old_configfile, new_configfile) + self._logger.warning("Deprecated profile file found: '%s'. " + + "Trying to copy it to new location '%s'.", + old_configfile, new_configfile) try: shutil.copy2(old_configfile, new_configfile) except shutil.Error: - self._logger.error("Unable to copy config file. Please copy it manually.", exc_info=True) + self._logger.error("Unable to copy config file. " + + "Please copy it manually.", + exc_info=True) raise # Read config @@ -76,25 +87,31 @@ class Jasper(object): stt_engine_type = self.config['stt_engine'] except KeyError: stt_engine_type = "sphinx" - self._logger.warning("stt_engine not specified in profile, defaulting to '%s'", stt_engine_type) + self._logger.warning("stt_engine not specified in profile, " + + "defaulting to '%s'", stt_engine_type) try: tts_engine_slug = self.config['tts_engine'] except KeyError: tts_engine_slug = tts.get_default_engine_slug() - logger.warning("tts_engine not specified in profile, defaulting to '%s'", tts_engine_slug) + logger.warning("tts_engine not specified in profile, defaulting " + + "to '%s'", tts_engine_slug) tts_engine_class = tts.get_engine_by_slug(tts_engine_slug) # Compile dictionary - sentences, dictionary, languagemodel = [jasperpath.config(filename) for filename in ("sentences.txt", "dictionary.dic", "languagemodel.lm")] + sentences = jasperpath.config("sentences.txt") + dictionary = jasperpath.config("dictionary.dic") + languagemodel = jasperpath.config("languagemodel.lm") vocabcompiler.compile(sentences, dictionary, languagemodel) # Initialize Mic - self.mic = Mic(tts_engine_class(), stt.PocketSphinxSTT(), stt.newSTTEngine(stt_engine_type, api_key=api_key)) + self.mic = Mic(tts_engine_class(), stt.PocketSphinxSTT(), + stt.newSTTEngine(stt_engine_type, api_key=api_key)) def run(self): if 'first_name' in self.config: - salutation = "How can I be of service, %s?" % self.config["first_name"] + salutation = ("How can I be of service, %s?" + % self.config["first_name"]) else: salutation = "How can I be of service?" self.mic.say(salutation) @@ -111,17 +128,19 @@ if __name__ == "__main__": logging.basicConfig() logger = logging.getLogger() - + if args.debug: logger.setLevel(logging.DEBUG) - if not args.no_network_check and not Diagnostics.check_network_connection(): - logger.warning("Network not connected. This may prevent Jasper from running properly.") + if (not args.no_network_check and + not Diagnostics.check_network_connection()): + logger.warning("Network not connected. This may prevent Jasper from " + + "running properly.") try: app = Jasper() except Exception: logger.exception("Error occured!", exc_info=True) sys.exit(1) - + app.run() -- cgit v1.3.1 From 804da384bff556d3caedf9ad01d57f2460aee151 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 6 Oct 2014 16:53:40 +0200 Subject: Remove non-working __main__ from g2p --- client/g2p.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/g2p.py b/client/g2p.py index 4e64a4a..89f2282 100644 --- a/client/g2p.py +++ b/client/g2p.py @@ -67,8 +67,3 @@ def translateFile(input_filename, output_filename=None): return None return out - -if __name__ == "__main__": - - translateFile(PHONETISAURUS_PATH + "/phonetisaurus/sentences.txt", - PHONETISAURUS_PATH + "/phonetisaurus/dictionary.dic") -- cgit v1.3.1