home *** CD-ROM | disk | FTP | other *** search
- # -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
- #
- # Copyright (C) 2006 - Gareth Murphy, Martin Szulecki,
- # Ed Catmur <ed@catmur.co.uk>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2, or (at your option)
- # any later version.
- #
- # The Rhythmbox authors hereby grant permission for non-GPL compatible
- # GStreamer plugins to be used and distributed together with GStreamer
- # and Rhythmbox. This permission is above and beyond the permissions granted
- # by the GPL license by which Rhythmbox is covered. If you modify this code
- # you may extend this exception to your version of the code, but you are not
- # obligated to do so. If you do not wish to do so, delete this exception
- # statement from your version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-
- import rhythmdb, rb
- import os
- import gtk
- import itertools
- import gobject
-
- from PodcastCoverArtSearch import PodcastCoverArtSearch
- from AmazonCoverArtSearch import AmazonCoverArtSearch
-
- from urllib import unquote
-
- try:
- # try to use the gio implementation, fall back to gnome-vfs if that
- # isn't available.
- import gio
- if gio.pygio_version > (2,15,2): # probably
- from LocalCoverArtSearchGIO import LocalCoverArtSearch
- except:
- from LocalCoverArtSearch import LocalCoverArtSearch
-
- ART_SEARCHES_LOCAL = [LocalCoverArtSearch]
- ART_SEARCHES_REMOTE = [PodcastCoverArtSearch, AmazonCoverArtSearch]
- OLD_ART_FOLDER = '~/.gnome2/rhythmbox/covers'
-
- ART_FOLDER = os.path.join(rb.user_cache_dir(), 'covers')
- ART_CACHE_EXTENSION_JPG = 'jpg'
- ART_CACHE_EXTENSION_PNG = 'png'
- ART_CACHE_FORMAT_JPG = 'jpeg'
- ART_CACHE_FORMAT_PNG = 'png'
- ART_CACHE_SETTINGS_JPG = {"quality": "100"}
- ART_CACHE_SETTINGS_PNG = {"compression": "9"}
-
- class TicketSystem:
- def __init__ (self):
- self.counter = itertools.count ()
- self.hash = {}
- self.dead = set ()
-
- def get (self, item):
- ticket = self.counter.next ()
- self.hash.setdefault (item, set ()).add (ticket)
- return ticket
-
- def bury (self, ticket):
- try:
- self.dead.remove (ticket)
- return True
- except KeyError:
- return False
-
- def forget (self, item, ticket):
- try:
- self.hash[item].remove (ticket)
- return True
- except KeyError:
- self.dead.remove (ticket)
- return False
-
- def purge (self, item):
- self.dead.update (self.hash.pop (item, set ()))
-
- def release (self, item, ticket):
- try:
- self.dead.update (self.hash.pop (item) - set([ticket]))
- return True
- except KeyError:
- self.dead.remove (ticket)
- return False
-
- class CoverArtDatabase (object):
- def __init__ (self):
- self.ticket = TicketSystem ()
-
- def build_art_cache_filename (self, db, entry, extension):
- artist = db.entry_get (entry, rhythmdb.PROP_ARTIST)
- album = db.entry_get (entry, rhythmdb.PROP_ALBUM)
- art_folder = os.path.expanduser (ART_FOLDER)
- old_art_folder = os.path.expanduser (OLD_ART_FOLDER)
- if not os.path.exists (art_folder) and os.path.exists (old_art_folder):
- parent = os.path.dirname(os.path.abspath(art_folder))
- if not os.path.exists (parent):
- os.makedirs (parent)
- os.rename (old_art_folder, art_folder)
- if not os.path.exists (art_folder):
- os.makedirs (art_folder)
-
- # FIXME: the following block of code is messy and needs to be redone ASAP
- return art_folder + '/%s - %s.%s' % (artist.replace ('/', '-'), album.replace ('/', '-'), extension)
-
- def engines (self, blist):
- for Engine in ART_SEARCHES_LOCAL:
- yield Engine (), Engine.__name__, False
- for Engine in ART_SEARCHES_REMOTE:
- if Engine.__name__ not in blist:
- yield Engine (), Engine.__name__, True
-
- def set_pixbuf_from_uri (self, db, entry, uri, callback):
- def loader_cb (data):
- self.set_pixbuf (db, entry, self.image_data_load (data), callback)
-
- l = rb.Loader()
- l.get_url (str (uri), loader_cb)
-
- def set_pixbuf (self, db, entry, pixbuf, callback):
- if entry is None or pixbuf is None:
- return
- if pixbuf.get_has_alpha():
- art_location = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_PNG)
- art_cache_format = ART_CACHE_FORMAT_PNG
- art_cache_settings = ART_CACHE_SETTINGS_PNG
- else:
- art_location = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_JPG)
- art_cache_format = ART_CACHE_FORMAT_JPG
- art_cache_settings = ART_CACHE_SETTINGS_JPG
- self.ticket.purge (entry)
- pixbuf.save (art_location, art_cache_format, art_cache_settings)
- callback (entry, pixbuf, art_location)
- for Engine in ART_SEARCHES_LOCAL:
- try:
- Engine ().save_pixbuf (db, entry, pixbuf)
- except AttributeError:
- pass
-
- def cancel_get_pixbuf (self, entry):
- self.ticket.purge (entry)
-
- def get_pixbuf (self, db, entry, callback):
- if entry is None:
- callback (entry, None, None)
- return
-
- st_artist = db.entry_get (entry, rhythmdb.PROP_ARTIST) or _("Unknown")
- st_album = db.entry_get (entry, rhythmdb.PROP_ALBUM) or _("Unknown")
-
- # replace quote characters
- # don't replace single quote: could be important punctuation
- for char in ["\""]:
- st_artist = st_artist.replace (char, '')
- st_album = st_album.replace (char, '')
-
- rb.Coroutine (self.image_search, db, st_album, st_artist, entry, callback).begin ()
-
- def image_search (self, plexer, db, st_album, st_artist, entry, callback):
- art_location_jpg = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_JPG)
- art_location_png = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_PNG)
- blist_location = self.build_art_cache_filename (db, entry, "rb-blist")
-
- art_location = None
- if os.path.exists (art_location_jpg):
- art_location = art_location_jpg
- if os.path.exists (art_location_png):
- art_location = art_location_png
-
- # Check local cache
- if art_location:
- self.ticket.purge (entry)
- pixbuf = gtk.gdk.pixbuf_new_from_file (art_location)
- callback (entry, pixbuf, art_location)
- return
-
- blist = self.read_blist (blist_location)
- ticket = self.ticket.get (entry)
- for engine, engine_name, engine_remote in self.engines (blist):
- plexer.clear ()
- engine.search (db, entry, plexer.send ())
- while True:
- yield None
- _, (engine, entry, results) = plexer.receive ()
- if not results:
- break
- for url in engine.get_best_match_urls (results):
- if str(url) == "":
- print "got empty url from engine %s." % (engine)
- continue
-
- l = rb.Loader()
- yield l.get_url (str (url), plexer.send ())
- _, (data, ) = plexer.receive ()
- pixbuf = self.image_data_load (data)
- if pixbuf:
- if self.ticket.release (entry, ticket):
- if engine_remote:
- if pixbuf.get_has_alpha ():
- pixbuf.save (art_location_png, ART_CACHE_FORMAT_PNG, ART_CACHE_SETTINGS_PNG)
- else:
- pixbuf.save (art_location_jpg, ART_CACHE_FORMAT_JPG, ART_CACHE_SETTINGS_JPG)
- uri = art_location
- else:
- uri = unquote (str (url))
- callback (entry, pixbuf, uri)
- self.write_blist (blist_location, blist)
- return
- if not engine.search_next ():
- if engine_remote:
- blist.append (engine_name)
- break
- if self.ticket.bury (ticket):
- self.write_blist (blist_location, blist)
- return
- if self.ticket.bury (ticket):
- self.write_blist (blist_location, blist)
- return
- if self.ticket.forget (entry, ticket):
- callback (entry, None, None)
- self.write_blist (blist_location, blist)
-
- def read_blist (self, blist_location):
- if os.path.exists (blist_location):
- return [line.strip () for line in file (blist_location)]
- else:
- return []
-
- def write_blist (self, blist_location, blist):
- if blist:
- blist_file = file (blist_location, 'w')
- blist_file.writelines (blist)
- blist_file.close ()
- elif os.path.exists (blist_location):
- os.unlink (blist_location)
-
- def image_data_load (self, data):
- if data and len (data) >= 1000:
- pbl = gtk.gdk.PixbufLoader ()
- try:
- if pbl.write (data) and pbl.close ():
- return pbl.get_pixbuf ()
- except gobject.GError:
- pass
- return None
-