Spotify ScreenSaver Toggle with D-bus

UPDATE! - see This post for the latest version of the spotify screensaver toggle

The latest version of the spotify client for linux (0.4.8) has added partial mpris support.

This is great as it means controlling spotify via D-bus is far easier than the previous hacks using wmctrl and xvkbd.

I've updated the screensaver toggle script to use spotify's D-bus interface to play/pause spotify. This also means that I've been able to de-couple the script from having to run the screensaver which a big improvement on the old version.

To run it just add the script to your start-up items.

Edit Startup Program Dialogue

Here's the code, which is a lot more concise compared to the previous version.

Updated to use Mpris2 as of revision 9 for v0.4.8.306...

#!/usr/bin/env python

""" 
Title: Spotify Screensaver Toggle
Author: Stuart Colville, http://muffinresearch.co.uk/
License: BSD

Requires Spotify Linux Preview.

Usage:

 * Save the script
 * Ensure it is executable: chmod +x /path/to/script
 * Add the script to your start-up items. 

"""

import dbus
import gobject
from dbus.mainloop.glib import DBusGMainLoop

class SpotifyScreenSaverPause(object):
    """This pauses and plays spotify when the screensaver is activated"""

    def __init__(self):
        """init class."""
        dbus_loop = DBusGMainLoop(set_as_default=True)
        self.bus = dbus.SessionBus(mainloop = dbus_loop)
        self.loop = gobject.MainLoop()
        self.start_listen()
        self.was_playing = 0
        self.loop.run()

    def start_listen(self):
        """Listen to screensaver ActiveChanged events from dbus."""
        screensaver = self.bus.get_object('org.gnome.ScreenSaver', 
                                                  '/org/gnome/ScreenSaver')
        screensaver.connect_to_signal("ActiveChanged", self.play_pause)

    def play_pause(self, *args, **kwargs):
        """Toggle play/pause."""
        try:
            spotify = self.bus.get_object("org.mpris.MediaPlayer2.spotify", "/")
            spotify_iface = dbus.Interface(spotify, dbus_interface="org.freedesktop.MediaPlayer2")
            if self.was_playing:
                spotify_iface.Play()
                self.was_playing = 0
            else:
                if spotify_iface.GetMetadata():
                    self.was_playing = 1
                spotify_iface.Pause()
        except dbus.exceptions.DBusException, e:
            # Ignore exception caused by spotify not being started.
            if e.get_dbus_name() != 'org.freedesktop.DBus.Error.ServiceUnknown':
                raise

if __name__ == "__main__":
    ss = SpotifyScreenSaverPause()

See the latest version of the code here Spotify Screensaver Toggle

If you want to investigate what's possible with D-bus and spotify, D-feet is an excellent tool for investigating D-bus:

D-feet D-bus DebuggerClick for a larger version.

Further Examples

Here's an example to get the metadata from a track currently playing. (Note: if Spotify isn't playing this will be an empty dictionary.)

>>> import dbus
>>> bus = dbus.SessionBus()
>>> spotify = bus.get_object("org.mpris.spotify", "/")
>>> spotify_iface = dbus.Interface(spotify, dbus_interface="org.freedesktop.MediaPlayer")
>>> spotify_iface.GetMetadata()
dbus.Dictionary({dbus.String(u'album'): dbus.String(u'Getz/Gilberto #2', variant_level=1), dbus.String(u'title'): dbus.String(u"Here's That Rainy Day", variant_level=1), dbus.String(u'artist'): dbus.String(u'Stan Getz', variant_level=1), dbus.String(u'year'): dbus.Int32(1964, variant_level=1), dbus.String(u'location'): dbus.String(u'spotify:track:1RIfT0Y0TwP2M38pLLfDeJ', variant_level=1), dbus.String(u'time'): dbus.UInt32(242L, variant_level=1), dbus.String(u'tracknumber'): dbus.Int32(4, variant_level=1)}, signature=dbus.Signature('sv'))
>>> print spotify_iface.GetMetadata().get("title")
Here's That Rainy Day
>>> print spotify_iface.GetMetadata().get("location")
spotify:track:1BTO5gV9xSAB7EOBKSlcFY
>>>

With the latest Spotify update to use MPRIS2 (0.4.8.306.xxx) - the TrackChanged signal has gone and there's no PropertiesChanged signal as per MPRIS2 spec http://www.mpris.org/2.0/spec/ I'll update the notification example script below as soon as that is rectified. It was there all along. I've updated the code to reflect the use of PropertiesChanged

To take that example further here's a demo that listens to the TrackChangedPropertiesChanged event. It uses the notification id so we can replace the notification details as you move through the tracks. This avoids waiting for the timeout before displaying the current track information.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Title: Spotify Notification Demo
Author: Stuart Colville, http://muffinresearch.co.uk
License: BSD

"""

import dbus
import gobject
from dbus.mainloop.glib import DBusGMainLoop
import pynotify

class SpotifyNotifier(object):

    def __init__(self):
        """initialise."""
        bus_loop = DBusGMainLoop(set_as_default=True)
        bus = dbus.SessionBus(mainloop=bus_loop)
        loop = gobject.MainLoop()
        self.spotify = bus.get_object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2")
        self.spotify.connect_to_signal("PropertiesChanged", self.track_changed)
        self.notify_id = None
        loop.run()

    def track_changed(self, interface, changed_props, invalidated_props):
        """Handle track changes."""
        metadata = changed_props.get("Metadata", {})
        if metadata:
            if pynotify.init("Spotify Notifier Demo"):

                title = unicode(metadata.get("xesam:title").encode("latin1"))
                album = unicode(metadata.get("xesam:album").encode("latin1"))
                artist = unicode(metadata.get("xesam:artist").encode("latin1"))
                
                alert = pynotify.Notification(title,
                                 "by %s from %s" % (artist, album))
                if self.notify_id:
                    alert.props.id = self.notify_id
                alert.set_urgency (pynotify.URGENCY_NORMAL)
                alert.show()
                self.notify_id = alert.props.id

if __name__ == "__main__":
    SpotifyNotifier()

Note this notification demo assumes spotify is running to set-up the listener, so it's not something you can just set running at start-up unless you use it to wrap the start-up of spotify.

Spotify Notifications of Track Changes

Both scripts are BSD licensed so feel free to use and adapt to your own purposes.

Show Comments