Pandora-UPnP?

So I feel fairly comfortable saying that I have ‘solved’ the problem of using MPD via UPnP at least in a rough slightly-better-than-prototype sense.  I will of course continue to polish MPD-UPnP but I wonder, how can I make Zhaan even more useful?

There is this nifty library called pianobar with this even more nifty python api.  Pianobar is an API to pandora.  I wonder how much effort it would take to wrap pandora in UPnP?

Update I had the following pseudo monolog on IRC with a friend of mine discussing the upnp-pandora idea.  Perhaps there are more upnp wrappers to come:

01:51 < zgold> Wouldn't that be awesome
01:51 < zgold> have one UI and one app on your N900
01:51 < zgold> to control any UPnP device in your house
01:51 < zgold> any MPD device on the house (even on the phone)
01:52 < zgold> and Pandora locally on the phone
01:52 < zgold> now if only the local media player exposed itself as a UPnP
 MediaRenderer
01:52 < zgold> Zhaan would be the universal media UI =P
01:52 < zgold> though
01:52 < zgold> it does have a dbus api
01:52 < zgold> I could build a UPnP MediaRender wrapper around that
01:52 < zgold> *hmmmmm*

Zhaan Controller

I had some pretty serious issues with my scratchbox environment but thanks to the PR1.2 SDK my life is much easier and things are working again.  Since things are working Zhaan has been making some lovely progress.  In particular I have now implemented a complete control panel in Zhaan.   I have also implemented the needed functions in MPD-UPnP to support this kind of controlling and the result, as you can see below, is effectively a full GUI frontend for MPD over UPnP.  What’s even better is that this same application can control *ANY* UPnP Renderer 🙂

I’ve been in good communication lately with Anderson Lizardo and the crew at PyMaemo and, with any luck, all of this will be available on the N900 via extras devel very very soon!

Zhaan Controller

MPD-UPnP Media Serving!

Just a quick update on the status of MPD-UPnP. The 1 person who watches my github repo will have already noticed my lastest big push: http://github.com/ZachGoldberg/MPD-UPnP/commit/8a7fce2b4a11e63bc91ae372f6dfcd59beabd7c7 . As of this commit one can start a server from which one can entirely control playback via MPD. Steps 1) and 2) from my previous post are now complete!

Controlling the new MPD server via Zhaan actually works rather pleasantly and bug-free. I am doing most of my work remotely and have Zhaan open via ssh+xorg. My roomates must be freaking out with the music constantly jumping around in my room! I cannot wait to port Zhaan to Maemo 5 and use this from my N900!

MPD-UPnP Status update

I have decided that, for now anyway. I am going to substitute my MPD-UPnP project for the Canola GUPnP plugin as part of my project for Penn. If time permits (and I can get Canola to work on my machine, sigh) I will still do the Canola integration.

So, to finish of MPD-UPnP we need the following features:

1) MPD as a Media Renderer of content that MPD already knows about
2) MPD as a Media Server of content MPD already knows about that only MPD can consume

To make this really awesome, we should consider also implementing:

3) MPD as a Media Renderer of arbitrary UPnP-exposed audio content
4) MPD as a Media Server of its own data to other peers.

1) and 2) require simply mapping MPD commands and database content to a UPnP interface. Components 3 and 4 require the wrapper to actually become a full fledged server (to host via UPnP all of MPD’s content for 4) and a full fledged proxy (for MPD to use ‘downloaded’ UPnP content, for 3).

As of now 1) is basically complete. I am working on 2) and have come across the need to generate DIDL data. Before we get into that, lets do a quick overview of how UPnP works for media browsing and serving.

UPnP, at its core, is merely a specification for device discovery (multicast + ssdp), state control (SOAP) and resource access (plain old HTTP 1.0/1.1). The most importaqnt here is the SOAP definitions. What functions should exist? What datatypes are passed? These questions are answered by several pre-defined UPnP specifications, such as a MediaServer (a ContentDirectory) or a MediaRenderer (an AVTransport). The specifications define the functions and arguements to for all of these devices and services.

One can imagine this works very well for simple things. For example, an AVTransport has a function “Play” which takes very few arguments (a speed is oen of the few significant ones). However, for something more complicated, like Browse on a ContentDirectory more than simple argument passing is required. Why? Well, lets say a user asks to Browse some folder foo which has 100 pieces of data. One could simply return a list of filenames, but what about all the other important data for those items? Their UPnP Class, if its audio what the title is, a resource url to download them etc. To solve this problem a DIDL (Digital Item Declaration Language) is used. The result of a Browse call isnt a simple list, its a DIDL. (DIDL is obsentisbly just XML, so the actual datatype that Browse returns is just a string).

So, back to the MPD wrapper. To implement a server we must be able to respond to Browse calls; hence we need to generate DIDL. GUPnP-AV provides a convenient mechanism for doing this. A simple usage looks as follows:


w = GUPnPAV.GUPnPDIDLLiteWriter.new("English")
container = w.add_container()
container.set_title("Container Title String (a folder name or other)")
didl_string = w.get_string()

You’ll notice this is rather not-pythonic. The writer keeps an internal reference to the container it returned in add_container such that when you later call writer.get_string() it knows the containers (and items and resources etc.) that it has allocated and how to render them as XML. It is, however, a rather nice API when all is said and done and is thus far working wonderfully for component #2 of MPD-UPnP.

It’s Alive — MPD + UPnP

I’ve put together a (working) quick and dirty script to wrap MPD in a UPnP Media Renderer. This is far from a complete solution, but it allows playing/pausing via UPnP Control Points. (Python-GUPnP CP works just dandy for it!)


from gi.repository import GUPnP, GObject, GLib
import mpd

CON_ID = None
MPDCLIENT = None

GObject.threads_init()

def setup_server():

ctx = GUPnP.Context(interface="eth0")

ctx.host_path("device.xml", "/device.xml")
ctx.host_path("AVTransport2.xml", "/AVTransport2.xml")

desc = "device.xml"
desc_loc = "./"

rd = GUPnP.RootDevice.new(ctx, desc, desc_loc)
rd.set_available(True)

return rd

def setup_mpd():
global CON_ID, MPDCLIENT
HOST = 'localhost'
PORT = '6600'
CON_ID = {'host':HOST, 'port':PORT}

MPDCLIENT = mpd.MPDClient()


def on_play_action(service, action):
print "Play"
MPDCLIENT.connect(**CON_ID)
MPDCLIENT.play()
MPDCLIENT.disconnect()

def on_pause_action(service, action):
print "Pause"
MPDCLIENT.connect(**CON_ID)
MPDCLIENT.pause()
MPDCLIENT.disconnect()

rd = setup_server()
print "UPnP MediaRenderer Service Exported"

setup_mpd()
print "MPD Client Setup"

service = rd.get_service("urn:schemas-upnp-org:service:AVTransport:1")
service.connect("action-invoked::Play", on_play_action)
service.connect("action-invoked::Pause", on_pause_action)

print "Awaiting commands..."
GObject.MainLoop().run()

Update: This is an updated version of the script that supports more commands and properly ends each UPnP action. Unfortunately it doesn’t entirely work because of either 1) A bug in g-i or 2) a problem with the gupnp api that will require a break to fix. Spent a lot of time today to understand the problem, hopefully we’ll have either a gupnp or a g-i fix in a few days.

Update 2: Now available on github. Zhaan (formerly known as “GUPnP-Python UPnP Control Point” has also been pushed)