edf39a3a45e70e0574ac97f553face40657f228f
[mussorgsky] / src / qml / aa_search.py
1 #!/usr/bin/env python2.5
2 import os
3 from aa_spec import getCoverArtFileName, getCoverArtThumbFileName, get_thumb_filename_for_path
4 from utils import UrllibWrapper
5 import dbus, time
6 import string
7 import urllib
8
9 try:
10     import libxml2
11     libxml_available = True
12 except ImportError:
13     libxml_available = False
14
15 try:
16     import PIL
17     import Image
18 except ImportError:
19     import sys
20     print "Please install python-imaging package"
21     sys.exit (-1)
22
23
24 LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace"
25 BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo"
26
27
28 BASE_MSN = "http://www.bing.com/images/search?q="
29 MSN_MEDIUM = "+filterui:imagesize-medium"
30 MSN_SMALL = "+filterui:imagesize-medium"
31 MSN_SQUARE = "+filterui:aspect-square"
32 MSN_PHOTO = "+filterui:photo-graphics"
33
34 CACHE_LOCATION = os.path.join (os.getenv ("HOME"), ".cache", "mussorgsky")
35 # LastFM:
36 # http://www.lastfm.es/api/show?service=290
37 #
38
39
40 import threading
41 class AADownloadThread (threading.Thread):
42
43     def __init__ (self, url, artist, album, counter):
44         threading.Thread.__init__ (self, target=self.grab_image, args=(url,))
45         self.thumbnailer = LocalThumbnailer ()
46         self.counter = counter
47         self.artistName = artist.replace (" ", "_")
48         self.albumName = album.replace (" ", "_")
49         self.image_path = None
50         self.thumb_path = None
51         self.urllib_wrapper = UrllibWrapper ()
52
53     def grab_image (self, image_url):
54         print "Working", self.counter
55         image = self.urllib_wrapper.get_url (image_url)
56         if (image):
57             self.image_path = os.path.join (CACHE_LOCATION, self.artistName + self.albumName + str(self.counter))
58             self.thumb_path = os.path.join (CACHE_LOCATION, self.artistName + self.albumName + str(self.counter) + "thumb")
59             self.urllib_wrapper.save_content_into_file (image, self.image_path)
60             self.thumbnailer.create (self.image_path, self.thumb_path)
61         
62     def get_result (self):
63         return self.image_path, self.thumb_path
64
65
66
67 class MussorgskyAlbumArt:
68
69     def __init__ (self):
70         bus = dbus.SessionBus ()
71
72         if (not os.path.exists (CACHE_LOCATION)):
73             os.makedirs (CACHE_LOCATION)
74             
75         self.thumbnailer = LocalThumbnailer ()
76         self.urllib_wrapper = UrllibWrapper ()
77
78     def get_possible_url (self, artist, album, amount=4):
79             results_page = self.__msn_images (artist, album)
80             return self.__get_url_from_msn_results_page (results_page)
81         
82
83     def get_album_art (self, artist, album, force=False):
84         """
85         Return a tuple (album_art, thumbnail_album_art)
86         """
87         filename = getCoverArtFileName (album)
88         thumbnail = getCoverArtThumbFileName (album)
89
90         album_art_available = False
91         if (os.path.exists (filename) and not force):
92             print "Album art already there " + filename
93             album_art_available = True
94         else:
95             results_page = self.__msn_images (artist, album)
96             for online_resource in self.__get_url_from_msn_results_page (results_page):
97                 print "Choosed:", online_resource
98                 content = self.urllib_wrapper.get_url (online_resource)
99                 if (content):
100                     print "Albumart: %s " % (filename)
101                     self.urllib_wrapper.save_content_into_file (content, filename)
102                     album_art_available = True
103                     break
104
105         if (not album_art_available):
106             return (None, None)
107
108         if (not os.path.exists (thumbnail) or force or album_art_available):
109             if (not self.__request_thumbnail (filename)):
110                 print "Failed doing thumbnail. Probably album art is not an image!"
111                 os.remove (filename)
112                 return (None, None)
113         else:
114             print "Thumbnail exists (and probably valid) " + thumbnail
115             
116         return (filename, thumbnail)
117
118
119     def get_alternatives (self, artist, album, max_alternatives=4):
120         """
121         return a list of paths of possible album arts
122         """
123         results_page = self.__msn_images (artist, album)
124         return self.__process_results_page (results_page, artist, album, max_alternatives)
125
126     def get_alternatives_free_text (self, search_text, max_alternatives=4):
127         results_page = self.__msn_images_free_text (search_text)
128         return self.__process_results_page (results_page, max_alternatives)
129
130     def __process_results_page (self, results_page, artist, album, max_alternatives):
131         counter = 0
132         threads = []
133         for image_url in self.__get_url_from_msn_results_page (results_page):
134             if (not image_url):
135                 # Some searches doesn't return anything at all!
136                 break
137
138             if (counter >= max_alternatives):
139                 break
140             
141             t = AADownloadThread (image_url, artist, album, counter)
142             t.start ()
143             threads.append (t)
144             counter += 1
145
146         for t in threads:
147             t.join (5)
148             if (t.isAlive ()):
149                 yield (None, None)
150             else:
151                 yield t.get_result ()
152             
153
154     def save_alternative (self, artist, album, img_path, thumb_path):
155         """
156         This is done now in the controller
157         """
158         if not os.path.exists (img_path) or not os.path.exists (thumb_path):
159             print "**** CRITICAL **** image in path", path, "doesn't exist!"
160             return (None, None)
161         
162         filename = getCoverArtFileName (album)
163         thumbnail = getCoverArtThumbFileName (album)
164
165         os.rename (img_path, filename)
166         os.rename (thumb_path, thumbnail)
167
168         return (filename, thumbnail)
169
170     def reset_alternative (self, artist, album):
171
172         for filepath in [getCoverArtFileName (album),
173                          getCoverArtThumbFileName (album)]:
174             if os.path.exists (filepath):
175                 os.remove (filepath)
176
177     def __msn_images (self, artist, album):
178
179         good_artist = self.__clean_string_for_search (artist)
180         good_album = self.__clean_string_for_search (album)
181         
182         if (good_album and good_artist):
183             full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE
184             print "Searching (album + artist): %s" % (full_try)
185             result = self.urllib_wrapper.get_url (full_try)
186             if (result and result.find ("no_results") == -1):
187                 return result
188
189         if (album):
190             if (album.lower ().find ("greatest hit") != -1):
191                 print "Ignoring '%s': too generic" % (album)
192                 pass
193             else:
194                 album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE
195                 print "Searching (album): %s" % (album_try)
196                 result = self.urllib_wrapper.get_url (album_try)
197                 if (result and result.find ("no_results") == -1):
198                     return result
199             
200         if (artist):
201             artist_try = BASE_MSN + good_artist + "+CD+music"  + MSN_SMALL + MSN_SQUARE + MSN_PHOTO
202             print "Searching (artist CD): %s" % (artist_try)
203             result = self.urllib_wrapper.get_url (artist_try)
204             if (result and result.find ("no_results") == -1):
205                 return result
206         
207         return None
208
209     def __msn_images_free_text (self, search_text):
210         full_try = BASE_MSN + self.__clean_string_for_search (search_text) + MSN_MEDIUM + MSN_SQUARE
211         result = self.urllib_wrapper.get_url (full_try)
212         return result
213     
214
215     def __get_url_from_msn_results_page (self, page):
216         if (not page):
217             return
218
219         current_option = None
220         starting_at = 0
221
222         # 500 is just a safe limit
223         for i in range (0, 500):
224             # Iterate until find a jpeg
225             start = page.find ("imgurl:"", starting_at)
226             if (start == -1):
227                 yield None
228             end = page.find ("&", start + len ("imgurl:""))
229             current_option = page [start + len ("imgurl:""): end].replace ("amp;", "")
230             if (current_option.lower().endswith (".jpg") or
231                 current_option.lower().endswith (".jpeg")):
232                 yield current_option
233             starting_at = end
234         
235
236     def __clean_string_for_search (self, text):
237         if (not text or len (text) < 1):
238             return None
239             
240         bad_stuff = "_:?\\-~"
241         clean = text
242         for c in bad_stuff:
243             clean = clean.replace (c, " ")
244
245         clean.replace ("/", "%2F")
246         clean = clean.replace (" CD1", "").replace(" CD2", "")
247         return urllib.quote(clean)
248
249     def __request_thumbnail (self, filename):
250         thumbFile = get_thumb_filename_for_path (filename)
251         return self.thumbnailer.create (filename, thumbFile)
252             
253
254
255 class LocalThumbnailer:
256     def __init__ (self):
257         self.THUMBNAIL_SIZE = (124,124)
258
259     def create (self, fullCoverFileName, thumbFile):
260         if (os.path.exists (fullCoverFileName)):
261             try:
262                 image = Image.open (fullCoverFileName)
263                 image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS )
264                 image.save (thumbFile, "JPEG")
265                 print "Thumbnail: " + thumbFile
266             except IOError, e:
267                 print e
268                 return False
269         return True
270             
271
272
273 if __name__ == "__main__":
274     import sys
275     from optparse import OptionParser
276
277     parser = OptionParser()
278     parser.add_option ("-p", "--print", dest="print_paths",
279                        action="store_true", default=True,
280                        help="Print the destination paths")
281     parser.add_option ("-r", "--retrieve", dest="retrieve",
282                        action="store_true", default=False,
283                        help="Try to retrieve the online content")
284     parser.add_option ("-m", "--multiple", dest="multiple",
285                        action="store_true", default=False,
286                        help="Show more than one option")
287     parser.add_option ("-a", "--artist", dest="artist", type="string",
288                        help="ARTIST to look for", metavar="ARTIST")
289     parser.add_option ("-b", "--album", dest="album", type="string",
290                        help="ALBUM to look for", metavar="ALBUM")
291
292     (options, args) = parser.parse_args ()
293     print options
294     if (not options.artist and not options.album):
295         parser.print_help ()
296         sys.exit (-1)
297
298     if (options.multiple and options.retrieve):
299         print "Multiple and retrieve are incompatible"
300         parser.print_help ()
301         sys.exit (-1)
302         
303     if options.print_paths and not options.retrieve:
304         print "Album art:", getCoverArtFileName (options.album)
305         print "Thumbnail:", getCoverArtThumbFileName (options.album)
306
307     if options.retrieve:
308         maa = MussorgskyAlbumArt ()
309         maa.get_album_art (options.artist, options.album)
310
311     if options.multiple:
312         start = time.time ()
313         maa = MussorgskyAlbumArt ()
314         for (img, thumb) in  maa.get_alternatives (options.artist, options.album, 5):
315             print img
316             print thumb
317         end = time.time ()
318         print end - start