774c2d4af7dcd233bea9e89689bf27303b5b75f0
[mussorgsky] / src / album_art.py
1 #!/usr/bin/env python2.5
2 import urllib2, urllib
3 import os
4 from album_art_spec import getCoverArtFileName, getCoverArtThumbFileName, get_thumb_filename_for_path
5 import dbus, time
6 import string
7
8 try:
9     import libxml2
10     libxml_available = True
11 except ImportError:
12     libxml_available = False
13
14 try:
15     import PIL
16     import Image
17     pil_available = True
18 except ImportError:
19     pil_available = False
20
21
22 # Set socket timeout
23 import socket
24 import urllib2
25
26 timeout = 5
27 socket.setdefaulttimeout(timeout)
28
29 LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace"
30 BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo"
31
32
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"
38
39 # LastFM:
40 # http://www.lastfm.es/api/show?service=290
41 #
42 class MussorgskyAlbumArt:
43
44     def __init__ (self):
45         bus = dbus.SessionBus ()
46         handle = time.time()
47         if (pil_available):
48             self.thumbnailer = LocalThumbnailer ()
49         else:
50             try:
51                 self.thumbnailer = bus.get_object ('org.freedesktop.thumbnailer',
52                                                    '/org/freedesktop/thumbnailer/Generic')
53             except dbus.exceptions.DBusException:
54                 print "No thumbnailer available"
55                 self.thumbnailer = None
56
57     def get_album_art (self, artist, album, force=False):
58         """
59         Return a tuple (album_art, thumbnail_album_art)
60         """
61         filename = getCoverArtFileName (album)
62         thumbnail = getCoverArtThumbFileName (album)
63
64         if (os.path.exists (filename) and not force):
65             print "Album art already there " + filename
66         else:
67             online_resource = self.__msn_images (artist, album)
68             if (online_resource):
69                 print "Choosed:", online_resource
70                 content = self.__get_url (online_resource)
71                 if (content):
72                     print "Albumart: %s " % (filename)
73                     self.__save_content_into_file (content, filename)
74                 else:
75                     return (None, None)
76             else:
77                 return (None, None)
78
79         if (os.path.exists (thumbnail) and not force):
80             print "Thumbnail exists " + thumbnail
81         else:
82             if (not self.__request_thumbnail (filename)):
83                 print "Failed doing thumbnail. Probably album art is not an image!"
84                 os.remove (filename)
85                 return (None, None)
86             
87         return (filename, thumbnail)
88
89     def __last_fm (self, artist, album):
90
91         if (not libxml_available):
92             return None
93         
94         if (not album or len (album) < 1):
95             return None
96         
97         URL = BASE_LASTFM + "&api_key=" + LASTFM_APIKEY
98         if (artist and len(artist) > 1):
99             URL += "&artist=" + urllib.quote(artist)
100         if (album):
101             URL += "&album=" + urllib.quote(album)
102             
103         print "Retrieving: %s" % (URL)
104         result = self.__get_url (URL)
105         if (not result):
106             return None
107         doc = libxml2.parseDoc (result)
108         image_nodes = doc.xpathEval ("//image[@size='large']")
109         if len (image_nodes) < 1:
110             return None
111         else:
112             return image_nodes[0].content
113
114     def __msn_images (self, artist, album):
115
116         good_artist = self.__clean_string_for_search (artist)
117         good_album = self.__clean_string_for_search (album)
118
119         if (good_album and good_artist):
120             full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE
121             print "Searching (album + artist): %s" % (full_try)
122             result = self.__get_url (full_try)
123             if (result):
124                 return self.__get_first_url_from_msn_results_page (result)
125
126         if (album):
127             if (album.lower ().find ("greatest hit") != -1):
128                 print "Ignoring '%s': too generic" % (album)
129                 pass
130             else:
131                 album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE
132                 print "Searching (album): %s" % (album_try)
133                 result = self.__get_url (album_try)
134                 if (result):
135                     return self.__get_first_url_from_msn_results_page (result)
136
137         if (artist):
138             artist_try = BASE_MSN + good_artist + "+CD+music"  + MSN_SMALL + MSN_SQUARE + MSN_PHOTO
139             print "Searching (artist CD): %s" % (artist_try)
140             result = self.__get_url (artist_try)
141             if (result):
142                 return self.__get_first_url_from_msn_results_page (result)
143             
144         return None
145
146
147     def __get_first_url_from_msn_results_page (self, page):
148
149         current_option = None
150         starting_at = 0
151         security_limit = 20
152
153         while (security_limit > 0):
154             # Iterate until find a jpeg
155             start = page.find ("furl=", starting_at)
156             if (start == -1):
157                 return None
158             end = page.find ("\"", start + len ("furl="))
159             current_option = page [start + len ("furl="): end].replace ("amp;", "")
160             if (current_option.lower().endswith (".jpg") or
161                 current_option.lower().endswith (".jpeg")):
162                 return current_option
163             starting_at = end
164             security_limit -= 1
165         return None
166             
167         
168
169     def __clean_string_for_search (self, text):
170         if (not text or len (text) < 1):
171             return None
172             
173         bad_stuff = "_:?\\-~"
174         clean = text
175         for c in bad_stuff:
176             clean = clean.replace (c, " ")
177
178         clean.replace ("/", "%2F")
179         clean = clean.replace (" CD1", "").replace(" CD2", "")
180         return urllib.quote(clean)
181
182     def __save_content_into_file (self, content, filename):
183         output = open (filename, 'w')
184         output.write (content)
185         output.close ()
186         
187     def __get_url (self, url):
188         request = urllib2.Request (url)
189         request.add_header ('User-Agent', 'Mussorgsky/0.1 Test')
190         opener = urllib2.build_opener ()
191         try:
192             return opener.open (request).read ()
193         except:
194             return None
195
196     def __request_thumbnail (self, filename):
197         if (not self.thumbnailer):
198             print "No thumbnailer available"
199             return
200         uri = "file://" + filename
201         handle = time.time ()
202         return self.thumbnailer.Queue ([uri], ["image/jpeg"], dbus.UInt32 (handle))
203             
204
205
206 class LocalThumbnailer:
207     def __init__ (self):
208         self.THUMBNAIL_SIZE = (124,124)
209
210     def Queue (self, uris, mimes, handle):
211         for i in range (0, len(uris)):
212             uri = uris[i]
213             fullCoverFileName = uri[7:]
214             if (os.path.exists (fullCoverFileName)):
215                 thumbFile = get_thumb_filename_for_path (fullCoverFileName)
216                 try:
217                     image = Image.open (fullCoverFileName)
218                     image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS )
219                     image.save( thumbFile, "JPEG" )
220                     print "Thumbnail: " + thumbFile
221                 except IOError, e:
222                     print e
223                     return False
224         return True
225             
226
227
228 if __name__ == "__main__":
229     import sys
230     from optparse import OptionParser
231
232     parser = OptionParser()
233     parser.add_option ("-p", "--print", dest="print_paths",
234                        action="store_true", default=True,
235                        help="Print the destination paths")
236     parser.add_option ("-r", "--retrieve", dest="retrieve",
237                        action="store_true", default=False,
238                        help="Try to retrieve the online content")
239     parser.add_option ("-a", "--artist", dest="artist", type="string",
240                        help="ARTIST to look for", metavar="ARTIST")
241     parser.add_option ("-b", "--album", dest="album", type="string",
242                        help="ALBUM to look for", metavar="ALBUM")
243
244     (options, args) = parser.parse_args ()
245     print options
246     if (not options.artist and not options.album):
247         parser.print_help ()
248         sys.exit (-1)
249         
250     if options.print_paths and not options.retrieve:
251         print "Album art:", getCoverArtFileName (options.album)
252         print "Thumbnail:", getCoverArtThumbFileName (options.album)
253
254         
255     if options.retrieve:
256         maa = MussorgskyAlbumArt ()
257         maa.get_album_art (options.artist, options.album)