Async album art showing thumbnails
[mussorgsky] / src / album_art.py
index 1933d72..7f81061 100755 (executable)
@@ -1,18 +1,29 @@
 #!/usr/bin/env python2.5
 import urllib2, urllib
-import libxml2
 import os
 from album_art_spec import getCoverArtFileName, getCoverArtThumbFileName, get_thumb_filename_for_path
 import dbus, time
 import string
 
 try:
+    import libxml2
+    libxml_available = True
+except ImportError:
+    libxml_available = False
+
+try:
     import PIL
     import Image
-    pil_available = True
-except ImportException:
-    pil_available = False
-    
+except ImportError:
+    print "Please install python-imaging package"
+    sys.exit (-1)
+
+# Set socket timeout
+import socket
+import urllib2
+
+timeout = 5
+socket.setdefaulttimeout(timeout)
 
 LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace"
 BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo"
@@ -24,6 +35,7 @@ MSN_SMALL = "+filterui:imagesize-medium"
 MSN_SQUARE = "+filterui:aspect-square"
 MSN_PHOTO = "+filterui:photo-graphics"
 
+CACHE_LOCATION = os.path.join (os.getenv ("HOME"), ".cache", "mussorgsky")
 # LastFM:
 # http://www.lastfm.es/api/show?service=290
 #
@@ -32,48 +44,90 @@ class MussorgskyAlbumArt:
     def __init__ (self):
         bus = dbus.SessionBus ()
         handle = time.time()
-        if (pil_available):
-            self.thumbnailer = LocalThumbnailer ()
-        else:
-            try:
-                self.thumbnailer = bus.get_object ('org.freedesktop.thumbnailer',
-                                                   '/org/freedesktop/thumbnailer/Generic')
-            except dbus.exceptions.DBusException:
-                print "No thumbnailer available"
-                self.thumbnailer = None
 
-    def get_album_art (self, artist, album):
+        if (not os.path.exists (CACHE_LOCATION)):
+            os.makedirs (CACHE_LOCATION)
+            
+        self.thumbnailer = LocalThumbnailer ()
+
+    def get_album_art (self, artist, album, force=False):
         """
         Return a tuple (album_art, thumbnail_album_art)
         """
         filename = getCoverArtFileName (album)
         thumbnail = getCoverArtThumbFileName (album)
 
-        if (os.path.exists (filename)):
+        album_art_available = False
+        if (os.path.exists (filename) and not force):
             print "Album art already there " + filename
+            album_art_available = True
         else:
-            online_resource = self.__msn_images (artist, album)
-            if (online_resource):
+            results_page = self.__msn_images (artist, album)
+            for online_resource in self.__get_url_from_msn_results_page (results_page):
+                print "Choosed:", online_resource
                 content = self.__get_url (online_resource)
                 if (content):
                     print "Albumart: %s " % (filename)
                     self.__save_content_into_file (content, filename)
-                else:
-                    return (None, None)
-            else:
-                return (None, None)
+                    album_art_available = True
+                    break
+
+        if (not album_art_available):
+            return (None, None)
 
-        if (os.path.exists (thumbnail)):
-            print "Thumbnail exists"
+        if (os.path.exists (thumbnail) and not force):
+            print "Thumbnail exists " + thumbnail
         else:
             if (not self.__request_thumbnail (filename)):
                 print "Failed doing thumbnail. Probably album art is not an image!"
                 os.remove (filename)
                 return (None, None)
+            
+        return (filename, thumbnail)
+
+
+    def get_alternatives (self, artist, album, max_alternatives=4):
+        """
+        return a list of paths of possible album arts
+        """
+        counter = 0
+        results_page = self.__msn_images (artist, album)
+        for image_url in self.__get_url_from_msn_results_page (results_page):
+            if (not image_url):
+                # Some searches doesn't return anything at all!
+                break
+
+            if (counter >= max_alternatives):
+                break
+            
+            image = self.__get_url (image_url)
+            if (image):
+                image_path = os.path.join (CACHE_LOCATION, "alternative-" + str(counter))
+                thumb_path = os.path.join (CACHE_LOCATION, "alternative-" + str(counter) + "thumb")
+                self.__save_content_into_file (image, image_path)
+                self.thumbnailer.create (image_path, thumb_path)
+                counter += 1
+                yield (image_path, thumb_path)
+
+
+    def save_alternative (self, artist, album, img_path, thumb_path):
+        if not os.path.exists (img_path) or not os.path.exists (thumb_path):
+            print "**** CRITICAL **** image in path", path, "doesn't exist!"
+            return (None, None)
+        
+        filename = getCoverArtFileName (album)
+        thumbnail = getCoverArtThumbFileName (album)
+
+        os.rename (img_path, filename)
+        os.rename (thumb_path, thumbnail)
 
         return (filename, thumbnail)
 
     def __last_fm (self, artist, album):
+
+        if (not libxml_available):
+            return None
+        
         if (not album or len (album) < 1):
             return None
         
@@ -101,10 +155,10 @@ class MussorgskyAlbumArt:
 
         if (good_album and good_artist):
             full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE
-            print "Retrieving (album + artist): %s" % (full_try)
+            print "Searching (album + artist): %s" % (full_try)
             result = self.__get_url (full_try)
-            if (result):
-                return self.__get_first_url_from_msn_results_page (result)
+            if (result and result.find ("no_results") == -1):
+                return result
 
         if (album):
             if (album.lower ().find ("greatest hit") != -1):
@@ -112,27 +166,42 @@ class MussorgskyAlbumArt:
                 pass
             else:
                 album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE
-                print "Retrieving (album): %s" % (album_try)
+                print "Searching (album): %s" % (album_try)
                 result = self.__get_url (album_try)
-                if (result):
-                    return self.__get_first_url_from_msn_results_page (result)
-
+                if (result and result.find ("no_results") == -1):
+                    return result
+            
         if (artist):
             artist_try = BASE_MSN + good_artist + "+CD+music"  + MSN_SMALL + MSN_SQUARE + MSN_PHOTO
-            print "Retrieving (artist CD): %s" % (artist_try)
+            print "Searching (artist CD): %s" % (artist_try)
             result = self.__get_url (artist_try)
-            if (result):
-                return self.__get_first_url_from_msn_results_page (result)
-            
+            if (result and result.find ("no_results") == -1):
+                return result
+        
         return None
 
 
-    def __get_first_url_from_msn_results_page (self, page):
-        start = page.find ("furl=")
-        if (start == -1):
-            return None
-        end = page.find ("\"", start + len ("furl="))
-        return page [start + len ("furl="): end].replace ("amp;", "")
+    def __get_url_from_msn_results_page (self, page):
+
+        if (not page):
+            return
+
+        current_option = None
+        starting_at = 0
+
+        # 500 is just a safe limit
+        for i in range (0, 500):
+            # Iterate until find a jpeg
+            start = page.find ("furl=", starting_at)
+            if (start == -1):
+                yield None
+            end = page.find ("\"", start + len ("furl="))
+            current_option = page [start + len ("furl="): end].replace ("amp;", "")
+            if (current_option.lower().endswith (".jpg") or
+                current_option.lower().endswith (".jpeg")):
+                yield current_option
+            starting_at = end
+        
 
     def __clean_string_for_search (self, text):
         if (not text or len (text) < 1):
@@ -162,12 +231,8 @@ class MussorgskyAlbumArt:
             return None
 
     def __request_thumbnail (self, filename):
-        if (not self.thumbnailer):
-            print "No thumbnailer available"
-            return
-        uri = "file://" + filename
-        handle = time.time ()
-        return self.thumbnailer.Queue ([uri], ["image/jpeg"], dbus.UInt32 (handle))
+        thumbFile = get_thumb_filename_for_path (fullCoverFileName)
+        return self.thumbnailer.create (filename, thumbFile)
             
 
 
@@ -175,32 +240,60 @@ class LocalThumbnailer:
     def __init__ (self):
         self.THUMBNAIL_SIZE = (124,124)
 
-    def Queue (self, uris, mimes, handle):
-        for i in range (0, len(uris)):
-            uri = uris[i]
-            fullCoverFileName = uri[7:]
-            if (os.path.exists (fullCoverFileName)):
-                thumbFile = get_thumb_filename_for_path (fullCoverFileName)
-                try:
-                    image = Image.open (fullCoverFileName)
-                except IOError, e:
-                    print e
-                    return False
+    def create (self, fullCoverFileName, thumbFile):
+        if (os.path.exists (fullCoverFileName)):
+            try:
+                image = Image.open (fullCoverFileName)
                 image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS )
-                image.save( thumbFile, "JPEG" )
+                image.save (thumbFile, "JPEG")
                 print "Thumbnail: " + thumbFile
+            except IOError, e:
+                print e
+                return False
         return True
             
 
 
 if __name__ == "__main__":
     import sys
-    if ( len (sys.argv) > 2):
-        artist = sys.argv[1]
-        album = sys.argv[2]
-    else:
-        print "ARTIST ALBUM"
+    from optparse import OptionParser
+
+    parser = OptionParser()
+    parser.add_option ("-p", "--print", dest="print_paths",
+                       action="store_true", default=True,
+                       help="Print the destination paths")
+    parser.add_option ("-r", "--retrieve", dest="retrieve",
+                       action="store_true", default=False,
+                       help="Try to retrieve the online content")
+    parser.add_option ("-m", "--multiple", dest="multiple",
+                       action="store_true", default=False,
+                       help="Show more than one option")
+    parser.add_option ("-a", "--artist", dest="artist", type="string",
+                       help="ARTIST to look for", metavar="ARTIST")
+    parser.add_option ("-b", "--album", dest="album", type="string",
+                       help="ALBUM to look for", metavar="ALBUM")
+
+    (options, args) = parser.parse_args ()
+    print options
+    if (not options.artist and not options.album):
+        parser.print_help ()
+        sys.exit (-1)
+
+    if (options.multiple and options.retrieve):
+        print "Multiple and retrieve are incompatible"
+        parser.print_help ()
         sys.exit (-1)
+        
+    if options.print_paths and not options.retrieve:
+        print "Album art:", getCoverArtFileName (options.album)
+        print "Thumbnail:", getCoverArtThumbFileName (options.album)
+
+    if options.retrieve:
+        maa = MussorgskyAlbumArt ()
+        maa.get_album_art (options.artist, options.album)
 
-    maa = MussorgskyAlbumArt ()
-    maa.get_album_art (artist, album)
+    if options.multiple:
+        maa = MussorgskyAlbumArt ()
+        for (img, thumb) in  maa.get_alternatives (options.artist, options.album, 5):
+            print img
+            print thumb