Sunday, 28 October 2012

Raspberry Pi - Python - Talking Twitter Client

Anyway, I was reading an article on the raspberry pi wiki about methods of implementing text to speech and that got me thinking about potential solutions which could make use of it.  I had this idea about a talking twitter client, a program which would automatically read tweets out as and when they arrived, so one evening while the wife was watching Holby City, this is what I created.




It uses oauth to authenticate with twitter before opening a twitter user stream, the program then waits for data to appear, when it does, it uses google translate to create an mp3 file which is then stream by mplayer and hey presto, the Pi talks the tweet!

Its not really a finished program, more, a proof of concept, but I thought it might be useful for other people who want to use twitter in their projects and need a starting point.  There is also a great tutorial about consuming twitter streams using python, where I got some of the code for this program

There are a few pre-requisites to getting this to work.

Create a twitter app
You need to create a twitter app using your twitter id, you can do this by visiting dev.twitter.com, signing on, and clicking create app; if you are having problems see a previous blog post of mine, automatically posting updates to twitter, which has some in-depth instructions.

Install python-oauth
I used leah's python oauth module to authenticate with twitter.

Install distribute
If you have never installed python modules before you are going to need to install the python setup tools, module, distribute, see blog post, python - installing modules, for info on how to do this.

Install git-core
In order to get the code from github you need to install git-core tools.

sudo apt-get install git-core

Get the code from git

git clone https://github.com/leah/python-oauth.git

Install the module

cd python-oauth
sudo python setup.py install

Install pycurl
pycurl is used to connect to the twitter streams.

sudo apt-get install python-pycurl

Install mplayer
mplayer is used to output the audio stream.

sudo apt-get install mplayer

Create talking twitter client program

nano ttc.py

Cut and paste the program into it and save.

#!/usr/bin/env python
# An experimental talking twitter client for the Raspberry Pi
# written in Python, by Martin O'Hanlon
# www.stuffaboutcode.com

from oauth.oauth import OAuthRequest, OAuthSignatureMethod_HMAC_SHA1
from hashlib import md5
import json, time, random, math, urllib, urllib2, pycurl, subprocess, sys

# twitter oauth keys, get your from dev.twitter.com
CONSUMER_KEY = 'consumer key'
CONSUMER_SECRET = 'consumer secret'
ACCESS_TOKEN = 'access token'
ACCESS_TOKEN_SECRET = 'access token secret'

# function to download a file from a url, used for testing
def downloadFile(url, fileName):
    fp = open(fileName, "wb")
    curl = pycurl.Curl()
    curl.setopt(pycurl.URL, url)
    curl.setopt(pycurl.WRITEDATA, fp)
    curl.perform()
    curl.close()
    fp.close()

# returns the appropriate google speech url for a particular phrase
def getGoogleSpeechURL(phrase):
    googleTranslateURL = "http://translate.google.com/translate_tts?tl=en&"
    parameters = {'q': phrase}
    data = urllib.urlencode(parameters)
    googleTranslateURL = "%s%s" % (googleTranslateURL,data)
    return googleTranslateURL

# function to download an mp3 file for a particular phrase, used for testing
def downloadSpeechFromText(phrase, fileName):
    googleSpeechURL = getGoogleSpeechURL(phrase)
    print googleSpeechURL
    downloadFile(googleSpeechURL, fileName)

# output phrase to audio using mplayer
def speakSpeechFromText(phrase):
    googleSpeechURL = getGoogleSpeechURL(phrase)
    subprocess.call(["mplayer",googleSpeechURL], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# class for managing tokens
class Token(object):
    def __init__(self,key,secret):
        self.key = key
        self.secret = secret

    def _generate_nonce(self):
        random_number = ''.join(str(random.randint(0, 9)) for i in range(40))
        m = md5(str(time.time()) + str(random_number))
        return m.hexdigest()

# talking twitter client
class TalkingTwitterStreamClient:
    def __init__(self, streamURL):
        self.streamURL = streamURL
        self.buffer = ""
        self.conn = pycurl.Curl()
        self.conn.setopt(pycurl.URL, self.streamURL)
        self.conn.setopt(pycurl.WRITEFUNCTION, self.on_receive)
        self.conn.perform()

    def on_receive(self, data):
        sys.stdout.write(".")
        self.buffer += data
        if data.endswith("\n") and self.buffer.strip():
            content = json.loads(self.buffer)
            self.buffer = ""
            #debug - output json from buffer
            #print content

            if "friends" in content:
                self.friends = content["friends"]
           
            if "text" in content:
                print u"{0[user][name]}: {0[text]}".format(content).encode('utf-8')
                speakSpeechFromText(u"A tweet from {0[user][name]}".format(content))
#downloadSpeechFromText(u"A tweet from {0[user][name]}".format(content), "./tweet.mp3")
                speakSpeechFromText(u"{0[text]}".format(content))
#downloadSpeechFromText(u"{0[text]}".format(content), "./tweet.mp3")

# get the url needed to open the twitter user stream, including signature after authentication
def getTwitterUserStreamURL():
    STREAM_URL = "https://userstream.twitter.com/2/user.json"

    access_token = Token(ACCESS_TOKEN,ACCESS_TOKEN_SECRET)
    consumer = Token(CONSUMER_KEY,CONSUMER_SECRET)
   
    parameters = {
        'oauth_consumer_key': CONSUMER_KEY,
        'oauth_token': access_token.key,
        'oauth_signature_method': 'HMAC-SHA1',
        'oauth_timestamp': str(int(time.time())),
        'oauth_nonce': access_token._generate_nonce(),
        'oauth_version': '1.0',
    }

    oauth_request = OAuthRequest.from_token_and_callback(access_token,
                    http_url=STREAM_URL,
                    parameters=parameters)
    signature_method = OAuthSignatureMethod_HMAC_SHA1()
    signature = signature_method.build_signature(oauth_request, consumer, access_token)

    parameters['oauth_signature'] = signature
    data = urllib.urlencode(parameters)
    return "%s?%s" % (STREAM_URL,data)

# Run Talking Twitter Client
client = TalkingTwitterStreamClient(getTwitterUserStreamURL())

#some useful debug commands, comment out running the client and uncomment the command
#get twitter stream url, including oauth signature
#print getTwitterUserStreamURL()
#download a speech file from google
#downloadSpeechFromText("hello, how are you today", "./downloadedFile.mp3")
#output phrase to audio
#speakSpeechFromText("hello, how are you today")
#start talking twitter client

Run the program

python ttc.py

Press CTRL C to exit and wait a few moments, this is for pycurl to give up the connection.

Limitations (found so far!)

Google translate seems to have a maximum length of string it will create an mp3 for, I found in testing that some tweets weren't output and it was because google didn't return an mp3 because it was too long.
There is no mechanism for recovering from errors, such as a the stream going down, it just falls over!

20 comments:

  1. Hey Martin,

    i found your great tutorial and i am happy to have it nearly working.

    but sadly i get an error when i start the ttc.py

    Here is the output which isnt very instructive for me:
    ------------------------------------------------
    .Traceback (most recent call last):
    File "ttc.py", line 70, in on_receive
    content = json.loads(self.buffer)
    File "/usr/lib/python2.7/json/__init__.py", line 326, in loads
    return _default_decoder.decode(s)
    File "/usr/lib/python2.7/json/decoder.py", line 365, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    File "/usr/lib/python2.7/json/decoder.py", line 383, in raw_decode
    raise ValueError("No JSON object could be decoded")
    ValueError: No JSON object could be decoded
    Traceback (most recent call last):
    File "ttc.py", line 112, in
    client = TalkingTwitterStreamClient(getTwitterUserStreamURL())
    File "ttc.py", line 64, in __init__
    self.conn.perform()
    pycurl.error: (23, 'Failed writing body (0 != 100)')
    ------------------------------------------------

    Do you have an idea where the problem could be?

    greetings

    ReplyDelete
    Replies
    1. It sounds like you arent receiving a valid return from twitter. Have you correctly setup the application in twitter?

      Delete
    2. on twitter i think i have done it right but the problem seems to be that i didn't download and put into the right directory the essential twitterizer and json files as you described in the other blog post:http://www.stuffaboutcode.com/2012/04/automatically-posting-updates-to.html

      i think i am going to find another solution because this seems to be too heavy for me :D

      i just want to let my raspi read the latest posts on my twitter account.

      So i thougt i could use the twitter api to get the new posts, put them into a text file or something and let the simple text-to-speech tool "festival" or "flite" read that.

      gonna work on this, after i got an usb sound card to avoid the bad noise at the analogue output on the pi.

      thanks for your reply

      greetings

      Delete
    3. Ok, well if you need any additional help, just ask. You shouldn't need either the json or twitterizer dll's, they are part of an entirely different solution which I built for Microsoft .net and Windows.

      Delete
    4. thanks for your kindness, i just got it working with use of the still available rss links and a few lines of python code and python-feedparser.

      combinated with my local siriproxy (also running on the same pi) its pretty lazy to ask siri about new tweets and let my raspberry answer :D

      iLove it

      maybe i am going to publish it on one of my old and long-time-not-updated or a new blog ;)

      keep up yours! and very impressive how fast and kind you answer to your comments ;)

      greetz

      Pete

      Delete
  2. Hello
    I am working on my RPi and i can send tweets. A question i have is HOW TO RECIEVE TWEETS AND HOW TO READ THE TWEET? We are doing a project where we are trying to connect the RPi to the Traffic light and we are change the signal by sending a tweet i.e RED/GREEN/YELLOW. I need the coding to read make RPi read the tweets and put hi/low voltage signal to the output pin, resulting in the signal changing.
    Also is there any other way we can do this…. like can i do by sending e-mails to raspberry pi that are readable and it will enable the output pin to HIGH.. please e-mail me at sahil_verma@live.ca
    I will look forward for your answer.

    Thank You for your help
    Regards
    Sahil Verma

    ReplyDelete
  3. The code above gives you what you need to wait for a tweet to arrive, read it when it does and then react to it. You just need to create your code to output to the gpio based on the content of the tweet.

    If you looking for a more indepth description than that, sorry! Unless of course your paying then lets do business! ;-)

    ReplyDelete
  4. I can get live tweets on my terminal but there is no audio to it?? Where did I go wrong?

    ReplyDelete
  5. Dont know, did you plug a speaker in? Try putting some headphones straight into the audio jack.

    ReplyDelete
  6. Hi Martin!

    Thanks for sharing this project! I am about to give it a try but I would like twitter to search for tweets with certain hashtags, any idea of how that would be implemated in your code?

    Best regards,

    Jacob

    ReplyDelete
    Replies
    1. Check out the streaming api's on twitter https://dev.twitter.com/docs/streaming-apis

      Delete
  7. Thanks, that looks like a good resource. I am though having problem with the original code of yours, when I try to run the program I get:

    " File "ttc.py", line 19
    fp = open(fileName, "wb")

    Syntax error: invalid syntax."

    I´m trying to get this to work to be used in a furniture that will talk through twitter about the place it´s in.

    Jacob

    ReplyDelete
    Replies
    1. Did you cut and paste the code from a browser? This can sometimes introduce errors with odd characters. Perhaps try again?

      Delete
  8. Hi Martin, I am creating a twitter application where your tutorial sounds really useful. After following the steps, I get this error:
    .Traceback (most recent call last):
    File "ttc.py", line 70, in on_receive
    content = json.loads(self.buffer)
    File "/usr/lib/python2.7/json/__init__.py", line 326, in loads
    return _default_decoder.decode(s)
    File "/usr/lib/python2.7/json/decoder.py", line 365, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    File "/usr/lib/python2.7/json/decoder.py", line 383, in raw_decode
    raise ValueError("No JSON object could be decoded")
    ValueError: No JSON object could be decoded
    Traceback (most recent call last):
    File "ttc.py", line 112, in
    client = TalkingTwitterStreamClient(getTwitterUserStreamURL())
    File "ttc.py", line 64, in __init__
    self.conn.perform()
    pycurl.error: (23, 'Failed writing body (0 != 1529)')

    You suggested Peter to check if the twitter application is setup properly. I did check twice, but have no clue what is missing. Your answer would be helpful.
    Thanks.
    Yesha

    ReplyDelete
  9. Help it runs fine for a little bit then crashes is there any advice you could give me to fix this



    Traceback (most recent call last):
    File "ttc.py", line 82, in on_receive
    speakSpeechFromText(u"{0[text]}".format(content))
    File "ttc.py", line 42, in speakSpeechFromText
    googleSpeechURL = getGoogleSpeechURL(phrase)
    File "ttc.py", line 30, in getGoogleSpeechURL
    data = urllib.urlencode(parameters)
    File "/usr/lib/python2.7/urllib.py", line 1312, in urlencode
    v = quote_plus(str(v))
    UnicodeEncodeError: 'ascii' codec can't encode character u'\u2026' in position 139: ordinal not in range(128)
    Traceback (most recent call last):
    File "ttc.py", line 112, in
    client = TalkingTwitterStreamClient(getTwitterUserStreamURL())
    File "ttc.py", line 64, in __init__
    self.conn.perform()

    ReplyDelete
  10. Picking Up The Tweets But Just Not Playing Audio

    ReplyDelete
    Replies
    1. An errors or just no sound? If there are no errors I would put my money on not the right output being selected. Are you using a speaker into the headphone jack or hdmi? Use 'sudo raspi-config', and Advanced, to pick the right audio output.

      Delete
    2. Thanks for your help:
      I Manage To Get Sound From The Internet Fine, When I Do CTRL+C it comes up with an error from translate. It is the same problem that RaiDevansh had.

      Delete
  11. hey martin,
    when copy and pasting your code are these the only things that need changing?

    CONSUMER_KEY = 'consumer key'
    CONSUMER_SECRET = 'consumer secret'
    ACCESS_TOKEN = 'access token'
    ACCESS_TOKEN_SECRET = 'access token secret'

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete

Note: only a member of this blog may post a comment.