RBMicroBlog, a demonstration Rhythmbox plugin

As part of Ubuntu Opportunistic Developer Week 2010, I gave an IRC talk about the basics of building Rhythmbox plugins. The talk described how to build a Rhythmbox Python plugin that provided a button which, when pushed, tweeted the currently playing song by using the Gwibber API. These are the supporting materials for that talk.

The talk itself

Today I'm going to talk about making a Rhythmbox plugin.
If you're using Lernid, you'll see slides with code examples. If you don't have Lernid, don't worry; all the code for this plugin is available, and you can look through it to see what I'm talking about, or you can load the slide deck yourself from http://www.kryogenix.org/code/RBMicroBlog
Specifically, I'm going to talk about making a Python plugin. If you're hoping for in-depth C knowledge, you're talking to the wrong dude.
Feel free to ask questions in #ubuntu-classroom-chat, and I'll answer them at the end; if someone could collect them, that'd be great.
What with this being Opportunistic Developer Week and all, I'm aiming for something which scratches a little itch.
The itch I want to scratch is explained by a tweet I saw from @mandel_macaque, which said


♥ Space Oddity by David Bowie #lastfm: http://bit.ly/15zv4
and I thought: hey, that's quite cool, he's tweeting songs that he's playing that he likes.
I'd like to do that. But I don't use last.fm, I use Rhythmbox to play my music.
So why not have a "tweet this song" button, so I can hit it when a song that I like comes along?
A Rhythmbox Python plugin comes in two parts: an .rb-plugin file, which describes your plugin, and a Python module.

Our first stage, then, is to set up the plugin itself.
Create the Rhythmbox plugins folder, which you might already have:
mkdir -p ~/.gnome2/rhythmbox/plugins
Now, create a folder for our plugin, which we'll call RBMicroBlog
mkdir -p ~/.gnome2/rhythmbox/plugins/RBMicroBlog
In there create a __init__.py file:
touch ~/.gnome2/rhythmbox/plugins/RBMicroBlog/__init__.py
and an .rb-plugin file:
gedit ~/.gnome2/rhythmbox/plugins/RBMicroBlog/RBMicroBlog.rb-plugin
Your rb-plugin file has a specific format:

[RB Plugin]
Loader=python
Module=RBMicroBlog
IAge=1
Name=Microblogging
Description=Microblog what you're listening to
Authors=Stuart Langridge <sil@kryogenix.org>
Copyright=Copyright © 2010 Stuart Langridge
Website=http://www.kryogenix.org/code/RBMicroBlog
The important lines in that are the two that tell Rhythmbox "this is a Python plugin", and "the module name for this plugin is RBMicroBlog".

Python, as you will (hopefully) know, treats a folder called X with an __init__.py file inside as a module named X.
So, we have our RBMicroBlog folder, with __init__.py inside, which is therefore a Python module
and also in that folder a .rb-plugin file which says "this plugin is provided by the RBMicroBlog module".
Right now, then, you have a plugin which doesn't do anything. Which means it has no bugs, true, but isn't that useful.
On to editing the Python. Opportunistic development is fun!
Basically, we want our plugin to do this:
  1. be a plugin that can be enabled and disabled
  2. put a "tweet this" button on the toolbar, with the Gwibber icon in it, and a tooltip of "microblog this song"
  3. when the button is pressed, work out which song is currently playing...
  4. and use the Gwibber API to microblog a message saying "Listening to: One Vision by Queen"
So, start with the basics.

A Rhythmbox plugin is built as a subclass of rb.Plugin, named the same as the plugin module.
So, a basic plugin would look like:
import rb
class RBMicroBlog(rb.Plugin):
    def activate(self, shell):
        print "Activate!"
    def deactivate(self, shell):
        print "Deactivate!"
Your plugin's "activate" function is called when the plugin is loaded, so it can do setup, and "deactivate" is called when the plugin is unloaded.
So, start Rhythmbox -- your plugin hasn't been enabled yet, so in Edit > Plugins, find "Microblogging", and tick it to turn it on.

You might be thinking: where does the output from my print statement go?
Rhythmbox hides output from plugins unless you want to see it. So, quit Rhythmbox, and restart it as rhythmbox -D RBMicroBlog Now, when you tick your Microblogging plugin on and off in Edit > Plugins, you should see output in the terminal:
(15:34:36) [0x8756028] [RBMicroBlog.activate] RBMicroBlog/__init__.py:9: Activate!
(15:34:38) [0x8756028] [RBMicroBlog.deactivate] RBMicroBlog/__init__.py:13: Deactivate!
Now, you've got an .rb-plugin file and a Python module, and it's loaded. That's the basics of every Python plugin. The only hard remaining bit is to, y'know, actually write the code that does what you want.
On, then, to stage 2: put a "tweet this" button on the toolbar.
This is about adding some UI, and is documented at http://live.gnome.org/RhythmboxPlugins/WritingGuide#Adding_UI
There are also more examples at http://live.gnome.org/Rhythmbox%20Plugins/Python%20Plugin%20Examples which is a very useful page.
If you come across more things that you want to do in Rhythmbox, please add them to that page!
(Also, adding those Rhythmbox plugin "snippets" to the python-snippets project in Launchpad would be pretty cool.)
Adding UI requires two stages: first, you define where you want to add the UI with some XML

and then you actually hook up a gtk.ActionGroup to the UI, as defined by the XML

To be honest, you can just borrow that bit from another plugin. Understanding gtk.Actions and gtk.ActionGroups is fine for those that want to, but if you just want to add a toolbar button and a menu item, steal the code from somewhere else (like this plugin).
Now, you'll have a button on the toolbar, and a menu item in the Tools menu. The button has no icon, though.
A bit more cookbook programming, now: how to load an icon and add it. The icon we want is /usr/share/pixmaps/gwibber.svg
So, some code to load that icon and make it available for your button

Again, don't worry too much about understanding this: the way Gtk deals with icons is pretty complicated. Borrow it for your own projects.
Now, you have a button on the toolbar and a menu item, and we've connected them up to call a function self.microblog.
Stage 3 is to implement that.
Handler functions, like this one, will be called with two parameters, "event" and "shell".
We want our microblog plugin to find out what's currently playing.
There are three ways you might approach the problem of "what do I do to find out what's playing?"
You could know already (if you're Jonoathan Matthew, the genius Rhythmbox maintainer)
You could look it up in the documentation (there isn't all that much; the pages linked above cover some things, but not lots)
Or you can do what I do, which is poke around in the Python objects.
For this, enable the Python Console plugin in Rhythmbox, and then run it from the Tools menu.
Now you have a Python console which is hooked up to Rhythmbox. You get a variable called "shell" for free, which is the Rhythmbox shell (and you'll notice that this shell is also being passed to your handler function).
In Python, to list all the properties of an object, use dir(). So, in the Python console, say: dir(shell)

Hm, that "get_player" looks useful.
So, say: shell.get_player()
<rb.ShellPlayer object at 0xa3ff9dc (RBShellPlayer at 0x9b68800)>
OK, and what's a "ShellPlayer"?
>>> player = shell.get_player()
>>> dir(player)
and "get_playing_entry" again looks useful
>>> player.get_playing_entry()
<RhythmDBEntry at 0xb3543150>
THe way Rhythmbox is set up is that there's a database, the RhythmDB, and each song in that database is defined by an "entry", of type RhythmDBEntry.
So, we've got the "entry" for the currently playing song. But an entry doesn't help much; we want the artist and the title.
Looking at the properties of the entry doesn't help much here, either.
Fortunately, the documentation comes to the rescue: http://live.gnome.org/Rhythmbox%20Plugins/Python%20Plugin%20Examples#How_do_I_get_the_metadata_details_of_a_song.3F
So, given an entry, we can get its title with shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE)
The first attempt at our microblog handler function, then, could look like this:
    def microblog(self, event, shell):
        entry = shell.get_player().get_playing_entry()
        title = shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE)
        artist = shell.props.db.entry_get(entry, rhythmdb.PROP_ARTIST)
        print "Listening to: %s by %s" % (title, artist)
and that should, when we hit the button, print out the message to the terminal.
And indeed it does (remember, rhythmbox -D RBMicroBlog !)
(16:38:44) [0x907a028] [RBMicroBlog.microblog] RBMicroBlog/__init__.py:112: Listening to: You Shook Me All Night Long by AC/DC
One more thing, though: what if there's nothing playing?
Well, poking around in dir(shell.get_player()) a bit more in the Python console reveals "get_playing".
So let's make the button not do anything if we're not playing.
Just add one new line to the top of the microblog function:
if not shell.get_player().get_playing(): return
OK, so now we have a button which prints out our microblog message. The final stage is to use Gwibber to actually post it for us.
Fortunately, someone's already done the work for us here. There's a snippet in the acire program which shows how to post a message via gwibber.
(If you don't know about acire, there's a session about it on Thursday; it's a library of useful Python snippets, and it's great.)
You can see the snippet itself, if you don't use acire, in Launchpad at http://bazaar.launchpad.net/~jonobacon/python-snippets/trunk/annotate/head:/gwibber/sendmessage.py
So add a couple more lines to our microblog function:
        gw = gwibber.lib.GwibberPublic()
        gw.SendMessage("Listening to: %s by %s" % (title, artist))

and we're all done!
This has been an incredibly brief tour of creating a Rhythmbox plugin, which ties into other cool bits of the Ubuntu platform.
If this has interested you, a few things you might want to try are:
Make the plugin tell you when it's successfully posted!
Change the plugin to disable the button and menu item when nothing is playing (you'll want to connect to Rhythmbox's start playing and stop playing signals)
Make "quickly create rhythmbox-plugin" work so no-one has to remember all the boilerplate
OK, that's it for the talk. I'll take some questions now.

Supporting materials

The plugin
The plugin files themselves, as a tarball. Save into .gnome2/rhythmbox/plugins
Slides
The supporting slide deck for the talk, as PDF or OpenOffice.org Impress

by Stuart Langridge