June 2, 2008
Hey Guys -- Appreciate My Musical Inclinations!
One of the things that’s been a hot topic of sorts in #banshee
that isn’t such a big thing outside of a few “minor” – I say minor
because even though it’s something I personally think is just the
bee’s knees isn’t so high on other folks’ lists – use-cases.
That’d be the fairly awesome and extensive array of DBus methods Banshee provides for users to interact with it in various ways. One of the most obvious ways is to retrieve the “now playing” information, that is, various information about the current track that Banshee is playing.
In Banshee 1.x, there’s a fairly easy way to find that out already:
wfarr@linux:~$ banshee-1 --query-all URI: file:///home/wfarr/Music/Death%20Cab%20For%20Cutie/We%20Have%20The%20Facts%20And%20Were%20Voting%20Yes/02.%20The%20Employment%20Pages.mp3 year: 2000 rating: 0 name: The Employment Pages disc: 0 artist: Death Cab for Cutie album: We Have the Facts and We're Voting Yes track-count: 10 length: 244.48 track-number: 2 position: 223.3 volume: 80 current-state: playing last-state: loaded can-pause: true can-seek: true
Well, that’s cool and all, but, some of that information is a bit unnecessary (not unlike people with glitter in their hair). It’s not especially useful in IRC channels either since spamming seems to have the adverse effect of getting you @KICK@ed pretty quickly.
So how then are we to do cool things?
Well, I wasn’t sure at first either.
See, Banshee provides a cool little property inside the
/org/bansheeproject/Banshee/PlayerEngine object called
CurrentTrack which is exactly what we want.
The issue seemingly arose from the fact that unless an object
provided the org.freedesktop.DBus interface and thus the
Get and Set methods, you were a bit S-O-L.
Notice I said “seemingly”.
In the best of traditions, I was wrong.
Yeah, totally unbelievable—I know!
But after the initial shock – ooh yeah feel that snarkiness -
I was corrected by RAOF who, interestingly enough, had gone
through the same stages as I.
It turns out, by no unlucky coincidence, that DBus supports
some magical foo that allows you to prepend any property name
with Get et voila, you now have an accessor method.
So, I’ve finally got a GetCurrentTrack method that’ll do the
trick of returning a dict of the track information to me.
How did I opt to implement it?
(require 'dbus) (defun-rcirc-command np (arg) "Figure out what's currently playing in Banshee (works with 1.0)." (interactive) (let* ((obj "org.bansheeproject.Banshee") (path "/org/bansheeproject/Banshee/PlayerEngine") (interface "org.bansheeproject.Banshee.PlayerEngine") (info (dbus-call-method :session obj path interface "GetCurrentTrack")) (artist (caadr (assoc "artist" info))) (album (caadr (assoc "album" info))) (name (caadr (assoc "name" info)))) (if (string= album artist) (setq album (concat album " (self-titled)"))) (rcirc-action (format "is now playing \"%s\" by %s on %s" name artist album)))) (defun rcirc-action (string) "Since IRC has some flaws in it, this function is here to basically allow you to define rcirc functions that make use of /ME type messages, easily." ;; Print locally (rcirc-print (rcirc-buffer-process) (rcirc-buffer-nick) "ACTION" target string) ;; Send to irc (rcirc-send-string process (format "PRIVMSG %s :\C-aACTION %s\C-a" target string)))
Yep—I heard the collective groan among you all.
Emacs Lisp.
I could go on about how neat Lisp is, even if Emacs Lisp isn’t the ideal one, but let’s not waste time. The above code requires a fairly recent version of Emacs compiled with DBus support enabled, and naturally, is reliant on rcirc (shipped with GNU Emacs).
rcirc-action is a bit of an ugly mess, by no real fault of rcirc.
IRC can be a bit of a pain in the backside to interface with, so
it took some ugly looking strings to whip it into compliance.
On the whole, the semantics of that function are a bit unimportant,
so let’s move on to the meat of the code.
Up until the assignment of artist et al. things look fairly simple,
and not unlike similar code you might see in other DBus bindings
(aside from syntax differences, etc).
Not everyone’s a Lisp fiend, so caadr isn’t something you’d be
all too familiar with.
To keep things brief, because of the nature of Lisp to manipulate
data stored in lists, various combinations of consecutive calls of
both car and cdr (both core axioms of any Lisp derivative; the
former returning the first item of a list, and the latter returning
a list of every item in the list except the first) in order to make
the code less ugly, lengthy, nested, and a handful of other things.
So, a little bit of a logical leap forward, we see that (caadr 'foo)
is really (car (car (cdr 'foo))).
That might seem confusing at first.
Why would I take the car of the car of a list.
That should cause issues, since generally that isn’t going to be a list.
It doesn’t in this case, because (assoc artist info) returns this:
("name" ("The Employment Pages"))Following this, here’s my own mock imitation of what a REPL would spit for various things:
> (setq name (assoc "name" info))
("name" ("The Employment Pages"))
> (cdr name)
(("The Employment Pages"))
> (car (cdr name))
("The Employment Pages")
> (car (car (cdr name)))
"The Employment Pages" Hopefully the Elisp version now makes sense. If it doesn’t, you probably don’t use, nor even like Lisp, in which case, you really ought. If it does and you now want to gouge your eyes out because you too hate Lisp, shame on you for not sharing my taste in languages.
But, hey, I’m feeling nice. I was going to do a version in ruby-dbus1, but it turns out that said library has a huge bug in its introspection right now, meaning that it wouldn’t work. So, you’ll have to settle for some very not-highlighted Python:
import dbus
bus = dbus.SessionBus()
proxy = bus.get_object('org.bansheeproject.Banshee',
'/org/bansheeproject/Banshee/PlayerEngine')
iface = dbus.Interface(proxy,
dbus_interface='org.bansheeproject.Banshee.PlayerEngine')
info = iface.GetCurrentTrack()
print "Now playing \"%s\" by %s on %s" % (info["name"], info["artist"], info["album"])The Python version is much simpler, and shorter than the Elisp version because the dicts that DBus uses are pretty near the same as what Python uses. Thus, Python has syntax for handling that foo. Such is life.
Anyway, brag about your tunes with flair (or whatever it is you call it).
1 Ruby-DBus is good stuff anyway.
Comments (Feed)
No, the collective groan was for the bit where you assumed anyone else on IRC gave a flying cupcake what you’re listening to. People seem to have been labouring under this misapprehension ever since some idiot first did a Dr. Frankenstein on WinAmp and mIRC…
Well, it’s a darn good thing decent IRC client support plugins which are more than capable of filtering out undesirable messages.
Emacs does have a repl. M-x ielm RET1
@Anonymous: Ah – thanks. I normally just use C-x C-e to evaluate code, and thus fetch the return values from the minibuffer.
ack! Banshee? ack! lisp? ack!