google-voice-call.py

Published
Last Updated
Author(s)
Enimihil
Tags
#code

Simple python script that automates the Google Voice web interface to make outgoing calls. Requires httplib2, simplejson, and BeautifulSoup

This script is not tested on Windows but should work, it looks for a file in the users home directory named .gvoice_auth to determine the credentials to use and the default number to use for the 'from' leg of the call. The file is a JSON encoded object with the following attributes:

user
Your username / email address that you use to login to Google Voice
passwd
The password you use to login to Google Voice
default_from : optional
The number to use for your leg of the call if you don't specify a different one.

For example:

{'user':'me@gmail.com',
 'passwd':'lamepasswd',
 'default_from':'+15855551234'}

Then, simply run the script:

$ python google-voice-call.py 5855551235
Call request returned: {"ok":true,"data":{"code":0}}

Here's the code.

google-voice-call.py (Source)

#!/usr/bin/env python
'''
 A ugly script to pretend that Google Voice has an API for calling out.
'''

import sys, os, re
import urllib, httplib2
from os import path
from simplejson import load
from BeautifulSoup import BeautifulSoup

def main():
    if not path.exists(path.join(
                        path.expanduser('~'),
                        '.gvoice_auth')):
        return """
You need to create a config file in your home directory\
called '.gvoice_auth' with your Google Account's credentials.\
"""
    else:
        creds = load(open(path.join(path.expanduser('~'),'.gvoice_auth')))

    if len(sys.argv) == 2:
        fromnum = creds['default_from']
        tonum = sys.argv[1]
    elif len(sys.argv) == 3:
        fromnum = sys.argv[2]
        tonum = sys.argv[1]
    else:
        return "Invalid number of arguments\nUsage: %s <number> from_number]" % sys.argv[0]

    auth_tok = authenticate(creds['user'], creds['passwd'], 'grandcentral')


    r = call(tonum, fromnum, auth_tok)

    print "Call request returned: ", r


def call(number, fromnum, auth):
    data = {
        'outgoingNumber': number,
        'forwardingNumber': fromnum,
        'remember': 0,
        }

    main_uri = "https://www.google.com/voice"
    call_uri = "https://www.google.com/voice/call/connect/"
    headers = { 'Content-Type': 'application/x-www-form-urlencoded',
                'Cookie': 'gv=%s' % auth }

    h = httplib2.Http()

    response, content = h.request(main_uri, 'GET', headers=headers)
    if not response['status'].startswith('2'):
        raise Exception(response['status'] + ': ' + content)

    soup = BeautifulSoup(content)
    _rnr_se = soup.find('input', attrs={'name':'_rnr_se'})

    data['_rnr_se'] = _rnr_se['value']

    response, content = h.request(call_uri, 'POST', body=urllib.urlencode(data),
                                  headers=headers)
    if response['status'].startswith('2'):
        return content
    else:
        raise Exception(response['status'] + ': ' + content)


def authenticate(user, passwd, service):
 '''
 Basic perform ClientLogin method of authenticating a Google Account.

 Returns the Auth=... bit required in the Authorization header.
 '''
    h = httplib2.Http()
    uri = 'https://www.google.com/accounts/ClientLogin'

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    request = urllib.urlencode({
        'Email': user,
        'Passwd': passwd,
        'service': service,
        })

    response, content = h.request(uri, 'POST', body=request, headers=headers)
    if response['status'] == '200':
        return re.search('Auth=(\S*)', content).group(1).strip()
    else:
        raise Exception(response['status'] + ': ' + content)

if __name__=='__main__':
    sys.exit(main())

UPnP ATSC Live Video Streaming

Published
Last Updated
Author(s)
Enimihil
Tags
#atsc #dvb #dvbstreamer #linux #mediatomb #ps3 #technology #upnp #video

This article will guide you through the process of setting up live video streaming from a DVB (ATSC) capture card in Linux to a DLNA UPnP Media Renderer that supports MPEG-2 Transport Streams (such as a PS3).

What you need

Mediatomb
Mediatomb is a DLNA/UPnP media server for Linux. You'll want to set up mediatomb to play regular files to your MediaRenderer before attempting the configuration in this guide. It helps to know how the javascript import and transcoding configuration in Mediatomb works, but this guide should be adequate as long as you don't need to do anything fancy.
DVBStreamer
This is the tool that directly talks to your DVB capture card and streams the video. This would be all that you need if your MediaRenderer understands how to listen to a UDP stream. The can't do this (or I couldn't figure out how to do this) so we need one more tool...
NetCat
The swiss-army knife of networking, this tool allows us to take the UDP stream that DVBStreamer gets us and pipe it through a FIFO with a simple script.

Configuring Mediatomb

The tricky bit to get streaming working correctly is getting Mediatomb to do the hard work of talking to the DLNA devices — in a language they understand.

You'll want to set up Mediatomb and get it talking to your UPnP media renderer before trying to do this; you can try to do it all at once, but don't say I didn't warn you.

Firstly, you need to use an import script rather than the built-in import provided by mediatomb for scanning files. The XML config for this will look something like the following:

<scripting script-charset="UTF-8">
 <common-script>/usr/share/mediatomb/js/common.js</common-script>
 <playlist-script>/usr/share/mediatomb/js/playlists.js</playlist-script>
 <virtual-layout type="js">
 <import-script>/etc/mediatomb/import.js</import-script>
 </virtual-layout>
</scripting>

This config goes in the "<import>" tag of the "config.xml" file.

You also need to add a map from a file extension to a mimetype, I used ".live" -> "video/x-dvb-stream", but you could use whatever you like, the mimetype doesn't escape mediatomb, it's just used so we can tell mediatomb to "transcode" the files into something else. Add the following to the "<extension-mimetype>" tag:

<map from="live" to="video/x-dvb-stream"/>

Finally, you need to add a transcoding profile for the "video/x-dvb-stream" mimetype to transcode it into a "video/mpeg" stream. The "<transcoding>" tag should look something like the following:

<transcoding enabled="yes">
 <mimetype-profile-mappings>
 <transcode mimetype="video/x-dvb-stream" using="dvb-stream"/>
 </mimetype-profile-mappings>
 <profiles>
 <profile name="dvb-stream" enabled="yes" type="external">
 <mimetype>video/mpeg</mimetype>
 <accept-url>no</accept-url>
 <first-resource>yes</first-resource>
 <hide-original-resource>yes</hide-original-resource>
 <agent command="/path/to/bin/dvbstream-live" arguments="%in %out"/>
 <buffer size="524288" fill-size="262144" chunk-size="1024"/>
 </profile>
 </profiles>
</transcoding>

Don't worry about the "dvbstream-live" script just yet, we need to get DVBStreamer set up first.

The final piece to the puzzle is the "import.js" script. This is where having some knowledge of javascript and what you want to do helps quite a bit. You should copy the default import.js script from wherever it's installed, and then add the following code:

function videoIsLiveChannel(obj) {
 var pattern = /(.*)\.live/;
 var match = pattern.exec(obj.title);

 if( match ) {
 var container_chain = new Array('video', 'television' );

 obj.title = match[1];

 print("Parsed channel info: " + obj.title);
 addCdsObject(obj, createContainerChain(container_chain));
 return true;
 } else {
 return false;
 }
}

You also need to add the following to the top of the "addVideo" function:

if(videoIsLiveChannel(obj)) {
 return;
}

This tells the import script that if the file to be imported is a ".live" file, we want to add it to the "video/television" section of the UPnP media library, sans ".live" ending. Strictly speaking, adding this to the import script is not necessary, but it makes the listings look a bit nicer.

Setting up DVBStreamer

DVBStreamer is a simple tool that reads from a DVB capture card and can stream (and filter) the broadcast to a "udp://" url. To get DVBStreamer up and running you will need to already have a "channels.conf" file, generated by a program like "dvbscan". Then simply run the "setupdvbstreamer" command with the appropriate options to get dvbstreamer working.

Use the DVBStreamer Wiki to look up how to do some basic tests, stream some video to a file, or use mplayer to watch a UDP stream; essentially, you want to verify that DVBStreamer is working correctly and doing what you expect.

To stream using the scripts below you need to run DVBStreamer as a daemon and set to stream to "udp://localhost:12346". Create a file "dvbstreamer.startup" with a single line:

setmrl udp://localhost:12346

Start DVBStreamer like this:

dvbstreamer -f dvbstreamer.startup -d

Now all you need to do is get the scripts to do the "transcoding" and create some ".live" files for your television channels.

The "transcoding" script(s)

There are two scripts that are used by the streaming system, one changes the channel in DVBStreamer so the correct video is streamed, the other pushes the stream from the UDP stream to a file (FIFO) which mediatomb provides as an argument.

Here's the first, the channel changing script. It requires python >=2.5 (and <3.0). It expects the path to a ".live" file as it's first (and only) argument, and connects to the DVBStreamer instance (using the default authentication credentials) to select the DVBStreamer service corresponding to the name of the file. e.g. "/home/media/21-1 WXXI-HD.live" would select the "21-1 WXXI-HD" DVBStreamer service.

#!/usr/bin/env python
from __future__ import with_statement

import sys
import socket
from contextlib import closing
from os import path

class DVBStreamerException(Exception):
    pass

def get_response(streamer):
    resp = []
    while True:
        try:
            resp.append(streamer.recv(4096))
        except socket.error, e:
            if resp and e.args[0] == 11: # temp unavail
                break
            elif e.args[0] == 11:
                pass
            else:
                raise
    if not resp:
        raise DVBStreamerException("Connection closed?")
    resp = ''.join(resp).splitlines()
    if  not resp[-1].startswith('DVBStreamer'):
        return resp + get_response(streamer)

    code = resp[-1].rsplit('/', 1)[-1]
    if int(code.split()[0]) != 0:
        raise DVBStreamerException(resp)
    else:
        return resp[:-1]

def dvbstreamer_cmd(cmd, auth=False, adapter=0):
    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
        sock.connect(('localhost', 54197+adapter))
        sock.setblocking(0)
        #print "connected, waiting for ready...",
        get_response(sock)
        #print 'got ready: '
        if auth:
            #print 'sending auth...',
            sock.send("auth dvbstreamer control\n")
            get_response(sock)
            #print 'auth success'
        print 'sending cmd: ', cmd.strip()
        sock.send(cmd)
        return get_response(sock)

def main():
    #l = open('/tmp/channel.log', 'w')
    #print >>l, sys.argv
    channel = sys.argv[1]
    if channel.endswith(".live"):
        channel = path.splitext(path.basename(channel))[0]
    #print >>l, channel
    dvbstreamer_cmd("select %s\n" % channel, auth=True)

    #l.close()

if __name__=='__main__':
    main()

Here's the (much simpler) script that calls the above script and hands off to netcat to push the stream to the FIFO.

#!/bin/bash
/path/to/dvbstream-channel "$1"
exec nc -l -u -p 12346 localhost > "$2"

Put these two scripts someplace you can remember and ensure they can be executed by the user running mediatomb.

Wrap-Up

That should be all there is to it, you can download a tarball containing the scripts and config snippets in the article to make life easier.