Guest User

Ask Babby

a guest
Sep 5th, 2011
359
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.71 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # "How is babby formed?" --kavya (http://www.youtube.com/watch?v=w_RaPOOVX1Y)
  5. #
  6. # ask_babby.py - Speak a question in natural language, get back an answer spoken
  7. # in natural language. Uses Google's speech recognition, Google's search, and
  8. # Yahoo Answers. This will only work on a Mac.
  9. #
  10. # Other people's code strung together by: Tony ([email protected])
  11. import sys
  12. import subprocess
  13. from subprocess import PIPE
  14. import StringIO
  15. import pyaudio
  16. import wave
  17. import random
  18. import time
  19. import threading
  20. import audio
  21. import urllib
  22. import urllib2
  23. import simplejson
  24. import re
  25. import HTMLParser
  26. from nltk import sent_tokenize
  27.  
  28. # If you send FLAC audio data to this URL, you get a transcription of the
  29. # audio as a response. This probably works better than any downloadable speech
  30. # to text software out there for short utterances like questions.
  31. GOOGLE_SR_URL = 'http://www.google.com/speech-api/v1/recognize?lang=en-us&client=chromium'
  32. SAMPLE_RATE = 16000
  33.  
  34. # The official and unofficial Google search API url.
  35. GOOGLE_API_URL = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&%s'
  36. GOOGLE_SEARCH_URL = 'http://www.google.com/search?%s'
  37.  
  38. # Some stuff used to deal with HTML.
  39. HTML_PARSER = HTMLParser.HTMLParser()
  40. ANSWER_PAT = re.compile(r'<div class="content">(.*?)</div>')
  41. URL_PAT = re.compile(r'http://[^ ]*')
  42. TAG_PAT = re.compile(r'<[^>]*?>')
  43. YA_URL_PAT = re.compile(r'href="(http://answers.yahoo.com/question/index\?qid=[^"]*)"')
  44.  
  45. # Pass the time by muttering these phrases
  46. THINKING_PHRASES = ["Wait a second", "I'm thinking", "I think I know this...",
  47.     'Let me see...']
  48.  
  49. # Records for the given number of seconds and returns the wav recording
  50. # I have no idea what these default settings actually mean. Check out
  51. # the pyaudio documentation for more information. This code is lifted
  52. # from one of their demo scripts.
  53. def sample_wav(secs, format=pyaudio.paInt16, channels=1, rate=SAMPLE_RATE,
  54.     frames_per_buffer=1024):
  55.  
  56.     p = pyaudio.PyAudio()
  57.     stream = p.open(format=format, channels=channels, rate=rate,
  58.                     input=True, frames_per_buffer=frames_per_buffer)
  59.  
  60.     all = []
  61.     for i in range(0, rate / frames_per_buffer * secs):
  62.         data = stream.read(frames_per_buffer)
  63.         all.append(data)
  64.  
  65.     stream.close()
  66.     p.terminate()
  67.  
  68.     data = ''.join(all)
  69.     obj = StringIO.StringIO()
  70.     wf = wave.open(obj, 'wb')
  71.     wf.setnchannels(channels)
  72.     wf.setsampwidth(p.get_sample_size(format))
  73.     wf.setframerate(rate)
  74.     wf.writeframes(data)
  75.     wf.close()
  76.     return obj.getvalue()
  77.  
  78. # Takes some wav data as input, and converts it to FLAC format. I could not
  79. # figure out how to do this in Python, so it just class the flac command line
  80. # utility by opening a subprocess.
  81. def wav_to_flac(wav_data, rate=SAMPLE_RATE):
  82.     cmd = ['flac', '--silent', '--sample-rate=%s' % rate, '-']
  83.     proc = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  84.     flac_data = proc.communicate(wav_data)[0]
  85.     return flac_data
  86.  
  87. # Takes some flac data as input and sends it to Google's speech recognition
  88. # API. I'm not sure if this is actually allowed, so this part of the code
  89. # could break at any time. In fact, it's probably not allowed at all since I
  90. # have to lie about the user agent.
  91. def flac_to_text(flac_data, rate=SAMPLE_RATE):
  92.     req = urllib2.Request(GOOGLE_SR_URL, data=flac_data)
  93.     req.add_header('User-Agent', 'Mozilla/5.0')
  94.     content_type = 'audio/x-flac; rate=%s' % rate
  95.     req.add_header('Content-Type', content_type)
  96.     res = urllib2.urlopen(req)
  97.     result = simplejson.loads(res.read())
  98.     # Google returns a JSON object that has a list of hypotheses about what
  99.     # the transcription should be. I just take the first one. This code will
  100.     # break if something goes wrong in opening the URL etc.
  101.     return result['hypotheses'][0]['utterance']
  102.  
  103. # Takes some text as input and does text -> speech using the Mac OS X "say"
  104. # command. This won't work on non-Mac systems.
  105. def say(s, voice='Alex', rate=250):
  106.     cmd = ['say', '-v', voice, '[[rate %s]] %s' % (rate,s)]
  107.     subprocess.call(cmd)
  108.  
  109. # Cleans up some text.
  110. def clean(text):
  111.     text = unicode(text,errors='ignore')
  112.     text = HTML_PARSER.unescape(TAG_PAT.sub('', text))
  113.     text = URL_PAT.sub(' ', text)
  114.     return text.replace('\n', ' ').replace('\r', ' ')
  115.  
  116. # Given a Yahoo Answers page, looks for the "Best Answer" and returns the
  117. # text. If there isn't one there then... this will break.
  118. def get_best_answer(data):
  119.     start = data.index('Best Answer')
  120.     got = ANSWER_PAT.findall(data[start:].replace('\n', ' '))[0]
  121.     return clean(got)
  122.  
  123. # Searches Yahoo Answers using the official Google search API. For some reason,
  124. # the results aren't as good as just scraping the results off of the regular
  125. # Google search engine result page. Returns the text of the Best Answer on the
  126. # answers page, or fails otherwise. Or breaks. Or something bad happens, I
  127. # really don't know.
  128. def get_yahoo_answer_official(q):
  129.     query = urllib.urlencode({'q' : 'site:answers.yahoo.com ' + q})
  130.     search_results = urllib.urlopen(GOOGLE_API_URL % query)
  131.     json = simplejson.loads(search_results.read())
  132.     results = json['responseData']['results']
  133.     url = urllib.unquote(results[0]['url'])
  134.     data = urllib.urlopen(url).read()
  135.     return get_best_answer(data)
  136.  
  137. # Searches Yahoo Answers using the "unofficial" Google search API, also known
  138. # as setting the user agent to be a desktop browser, and then scraping the
  139. # results page.
  140. def get_yahoo_answer_unofficial(q):
  141.     query = urllib.urlencode({'q': 'site:answers.yahoo.com ' + q})
  142.     req = urllib2.Request(url=GOOGLE_SEARCH_URL % query)
  143.     req.add_header('User-Agent', 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.6) Gecko/2009011912 Firefox/3.0.6')
  144.     req.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
  145.     req.add_header('Accept-Language', 'en-us,en;q=0.5')
  146.     out = urllib2.urlopen(req)
  147.     data = out.read()
  148.     data = data.replace("\n", ' ')
  149.     url = YA_URL_PAT.findall(data)[0]
  150.     data = urllib.urlopen(url).read()
  151.     return get_best_answer(data)
  152.  
  153. # A class for fetching the best Yahoo answer in the background, given an
  154. # wav audio recording of a question.
  155. class AnswerFetcher(threading.Thread):
  156.     def __init__(self, wav_data):
  157.         threading.Thread.__init__(self)
  158.         self.wav_data = wav_data
  159.         self.answer = None
  160.     def run(self):
  161.         try:
  162.             flac_data = wav_to_flac(self.wav_data)
  163.             question = flac_to_text(flac_data)
  164.             self.answer = get_yahoo_answer_unofficial(question)
  165.         # Whatever, just catch the error.
  166.         except Exception, e:
  167.             print >>sys.stderr, e
  168.             self.answer = "Sorry. I'm too stupid to answer."
  169.  
  170. # Prints text out one character at a time to make it look oh so cool. The
  171. # font is hard coded to be white on pink, because that is the ultimate font
  172. # choice and should never be changed.
  173. class SlowPrinter(threading.Thread):
  174.     def __init__(self, text, pause=0.05):
  175.         threading.Thread.__init__(self)
  176.         self.text = text
  177.         self.pause = pause
  178.         self.kill = False
  179.     def run(self):
  180.         # This means print white text on a pink background, bold
  181.         sys.stdout.write('\x1b[1m\x1b[45m\x1b[37m')
  182.         for c in self.text:
  183.             if self.kill: break
  184.             sys.stdout.write(c)
  185.             sys.stdout.flush()
  186.             time.sleep(self.pause)
  187.         sys.stdout.write('\x1b[0m')
  188.         sys.stdout.write('\n')
  189.         sys.stdout.flush()
  190.  
  191. # Breaks a text into sentences, and "says" the sentences one by one.
  192. def say_sent_by_sent(text, voice='Alex', rate=150):
  193.     sents = sent_tokenize(text)
  194.     for sent in sents:
  195.         try:
  196.             sp = SlowPrinter(sent)
  197.             sp.start()
  198.             say(sent, voice=voice, rate=rate)
  199.             sp.join()
  200.         except KeyboardInterrupt, e:
  201.             sp.kill = True
  202.             break
  203.  
  204. # The main routine for Babby. Records a question and says the answer.
  205. def interface(secs):
  206.     wav_data = sample_wav(secs)
  207.     fetcher = AnswerFetcher(wav_data)
  208.     fetcher.start()
  209.     while fetcher.answer is None:
  210.         say(random.choice(THINKING_PHRASES), rate=130)
  211.         time.sleep(1)
  212.     fetcher.join()
  213.     answer = fetcher.answer
  214.     say_sent_by_sent(answer, rate=180)
  215.  
  216. # Keeps asking for questions and answering them. Press any key and then
  217. # enter to record a question. Type "exit" to exit, or just Control-C.
  218. if __name__ == '__main__':
  219.     while True:
  220.         x = raw_input('ASK BABBY>')
  221.         if x == 'exit': break
  222.         dots = SlowPrinter('.....', pause=1)
  223.         dots.start()
  224.         interface(5)
  225.         dots.join()
Advertisement
Add Comment
Please, Sign In to add comment