Monday, January 30, 2012

Spotify: Song change notifications with AlbumArt using libnotify

I have borrowed the original script from here.

But, the script had minor issues. I have updated the script to use “mpris:artUrl” instead of taking the artwork from last.fm. No need of xml module anymore. Also, if the first notification was left unattended, I wasn't shown the notification for next song. Corrected that too.



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

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

"""

import os
import dbus
import gobject
import pynotify
import httplib2
from urllib import quote
from cgi import escape
from hashlib import md5
from dbus.mainloop.glib import DBusGMainLoop
from dbus.exceptions import DBusException

HTTP_CACHE_DIR = os.path.expanduser("~/.cache/spotify/http/")
IMAGE_CACHE_DIR = os.path.expanduser("~/.cache/spotify/art/")

MISSING = os.path.realpath(os.path.join(os.path.dirname(__file__), "missing.png"))

if not os.path.isdir(HTTP_CACHE_DIR):
    os.makedirs(HTTP_CACHE_DIR)
if not os.path.isdir(IMAGE_CACHE_DIR):
    os.makedirs(IMAGE_CACHE_DIR)

class SpotifyNotifier(object):

    def __init__(self):
        """initialise."""
        bus_loop = DBusGMainLoop(set_as_default=True)
        self.bus = dbus.SessionBus(mainloop=bus_loop)
        loop = gobject.MainLoop()
        self.http = httplib2.Http(HTTP_CACHE_DIR)
        self.notify_id = None
        try:
            self.props_changed_listener()
        except DBusException, e:
            if not ("org.mpris.MediaPlayer2.spotify "
                    "was not provided") in e.get_dbus_message():
                raise
        self.session_bus = self.bus.get_object("org.freedesktop.DBus",
                                 "/org/freedesktop/DBus")
        self.session_bus.connect_to_signal("NameOwnerChanged",
                                        self.handle_name_owner_changed,
                                        arg0="org.mpris.MediaPlayer2.spotify")

        loop.run()

    def props_changed_listener(self):
        """Hook up callback to PropertiesChanged event."""
        self.spotify = self.bus.get_object("org.mpris.MediaPlayer2.spotify",
                                           "/org/mpris/MediaPlayer2")
        self.spotify.connect_to_signal("PropertiesChanged",
                                        self.handle_properties_changed)

    def handle_name_owner_changed(self, name, older_owner, new_owner):
        """Introspect the NameOwnerChanged signal to work out if spotify has started."""
        if name == "org.mpris.MediaPlayer2.spotify":
            if new_owner:
                # spotify has been launched - hook it up.
                self.props_changed_listener()
            else:
                self.spotify = None

    def handle_properties_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('utf8'))
                album = unicode(metadata.get("xesam:album").encode('utf8'))
                artist = unicode(metadata.get("xesam:artist")[0].encode('utf8'))
                image_url = unicode(metadata.get("mpris:artUrl"))
                hash_ = md5()
                hash_.update("%s-%s" % (artist, album))
                hash_path = hash_.hexdigest()
                image_path = os.path.join(IMAGE_CACHE_DIR, hash_path)
                if not os.path.exists(image_path):
                    try:
                        response, image_contents = self.http.request(image_url)
                        if image_contents:
                            fh = open(image_path, "w")
                            fh.write(image_contents)
                            fh.close()  
                    except:
                        image_path = MISSING

                if image_path != MISSING:
                    alert = pynotify.Notification(title.replace("&","&amp;"),"by <b>%s</b> from <b>%s</b>" % (artist.replace("&","&amp;"), album.replace("&","&amp;")), image_path)
                    if self.notify_id:
                        alert.props.id = self.notify_id
                    alert.set_urgency (pynotify.URGENCY_CRITICAL)
                    alert.set_timeout(3)
                    alert.show()
                    alert.close()
                    self.notify_id = alert.props.id


if __name__ == "__main__":
    SpotifyNotifier()


4 comments:

  1. Thanks, would you mind me publishing a fork of this on GitHub?

    I've modified the script to use libnotify instead of pynotify as this works better on my setup...

    ReplyDelete
  2. I'll be glad. Go ahead and make it better. :)

    ReplyDelete
  3. Well, thanks a lot. My improvements (I hope :D) can be found at the following URL:
    https://github.com/bdalenoord/spotify-ob-wrapper

    ReplyDelete
  4. Would you make a rainmeter skin/script that hooks cover art?

    ReplyDelete