home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / rhythmbox / plugins / artdisplay / CoverArtDatabase.py < prev    next >
Encoding:
Python Source  |  2009-04-07  |  8.3 KB  |  257 lines

  1. # -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- 
  2. #
  3. # Copyright (C) 2006 - Gareth Murphy, Martin Szulecki, 
  4. # Ed Catmur <ed@catmur.co.uk>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2, or (at your option)
  9. # any later version.
  10. #
  11. # The Rhythmbox authors hereby grant permission for non-GPL compatible
  12. # GStreamer plugins to be used and distributed together with GStreamer
  13. # and Rhythmbox. This permission is above and beyond the permissions granted
  14. # by the GPL license by which Rhythmbox is covered. If you modify this code
  15. # you may extend this exception to your version of the code, but you are not
  16. # obligated to do so. If you do not wish to do so, delete this exception
  17. # statement from your version.
  18. # This program is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21. # GNU General Public License for more details.
  22. #
  23. # You should have received a copy of the GNU General Public License
  24. # along with this program; if not, write to the Free Software
  25. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26.  
  27. import rhythmdb, rb
  28. import os
  29. import gtk
  30. import itertools
  31. import gobject
  32.  
  33. from PodcastCoverArtSearch import PodcastCoverArtSearch
  34. from AmazonCoverArtSearch import AmazonCoverArtSearch
  35.  
  36. from urllib import unquote
  37.  
  38. try:
  39.     # try to use the gio implementation, fall back to gnome-vfs if that
  40.     # isn't available.
  41.     import gio
  42.     if gio.pygio_version > (2,15,2):    # probably
  43.         from LocalCoverArtSearchGIO import LocalCoverArtSearch
  44. except:
  45.     from LocalCoverArtSearch import LocalCoverArtSearch
  46.  
  47. ART_SEARCHES_LOCAL = [LocalCoverArtSearch]
  48. ART_SEARCHES_REMOTE = [PodcastCoverArtSearch, AmazonCoverArtSearch]
  49. OLD_ART_FOLDER = '~/.gnome2/rhythmbox/covers'
  50.  
  51. ART_FOLDER = os.path.join(rb.user_cache_dir(), 'covers')
  52. ART_CACHE_EXTENSION_JPG = 'jpg'
  53. ART_CACHE_EXTENSION_PNG = 'png'
  54. ART_CACHE_FORMAT_JPG = 'jpeg'
  55. ART_CACHE_FORMAT_PNG = 'png'
  56. ART_CACHE_SETTINGS_JPG = {"quality": "100"}
  57. ART_CACHE_SETTINGS_PNG = {"compression": "9"}
  58.  
  59. class TicketSystem:
  60.     def __init__ (self):
  61.         self.counter = itertools.count ()
  62.         self.hash = {}
  63.         self.dead = set ()
  64.  
  65.     def get (self, item):
  66.         ticket = self.counter.next ()
  67.         self.hash.setdefault (item, set ()).add (ticket)
  68.         return ticket
  69.  
  70.     def bury (self, ticket):
  71.         try:
  72.             self.dead.remove (ticket)
  73.             return True
  74.         except KeyError:
  75.             return False
  76.  
  77.     def forget (self, item, ticket):
  78.         try:
  79.             self.hash[item].remove (ticket)
  80.             return True
  81.         except KeyError:
  82.             self.dead.remove (ticket)
  83.             return False
  84.  
  85.     def purge (self, item):
  86.         self.dead.update (self.hash.pop (item, set ()))
  87.  
  88.     def release (self, item, ticket):
  89.         try:
  90.             self.dead.update (self.hash.pop (item) - set([ticket]))
  91.             return True
  92.         except KeyError:
  93.             self.dead.remove (ticket)
  94.             return False
  95.  
  96. class CoverArtDatabase (object):
  97.     def __init__ (self):
  98.         self.ticket = TicketSystem ()
  99.  
  100.     def build_art_cache_filename (self, db, entry, extension):
  101.         artist = db.entry_get (entry, rhythmdb.PROP_ARTIST)
  102.         album = db.entry_get (entry, rhythmdb.PROP_ALBUM)
  103.         art_folder = os.path.expanduser (ART_FOLDER)
  104.         old_art_folder = os.path.expanduser (OLD_ART_FOLDER)
  105.         if not os.path.exists (art_folder) and os.path.exists (old_art_folder):
  106.             parent = os.path.dirname(os.path.abspath(art_folder))
  107.             if not os.path.exists (parent):
  108.                 os.makedirs (parent)
  109.             os.rename (old_art_folder, art_folder)
  110.         if not os.path.exists (art_folder):
  111.             os.makedirs (art_folder)
  112.  
  113.         # FIXME: the following block of code is messy and needs to be redone ASAP
  114.         return art_folder + '/%s - %s.%s' % (artist.replace ('/', '-'), album.replace ('/', '-'), extension)    
  115.  
  116.     def engines (self, blist):
  117.         for Engine in ART_SEARCHES_LOCAL:
  118.             yield Engine (), Engine.__name__, False
  119.         for Engine in ART_SEARCHES_REMOTE:
  120.             if Engine.__name__ not in blist:
  121.                 yield Engine (), Engine.__name__, True
  122.     
  123.     def set_pixbuf_from_uri (self, db, entry, uri, callback):
  124.         def loader_cb (data):
  125.             self.set_pixbuf (db, entry, self.image_data_load (data), callback)
  126.  
  127.         l = rb.Loader()
  128.         l.get_url (str (uri), loader_cb)
  129.  
  130.     def set_pixbuf (self, db, entry, pixbuf, callback):
  131.         if entry is None or pixbuf is None:
  132.             return
  133.         if pixbuf.get_has_alpha():
  134.             art_location = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_PNG)
  135.             art_cache_format = ART_CACHE_FORMAT_PNG
  136.             art_cache_settings = ART_CACHE_SETTINGS_PNG
  137.         else:
  138.             art_location = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_JPG)
  139.             art_cache_format = ART_CACHE_FORMAT_JPG
  140.             art_cache_settings = ART_CACHE_SETTINGS_JPG
  141.         self.ticket.purge (entry)
  142.         pixbuf.save (art_location, art_cache_format, art_cache_settings)
  143.         callback (entry, pixbuf, art_location)
  144.         for Engine in ART_SEARCHES_LOCAL:
  145.             try:
  146.                 Engine ().save_pixbuf (db, entry, pixbuf)
  147.             except AttributeError:
  148.                 pass
  149.  
  150.     def cancel_get_pixbuf (self, entry):
  151.         self.ticket.purge (entry)
  152.   
  153.     def get_pixbuf (self, db, entry, callback):
  154.         if entry is None:
  155.             callback (entry, None, None)
  156.             return
  157.             
  158.         st_artist = db.entry_get (entry, rhythmdb.PROP_ARTIST) or _("Unknown")
  159.         st_album = db.entry_get (entry, rhythmdb.PROP_ALBUM) or _("Unknown")
  160.  
  161.         # replace quote characters
  162.         # don't replace single quote: could be important punctuation
  163.         for char in ["\""]:
  164.             st_artist = st_artist.replace (char, '')
  165.             st_album = st_album.replace (char, '')
  166.  
  167.         rb.Coroutine (self.image_search, db, st_album, st_artist, entry, callback).begin ()
  168.  
  169.     def image_search (self, plexer, db, st_album, st_artist, entry, callback):
  170.         art_location_jpg = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_JPG)
  171.         art_location_png = self.build_art_cache_filename (db, entry, ART_CACHE_EXTENSION_PNG)
  172.         blist_location = self.build_art_cache_filename (db, entry, "rb-blist")
  173.  
  174.         art_location = None
  175.         if os.path.exists (art_location_jpg):
  176.             art_location = art_location_jpg
  177.         if os.path.exists (art_location_png):
  178.             art_location = art_location_png
  179.  
  180.         # Check local cache
  181.         if art_location:
  182.             self.ticket.purge (entry)
  183.             pixbuf = gtk.gdk.pixbuf_new_from_file (art_location)    
  184.             callback (entry, pixbuf, art_location)
  185.             return
  186.  
  187.         blist = self.read_blist (blist_location)
  188.         ticket = self.ticket.get (entry)
  189.         for engine, engine_name, engine_remote in self.engines (blist):
  190.             plexer.clear ()
  191.             engine.search (db, entry, plexer.send ())
  192.             while True:
  193.                 yield None
  194.                 _, (engine, entry, results) = plexer.receive ()
  195.                 if not results:
  196.                     break
  197.                 for url in engine.get_best_match_urls (results):
  198.                     if str(url) == "":
  199.                         print "got empty url from engine %s." % (engine)
  200.                         continue
  201.  
  202.                     l = rb.Loader()
  203.                     yield l.get_url (str (url), plexer.send ())
  204.                     _, (data, ) = plexer.receive ()
  205.                     pixbuf = self.image_data_load (data)
  206.                     if pixbuf:
  207.                         if self.ticket.release (entry, ticket):
  208.                             if engine_remote:
  209.                                 if pixbuf.get_has_alpha ():
  210.                                     pixbuf.save (art_location_png, ART_CACHE_FORMAT_PNG, ART_CACHE_SETTINGS_PNG)
  211.                                 else:
  212.                                     pixbuf.save (art_location_jpg, ART_CACHE_FORMAT_JPG, ART_CACHE_SETTINGS_JPG)
  213.                                 uri = art_location
  214.                             else:
  215.                                 uri = unquote (str (url))
  216.                             callback (entry, pixbuf, uri)
  217.                         self.write_blist (blist_location, blist)
  218.                         return
  219.                 if not engine.search_next ():
  220.                     if engine_remote:
  221.                         blist.append (engine_name)
  222.                     break
  223.                 if self.ticket.bury (ticket):
  224.                     self.write_blist (blist_location, blist)
  225.                     return
  226.             if self.ticket.bury (ticket):
  227.                 self.write_blist (blist_location, blist)
  228.                 return
  229.         if self.ticket.forget (entry, ticket):
  230.             callback (entry, None, None)
  231.         self.write_blist (blist_location, blist)
  232.  
  233.     def read_blist (self, blist_location):
  234.         if os.path.exists (blist_location):
  235.             return [line.strip () for line in file (blist_location)]
  236.         else:
  237.             return []
  238.  
  239.     def write_blist (self, blist_location, blist):
  240.         if blist:
  241.             blist_file = file (blist_location, 'w')
  242.             blist_file.writelines (blist)
  243.             blist_file.close ()
  244.         elif os.path.exists (blist_location):
  245.             os.unlink (blist_location)
  246.  
  247.     def image_data_load (self, data):
  248.         if data and len (data) >= 1000:
  249.             pbl = gtk.gdk.PixbufLoader ()
  250.             try:
  251.                 if pbl.write (data) and pbl.close ():
  252.                     return pbl.get_pixbuf ()
  253.             except gobject.GError:
  254.                 pass
  255.         return None
  256.