Plugins: albumart.py

File albumart.py, 10.8 kB (added by antonio.riva@gmail.com, 1 year ago)

Added search capabilities in the album downloader

Line 
1 #Copyright 2005 Eduardo Gonzalez, Niklas Janlert, Antonio Riva
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 threading
12 from cStringIO import StringIO
13 import gtk
14 import gobject
15 import pango
16 import util
17 import qltk
18 import config
19
20 from plugins.songsmenu import SongsMenuPlugin
21
22 if sys.version_info < (2, 4): from sets import Set as set
23
24 try:
25     import amazon
26 except ImportError:
27     import _amazon as amazon
28
29 class AlbumArtWindow(gtk.Window):
30     def __init__(self, songs):
31         gtk.Window.__init__(self)
32         self.set_border_width(12)
33         self.set_title("AlbumArt")
34         self.set_default_size(650, 350)
35        
36         #TreeView stuff
37         self.liststore = liststore = gtk.ListStore(object, str)
38         treeview = gtk.TreeView(liststore)
39         treeview.set_headers_visible(False)
40         selection = treeview.get_selection()
41         selection.set_mode(gtk.SELECTION_SINGLE)
42         selection.connect("changed", self.__preview)
43
44
45         self.url = url = gtk.Entry()
46         url.set_text(songs[0]("artist") + " " + songs[0]("album"))
47        
48         urlButton = gtk.Button("Search")
49         urlButton.connect("clicked", self.__start_search, url,liststore)
50         url.connect("key-release-event", self.key_start_search, url,liststore)
51
52        
53         urlBox= gtk.HBox()
54         urlBox.pack_start(url, expand=True, fill=True)
55         urlBox.pack_start(urlButton, expand=False, fill=False)
56      
57
58         rend = gtk.CellRendererPixbuf()
59         def cell_data(column, cell, model, iter):
60             cell.set_property("pixbuf", model[iter][0]["thumb"])
61         tvcol1 = gtk.TreeViewColumn("Pixbuf", rend)
62         tvcol1.set_cell_data_func(rend, cell_data)
63         tvcol1.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
64         rend.set_property('xpad', 2)
65         rend.set_property('ypad', 2)
66         rend.set_property('width', 56)
67         rend.set_property('height', 56)
68         treeview.append_column(tvcol1)
69
70         rend = gtk.CellRendererText()
71         rend.set_property("ellipsize", pango.ELLIPSIZE_END)
72         tvcol2 = gtk.TreeViewColumn("Info", rend, markup=1)
73         treeview.append_column(tvcol2)
74
75         sw = gtk.ScrolledWindow()
76         sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
77         sw.set_shadow_type(gtk.SHADOW_IN)
78         sw.add(treeview)
79
80         #Image frame and save button
81         self.image = image = gtk.Image()
82         frame = gtk.Frame()
83         frame.set_shadow_type(gtk.SHADOW_IN)
84         frame.add(image)
85        
86         vbox = gtk.VBox(spacing=5)
87         vbox.pack_start(frame)
88         self.button = button = gtk.Button(stock=gtk.STOCK_SAVE)
89         button.set_sensitive(False)
90         def save_cb(button, combo):
91             model, path = selection.get_selected()
92             data = model[path][0]["cover_data"]
93             fname = self.__get_fname(songs, combo)
94             self.__save_cover(data, fname)
95         combo = gtk.combo_box_new_text()
96         try: set_fn = config.get("plugins", "cover_fn")
97         except: set_fn = ".folder.jpg"
98         active = -1
99         for i, fn in enumerate([".folder.jpg", "folder.jpg", "cover.jpg"]):
100             combo.append_text(fn)
101             if fn == set_fn: active = i
102         if active == -1:
103             combo.append_text(set_fn)
104             combo.set_active(len(combo.get_model()) - 1)
105         else: combo.set_active(active)
106         button.connect("clicked", save_cb, combo)
107         bbox = gtk.HButtonBox()
108         bbox.pack_start(combo)
109         bbox.pack_start(button, expand=False, fill=False)
110         bbox.set_layout(gtk.BUTTONBOX_SPREAD)
111         vbox.pack_start(bbox, expand=False, fill=False)
112        
113        
114         hpaned = gtk.HPaned()
115         hpaned.pack1(sw)
116         hpaned.pack2(vbox)
117         hpaned.set_position(300)
118        
119         vbox= gtk.VBox()
120         vbox.pack_start(urlBox, expand=False, fill=False)
121         vbox.pack_start(hpaned, expand=True, fill=True)
122         self.add(vbox)
123        
124        
125         thread = threading.Thread(target=self.__search, args=(songs,))
126         thread.setDaemon(True)
127         thread.start()
128
129         self.show_all()
130        
131     def key_start_search(self, widget,event, entry,liststore):
132         if(event.keyval==65293):
133             self.__start_search( widget, entry,liststore)
134        
135    
136     def __start_search(self, widget, entry,liststore):
137         liststore.clear()
138         entry_text = entry.get_text()
139         print "Search: %s\n" % entry_text
140         thread = threading.Thread(target=self.__search_string, args=(entry_text,))
141         thread.setDaemon(True)
142         thread.start()
143
144     def __search_string(self, query):
145        
146         amazon.setLicense("0RKH4ZH1JCFZHMND91G2")
147
148         try:
149             query = query.encode("latin1", 'replace')
150             bags = amazon.searchByKeyword(
151                 query, type="lite", product_line="music")
152         except amazon.AmazonError, msg:
153             dialog = qltk.Message(gtk.MESSAGE_ERROR, None, "Search error", msg)
154             dialog.connect('response', self.__destroy_cb)
155             gobject.idle_add(dialog.show)
156         except UnicodeEncodeError, msg:
157             dialog = qltk.Message(gtk.MESSAGE_ERROR, None, "Encoding error",
158                                   msg)
159             dialog.connect('response', self.__destroy_cb)
160             gobject.idle_add(dialog.show)
161         else:
162             # Just keep the top 10 matches
163             for bag in bags:
164                 gobject.idle_add(self.__add_bag, self.liststore, bag)
165    
166    
167     def __search(self, songs):
168         query = songs[0]("artist") + " " + songs[0]("album")
169         self.__search_string(query)
170
171     def __destroy_cb(self, widget, *args):
172         widget.destroy()
173         #self.destroy()
174
175     def __add_bag(self, model, bag):
176         # Text part
177         title = util.escape(getattr(bag, "ProductName", ""))
178         artist = (getattr(bag, "Artists", None) and
179                   getattr(bag.Artists, "Artist", None) or "")
180         if isinstance(artist, list):
181             artist = ", ".join(artist)
182         artist = util.escape(artist)
183         if hasattr(bag, "ReleaseDate"):
184             date = "(%s)" %util.escape(bag.ReleaseDate)
185         else:
186             date = ""
187         markup = "<i><b>%s</b></i> %s\n%s" %(title, date, artist)
188
189         item = {"bag": bag, "thumb": None, "thumb_data": ""}
190         iter = model.append([item, markup])
191
192         # Image part
193         urlinfo = urllib.urlopen(bag.ImageUrlSmall)
194         sock = urlinfo.fp._sock
195         sock.setblocking(0)
196         data = StringIO()
197
198         loader = gtk.gdk.PixbufLoader()
199         loader.connect("closed", self.__got_thumb_cb, data, item, model, iter)
200
201         gobject.io_add_watch(
202             sock, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
203             self.__copy_image, loader, data)
204
205     def __got_thumb_cb(self, loader, data, item, model, iter):
206         cover = loader.get_pixbuf()
207         if cover.get_width() > 1:
208             w = h = 48
209             cover = cover.scale_simple(w, h, gtk.gdk.INTERP_NEAREST)
210             thumb = gtk.gdk.Pixbuf(
211                 gtk.gdk.COLORSPACE_RGB, True, 8, w + 2, h + 2)
212             thumb.fill(0x000000ff)
213             cover.copy_area(0, 0, w, h, thumb, 1, 1)
214             item["thumb"] = thumb
215             item["thumb_data"] = data.getvalue()
216             model.row_changed(model.get_path(iter), iter)
217        
218     def __preview(self, selection):
219         model, path = selection.get_selected()
220         item = model[path][0]
221         self.image.hide()
222         self.button.set_sensitive(False)
223        
224         if item["thumb"]: # If there exists no thumbnail, then nothing bigger.
225             if "cover" not in item:
226                 self.__get_cover(item, item["bag"].ImageUrlLarge)
227             else:
228                 self.image.set_from_pixbuf(item["cover"])
229                 self.image.show()
230                 self.button.set_sensitive(True)
231
232     def __get_cover(self, item, url):
233         data = StringIO()
234         urlinfo = urllib.urlopen(url)
235         sock = urlinfo.fp._sock
236         sock.setblocking(0)
237         loader = gtk.gdk.PixbufLoader()
238         gobject.io_add_watch(
239             sock, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
240             self.__copy_image, loader, data)
241         loader.connect("closed", self.__got_cover_cb, data, item, url)
242         def update(loader, x, y, w, h, image):
243             if (w, h) > (1, 1):
244                 image.set_from_pixbuf(loader.get_pixbuf())
245                 image.show()
246         loader.connect("area-updated", update, self.image)
247
248     def __got_cover_cb(self, loader, data, item, url):
249         cover = loader.get_pixbuf()
250         # For some reason we get a 1x1 image if the given size didn't exist
251         if cover.get_width() > 1:
252             item["cover"] = cover
253             item["cover_data"] = data.getvalue()
254             self.image.set_from_pixbuf(item["cover"])
255             self.button.set_sensitive(True)
256         elif url == item["bag"].ImageUrlLarge:
257             self.__get_cover(item, item["bag"].ImageUrlMedium)
258         else:
259             item["cover"] = item["thumb"]
260             item["cover_data"] = item["thumb_data"]
261             self.image.set_from_pixbuf(item["cover"])
262             self.button.set_sensitive(True)
263
264     def __copy_image(self, src, condition, loader, data):
265         if condition in (gobject.IO_ERR, gobject.IO_HUP):
266             loader.close()
267             src.close()
268             return False
269         else: # Read
270             buf = src.recv(1024)
271             if buf:
272                 loader.write(buf)
273                 data.write(buf)
274                 return True # Run again
275             else:
276                 loader.close()
277                 src.close()
278                 return False
279
280     def __save_cover(self, data, fname):
281         if os.path.exists(fname) and not qltk.ConfirmAction(None,
282             "File exists", "The file <b>%s</b> already exists."
283             "\n\nOverwrite?" %util.escape(fname)).run():
284             return
285
286         f = open(fname, "w")
287         f.write(data)
288         f.close()
289         self.destroy()
290
291     def __get_fname(self, songs, combo):
292         append = combo.get_model()[(combo.get_active(),)][0]
293         dirname = songs[0]("~dirname")
294         fname = os.path.join(dirname, append)
295         #print "Will save to", fname
296         config.set("plugins", "cover_fn", append)
297         return fname
298
299 class DownloadAlbumArt(SongsMenuPlugin):
300     PLUGIN_ID = "Download Album art"
301     PLUGIN_NAME = _("Download Album Art")
302     PLUGIN_DESC = "Downloads album covers from Amazon.com"
303     PLUGIN_ICON = gtk.STOCK_FIND
304     PLUGIN_VERSION = "0.25"
305
306     def PluginPreferences(parent):
307         b = gtk.Button("Visit Amazon.com")
308         b.connect('clicked', lambda s: util.website('http://www.amazon.com/'))
309         return b
310     PluginPreferences = staticmethod(PluginPreferences)
311
312     plugin_album = AlbumArtWindow