Banshee Plugin
- From: William Friesen <wfriesen gmail com>
- To: kupfer-list gnome org
- Subject: Banshee Plugin
- Date: Mon, 4 Apr 2011 16:46:36 +1000
Here is a Banshee plugin that is heavily based off of the existing
Rhythmbox plugin. Seeing as Ubuntu 11.04 will be shipping with Banshee
as the default media player, some may find this useful.
Any feedback/suggestions welcome.
Thanks.
__kupfer_name__ = _("Banshee")
__kupfer_sources__ = ("BansheeSource", )
__description__ = _("Play and enqueue tracks and browse the music library")
__version__ = ""
__author__ = ("William Friesen <wfriesen gmail com>, "
"Ulrik Sverdrup <ulrik sverdrup gmail com>")
import dbus
import unicodedata
from hashlib import md5
from contextlib import closing
import os
import sqlite3
from kupfer.objects import (Leaf, Source, Action, RunnableLeaf,
SourceLeaf )
from kupfer import icons, utils, config
from kupfer import task
from kupfer.obj.apps import AppLeafContentMixin
from kupfer.plugin import rhythmbox_support
from kupfer.plugin.rhythmbox import _locale_sort_artist_album_songs
NEEDED_KEYS= set(("title", "artist", "album", "track-number", "location", ))
UNICODE_KEYS = set(("title", "artist", "album"))
def get_banshee_songs(dbfile, typ="song", keys=NEEDED_KEYS):
"""Return a list of info dictionaries for all songs
in a Banshee library database file, with dictionary
keys as given in @keys.
"""
banshee_dbfile = os.path.expanduser(dbfile)
lSongs = []
with closing(sqlite3.connect(banshee_dbfile, timeout=1)) as conn:
c = conn.cursor()
c.execute("""SELECT CoreAlbums.Title, CoreArtists.Name, CoreTracks.Title, CoreTracks.Uri, CoreTracks.TrackNumber
FROM CoreTracks
JOIN CoreArtists ON CoreTracks.ArtistID = CoreArtists.ArtistID
JOIN CoreAlbums ON CoreTracks.AlbumID = CoreAlbums.AlbumID""")
for album, artist, title, uri, track_no in c:
info = {}
info["title"] = title
info["artist"] = artist
info["album"] = album
info["track-number"] = track_no
info["location"] = uri
lSongs.append(info)
return lSongs
def _create_dbus_connection(activate=True, object_name="SourceManager/PlayQueue", iface_name="PlayQueue"):
SERVICE_NAME = "org.bansheeproject.Banshee"
OBJECT_NAME = "/org/bansheeproject/Banshee/%s" % object_name
IFACE_NAME = "org.bansheeproject.Banshee.%s" % iface_name
interface = None
obj = None
sbus = dbus.SessionBus()
try:
#check for running Banshee (code from note.py)
proxy_obj = sbus.get_object('org.freedesktop.DBus',
'/org/freedesktop/DBus')
dbus_iface = dbus.Interface(proxy_obj, 'org.freedesktop.DBus')
if activate or dbus_iface.NameHasOwner(SERVICE_NAME):
obj = sbus.get_object(SERVICE_NAME, OBJECT_NAME)
if obj:
interface = dbus.Interface(obj, IFACE_NAME)
except dbus.exceptions.DBusException, err:
pretty.print_debug(err)
return interface
def _create_dbus_connection_playbackcontroller():
return _create_dbus_connection(object_name="PlaybackController", iface_name="PlaybackController")
def _tostr(ustr):
return ustr.encode("UTF-8")
def play_song(info):
song = []
song.append(info)
enqueue_songs(song)
interface = _create_dbus_connection_playbackcontroller()
if not interface:
return
interface.First()
def enqueue_songs(info):
songs = list(info)
if not songs:
return
interface = _create_dbus_connection()
if not interface:
return
for song in songs:
uri = _tostr(song["location"])
interface.EnqueueUri(uri, False)
class EnqueueTask(task.ThreadTask):
def __init__(self, songs):
super(task.Task, self).__init__()
self.songs = songs
def thread_do(self):
enqueue_songs(self.songs)
class Play (RunnableLeaf):
def __init__(self):
RunnableLeaf.__init__(self, name=_("Play"))
def run(self):
utils.spawn_async(("banshee-1", "--play"))
def get_description(self):
return _("Resume playback in Banshee")
def get_icon_name(self):
return "media-playback-start"
class Pause (RunnableLeaf):
def __init__(self):
RunnableLeaf.__init__(self, name=_("Pause"))
def run(self):
utils.spawn_async(("banshee-1", "--pause"))
def get_description(self):
return _("Pause playback in Banshee")
def get_icon_name(self):
return "media-playback-pause"
class Next (RunnableLeaf):
def __init__(self):
RunnableLeaf.__init__(self, name=_("Next"))
def run(self):
utils.spawn_async(("banshee", "--next"))
def get_description(self):
return _("Jump to next track in Banshee")
def get_icon_name(self):
return "media-skip-forward"
class Previous (RunnableLeaf):
def __init__(self):
RunnableLeaf.__init__(self, name=_("Previous"))
def run(self):
utils.spawn_async(("banshee", "--previous"))
def get_description(self):
return _("Jump to previous track in Banshee")
def get_icon_name(self):
return "media-skip-backward"
class ClearQueue (RunnableLeaf):
def __init__(self):
RunnableLeaf.__init__(self, name=_("Clear Queue"))
def run(self):
interface = _create_dbus_connection()
if not interface:
return
interface.Clear()
def get_icon_name(self):
return "edit-clear"
def _songs_from_leaf(leaf):
"return a sequence of songs from @leaf"
if isinstance(leaf, SongLeaf):
return (leaf.object, )
if isinstance(leaf, TrackCollection):
return list(leaf.object)
class PlayTracks (Action):
rank_adjust = 5
def __init__(self):
Action.__init__(self, _("Play"))
def is_async(self):
return True
def activate(self, leaf):
return self.activate_multiple((leaf, ))
def activate_multiple(self, objects):
# for multiple dispatch, play the first and enqueue the rest
to_enqueue = []
objects = iter(objects)
# take only the first object in the first loop
# notice the break
for leaf in objects:
songs = _songs_from_leaf(leaf)
if not songs:
continue
play_song(songs[0])
to_enqueue.extend(songs[1:])
break
for leaf in objects:
to_enqueue.extend(_songs_from_leaf(leaf))
if to_enqueue:
return EnqueueTask(to_enqueue)
def get_description(self):
return _("Play tracks in Banshee")
def get_icon_name(self):
return "media-playback-start"
class Enqueue (Action):
def __init__(self):
Action.__init__(self, _("Enqueue"))
def is_async(self):
return True
def activate(self, leaf):
return self.activate_multiple((leaf, ))
def activate_multiple(self, objects):
to_enqueue = []
for leaf in objects:
to_enqueue.extend(_songs_from_leaf(leaf))
return EnqueueTask(to_enqueue)
def get_description(self):
return _("Add tracks to the play queue")
def get_gicon(self):
return icons.ComposedIcon("gtk-execute", "media-playback-start")
def get_icon_name(self):
return "media-playback-start"
class SongLeaf (Leaf):
serializable = 1
def __init__(self, info, name=None):
"""Init with song info
@info: Song information dictionary
"""
if not name: name = info["title"]
Leaf.__init__(self, info, name)
def repr_key(self):
"""To distinguish songs by the same name"""
return (self.object["title"], self.object["artist"],
self.object["album"])
def get_actions(self):
yield PlayTracks()
yield Enqueue()
def get_description(self):
# TRANS: Song description
return _("by %(artist)s from %(album)s") % {
"artist": self.object["artist"],
"album": self.object["album"],
}
def get_icon_name(self):
return "audio-x-generic"
class CollectionSource (Source):
def __init__(self, leaf):
Source.__init__(self, unicode(leaf))
self.leaf = leaf
def get_items(self):
for song in self.leaf.object:
yield SongLeaf(song)
def repr_key(self):
return self.leaf.repr_key()
def get_description(self):
return self.leaf.get_description()
def get_thumbnail(self, w, h):
return self.leaf.get_thumbnail(w, h)
def get_gicon(self):
return self.leaf.get_gicon()
def get_icon_name(self):
return self.leaf.get_icon_name()
class TrackCollection (Leaf):
"""A generic track collection leaf, such as one for
an Album or an Artist
"""
def __init__(self, info, name):
"""Init with track collection
@info: Should be a sequence of song information dictionaries
"""
Leaf.__init__(self, info, name)
def get_actions(self):
yield PlayTracks()
yield Enqueue()
def has_content(self):
return True
def content_source(self, alternate=False):
return CollectionSource(self)
def get_icon_name(self):
return "media-optical"
class AlbumLeaf (TrackCollection):
def get_description(self):
artist = None
for song in self.object:
if not artist:
artist = song["artist"]
elif artist != song["artist"]:
# TRANS: Multiple artist description "Artist1 et. al. "
artist = _("%s et. al.") % artist
break
# TRANS: Album description "by Artist"
return _("by %s") % (artist, )
def get_thumbnail(self, width, height):
if not hasattr(self, "cover_file"):
artist = self.object[0]["artist"]
album = self.object[0]["album"]
hash_string = "%s\t%s" % (artist, album)
hash_string = unicodedata.normalize("NFKD", hash_string)
cache_name = "album-%s.jpg" % (md5(hash_string).hexdigest())
self.cover_file = config.get_cache_file(("media-art", cache_name))
return icons.get_pixbuf_from_file(self.cover_file, width, height)
class ArtistAlbumsSource (CollectionSource):
def get_items(self):
albums = {}
for song in self.leaf.object:
album = song["album"]
album_list = albums.get(album, [])
album_list.append(song)
albums[album] = album_list
for album in albums:
yield AlbumLeaf(albums[album], album)
def should_sort_lexically(self):
return True
class ArtistLeaf (TrackCollection):
def get_description(self):
# TRANS: Artist songs collection description
return _("Tracks by %s") % (unicode(self), )
def get_gicon(self):
return icons.ComposedIcon("media-optical", "system-users")
def content_source(self, alternate=False):
if alternate:
return CollectionSource(self)
return ArtistAlbumsSource(self)
class BansheeAlbumsSource (Source):
def __init__(self, library):
Source.__init__(self, _("Albums"))
self.library = library
def get_items(self):
for album in self.library:
yield AlbumLeaf(self.library[album], album)
def should_sort_lexically(self):
return True
def get_description(self):
return _("Music albums in Banshee library")
def get_gicon(self):
return icons.ComposedIcon("media-player-banshee", "media-optical",
emblem_is_fallback=True)
def get_icon_name(self):
return "banshee"
def provides(self):
yield AlbumLeaf
class BansheeArtistsSource (Source):
def __init__(self, library):
Source.__init__(self, _("Artists"))
self.library = library
def get_items(self):
for artist in self.library:
yield ArtistLeaf(self.library[artist], artist)
def should_sort_lexically(self):
return True
def get_description(self):
return _("Music artists in Banshee library")
def get_gicon(self):
return icons.ComposedIcon("media-player-banshee", "system-users",
emblem_is_fallback=True)
def get_icon_name(self):
return "banshee"
def provides(self):
yield ArtistLeaf
class BansheeSongsSource (Source):
"""The whole song library in Leaf representation"""
def __init__(self, library):
Source.__init__(self, _("Songs"))
self.library = library
def get_items(self):
for song in _locale_sort_artist_album_songs(self.library):
yield SongLeaf(song)
def get_actions(self):
return ()
def get_description(self):
return _("Songs in Banshee library")
def get_gicon(self):
return icons.ComposedIcon("media-player-banshee", "audio-x-generic",
emblem_is_fallback=True)
def provides(self):
yield SongLeaf
class BansheeSource (AppLeafContentMixin, Source):
appleaf_content_id = "banshee-1"
def __init__(self):
Source.__init__(self, _("Banshee"))
def get_items(self):
try:
dbfile = config.get_config_file("banshee.db", "banshee-1")
songs = get_banshee_songs(dbfile=dbfile)
except StandardError, e:
self.output_error(e)
songs = []
albums = rhythmbox_support.parse_rhythmbox_albums(songs)
artists = rhythmbox_support.parse_rhythmbox_artists(songs)
yield Play()
yield Pause()
yield Next()
yield Previous()
yield ClearQueue()
artist_source = BansheeArtistsSource(artists)
album_source = BansheeAlbumsSource(albums)
songs_source = BansheeSongsSource(artists)
yield SourceLeaf(artist_source)
yield SourceLeaf(album_source)
yield SourceLeaf(songs_source)
def get_description(self):
return _("Play and enqueue tracks and browse the music library")
def get_icon_name(self):
return "media-player-banshee"
def provides(self):
yield RunnableLeaf
yield SourceLeaf
yield SongLeaf
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]