Implemented MSN album art retrieval
[mussorgsky] / src / album_art.py
1 #!/usr/bin/env python2.5
2 import urllib2, urllib
3 import libxml2
4 import os
5 from album_art_spec import getCoverArtFileName, getCoverArtThumbFileName, get_thumb_filename_for_path
6 import dbus, time
7 import string
8
9 try:
10     import PIL
11     import Image
12     pil_available = True
13 except ImportException:
14     pil_available = False
15     
16
17 LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace"
18 BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo"
19
20
21 BASE_MSN = "http://www.bing.com/images/search?q="
22 MSN_MEDIUM = "+filterui:imagesize-medium"
23 MSN_SMALL = "+filterui:imagesize-medium"
24 MSN_SQUARE = "+filterui:aspect-square"
25 MSN_PHOTO = "+filterui:photo-graphics"
26
27 # LastFM:
28 # http://www.lastfm.es/api/show?service=290
29 #
30 class MussorgskyAlbumArt:
31
32     def __init__ (self):
33         bus = dbus.SessionBus ()
34         handle = time.time()
35         if (pil_available):
36             self.thumbnailer = LocalThumbnailer ()
37         else:
38             try:
39                 self.thumbnailer = bus.get_object ('org.freedesktop.thumbnailer',
40                                                    '/org/freedesktop/thumbnailer/Generic')
41             except dbus.exceptions.DBusException:
42                 print "No thumbnailer available"
43                 self.thumbnailer = None
44
45     def get_album_art (self, artist, album):
46         """
47         Return a tuple (album_art, thumbnail_album_art)
48         """
49         filename = getCoverArtFileName (album)
50         thumbnail = getCoverArtThumbFileName (album)
51
52         if (os.path.exists (filename)):
53             print "Album art already there " + filename
54         else:
55             online_resource = self.__msn_images (artist, album)
56             if (online_resource):
57                 content = self.__get_url (online_resource)
58                 if (content):
59                     print "Saved %s -> %s " % (online_resource, filename)
60                     self.__save_content_into_file (content, filename)
61                 else:
62                     return (None, None)
63             else:
64                 return (None, None)
65
66         if (os.path.exists (thumbnail)):
67             print "Thumbnail exists"
68         else:
69             if (not self.__request_thumbnail (filename)):
70                 print "Failed doing thumbnail. Probably album art is not an image!"
71                 os.remove (filename)
72                 return (None, None)
73
74         return (filename, thumbnail)
75
76     def __last_fm (self, artist, album):
77         if (not album or len (album) < 1):
78             return None
79         
80         URL = BASE_LASTFM + "&api_key=" + LASTFM_APIKEY
81         if (artist and len(artist) > 1):
82             URL += "&artist=" + urllib.quote(artist)
83         if (album):
84             URL += "&album=" + urllib.quote(album)
85             
86         print "Retrieving: %s" % (URL)
87         result = self.__get_url (URL)
88         if (not result):
89             return None
90         doc = libxml2.parseDoc (result)
91         image_nodes = doc.xpathEval ("//image[@size='large']")
92         if len (image_nodes) < 1:
93             return None
94         else:
95             return image_nodes[0].content
96
97     def __msn_images (self, artist, album):
98
99         good_artist = self.__clean_string_for_search (artist)
100         good_album = self.__clean_string_for_search (album)
101
102         print good_artist
103         if (good_album and good_artist):
104             full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE
105             print "Retrieving (album + artist): %s" % (full_try)
106             result = self.__get_url (full_try)
107             if (result):
108                 return self.__get_first_url_from_msn_results_page (result)
109
110         if (album):
111             if (album.lower ().find ("greatest hit") != -1):
112                 print "Ignoring '%s': too generic" % (album)
113                 pass
114             else:
115                 album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE
116                 print "Retrieving (album): %s" % (album_try)
117                 result = self.__get_url (album_try)
118                 if (result):
119                     return self.__get_first_url_from_msn_results_page (result)
120
121         if (artist):
122             artist_try = BASE_MSN + good_artist + "+CD+music"  + MSN_SMALL + MSN_SQUARE + MSN_PHOTO
123             print "Retrieving (artist CD): %s" % (artist_try)
124             result = self.__get_url (artist_try)
125             if (result):
126                 return self.__get_first_url_from_msn_results_page (result)
127             
128         return None
129
130
131     def __get_first_url_from_msn_results_page (self, page):
132         start = page.find ("furl=")
133         if (start == -1):
134             return None
135         end = page.find ("\"", start + len ("furl="))
136         return page [start + len ("furl="): end].replace ("amp;", "")
137
138     def __clean_string_for_search (self, text):
139         if (not text or len (text) < 1):
140             return None
141             
142         bad_stuff = "_:?\\-~"
143         clean = text
144         for c in bad_stuff:
145             clean = clean.replace (c, " ")
146
147         clean.replace ("/", "%2F")
148         clean = clean.replace (" CD1", "").replace(" CD2", "")
149         return urllib.quote(clean)
150
151     def __save_content_into_file (self, content, filename):
152         output = open (filename, 'w')
153         output.write (content)
154         output.close ()
155         
156     def __get_url (self, url):
157         request = urllib2.Request (url)
158         request.add_header ('User-Agent', 'Mussorgsky/0.1 Test')
159         opener = urllib2.build_opener ()
160         try:
161             return opener.open (request).read ()
162         except:
163             return None
164
165     def __request_thumbnail (self, filename):
166         if (not self.thumbnailer):
167             print "No thumbnailer available"
168             return
169         uri = "file://" + filename
170         handle = time.time ()
171         print "Call to thumbnailer"
172         return self.thumbnailer.Queue ([uri], ["image/jpeg"], dbus.UInt32 (handle))
173             
174
175
176 class LocalThumbnailer:
177     def __init__ (self):
178         self.THUMBNAIL_SIZE = (124,124)
179
180     def Queue (self, uris, mimes, handle):
181         for i in range (0, len(uris)):
182             uri = uris[i]
183             fullCoverFileName = uri[7:]
184             print fullCoverFileName
185             if (os.path.exists (fullCoverFileName)):
186                 thumbFile = get_thumb_filename_for_path (fullCoverFileName)
187                 try:
188                     image = Image.open (fullCoverFileName)
189                 except IOError, e:
190                     print e
191                     return False
192                 image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS )
193                 image.save( thumbFile, "JPEG" )
194                 print "Thumbnail: " + thumbFile
195         return True
196             
197
198
199 if __name__ == "__main__":
200     import sys
201     if ( len (sys.argv) > 2):
202         artist = sys.argv[1]
203         album = sys.argv[2]
204     else:
205         print "ARTIST ALBUM"
206         sys.exit (-1)
207
208     maa = MussorgskyAlbumArt ()
209     print "Artist %s - Album %s" % (artist, album)
210     print maa.get_album_art (artist, album)