1 #!/usr/bin/env python2.5
4 from album_art_spec import getCoverArtFileName, getCoverArtThumbFileName, get_thumb_filename_for_path
10 libxml_available = True
12 libxml_available = False
27 socket.setdefaulttimeout(timeout)
29 LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace"
30 BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo"
33 BASE_MSN = "http://www.bing.com/images/search?q="
34 MSN_MEDIUM = "+filterui:imagesize-medium"
35 MSN_SMALL = "+filterui:imagesize-medium"
36 MSN_SQUARE = "+filterui:aspect-square"
37 MSN_PHOTO = "+filterui:photo-graphics"
39 CACHE_LOCATION = os.path.join (os.getenv ("HOME"), ".cache", "mussorgsky")
41 # http://www.lastfm.es/api/show?service=290
43 class MussorgskyAlbumArt:
46 bus = dbus.SessionBus ()
49 if (not os.path.exists (CACHE_LOCATION)):
50 os.makedirs (CACHE_LOCATION)
53 self.thumbnailer = LocalThumbnailer ()
56 self.thumbnailer = bus.get_object ('org.freedesktop.thumbnailer',
57 '/org/freedesktop/thumbnailer/Generic')
58 except dbus.exceptions.DBusException:
59 print "No thumbnailer available"
60 self.thumbnailer = None
62 def get_album_art (self, artist, album, force=False):
64 Return a tuple (album_art, thumbnail_album_art)
66 filename = getCoverArtFileName (album)
67 thumbnail = getCoverArtThumbFileName (album)
69 album_art_available = False
70 if (os.path.exists (filename) and not force):
71 print "Album art already there " + filename
72 album_art_available = True
74 results_page = self.__msn_images (artist, album)
75 for online_resource in self.__get_url_from_msn_results_page (results_page):
76 print "Choosed:", online_resource
77 content = self.__get_url (online_resource)
79 print "Albumart: %s " % (filename)
80 self.__save_content_into_file (content, filename)
81 album_art_available = True
84 if (not album_art_available):
87 if (os.path.exists (thumbnail) and not force):
88 print "Thumbnail exists " + thumbnail
90 if (not self.__request_thumbnail (filename)):
91 print "Failed doing thumbnail. Probably album art is not an image!"
95 return (filename, thumbnail)
98 def get_alternatives (self, artist, album, no_alternatives):
100 return a list of paths of possible album arts
102 results_page = self.__msn_images (artist, album)
104 for image_url in self.__get_url_from_msn_results_page (results_page):
106 # Some searches doesn't return anything at all!
109 image = self.__get_url (image_url)
111 image_path = os.path.join (CACHE_LOCATION, "alternative-" + str(len(valid_images)))
112 self.__save_content_into_file (image, image_path)
113 valid_images.append (image_path)
114 if (len (valid_images) > no_alternatives):
118 def save_alternative (self, artist, album, path):
119 if not os.path.exists (path):
120 print "**** CRITICAL **** image in path", path, "doesn't exist!"
123 filename = getCoverArtFileName (album)
124 thumbnail = getCoverArtThumbFileName (album)
126 os.rename (path, filename)
127 if (not self.__request_thumbnail (filename)):
128 print "Something wrong creating the thumbnail!"
129 return (filename, thumbnail)
131 def __last_fm (self, artist, album):
133 if (not libxml_available):
136 if (not album or len (album) < 1):
139 URL = BASE_LASTFM + "&api_key=" + LASTFM_APIKEY
140 if (artist and len(artist) > 1):
141 URL += "&artist=" + urllib.quote(artist)
143 URL += "&album=" + urllib.quote(album)
145 print "Retrieving: %s" % (URL)
146 result = self.__get_url (URL)
149 doc = libxml2.parseDoc (result)
150 image_nodes = doc.xpathEval ("//image[@size='large']")
151 if len (image_nodes) < 1:
154 return image_nodes[0].content
156 def __msn_images (self, artist, album):
158 good_artist = self.__clean_string_for_search (artist)
159 good_album = self.__clean_string_for_search (album)
161 if (good_album and good_artist):
162 full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE
163 print "Searching (album + artist): %s" % (full_try)
164 result = self.__get_url (full_try)
165 if (result.find ("no_results") == -1):
169 if (album.lower ().find ("greatest hit") != -1):
170 print "Ignoring '%s': too generic" % (album)
173 album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE
174 print "Searching (album): %s" % (album_try)
175 result = self.__get_url (album_try)
176 if (result.find ("no_results") == -1):
180 artist_try = BASE_MSN + good_artist + "+CD+music" + MSN_SMALL + MSN_SQUARE + MSN_PHOTO
181 print "Searching (artist CD): %s" % (artist_try)
182 result = self.__get_url (artist_try)
183 if (result.find ("no_results") == -1):
189 def __get_url_from_msn_results_page (self, page):
194 current_option = None
197 # 500 is just a safe limit
198 for i in range (0, 500):
199 # Iterate until find a jpeg
200 start = page.find ("furl=", starting_at)
203 end = page.find ("\"", start + len ("furl="))
204 current_option = page [start + len ("furl="): end].replace ("amp;", "")
205 if (current_option.lower().endswith (".jpg") or
206 current_option.lower().endswith (".jpeg")):
212 def __clean_string_for_search (self, text):
213 if (not text or len (text) < 1):
216 bad_stuff = "_:?\\-~"
219 clean = clean.replace (c, " ")
221 clean.replace ("/", "%2F")
222 clean = clean.replace (" CD1", "").replace(" CD2", "")
223 return urllib.quote(clean)
225 def __save_content_into_file (self, content, filename):
226 output = open (filename, 'w')
227 output.write (content)
230 def __get_url (self, url):
231 request = urllib2.Request (url)
232 request.add_header ('User-Agent', 'Mussorgsky/0.1 Test')
233 opener = urllib2.build_opener ()
235 return opener.open (request).read ()
239 def __request_thumbnail (self, filename):
240 if (not self.thumbnailer):
241 print "No thumbnailer available"
243 uri = "file://" + filename
244 handle = time.time ()
245 return self.thumbnailer.Queue ([uri], ["image/jpeg"], dbus.UInt32 (handle))
249 class LocalThumbnailer:
251 self.THUMBNAIL_SIZE = (124,124)
253 def Queue (self, uris, mimes, handle):
254 for i in range (0, len(uris)):
256 fullCoverFileName = uri[7:]
257 if (os.path.exists (fullCoverFileName)):
258 thumbFile = get_thumb_filename_for_path (fullCoverFileName)
260 image = Image.open (fullCoverFileName)
261 image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS )
262 image.save( thumbFile, "JPEG" )
263 print "Thumbnail: " + thumbFile
271 if __name__ == "__main__":
273 from optparse import OptionParser
275 parser = OptionParser()
276 parser.add_option ("-p", "--print", dest="print_paths",
277 action="store_true", default=True,
278 help="Print the destination paths")
279 parser.add_option ("-r", "--retrieve", dest="retrieve",
280 action="store_true", default=False,
281 help="Try to retrieve the online content")
282 parser.add_option ("-m", "--multiple", dest="multiple",
283 action="store_true", default=False,
284 help="Show more than one option")
285 parser.add_option ("-a", "--artist", dest="artist", type="string",
286 help="ARTIST to look for", metavar="ARTIST")
287 parser.add_option ("-b", "--album", dest="album", type="string",
288 help="ALBUM to look for", metavar="ALBUM")
290 (options, args) = parser.parse_args ()
292 if (not options.artist and not options.album):
296 if (options.multiple and options.retrieve):
297 print "Multiple and retrieve are incompatible"
301 if options.print_paths and not options.retrieve:
302 print "Album art:", getCoverArtFileName (options.album)
303 print "Thumbnail:", getCoverArtThumbFileName (options.album)
306 maa = MussorgskyAlbumArt ()
307 maa.get_album_art (options.artist, options.album)
310 maa = MussorgskyAlbumArt ()
311 alt = maa.get_alternatives (options.artist, options.album, 5)