summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2014-10-06 16:59:36 +0200
committerschneefux <schneefux+commit@schneefux.xyz>2014-10-06 16:59:36 +0200
commitedca8b7407d7c9b1497ba1722d1b66ffa4b4c234 (patch)
treed544987379415ac557751401ea0b0c7de81530b7 /client
parent890a5798fcf7f9b9dc06529aa4bc7fe903d7ca1a (diff)
parent804da384bff556d3caedf9ad01d57f2460aee151 (diff)
downloadjasper-client-edca8b7407d7c9b1497ba1722d1b66ffa4b4c234.tar.gz
jasper-client-edca8b7407d7c9b1497ba1722d1b66ffa4b4c234.zip
Merge pull request #217 from Holzhaus/style-fixes
Style fixes
Diffstat (limited to 'client')
-rw-r--r--client/app_utils.py30
-rw-r--r--client/brain.py41
-rw-r--r--client/conversation.py22
-rwxr-xr-xclient/diagnose.py24
-rw-r--r--client/g2p.py14
-rw-r--r--client/jasperpath.py3
-rw-r--r--client/local_mic.py6
-rw-r--r--client/mic.py52
-rw-r--r--client/modules/Birthday.py18
-rw-r--r--client/modules/Gmail.py7
-rw-r--r--client/modules/HN.py28
-rw-r--r--client/modules/Joke.py5
-rw-r--r--client/modules/Life.py3
-rw-r--r--client/modules/MPDControl.py73
-rw-r--r--client/modules/News.py24
-rw-r--r--client/modules/Notifications.py14
-rw-r--r--client/modules/Time.py3
-rw-r--r--client/modules/Unclear.py3
-rw-r--r--client/modules/Weather.py37
-rw-r--r--client/notifier.py7
-rw-r--r--client/populate.py53
-rw-r--r--client/stt.py104
-rw-r--r--client/test.py34
-rw-r--r--client/test_mic.py6
-rw-r--r--client/tts.py114
-rw-r--r--client/vocabcompiler.py7
26 files changed, 463 insertions, 269 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..89f2282 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:
@@ -64,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")
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 += "</ul>"
- 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 += "</ul>"
- 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):