Tuesday, December 29, 2009

Talking Twitter client

So I learnt this afternoon about this Linux utility called festival. It's a text to speech conversion program. Running it is as simple as

echo "Hello world" | festival --tts

Moreover, installing it on Fedora is as easy as

sudo yum install -y festival

After that, a bit of a bash and a bit of a python and I had a twit-to-speech utility running.

The code is simply this much:

#!/bin/bash

TWITTERURL="http://twitter.com/statuses/friends_timeline.json"
JSON="/tmp/twittline.json"
SPEECH="/tmp/twt.message"
PYCODE="/tmp/twt2speech.py"

read -p "Username: " TUSER && \
read -sp "Password: " TPASS && \
curl -s -u $TUSER:$TPASS $TWITTERURL > $JSON

cat > $PYCODE << "EOF"
import json
import sys
import re
urlp="(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"
twits = json.load(open(sys.argv[1]))
for twit in twits:
    text = twit['user']['name']+' says: '+twit['text']
    text = re.sub(urlp, '', text) 
    print text
EOF

python $PYCODE $JSON > $SPEECH

while read line
do
notify-send -t 15000 "$line"
echo $line | festival --tts
sleep 1
done < $SPEECH

echo "THE END" | festival --tts

code syntax highlighting by GVIM

The above script will ask your twitter credentials, fetch latest 20 twits in friends' timeline, save into a JSON file. A short python script parses the JSON, extracts twit text and user's name from it and outputs in a sanitized format (it removes URLs, because there is no use hearing them).

The sanitized output is  saved in another text file, which is piped one line at a time to festival. In case the speech is not clear, it also shows the text in a pop-up using notify-send.

A full script with some error checking can be found here.

5 comments:

tonyyoungblood said...

Excellent program! I'd like to modify it to speak tweets with a search keyword, and I'm getting an error. Here is the part I modified:
___________________________________

#TWITTERURL="http://search.twitter.com/search.json?q=intangible"
TWITTERURL="http://twitter.com/statuses/friends_timeline.json"
JSON="/tmp/twittline.json"
SPEECH="/tmp/twt.message"
PYCODE="/tmp/twt2speech.py"

#read -p "Username: " TUSER && \
#read -sp "Password: " TPASS && \
#curl -s -u $TUSER:$TPASS $TWITTERURL > $JSON
curl -s -r 0-1000 $TWITTERURL > $JSON
_____________________________________

At this point, it's set to search for the keyword "intangible." I added the range of 0-1000 to prevent the Twitter download limit. I'll probably increase the range when I get it working. But now when I run it, I get this error:

Traceback (most recent call last):
File "/tmp/twt2speech.py", line 7, in
text = twit['user']['name']+' says: '+twit['text']
TypeError: string indices must be integers

Do you know what could be causing it?

Best,
Tony

tonyyoungblood said...

Okay, I made a mistake in my previous post. The first line of the code should be uncommented and the second line commented. Running that that way yields:

___________________________________
Traceback (most recent call last):
File "/tmp/twt2speech.py", line 5, in
twits = json.load(open(sys.argv[1]))
File "/usr/lib/python2.6/json/__init__.py", line 267, in load
parse_constant=parse_constant, **kw)
File "/usr/lib/python2.6/json/__init__.py", line 307, in loads
return _default_decoder.decode(s)
File "/usr/lib/python2.6/json/decoder.py", line 319, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python2.6/json/decoder.py", line 336, in raw_decode
obj, end = self._scanner.iterscan(s, **kw).next()
File "/usr/lib/python2.6/json/scanner.py", line 55, in iterscan
rval, next_pos = action(m, context)
File "/usr/lib/python2.6/json/decoder.py", line 183, in JSONObject
value, end = iterscan(s, idx=end, context=context).next()
File "/usr/lib/python2.6/json/scanner.py", line 55, in iterscan
rval, next_pos = action(m, context)
File "/usr/lib/python2.6/json/decoder.py", line 217, in JSONArray
value, end = iterscan(s, idx=end, context=context).next()
File "/usr/lib/python2.6/json/scanner.py", line 55, in iterscan
rval, next_pos = action(m, context)
File "/usr/lib/python2.6/json/decoder.py", line 183, in JSONObject
value, end = iterscan(s, idx=end, context=context).next()
File "/usr/lib/python2.6/json/scanner.py", line 55, in iterscan
rval, next_pos = action(m, context)
File "/usr/lib/python2.6/json/decoder.py", line 155, in JSONString
return scanstring(match.string, match.end(), encoding, strict)
ValueError: Unterminated string starting at: line 1 column 942 (char 942)

tonyyoungblood said...

I increased the range to 10000 and now I'm getting the original error again. :-(

yomguy said...

Hi !

Nice idea but could be much simpler using the python-twitter module :
http://code.google.com/p/python-twitter/

Example :

import os
import twitter

name = 'me'
pass = '******'

api = twitter.Api(username=name, password=pass)
timeline = api.GetUserTimeline()
for post in timeline:
os.system('echo "' + post.text.encode('utf8') + '" | festival --tts')


The last line should be indented.
;)

yomguy said...

Sorry,

timeline = api.GetFriendsTimeline()

should be more interesting !
:-]