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
18 print "Please install python-imaging package"
26 socket.setdefaulttimeout(timeout)
28 LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace"
29 BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo"
32 BASE_MSN = "http://www.bing.com/images/search?q="
33 MSN_MEDIUM = "+filterui:imagesize-medium"
34 MSN_SMALL = "+filterui:imagesize-medium"
35 MSN_SQUARE = "+filterui:aspect-square"
36 MSN_PHOTO = "+filterui:photo-graphics"
38 CACHE_LOCATION = os.path.join (os.getenv ("HOME"), ".cache", "mussorgsky")
40 # http://www.lastfm.es/api/show?service=290
42 class MussorgskyAlbumArt:
45 bus = dbus.SessionBus ()
48 if (not os.path.exists (CACHE_LOCATION)):
49 os.makedirs (CACHE_LOCATION)
51 self.thumbnailer = LocalThumbnailer ()
53 def get_album_art (self, artist, album, force=False):
55 Return a tuple (album_art, thumbnail_album_art)
57 filename = getCoverArtFileName (album)
58 thumbnail = getCoverArtThumbFileName (album)
60 album_art_available = False
61 if (os.path.exists (filename) and not force):
62 print "Album art already there " + filename
63 album_art_available = True
65 results_page = self.__msn_images (artist, album)
66 for online_resource in self.__get_url_from_msn_results_page (results_page):
67 print "Choosed:", online_resource
68 content = self.__get_url (online_resource)
70 print "Albumart: %s " % (filename)
71 self.__save_content_into_file (content, filename)
72 album_art_available = True
75 if (not album_art_available):
78 if (os.path.exists (thumbnail) and not force):
79 print "Thumbnail exists " + thumbnail
81 if (not self.__request_thumbnail (filename)):
82 print "Failed doing thumbnail. Probably album art is not an image!"
86 return (filename, thumbnail)
89 def get_alternatives (self, artist, album, max_alternatives=4):
91 return a list of paths of possible album arts
94 results_page = self.__msn_images (artist, album)
95 for image_url in self.__get_url_from_msn_results_page (results_page):
97 # Some searches doesn't return anything at all!
100 if (counter >= max_alternatives):
103 image = self.__get_url (image_url)
105 image_path = os.path.join (CACHE_LOCATION, "alternative-" + str(counter))
106 thumb_path = os.path.join (CACHE_LOCATION, "alternative-" + str(counter) + "thumb")
107 self.__save_content_into_file (image, image_path)
108 self.thumbnailer.create (image_path, thumb_path)
110 yield (image_path, thumb_path)
113 def save_alternative (self, artist, album, img_path, thumb_path):
114 if not os.path.exists (img_path) or not os.path.exists (thumb_path):
115 print "**** CRITICAL **** image in path", path, "doesn't exist!"
118 filename = getCoverArtFileName (album)
119 thumbnail = getCoverArtThumbFileName (album)
121 os.rename (img_path, filename)
122 os.rename (thumb_path, thumbnail)
124 return (filename, thumbnail)
126 def __last_fm (self, artist, album):
128 if (not libxml_available):
131 if (not album or len (album) < 1):
134 URL = BASE_LASTFM + "&api_key=" + LASTFM_APIKEY
135 if (artist and len(artist) > 1):
136 URL += "&artist=" + urllib.quote(artist)
138 URL += "&album=" + urllib.quote(album)
140 print "Retrieving: %s" % (URL)
141 result = self.__get_url (URL)
144 doc = libxml2.parseDoc (result)
145 image_nodes = doc.xpathEval ("//image[@size='large']")
146 if len (image_nodes) < 1:
149 return image_nodes[0].content
151 def __msn_images (self, artist, album):
153 good_artist = self.__clean_string_for_search (artist)
154 good_album = self.__clean_string_for_search (album)
156 if (good_album and good_artist):
157 full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE
158 print "Searching (album + artist): %s" % (full_try)
159 result = self.__get_url (full_try)
160 if (result and result.find ("no_results") == -1):
164 if (album.lower ().find ("greatest hit") != -1):
165 print "Ignoring '%s': too generic" % (album)
168 album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE
169 print "Searching (album): %s" % (album_try)
170 result = self.__get_url (album_try)
171 if (result and result.find ("no_results") == -1):
175 artist_try = BASE_MSN + good_artist + "+CD+music" + MSN_SMALL + MSN_SQUARE + MSN_PHOTO
176 print "Searching (artist CD): %s" % (artist_try)
177 result = self.__get_url (artist_try)
178 if (result and result.find ("no_results") == -1):
184 def __get_url_from_msn_results_page (self, page):
189 current_option = None
192 # 500 is just a safe limit
193 for i in range (0, 500):
194 # Iterate until find a jpeg
195 start = page.find ("furl=", starting_at)
198 end = page.find ("\"", start + len ("furl="))
199 current_option = page [start + len ("furl="): end].replace ("amp;", "")
200 if (current_option.lower().endswith (".jpg") or
201 current_option.lower().endswith (".jpeg")):
206 def __clean_string_for_search (self, text):
207 if (not text or len (text) < 1):
210 bad_stuff = "_:?\\-~"
213 clean = clean.replace (c, " ")
215 clean.replace ("/", "%2F")
216 clean = clean.replace (" CD1", "").replace(" CD2", "")
217 return urllib.quote(clean)
219 def __save_content_into_file (self, content, filename):
220 output = open (filename, 'w')
221 output.write (content)
224 def __get_url (self, url):
225 request = urllib2.Request (url)
226 request.add_header ('User-Agent', 'Mussorgsky/0.1 Test')
227 opener = urllib2.build_opener ()
229 return opener.open (request).read ()
233 def __request_thumbnail (self, filename):
234 thumbFile = get_thumb_filename_for_path (fullCoverFileName)
235 return self.thumbnailer.create (filename, thumbFile)
239 class LocalThumbnailer:
241 self.THUMBNAIL_SIZE = (124,124)
243 def create (self, fullCoverFileName, thumbFile):
244 if (os.path.exists (fullCoverFileName)):
246 image = Image.open (fullCoverFileName)
247 image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS )
248 image.save (thumbFile, "JPEG")
249 print "Thumbnail: " + thumbFile
257 if __name__ == "__main__":
259 from optparse import OptionParser
261 parser = OptionParser()
262 parser.add_option ("-p", "--print", dest="print_paths",
263 action="store_true", default=True,
264 help="Print the destination paths")
265 parser.add_option ("-r", "--retrieve", dest="retrieve",
266 action="store_true", default=False,
267 help="Try to retrieve the online content")
268 parser.add_option ("-m", "--multiple", dest="multiple",
269 action="store_true", default=False,
270 help="Show more than one option")
271 parser.add_option ("-a", "--artist", dest="artist", type="string",
272 help="ARTIST to look for", metavar="ARTIST")
273 parser.add_option ("-b", "--album", dest="album", type="string",
274 help="ALBUM to look for", metavar="ALBUM")
276 (options, args) = parser.parse_args ()
278 if (not options.artist and not options.album):
282 if (options.multiple and options.retrieve):
283 print "Multiple and retrieve are incompatible"
287 if options.print_paths and not options.retrieve:
288 print "Album art:", getCoverArtFileName (options.album)
289 print "Thumbnail:", getCoverArtThumbFileName (options.album)
292 maa = MussorgskyAlbumArt ()
293 maa.get_album_art (options.artist, options.album)
296 maa = MussorgskyAlbumArt ()
297 for (img, thumb) in maa.get_alternatives (options.artist, options.album, 5):