Plugins: albumart2.py

File albumart2.py, 15.5 kB (added by christoph.reiter@gmx.at, 11 months ago)

Add support for Walmart.com and Darktown.to cover search.

Line 
1 #Copyright 2005 Eduardo Gonzalez, Niklas Janlert, Christoph Reiter
2 #Amazon API code by Mark Pilgrim
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 2 as
6 # published by the Free Software Foundation
7
8 import os
9 import sys
10 import urllib
11 import re
12 import time
13 import threading
14 from cStringIO import StringIO
15 import gtk
16 import gobject
17 import pango
18 import util
19 import qltk
20 import config
21
22 from plugins.songsmenu import SongsMenuPlugin
23
24 if sys.version_info < (2, 4): from sets import Set as set
25
26 try:
27     import amazon
28 except ImportError:
29     import _amazon as amazon
30
31 class AlbumArtWindow(gtk.Window):
32     def __init__(self, songs):
33         gtk.Window.__init__(self)
34         self.set_border_width(12)
35         self.set_title("AlbumArt")
36         self.set_default_size(720, 450)
37
38         #TreeView stuff
39         self.liststore = liststore = gtk.ListStore(object, str)
40         treeview = gtk.TreeView(liststore)
41         treeview.set_headers_visible(False)
42         selection = treeview.get_selection()
43         selection.set_mode(gtk.SELECTION_SINGLE)
44         selection.connect("changed", self.__preview)
45
46         rend = gtk.CellRendererPixbuf()
47         def cell_data(column, cell, model, iter):
48             cell.set_property("pixbuf", model[iter][0]["thumb"])
49         tvcol1 = gtk.TreeViewColumn("Pixbuf", rend)
50         tvcol1.set_cell_data_func(rend, cell_data)
51         tvcol1.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
52         rend.set_property('xpad', 2)
53         rend.set_property('ypad', 2)
54         rend.set_property('width', 56)
55         rend.set_property('height', 56)
56         treeview.append_column(tvcol1)
57
58         rend = gtk.CellRendererText()
59         rend.set_property("ellipsize", pango.ELLIPSIZE_END)
60         tvcol2 = gtk.TreeViewColumn("Info", rend, markup=1)
61         treeview.append_column(tvcol2)
62
63         sw = gtk.ScrolledWindow()
64         sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
65         sw.set_shadow_type(gtk.SHADOW_IN)
66         sw.add(treeview)
67
68         #Image frame and save button
69         self.image = image = gtk.Image()
70         frame = gtk.Frame()
71         frame.set_shadow_type(gtk.SHADOW_IN)
72         frame.add(image)
73         scrolled = gtk.ScrolledWindow()
74         scrolled.add_with_viewport(frame)
75         scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
76         vbox = gtk.VBox(spacing=5)
77         vbox.pack_start(scrolled)
78         self.button = button = gtk.Button(stock=gtk.STOCK_SAVE)
79         button.set_sensitive(False)
80         def save_cb(button, combo):
81             model, path = selection.get_selected()
82             data = model[path][0]["cover_data"]
83             fname = self.__get_fname(songs, combo)
84             self.__save_cover(data, fname)
85         combo = gtk.combo_box_new_text()
86         try: set_fn = config.get("plugins", "cover_fn")
87         except: set_fn = ".folder.jpg"
88         active = -1
89         for i, fn in enumerate([".folder.jpg", "folder.jpg", "cover.jpg"]):
90             combo.append_text(fn)
91             if fn == set_fn: active = i
92         if active == -1:
93             combo.append_text(set_fn)
94             combo.set_active(len(combo.get_model()) - 1)
95         else: combo.set_active(active)
96         button.connect("clicked", save_cb, combo)
97         bbox = gtk.HButtonBox()
98         bbox.pack_start(combo)
99         bbox.pack_start(button, expand=False, fill=False)
100         bbox.set_layout(gtk.BUTTONBOX_SPREAD)
101         vbox.pack_start(bbox, expand=False, fill=False)
102
103         hpaned = gtk.HPaned()
104         hpaned.pack1(sw)
105         hpaned.pack2(vbox)
106         hpaned.set_position(300)
107         self.add(hpaned)
108
109         thread = threading.Thread(target=self.__search, args=(songs,))
110         thread.setDaemon(True)
111         thread.start()
112
113         self.show_all()
114
115     def __search_amazon(self, query):
116         amazon.setLicense("0RKH4ZH1JCFZHMND91G2")
117         try:
118             bags = amazon.searchByKeyword(
119                 query, type="lite", product_line="music")
120         except:
121             pass
122         else:
123             #delete products with no images and return the rest
124             count=0
125             while count < self.max_albums and count < len(bags):
126                 if len(bags[count].ImageUrlLarge) and \
127                    len(bags[count].ImageUrlSmall):
128                     count+=1
129                 else:
130                     del bags[count]
131             return bags[:self.max_albums]
132         return []
133            
134     def __search_darktown(self, query):
135         class item: pass
136         bags = []
137        
138         #Artists @ Darkdown often miss the leading "The", better remove it
139         #for better search results - also ' and . should be removed
140         if query[:4] == "The ":
141             query = query[4:]
142         query = query.replace("'"," ")
143         query = query.replace(".","")
144        
145         mainUrl = urllib.urlopen('http://www.darktown.to/search.php?'
146                 'action=search&what='+urllib.quote(query)+'&category=audio')
147         mainData = mainUrl.read()
148         mainRe = re.findall('javascript:openCentered\(\'(.*?)Front\'', mainData)
149        
150         count = 0
151         for result in mainRe:
152             if count >= self.max_albums: break
153             count += 1
154            
155             resultUrl = urllib.urlopen('http://www.darktown.to'+result+'Front')
156             resultData = resultUrl.read()
157
158             resultImgBig = re.findall('href="(.*?)">DOWNLOAD</a>', resultData)
159             resultImgSmall = re.findall('src="(.*?)"', resultData)
160             resultArtist = re.findall('<b><font size=4>(.*?)</font', resultData)
161             resultTitle = re.findall('</font></b><br><b>(.*?)</b><br><br><b>',
162                                      resultData)
163
164             cover = item()
165             cover.ImageUrlSmall = resultImgSmall[0]
166             cover.ImageUrlLarge = resultImgBig[0]
167             cover.Artists = item()
168             cover.Artists.Artist = resultArtist[0].decode("latin1", "replace")
169             cover.ProductName = resultTitle[0].decode("latin1", "replace")
170            
171             bags.append(cover)
172  
173         return bags
174        
175     def __search_walmart(self, query):
176         class item: pass
177         bags = []
178
179         mainUrl = urllib.urlopen('http://www.walmart.com/search/search-ng.do?'
180                                  'search_constraint=4104&search_query='
181                                  +urllib.quote(query)+'&ic='
182                                  +str(self.max_albums)+'_0')
183
184         mainData = mainUrl.read()
185        
186         #abort if nothing was found
187         if mainData.find('no matches found') != -1 or \
188             mainData.find('not find any matches') != -1:
189             return bags
190        
191         countRe = re.findall('<h1>(\d*) items found for', mainData)
192
193         #walmart will redirect to the specific album page
194         #if the query exactly matches the product title ... so 2 ways needed
195         if len(countRe):
196        
197             #abort if it returns too much shit
198             if int(countRe[0]) > 50:
199                 return bags
200                
201             mainRe = re.findall('<div class="ItemPic"><a href=\'.*?\'><img src='
202                                 '\'(.*?)\' width="60" height="60" border="0" '
203                                 'alt=\'(.*?)\'', mainData)
204        
205             artistRe = re.findall('Artist: <span class="BodySLtgry">'
206                                   '(<a href=\'.*?\'>|)(.*?)(</a>|)</span>',
207                                   mainData)
208            
209             for num in xrange(len(mainRe)): 
210                 cover = item()
211                 cover.ImageUrlSmall = mainRe[num][0]
212                 cover.ImageUrlLarge = mainRe[num][0][:-9]+"500X500.jpg"
213                 cover.Artists = item()
214                 cover.Artists.Artist = artistRe[num][1]
215                 cover.ProductName = mainRe[num][1]
216                
217                 bags.append(cover)
218            
219         else:
220             mainRe = re.findall('<img src=\'(.*?)\' width="150" height="150" '
221                                 'border="0" alt=\'(.*?)\'', mainData)
222             artistRe = re.findall('<span class="BodyXSLtgry">&gt;</span>&nbsp;'
223                                   '<a href=".*?">(.*?)</a>&nbsp;\s*', mainData)
224          
225             cover = item()
226             cover.ImageUrlSmall = mainRe[0][0][:-11]+"60X60.gif"
227             cover.ImageUrlLarge = mainRe[0][0][:-11]+"500X500.jpg"
228             cover.Artists = item()
229             cover.Artists.Artist = artistRe[-1]
230             cover.ProductName = mainRe[0][1]
231            
232             bags.append(cover)
233            
234         return bags
235        
236     def __search(self, songs):
237         #use albumartist if available
238         if songs[0]("albumartist"):
239             artist = songs[0]("albumartist")
240         else:
241             artist = songs[0]("artist")
242         query = artist + " " + songs[0]("album")
243         query = query.encode("latin1", "replace")
244
245         #max covers per site
246         self.max_albums = 5
247        
248         #show search status row
249         iter = self.liststore.insert(0)
250         self.liststore.set(iter, 0, {"bag":None,"thumb":None,"thumb_data":""})
251         self.liststore.set_value(iter, 1, "<i><b>searching...</b></i>")
252        
253         engines = [["Amazon",self.__search_amazon],
254                    ["Walmart",self.__search_walmart],
255                    ["Darktown",self.__search_darktown]]
256        
257         #search
258         for i in xrange(len(engines)):
259             try:
260                 bags = engines[i][1](query)
261                 for bag in bags:
262                     gobject.idle_add(self.__add_bag, self.liststore, bag)
263                 self.liststore.set_value(iter, 1, "<i>["+str(i+1)+"/"
264                                             +str(len(engines))+"] <b>"
265                                             +engines[i][0]+": </b></i>"
266                                             +str(len(bags))+" found...")
267             except:
268                 self.liststore.set_value(iter, 1, "<i>["+str(i+1)+"/"
269                                             +str(len(engines))+"] <b>"
270                                             +engines[i][0]+": </b></i>"
271                                             "failed...")
272
273         #remove status entry after 2 secs
274         time.sleep(2)
275         self.liststore.remove(iter)
276
277     def __add_bag(self, model, bag):
278         # Text part
279         title = util.escape(getattr(bag, "ProductName", ""))
280         artist = (getattr(bag, "Artists", None) and
281                   getattr(bag.Artists, "Artist", None) or "")
282         if isinstance(artist, list):
283             artist = ", ".join(artist)
284         artist = util.escape(artist)
285         if hasattr(bag, "ReleaseDate"):
286             date = "(%s)" %util.escape(bag.ReleaseDate)
287         else:
288             date = ""
289         markup = "<i><b>%s</b></i> %s\n%s" %(title, date, artist)
290
291         item = {"bag": bag, "thumb": None, "thumb_data": ""}
292         iter = model.append([item, markup])
293
294         # Image part
295         urlinfo = urllib.urlopen(bag.ImageUrlSmall)
296         sock = urlinfo.fp._sock
297         sock.setblocking(0)
298         data = StringIO()
299
300         loader = gtk.gdk.PixbufLoader()
301         loader.connect("closed", self.__got_thumb_cb, data, item, model, iter)
302
303         gobject.io_add_watch(
304             sock, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
305             self.__copy_image, loader, data)
306
307     def __got_thumb_cb(self, loader, data, item, model, iter):
308         cover = loader.get_pixbuf()
309         if cover.get_width() > 1:
310             w = h = 48
311             cover = cover.scale_simple(w, h, gtk.gdk.INTERP_NEAREST)
312             thumb = gtk.gdk.Pixbuf(
313                 gtk.gdk.COLORSPACE_RGB, True, 8, w + 2, h + 2)
314             thumb.fill(0x000000ff)
315             cover.copy_area(0, 0, w, h, thumb, 1, 1)
316             item["thumb"] = thumb
317             item["thumb_data"] = data.getvalue()
318             model.row_changed(model.get_path(iter), iter)
319        
320     def __preview(self, selection):
321         model, path = selection.get_selected()
322         item = model[path][0]
323         self.image.hide()
324         self.button.set_sensitive(False)
325        
326         if item["thumb"]: # If there exists no thumbnail, then nothing bigger.
327             if "cover" not in item:
328                 self.__get_cover(item, item["bag"].ImageUrlLarge)
329             else:
330                 self.image.set_from_pixbuf(item["cover"])
331                 self.image.show()
332                 self.button.set_sensitive(True)
333
334     def __get_cover(self, item, url):
335         data = StringIO()
336         urlinfo = urllib.urlopen(url)
337         sock = urlinfo.fp._sock
338         sock.setblocking(0)
339         loader = gtk.gdk.PixbufLoader()
340         gobject.io_add_watch(
341             sock, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
342             self.__copy_image, loader, data)
343         loader.connect("closed", self.__got_cover_cb, data, item, url)
344         def update(loader, x, y, w, h, image):
345             if (w, h) > (1, 1):
346                 image.set_from_pixbuf(loader.get_pixbuf())
347                 image.show()
348         loader.connect("area-updated", update, self.image)
349
350     def __got_cover_cb(self, loader, data, item, url):
351         cover = loader.get_pixbuf()
352         # For some reason we get a 1x1 image if the given size didn't exist
353         if cover.get_width() > 1:
354             item["cover"] = cover
355             item["cover_data"] = data.getvalue()
356             self.image.set_from_pixbuf(item["cover"])
357             self.button.set_sensitive(True)
358         elif url == item["bag"].ImageUrlLarge:
359             self.__get_cover(item, item["bag"].ImageUrlMedium)
360         else:
361             item["cover"] = item["thumb"]
362             item["cover_data"] = item["thumb_data"]
363             self.image.set_from_pixbuf(item["cover"])
364             self.button.set_sensitive(True)
365
366     def __copy_image(self, src, condition, loader, data):
367         if condition in (gobject.IO_ERR, gobject.IO_HUP):
368             loader.close()
369             src.close()
370             return False
371         else: # Read
372             buf = src.recv(1024)
373             if buf:
374                 loader.write(buf)
375                 data.write(buf)
376                 return True # Run again
377             else:
378                 loader.close()
379                 src.close()
380                 return False
381
382     def __save_cover(self, data, fname):
383         if os.path.exists(fname) and not qltk.ConfirmAction(None,
384             "File exists", "The file <b>%s</b> already exists."
385             "\n\nOverwrite?" %util.escape(fname)).run():
386             return
387
388         f = open(fname, "w")
389         f.write(data)
390         f.close()
391         self.destroy()
392
393     def __get_fname(self, songs, combo):
394         append = combo.get_model()[(combo.get_active(),)][0]
395         dirname = songs[0]("~dirname")
396         fname = os.path.join(dirname, append)
397         #print "Will save to", fname
398         config.set("plugins", "cover_fn", append)
399         return fname
400
401 class DownloadAlbumArt(SongsMenuPlugin):
402     PLUGIN_ID = "Download Album art"
403     PLUGIN_NAME = _("Download Album Art")
404     PLUGIN_DESC = "Downloads album covers from Amazon.com, " \
405                   "Walmart.com and Darktown.to"
406     PLUGIN_ICON = gtk.STOCK_FIND
407     PLUGIN_VERSION = "0.25"
408
409     def PluginPreferences(parent):
410         vbox = gtk.VBox(spacing=5)
411         vbox.set_border_width(5)
412         bAM = gtk.Button("Visit Amazon.com")
413         bAM.connect('clicked', lambda s:util.website('http://www.amazon.com/'))
414         vbox.pack_start(bAM)
415         bWM = gtk.Button("Visit Walmart.com")
416         bWM.connect('clicked', lambda s:util.website('http://www.walmart.com/'))
417         vbox.pack_start(bWM)
418         bDT = gtk.Button("Visit Darktown.to")
419         bDT.connect('clicked', lambda s:util.website('http://www.darktown.to/'))
420         vbox.pack_start(bDT)
421         return vbox
422        
423     PluginPreferences = staticmethod(PluginPreferences)
424
425     plugin_album = AlbumArtWindow