Plugins: albumart.3.py

File albumart.3.py, 10.8 kB (added by jmcantrell@gmail.com, 2 months ago)

Updated version of albumart.py that uses PyAWS instead of the obsolete pyamazon.

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 # Last Modified: Mon 2008-05-05 14:54:27 (-0400)
9 # Updated for new Amazon API by Jeremy Cantrell <jmcantrell@gmail.com>
10
11 import os
12 import sys
13 import urllib
14 import threading
15 from cStringIO import StringIO
16 import gtk
17 import gobject
18 import pango
19 import util
20 import qltk
21 import config
22
23 from plugins.songsmenu import SongsMenuPlugin
24
25 if sys.version_info < (2, 4): from sets import Set as set
26
27 from pyaws import ecs
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         ecs.setLicenseKey("0RKH4ZH1JCFZHMND91G2")
146         try:
147             query = query.encode("latin1", 'replace')
148             bags = ecs.ItemSearch(query, SearchIndex='Music',
149                     ResponseGroup='Images,Small')
150         except ecs.AWSException, msg:
151             dialog = qltk.Message(gtk.MESSAGE_ERROR, None, "Search error", msg)
152             dialog.connect('response', self.__destroy_cb)
153             gobject.idle_add(dialog.show)
154         except UnicodeEncodeError, msg:
155             dialog = qltk.Message(gtk.MESSAGE_ERROR, None, "Encoding error",
156                                   msg)
157             dialog.connect('response', self.__destroy_cb)
158             gobject.idle_add(dialog.show)
159         else:
160             # Just keep the top 10 matches
161             for bag in bags:
162                 gobject.idle_add(self.__add_bag, self.liststore, bag)
163
164
165     def __search(self, songs):
166         query = songs[0]("artist") + " " + songs[0]("album")
167         self.__search_string(query)
168
169     def __destroy_cb(self, widget, *args):
170         widget.destroy()
171         #self.destroy()
172
173     def __add_bag(self, model, bag):
174         # Text part
175         title = util.escape(getattr(bag, "Title", ""))
176         artist = (getattr(bag, "Artists", None) and
177                   getattr(bag.Artists, "Artist", None) or "")
178         if isinstance(artist, list):
179             artist = ", ".join(artist)
180         artist = util.escape(artist)
181         if hasattr(bag, "ReleaseDate"):
182             date = "(%s)" %util.escape(bag.ReleaseDate)
183         else:
184             date = ""
185         markup = "<i><b>%s</b></i> %s\n%s" %(title, date, artist)
186
187         item = {"bag": bag, "thumb": None, "thumb_data": ""}
188         iter = model.append([item, markup])
189
190         # Image part
191         if getattr(bag, 'SmallImage', None):
192             urlinfo = urllib.urlopen(bag.SmallImage.URL)
193             sock = urlinfo.fp._sock
194             sock.setblocking(0)
195             data = StringIO()
196
197         loader = gtk.gdk.PixbufLoader()
198         loader.connect("closed", self.__got_thumb_cb, data, item, model, iter)
199
200         gobject.io_add_watch(
201             sock, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
202             self.__copy_image, loader, data)
203
204     def __got_thumb_cb(self, loader, data, item, model, iter):
205         cover = loader.get_pixbuf()
206         if cover.get_width() > 1:
207             w = h = 48
208             cover = cover.scale_simple(w, h, gtk.gdk.INTERP_NEAREST)
209             thumb = gtk.gdk.Pixbuf(
210                 gtk.gdk.COLORSPACE_RGB, True, 8, w + 2, h + 2)
211             thumb.fill(0x000000ff)
212             cover.copy_area(0, 0, w, h, thumb, 1, 1)
213             item["thumb"] = thumb
214             item["thumb_data"] = data.getvalue()
215             model.row_changed(model.get_path(iter), iter)
216
217     def __preview(self, selection):
218         model, path = selection.get_selected()
219         item = model[path][0]
220         self.image.hide()
221         self.button.set_sensitive(False)
222
223         if item["thumb"]: # If there exists no thumbnail, then nothing bigger.
224             if "cover" not in item:
225                 self.__get_cover(item, item["bag"].LargeImage.URL)
226             else:
227                 self.image.set_from_pixbuf(item["cover"])
228                 self.image.show()
229                 self.button.set_sensitive(True)
230
231     def __get_cover(self, item, url):
232         data = StringIO()
233         urlinfo = urllib.urlopen(url)
234         sock = urlinfo.fp._sock
235         sock.setblocking(0)
236         loader = gtk.gdk.PixbufLoader()
237         gobject.io_add_watch(
238             sock, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
239             self.__copy_image, loader, data)
240         loader.connect("closed", self.__got_cover_cb, data, item, url)
241         def update(loader, x, y, w, h, image):
242             if (w, h) > (1, 1):
243                 image.set_from_pixbuf(loader.get_pixbuf())
244                 image.show()
245         loader.connect("area-updated", update, self.image)
246
247     def __got_cover_cb(self, loader, data, item, url):
248         cover = loader.get_pixbuf()
249         # For some reason we get a 1x1 image if the given size didn't exist
250         if cover.get_width() > 1:
251             item["cover"] = cover
252             item["cover_data"] = data.getvalue()
253             self.image.set_from_pixbuf(item["cover"])
254             self.button.set_sensitive(True)
255         elif url == item["bag"].LargeImage.URL:
256             self.__get_cover(item, item["bag"].MediumImage.URL)
257         else:
258             item["cover"] = item["thumb"]
259             item["cover_data"] = item["thumb_data"]
260             self.image.set_from_pixbuf(item["cover"])
261             self.button.set_sensitive(True)
262
263     def __copy_image(self, src, condition, loader, data):
264         if condition in (gobject.IO_ERR, gobject.IO_HUP):
265             loader.close()
266             src.close()
267             return False
268         else: # Read
269             buf = src.recv(1024)
270             if buf:
271                 loader.write(buf)
272                 data.write(buf)
273                 return True # Run again
274             else:
275                 loader.close()
276                 src.close()
277                 return False
278
279     def __save_cover(self, data, fname):
280         if os.path.exists(fname) and not qltk.ConfirmAction(None,
281             "File exists", "The file <b>%s</b> already exists."
282             "\n\nOverwrite?" %util.escape(fname)).run():
283             return
284
285         f = open(fname, "w")
286         f.write(data)
287         f.close()
288         self.destroy()
289
290     def __get_fname(self, songs, combo):
291         append = combo.get_model()[(combo.get_active(),)][0]
292         dirname = songs[0]("~dirname")
293         fname = os.path.join(dirname, append)
294         #print "Will save to", fname
295         config.set("plugins", "cover_fn", append)
296         return fname
297
298 class DownloadAlbumArt(SongsMenuPlugin):
299     PLUGIN_ID = "Download Album art"
300     PLUGIN_NAME = _("Download Album Art")
301     PLUGIN_DESC = "Downloads album covers from Amazon.com"
302     PLUGIN_ICON = gtk.STOCK_FIND
303     PLUGIN_VERSION = "0.25"
304
305     def PluginPreferences(parent):
306         b = gtk.Button("Visit Amazon.com")
307         b.connect('clicked', lambda s: util.website('http://www.amazon.com/'))
308         return b
309     PluginPreferences = staticmethod(PluginPreferences)
310
311     plugin_album = AlbumArtWindow