summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2014-07-23 21:21:17 -0700
committerschneefux <schneefux+commit@schneefux.xyz>2014-07-23 21:21:17 -0700
commit2c55f4da462383ee6c7b89ea44e5c40cd5badf0c (patch)
tree078672f2b221cfb7d0aa765f438002a277914c9f
parent8a5e5ed385a5c467260c2f1b20816a66cebfc1c6 (diff)
downloadjasper-client-2c55f4da462383ee6c7b89ea44e5c40cd5badf0c.tar.gz
jasper-client-2c55f4da462383ee6c7b89ea44e5c40cd5badf0c.zip
Google STT works on OS X
-rw-r--r--.gitignore1
-rwxr-xr-xclient/conversation.py1
-rw-r--r--client/main.py6
-rw-r--r--client/mic.py33
-rw-r--r--client/modules/Birthday.py64
-rw-r--r--client/modules/Gmail.py136
-rw-r--r--client/modules/HN.py130
-rw-r--r--client/modules/Joke.py63
-rw-r--r--client/modules/Life.py32
-rw-r--r--client/modules/News.py122
-rw-r--r--client/modules/Notifications.py55
-rw-r--r--client/modules/Time.py33
-rw-r--r--client/modules/Unclear.py29
-rw-r--r--client/modules/Weather.py110
-rwxr-xr-xclient/modules/__init__.py0
-rw-r--r--client/modules/app_utils.py123
-rw-r--r--client/notifier.py4
-rw-r--r--client/populate.py3
-rw-r--r--client/stt.py101
19 files changed, 125 insertions, 921 deletions
diff --git a/.gitignore b/.gitignore
index 08cf7c5..5a2333a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ client/say.wav
*.swp
server/modules/build/*
client/build/*
+client/modules/*
client/profile.yml
build/*
server/dump.rdb
diff --git a/client/conversation.py b/client/conversation.py
index bad5073..9f7cc22 100755
--- a/client/conversation.py
+++ b/client/conversation.py
@@ -51,6 +51,7 @@ class Conversation(object):
if threshold:
input = self.mic.activeListen(threshold)
+ print "I just heard '%s'" % input
if input:
self.delegateInput(input)
else:
diff --git a/client/main.py b/client/main.py
index 35ef715..4a54f3f 100644
--- a/client/main.py
+++ b/client/main.py
@@ -1,6 +1,8 @@
import yaml
import sys
import speaker
+import stt
+from stt import PocketSphinxSTT
from conversation import Conversation
@@ -21,8 +23,8 @@ if __name__ == "__main__":
profile = yaml.safe_load(open("profile.yml", "r"))
- mic = Mic(speaker.newSpeaker(), "languagemodel.lm", "dictionary.dic",
- "languagemodel_persona.lm", "dictionary_persona.dic")
+ # TODO: rename 'api_key' to 'google_stt_api_key'
+ mic = Mic(speaker.newSpeaker(), PocketSphinxSTT(), stt.newSTTEngine(profile['api_key']))
mic.say("How can I be of service?")
diff --git a/client/mic.py b/client/mic.py
index f4b70df..48f14de 100644
--- a/client/mic.py
+++ b/client/mic.py
@@ -10,19 +10,12 @@ import pyaudio
import alteration
-# quirky bug where first import doesn't work
-try:
- import pocketsphinx as ps
-except:
- import pocketsphinx as ps
-
-
class Mic:
speechRec = None
speechRec_persona = None
- def __init__(self, speaker, lmd, dictd, lmd_persona, dictd_persona, lmd_music=None, dictd_music=None):
+ def __init__(self, speaker, passive_stt_engine, active_stt_engine):
"""
Initiates the pocketsphinx instance.
@@ -34,13 +27,8 @@ class Mic:
dictd_persona -- filename of the 'Persona' dictionary (.dic)
"""
self.speaker = speaker
- hmdir = "/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k"
-
- if lmd_music and dictd_music:
- self.speechRec_music = ps.Decoder(hmm = hmdir, lm = lmd_music, dict = dictd_music)
- self.speechRec_persona = ps.Decoder(
- hmm=hmdir, lm=lmd_persona, dict=dictd_persona)
- self.speechRec = ps.Decoder(hmm=hmdir, lm=lmd, dict=dictd)
+ self.passive_stt_engine = passive_stt_engine
+ self.active_stt_engine = active_stt_engine
def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False):
"""
@@ -210,7 +198,8 @@ class Mic:
write_frames.close()
# check if PERSONA was said
- transcribed = self.transcribe(AUDIO_FILE, PERSONA_ONLY=True)
+ #transcribed = self.transcribe(AUDIO_FILE, PERSONA_ONLY=True)
+ transcribed = self.passive_stt_engine.transcribe(AUDIO_FILE, PERSONA_ONLY=True)
if PERSONA in transcribed:
return (THRESHOLD, PERSONA)
@@ -223,7 +212,8 @@ class Mic:
"""
AUDIO_FILE = "active.wav"
- RATE = 16000
+ #RATE = 16000
+ RATE = 44100
CHUNK = 1024
LISTEN_TIME = 12
@@ -232,7 +222,8 @@ class Mic:
if not os.path.exists(AUDIO_FILE):
return None
- return self.transcribe(AUDIO_FILE)
+ #return self.transcribe(AUDIO_FILE)
+ return self.active_stt_engine.transcribe(AUDIO_FILE)
# check if no threshold provided
if THRESHOLD == None:
@@ -285,9 +276,11 @@ class Mic:
# os.system("sox "+AUDIO_FILE+" temp.wav vol 20dB")
if MUSIC:
- return self.transcribe(AUDIO_FILE, MUSIC=True)
+ #return self.transcribe(AUDIO_FILE, MUSIC=True)
+ return self.active_stt_engine.transcribe(AUDIO_FILE, MUSIC=True)
- return self.transcribe(AUDIO_FILE)
+ #return self.transcribe(AUDIO_FILE)
+ return self.active_stt_engine.transcribe(AUDIO_FILE)
def say(self, phrase, OPTIONS=" -vdefault+m3 -p 40 -s 160 --stdout > say.wav"):
# alter phrase before speaking
diff --git a/client/modules/Birthday.py b/client/modules/Birthday.py
deleted file mode 100644
index 1388443..0000000
--- a/client/modules/Birthday.py
+++ /dev/null
@@ -1,64 +0,0 @@
-import datetime
-import re
-from facebook import *
-from app_utils import getTimezone
-
-WORDS = ["BIRTHDAY"]
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, by listing the user's
- Facebook friends with birthdays today.
-
- 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)
- """
- oauth_access_token = profile['keys']["FB_TOKEN"]
-
- graph = 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.")
- return
- except:
- mic.say(
- "I apologize, there's a problem with that service at the moment.")
- return
-
- needle = datetime.datetime.now(tz=getTimezone(profile)).strftime("%m/%d")
-
- people = []
- for person in results['data']:
- try:
- if needle in person['birthday']:
- people.append(person['name'])
- except:
- continue
-
- if len(people) > 0:
- if len(people) == 1:
- output = people[0] + " has a birthday today."
- else:
- output = "Your friends with birthdays today are " + \
- ", ".join(people[:-1]) + " and " + people[-1] + "."
- else:
- output = "None of your friends have birthdays today."
-
- mic.say(output)
-
-
-def isValid(text):
- """
- Returns True if the input is related to birthdays.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'birthday', text, re.IGNORECASE))
diff --git a/client/modules/Gmail.py b/client/modules/Gmail.py
deleted file mode 100644
index b401ae6..0000000
--- a/client/modules/Gmail.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import imaplib
-import email
-import re
-from dateutil import parser
-from app_utils import *
-
-WORDS = ["EMAIL", "INBOX"]
-
-
-def getSender(email):
- """
- Returns the best-guess sender of an email.
-
- Arguments:
- email -- the email whose sender is desired
-
- Returns:
- Sender of the email.
- """
- sender = email['From']
- m = re.match(r'(.*)\s<.*>', sender)
- if m:
- return m.group(1)
- return sender
-
-
-def getDate(email):
- return parser.parse(email.get('date'))
-
-
-def getMostRecentDate(emails):
- """
- Returns the most recent date of any email in the list provided.
-
- Arguments:
- emails -- a list of emails to check
-
- Returns:
- Date of the most recent email.
- """
- dates = [getDate(e) for e in emails]
- dates.sort(reverse=True)
- if dates:
- return dates[0]
- return None
-
-
-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)
- since -- if provided, no emails before this date will be returned
- markRead -- if True, marks all returned emails as read in target inbox
-
- Returns:
- A list of unread email objects.
- """
- conn = imaplib.IMAP4_SSL('imap.gmail.com')
- conn.debug = 0
- conn.login(profile['gmail_address'], profile['gmail_password'])
- conn.select(readonly=(not markRead))
-
- msgs = []
- (retcode, messages) = conn.search(None, '(UNSEEN)')
-
- if retcode == 'OK' and messages != ['']:
- numUnread = len(messages[0].split(' '))
- if limit and numUnread > limit:
- return numUnread
-
- for num in messages[0].split(' '):
- # parse email RFC822 format
- ret, data = conn.fetch(num, '(RFC822)')
- msg = email.message_from_string(data[0][1])
-
- if not since or getDate(msg) > since:
- msgs.append(msg)
- conn.close()
- conn.logout()
-
- return msgs
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, with a summary of
- the user's Gmail inbox, reporting on the number of unread emails
- in the inbox, as well as their senders.
-
- 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)
- """
- try:
- msgs = fetchUnreadEmails(profile, limit=5)
-
- if isinstance(msgs, int):
- response = "You have %d unread emails." % msgs
- mic.say(response)
- return
-
- senders = [getSender(e) for e in msgs]
- except imaplib.IMAP4.error:
- mic.say(
- "I'm sorry. I'm not authenticated to work with your Gmail.")
- return
-
- if not senders:
- mic.say("You have no unread emails.")
- elif len(senders) == 1:
- mic.say("You have one unread email from " + senders[0] + ".")
- else:
- response = "You have %d unread emails" % len(
- senders)
- unique_senders = list(set(senders))
- if len(unique_senders) > 1:
- unique_senders[-1] = 'and ' + unique_senders[-1]
- response += ". Senders include: "
- response += '...'.join(senders)
- else:
- response += " from " + unittest[0]
-
- mic.say(response)
-
-
-def isValid(text):
- """
- Returns True if the input is related to email.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\bemail\b', text, re.IGNORECASE))
diff --git a/client/modules/HN.py b/client/modules/HN.py
deleted file mode 100644
index 493fa1d..0000000
--- a/client/modules/HN.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import urllib2
-import re
-import random
-from bs4 import BeautifulSoup
-import app_utils
-from semantic.numbers import NumberService
-
-WORDS = ["HACKER", "NEWS", "YES", "NO", "FIRST", "SECOND", "THIRD"]
-
-PRIORITY = 4
-
-URL = 'http://news.ycombinator.com'
-
-
-class HNStory:
-
- def __init__(self, title, URL):
- self.title = title
- self.URL = URL
-
-
-def getTopStories(maxResults=None):
- """
- Returns the top headlines from Hacker News.
-
- Arguments:
- maxResults -- if provided, returns a random sample of size maxResults
- """
- hdr = {'User-Agent': 'Mozilla/5.0'}
- req = urllib2.Request(URL, headers=hdr)
- page = urllib2.urlopen(req).read()
- soup = BeautifulSoup(page)
- matches = soup.findAll('td', class_="title")
- matches = [m.a for m in matches if m.a and m.text != u'More']
- matches = [HNStory(m.text, m['href']) for m in matches]
-
- if maxResults:
- num_stories = min(maxResults, len(matches))
- return random.sample(matches, num_stories)
-
- return matches
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, with a sample of
- Hacker News's top headlines, sending them to the user over email
- if desired.
-
- 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)
- """
- mic.say("Pulling up some stories.")
- stories = getTopStories(maxResults=3)
- all_titles = '... '.join(str(idx + 1) + ") " +
- story.title for idx, story in enumerate(stories))
-
- def handleResponse(text):
-
- def extractOrdinals(text):
- output = []
- service = NumberService()
- for w in text.split():
- if w in service.__ordinals__:
- output.append(service.__ordinals__[w])
- return [service.parse(w) for w in output]
-
- chosen_articles = extractOrdinals(text)
- send_all = chosen_articles is [] and app_utils.isPositive(text)
-
- if send_all or chosen_articles:
- mic.say("Sure, just give me a moment")
-
- if profile['prefers_email']:
- body = "<ul>"
-
- def formatArticle(article):
- tiny_url = app_utils.generateTinyURL(article.URL)
-
- if profile['prefers_email']:
- return "<li><a href=\'%s\'>%s</a></li>" % (tiny_url,
- article.title)
- else:
- return article.title + " -- " + tiny_url
-
- for idx, article in enumerate(stories):
- if send_all or (idx + 1) in chosen_articles:
- article_link = formatArticle(article)
-
- 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.")
- 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.")
- return
-
- mic.say("All done.")
-
- else:
- mic.say("OK I will not send any articles")
-
- 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?")
- handleResponse(mic.activeListen())
-
- else:
- mic.say(
- "Here are some front-page articles. " + all_titles)
-
-
-def isValid(text):
- """
- Returns True if the input is related to Hacker News.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\b(hack(er)?|HN)\b', text, re.IGNORECASE))
diff --git a/client/modules/Joke.py b/client/modules/Joke.py
deleted file mode 100644
index 52b3eb9..0000000
--- a/client/modules/Joke.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import random
-import re
-
-WORDS = ["JOKE", "KNOCK KNOCK"]
-
-
-def getRandomJoke(filename="../static/text/JOKES.txt"):
- jokeFile = open(filename, "r")
- jokes = []
- start = ""
- end = ""
- for line in jokeFile.readlines():
- line = line.replace("\n", "")
-
- if start == "":
- start = line
- continue
-
- if end == "":
- end = line
- continue
-
- jokes.append((start, end))
- start = ""
- end = ""
-
- jokes.append((start, end))
- joke = random.choice(jokes)
- return joke
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, by telling a joke.
-
- 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)
- """
- joke = getRandomJoke()
-
- mic.say("Knock knock")
-
- def firstLine(text):
- mic.say(joke[0])
-
- def punchLine(text):
- mic.say(joke[1])
-
- punchLine(mic.activeListen())
-
- firstLine(mic.activeListen())
-
-
-def isValid(text):
- """
- Returns True if the input is related to jokes/humor.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\bjoke\b', text, re.IGNORECASE))
diff --git a/client/modules/Life.py b/client/modules/Life.py
deleted file mode 100644
index 2c79ee1..0000000
--- a/client/modules/Life.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import random
-import re
-
-WORDS = ["MEANING", "OF", "LIFE"]
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, by relaying the
- meaning of life.
-
- 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)
- """
- messages = ["It's 42, you idiot.",
- "It's 42. How many times do I have to tell you?"]
-
- message = random.choice(messages)
-
- mic.say(message)
-
-
-def isValid(text):
- """
- Returns True if the input is related to the meaning of life.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\bmeaning of life\b', text, re.IGNORECASE))
diff --git a/client/modules/News.py b/client/modules/News.py
deleted file mode 100644
index 59495a3..0000000
--- a/client/modules/News.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import feedparser
-import app_utils
-import re
-from semantic.numbers import NumberService
-
-WORDS = ["NEWS", "YES", "NO", "FIRST", "SECOND", "THIRD"]
-
-PRIORITY = 3
-
-URL = 'http://news.ycombinator.com'
-
-
-class Article:
-
- def __init__(self, title, URL):
- self.title = title
- self.URL = URL
-
-
-def getTopArticles(maxResults=None):
- d = feedparser.parse("http://news.google.com/?output=rss")
-
- count = 0
- articles = []
- for item in d['items']:
- articles.append(Article(item['title'], item['link'].split("&url=")[1]))
- count += 1
- if maxResults and count > maxResults:
- break
-
- return articles
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, with a summary of
- the day's top news headlines, sending them to the user over email
- if desired.
-
- 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)
- """
- mic.say("Pulling up the news")
- articles = getTopArticles(maxResults=3)
- titles = [" ".join(x.title.split(" - ")[:-1]) for x in articles]
- all_titles = "... ".join(str(idx + 1) + ")" +
- title for idx, title in enumerate(titles))
-
- def handleResponse(text):
-
- def extractOrdinals(text):
- output = []
- service = NumberService()
- for w in text.split():
- if w in service.__ordinals__:
- output.append(service.__ordinals__[w])
- return [service.parse(w) for w in output]
-
- chosen_articles = extractOrdinals(text)
- send_all = chosen_articles is [] and app_utils.isPositive(text)
-
- if send_all or chosen_articles:
- mic.say("Sure, just give me a moment")
-
- if profile['prefers_email']:
- body = "<ul>"
-
- def formatArticle(article):
- tiny_url = app_utils.generateTinyURL(article.URL)
-
- if profile['prefers_email']:
- return "<li><a href=\'%s\'>%s</a></li>" % (tiny_url,
- article.title)
- else:
- return article.title + " -- " + tiny_url
-
- for idx, article in enumerate(articles):
- if send_all or (idx + 1) in chosen_articles:
- article_link = formatArticle(article)
-
- 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.")
- 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.")
- return
-
- mic.say("All set")
-
- else:
-
- mic.say("OK I will not send any articles")
-
- 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?")
- handleResponse(mic.activeListen())
-
- else:
- mic.say(
- "Here are the current top headlines. " + all_titles)
-
-
-def isValid(text):
- """
- Returns True if the input is related to the news.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\b(news|headline)\b', text, re.IGNORECASE))
diff --git a/client/modules/Notifications.py b/client/modules/Notifications.py
deleted file mode 100644
index fc91da1..0000000
--- a/client/modules/Notifications.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import re
-from facebook import *
-
-
-WORDS = ["FACEBOOK", "NOTIFICATION"]
-
-
-def handle(text, mic, profile):
- """
- Responds to user-input, typically speech text, with a summary of
- the user's Facebook notifications, including a count and details
- related to each individual notification.
-
- 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)
- """
- oauth_access_token = profile['keys']['FB_TOKEN']
-
- graph = 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.")
- return
- except:
- mic.say(
- "I apologize, there's a problem with that service at the moment.")
-
- if not len(results['data']):
- mic.say("You have no Facebook notifications. ")
- return
-
- updates = []
- for notification in results['data']:
- updates.append(notification['title'])
-
- count = len(results['data'])
- mic.say("You have " + str(count) +
- " Facebook notifications. " + " ".join(updates) + ". ")
-
- return
-
-
-def isValid(text):
- """
- Returns True if the input is related to Facebook notifications.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\bnotification|Facebook\b', text, re.IGNORECASE))
diff --git a/client/modules/Time.py b/client/modules/Time.py
deleted file mode 100644
index 2fe8a10..0000000
--- a/client/modules/Time.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import datetime
-import re
-from app_utils import getTimezone
-from semantic.dates import DateService
-
-WORDS = ["TIME"]
-
-
-def handle(text, mic, profile):
- """
- Reports the current time based on the user's timezone.
-
- 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)
- """
-
- tz = getTimezone(profile)
- now = datetime.datetime.now(tz=tz)
- service = DateService()
- response = service.convertTime(now)
- mic.say("It is %s right now." % response)
-
-
-def isValid(text):
- """
- Returns True if input is related to the time.
-
- Arguments:
- text -- user-input, typically transcribed speech
- """
- return bool(re.search(r'\btime\b', text, re.IGNORECASE))
diff --git a/client/modules/Unclear.py b/client/modules/Unclear.py
deleted file mode 100644
index 23ab2ed..0000000
--- a/client/modules/Unclear.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from sys import maxint
-import random
-
-WORDS = []
-
-PRIORITY = -(maxint + 1)
-
-
-def handle(text, mic, profile):
- """
- Reports that the user has unclear or unusable input.
-
- 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)
- """
-
- messages = ["I'm sorry, could you repeat that?",
- "My apologies, could you try saying that again?",
- "Say that again?", "I beg your pardon?"]
-
- message = random.choice(messages)
-
- mic.say(message)
-
-
-def isValid(text):
- return True
diff --git a/client/modules/Weather.py b/client/modules/Weather.py
deleted file mode 100644
index ad1ac29..0000000
--- a/client/modules/Weather.py
+++ /dev/null
@@ -1,110 +0,0 @@
-import re
-import datetime
-import feedparser
-from app_utils import getTimezone
-from semantic.dates import DateService
-
-WORDS = ["WEATHER", "TODAY", "TOMORROW"]
-
-
-def replaceAcronyms(text):
- """Replaces some commonly-used acronyms for an improved verbal weather report."""
-
- def parseDirections(text):
- words = {
- 'N': 'north',
- 'S': 'south',
- 'E': 'east',
- 'W': 'west',
- }
- output = [words[w] for w in list(text)]
- return ' '.join(output)
- acronyms = re.findall(r'\b([NESW]+)\b', text)
-
- for w in acronyms:
- text = text.replace(w, parseDirections(w))
-
- text = re.sub(r'(\b\d+)F(\b)', '\g<1> Fahrenheit\g<2>', text)
- text = re.sub(r'(\b)mph(\b)', '\g<1>miles per hour\g<2>', text)
- text = re.sub(r'(\b)in\.', '\g<1>inches', text)
-
- return text
-
-
-def getForecast(profile):
- return feedparser.parse("http://rss.wunderground.com/auto/rss_full/"
- + str(profile['location']))['entries']
-
-
-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).
-
- 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)
- """
-
- 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.")
- return
-
- tz = getTimezone(profile)
-
- service = DateService(tz=tz)
- date = service.extractDay(text)
- if not date:
- date = datetime.datetime.now(tz=tz)
- weekday = service.__daysOfWeek__[date.weekday()]
-
- if date.weekday() == datetime.datetime.now(tz=tz).weekday():
- date_keyword = "Today"
- elif date.weekday() == (
- datetime.datetime.now(tz=tz).weekday() + 1) % 7:
- date_keyword = "Tomorrow"
- else:
- date_keyword = "On " + weekday
-
- forecast = getForecast(profile)
-
- output = None
-
- 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
- else:
- weather_desc = entry['summary'].split('-')[1] #US forecasts
-
- if weekday == date_desc:
- output = date_keyword + \
- ", the weather will be " + weather_desc + "."
- break
- except:
- continue
-
- if output:
- output = replaceAcronyms(output)
- mic.say(output)
- else:
- mic.say(
- "I'm sorry. I can't see that far ahead.")
-
-
-def isValid(text):
- """
- Returns True if the text is related to the weather.
-
- 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))
diff --git a/client/modules/__init__.py b/client/modules/__init__.py
deleted file mode 100755
index e69de29..0000000
--- a/client/modules/__init__.py
+++ /dev/null
diff --git a/client/modules/app_utils.py b/client/modules/app_utils.py
deleted file mode 100644
index 7e39cbf..0000000
--- a/client/modules/app_utils.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import smtplib
-from email.MIMEText import MIMEText
-import urllib2
-import re
-import requests
-from pytz import timezone
-
-
-def sendEmail(SUBJECT, BODY, TO, FROM, SENDER, PASSWORD, SMTP_SERVER):
- """Sends an HTML email."""
- for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8':
- try:
- BODY.encode(body_charset)
- except UnicodeError:
- pass
- else:
- break
- msg = MIMEText(BODY.encode(body_charset), 'html', body_charset)
- msg['From'] = SENDER
- msg['To'] = TO
- msg['Subject'] = SUBJECT
-
- SMTP_PORT = 587
- session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
- session.starttls()
- session.login(FROM, PASSWORD)
- session.sendmail(SENDER, TO, msg.as_string())
- session.quit()
-
-
-def emailUser(profile, SUBJECT="", BODY=""):
- """
- Sends an email.
-
- 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."""
- if profile['carrier'] is None or not profile['phone_number']:
- return None
-
- return str(profile['phone_number']) + "@" + profile['carrier']
-
- if profile['prefers_email'] and profile['gmail_address']:
- # add footer
- if BODY:
- BODY = profile['first_name'] + \
- ",<br><br>Here are your top headlines:" + BODY
- BODY += "<br>Sent from your Jasper"
-
- recipient = profile['gmail_address']
- if profile['first_name'] and profile['last_name']:
- recipient = profile['first_name'] + " " + \
- profile['last_name'] + " <%s>" % recipient
- else:
- recipient = generateSMSEmail(profile)
-
- if not recipient:
- return False
-
- try:
- if 'mailgun' in profile:
- user = profile['mailgun']['username']
- password = profile['mailgun']['password']
- server = 'smtp.mailgun.org'
- else:
- user = profile['gmail_address']
- password = profile['gmail_password']
- server = 'smtp.gmail.com'
- sendEmail(SUBJECT, BODY, recipient, user,
- "Jasper <jasper>", password, server)
-
- return True
- except:
- return False
-
-
-def getTimezone(profile):
- """
- Returns the pytz timezone for a given profile.
-
- Arguments:
- profile -- contains information related to the user (e.g., email address)
- """
- try:
- return timezone(profile['timezone'])
- except:
- return None
-
-
-def generateTinyURL(URL):
- """
- Generates a compressed URL.
-
- Arguments:
- URL -- the original URL to-be compressed
- """
- target = "http://tinyurl.com/api-create.php?url=" + URL
- response = urllib2.urlopen(target)
- return response.read()
-
-
-def isNegative(phrase):
- """
- Returns True if the input phrase has a negative sentiment.
-
- Arguments:
- phrase -- the input phrase to-be evaluated
- """
- return bool(re.search(r'\b(no(t)?|don\'t|stop|end)\b', phrase, re.IGNORECASE))
-
-
-def isPositive(phrase):
- """
- Returns True if the input phrase has a positive sentiment.
-
- Arguments:
- phrase -- the input phrase to-be evaluated
- """
- return re.search(r'\b(sure|yes|yeah|go)\b', phrase, re.IGNORECASE)
diff --git a/client/notifier.py b/client/notifier.py
index 341ae04..5e33c5b 100644
--- a/client/notifier.py
+++ b/client/notifier.py
@@ -1,5 +1,5 @@
import Queue
-from modules import Gmail
+#from modules import Gmail
from apscheduler.scheduler import Scheduler
import logging
logging.basicConfig()
@@ -24,7 +24,7 @@ class Notifier(object):
]
sched = Scheduler()
- sched.start()
+ #sched.start()
sched.add_interval_job(self.gather, seconds=30)
def gather(self):
diff --git a/client/populate.py b/client/populate.py
index 48b8670..4778a74 100644
--- a/client/populate.py
+++ b/client/populate.py
@@ -84,6 +84,9 @@ def run():
response = raw_input("Please choose email (E) or text message (T): ")
profile['prefers_email'] = (response == 'E')
+ print ("\nIf you wish to depend on the Google Speech To Text API, please enter your API key, or leave blank to use Jasper's default speech to text implementation.")
+ simple_request('api_key', 'API Key')
+
# write to profile
print("Writing to profile...")
outputFile = open("profile.yml", "w")
diff --git a/client/stt.py b/client/stt.py
new file mode 100644
index 0000000..a62cf38
--- /dev/null
+++ b/client/stt.py
@@ -0,0 +1,101 @@
+import os
+import traceback
+import json
+import urllib2
+
+class PocketSphinxSTT(object):
+
+ def __init__(self, lmd = "languagemodel.lm", dictd = "dictionary.dic", lmd_persona = "languagemodel_persona.lm", dictd_persona = "dictionary_persona.dic", lmd_music=None, dictd_music=None):
+ """
+ Initiates the pocketsphinx instance.
+
+ Arguments:
+ 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')
+ dictd_persona -- filename of the 'Persona' dictionary (.dic)
+ """
+
+ # quirky bug where first import doesn't work
+ try:
+ import pocketsphinx as ps
+ except:
+ import pocketsphinx as ps
+
+ hmdir = "/usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k"
+
+ if lmd_music and dictd_music:
+ self.speechRec_music = ps.Decoder(hmm = hmdir, lm = lmd_music, dict = dictd_music)
+ self.speechRec_persona = ps.Decoder(
+ hmm=hmdir, lm=lmd_persona, dict=dictd_persona)
+ self.speechRec = ps.Decoder(hmm=hmdir, lm=lmd, dict=dictd)
+
+ def transcribe(self, audio_file_path, PERSONA_ONLY=False, MUSIC=False):
+ """
+ Performs STT, transcribing an audio file and returning the result.
+
+ Arguments:
+ audio_file_path -- the path to the audio file to-be transcribed
+ PERSONA_ONLY -- if True, uses the 'Persona' language model and dictionary
+ MUSIC -- if True, uses the 'Music' language model and dictionary
+ """
+
+ wavFile = file(audio_file_path, 'rb')
+ wavFile.seek(44)
+
+ if MUSIC:
+ self.speechRec_music.decode_raw(wavFile)
+ result = self.speechRec_music.get_hyp()
+ elif PERSONA_ONLY:
+ self.speechRec_persona.decode_raw(wavFile)
+ result = self.speechRec_persona.get_hyp()
+ else:
+ self.speechRec.decode_raw(wavFile)
+ result = self.speechRec.get_hyp()
+
+ print "==================="
+ print "JASPER: " + result[0]
+ print "==================="
+
+ return result[0]
+
+
+class GoogleSTT(object):
+
+ RATE = 44100
+
+ def __init__(self, api_key):
+ self.api_key = api_key
+
+ def transcribe(self, audio_file):
+
+ AUDIO_FILE_FLAC = "active.flac"
+ os.system("ffmpeg -y -i %s -c:a flac -ab 44100 %s" % (audio_file, AUDIO_FILE_FLAC))
+
+ 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")
+ flac = open(AUDIO_FILE_FLAC, 'rb')
+ data = flac.read()
+ flac.close()
+ try:
+ req = urllib2.Request(
+ url,
+ data=data,
+ headers={
+ 'Content-type': 'audio/x-flac; rate=%s' % GoogleSTT.RATE})
+ response_url = urllib2.urlopen(req)
+ response_read = response_url.read()
+ response_read = response_read.decode('utf-8')
+ decoded = json.loads(response_read.split("\n")[1])
+ print response_read
+ text = decoded['result'][0]['alternative'][0]['transcript']
+ print text
+ return text
+ except Exception:
+ traceback.print_exc()
+
+def newSTTEngine(api_key = None):
+ if api_key:
+ return GoogleSTT(api_key)
+ else:
+ return PocketSphinxSTT()