AlbumCover: refresh has to take two parameters.
[nephilim.git] / plugins / AlbumCover.py
blobd8b1f241ee85f56e193c7a78f96b432521012c11
1 from PyQt4 import QtGui, QtCore
2 from PyQt4.QtCore import QVariant
3 from traceback import print_exc
4 import os
5 import shutil
6 import logging
8 from clPlugin import Plugin
9 from misc import ORGNAME, APPNAME
11 # FETCH MODES
12 AC_NO_FETCH = 0
13 AC_FETCH_LOCAL_DIR = 1
14 AC_FETCH_INTERNET = 2
16 class wgAlbumCover(QtGui.QLabel):
17 " container for the image"
18 img = None
19 imgLoaded = False
20 p = None
21 cover_dirname = None
22 cover_filepath = None
23 menu = None
24 def __init__(self, p, parent=None):
25 QtGui.QWidget.__init__(self,parent)
26 self.p=p
27 self.setAlignment(QtCore.Qt.AlignCenter)
29 # popup menu
30 self.menu = QtGui.QMenu("album")
31 select_file_action = self.menu.addAction('Select cover file...')
32 self.connect(select_file_action, QtCore.SIGNAL('triggered()'), self.select_cover_file)
33 fetch_amazon_action = self.menu.addAction('Fetch cover from Amazon.')
34 self.connect(fetch_amazon_action, QtCore.SIGNAL('triggered()'), self.fetch_amazon)
36 def mousePressEvent(self, event):
37 if event.button()==QtCore.Qt.RightButton:
38 self.menu.popup(event.globalPos())
40 def select_cover_file(self):
41 try:
42 song = self.p.monty.getCurrentSong()
43 file = QtGui.QFileDialog.getOpenFileName(self
44 , "Select album cover for %s - %s"%(song.getArtist(), song.getAlbum())
45 , self.cover_dirname
46 , ""
48 if file:
49 shutil.copy(file, self.cover_filepath)
50 else:
51 return
52 except IOError:
53 logging.info("Error setting cover file.")
54 self.refresh()
56 def get_cover(self):
57 if self.imgLoaded:
58 return self.pixmap()
59 return None
61 def refresh(self, params = None):
62 logging.info("refreshing cover")
63 song = self.p.monty.getCurrentSong()
64 if not song:
65 self.clear()
66 self.update()
67 return
69 dirname = unicode(self.p.settings.value(self.p.getName() + '/coverdir').toString())
70 self.cover_dirname = dirname.replace('$musicdir', self.p.settings.value('MPD/music_dir').toString()).replace('$songdir', os.path.dirname(song.getFilepath()))
71 filebase = unicode(self.p.settings.value(self.p.getName() + '/covername').toString())
72 self.cover_filepath = os.path.join(self.cover_dirname, song.expand_tags(filebase).replace(os.path.sep, '_'))
73 self.fetchCover(song)
75 def fetchCover(self, song):
76 """Fetch cover (from internet or local dir)"""
77 # set default cover
79 if not os.path.exists(self.cover_filepath):
80 success = False
81 for i in [0, 1]:
82 src = self.p.settings.value(self.p.getName() + '/action%i'%i).toInt()[0]
83 if src != AC_NO_FETCH:
84 if self.fetchCoverSrc(song, src):
85 success = True
86 break
87 if not success:
88 self.imgLoaded = False
89 self.setPixmap(QtGui.QPixmap('gfx/no-cd-cover.png').scaled(self.size(), QtCore.Qt.KeepAspectRatio,
90 QtCore.Qt.SmoothTransformation))
91 return
93 try:
94 self.setPixmap(QtGui.QPixmap(self.cover_filepath).scaled(self.size(), QtCore.Qt.KeepAspectRatio,
95 QtCore.Qt.SmoothTransformation))
96 self.imgLoaded = True
97 logging.info("cover set!")
98 except IOError:
99 logging.warning("Error loading album cover" + self.cover_filepath)
101 self.update()
103 def getLocalACPath(self, song):
104 """Get the local path of an albumcover."""
105 covers = ['cover', 'album', 'front']
107 # fetch gfx extensions
108 exts = QtGui.QImageReader().supportedImageFormats()
109 exts = map(lambda ext: '*.' + unicode(ext), exts)
111 # fetch cover album titles
112 filter = []
113 for cover in covers:
114 for ext in exts:
115 filter.append(cover.strip() + ext)
117 dir = QtCore.QDir(self.cover_dirname)
118 if not dir:
119 logging.warning('Error opening directory' + self.cover_dirname)
120 return None;
121 dir.setNameFilters(filter)
122 files = dir.entryList()
123 if files:
124 return unicode(dir.filePath(files[0]))
125 # if this failed, try any supported image
126 dir.setNameFilters(exts)
127 files = dir.entryList()
128 if files:
129 return unicode(dir.filePath(files[0]))
130 logging.info("done probing: no matching albumcover found")
131 return None
133 def fetch_amazon(self):
134 self.fetchCoverSrc(self.p.monty.getCurrentSong(), AC_FETCH_INTERNET)
135 self.refresh()
137 def fetchCoverSrc(self, song, src):
138 """Fetch the album cover for $song from $src."""
139 if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]:
140 logging.warning("wgAlbumCover::fetchCover - invalid source "+str(src))
141 return False
143 if src == AC_FETCH_INTERNET:
144 # look on the internetz!
145 try:
146 if not song.getArtist() or not song.getAlbum():
147 return False
148 # get the url from amazon WS
149 coverURL=AmazonAlbumImage(song.getArtist(), song.getAlbum()).fetch()
150 logging.info("fetch from Amazon")
151 if not coverURL:
152 logging.info("not found on Amazon")
153 return False
154 # read the url, i.e. retrieve image data
155 img=urllib.urlopen(coverURL)
156 # open file, and write the read of img!
157 f=open(self.cover_filepath,'wb')
158 f.write(img.read())
159 f.close()
160 return True
161 except:
162 logging.info("failed to download cover from Amazon")
163 print_exc()
164 return False
166 if src == AC_FETCH_LOCAL_DIR:
167 file=self.getLocalACPath(song)
168 try:
169 shutil.copy(file, self.cover_filepath)
170 return True
171 except:
172 logging.info("Failed to create cover file")
173 return False
175 class pluginAlbumCover(Plugin):
176 o = None
177 DEFAULTS = {'coverdir' : '$musicdir/$songdir', 'covername' : '.cover_monty_$artist_$album',
178 'action0' : 1, 'action1' : 1}
179 def __init__(self, winMain):
180 Plugin.__init__(self, winMain, 'AlbumCover')
182 def _load(self):
183 self.o = wgAlbumCover(self, None)
184 self.monty.add_listener('onSongChange' , self.o.refresh)
185 self.monty.add_listener('onReady' , self.o.refresh)
186 self.monty.add_listener('onDisconnect' , self.o.refresh)
187 self.monty.add_listener('onStateChange', self.o.refresh)
188 self.o.refresh()
189 def _unload(self):
190 self.o = None
191 def getInfo(self):
192 return "Display the album cover of the currently playing album."
193 def getExtInfo(self):
194 return "Displays the album cover of the currently playing album in a widget.\n" \
195 "This album cover can be fetched from various locations:\n" \
196 " local dir: the directory in which the album is located;\n" \
197 " internet: look on amazon for the album and corresponding cover\n" \
198 "Settings:\n" \
199 " albumcover.fetch$i: what source to fetch from on step $i. If step $i fails, move on to step $i+1;\n" \
200 " albumcover.downloadto: where to download album covers from internet to. This string can contain the normal tags of the current playing song, plus $music_dir and $cover.\n" \
201 " albumcover.files: comma separated list of filenames (without extension)to be considered an album cover. Extensions jpg, jpeg, png, gif and bmp are used.\n"
203 def getWidget(self):
204 return self.o
206 def _getDockWidget(self):
207 return self._createDock(self.o)
209 class SettingsWidgetAlbumCover(Plugin.SettingsWidget):
210 actions = []
211 coverdir = None
212 covername = None
214 def __init__(self, plugin):
215 Plugin.SettingsWidget.__init__(self, plugin)
216 self.settings.beginGroup(self.plugin.getName())
218 self.actions = [QtGui.QComboBox(), QtGui.QComboBox()]
219 for i,action in enumerate(self.actions):
220 action.addItem("No action.")
221 action.addItem("Local dir")
222 action.addItem("Amazon")
223 action.setCurrentIndex(self.settings.value('action' + str(i)).toInt()[0])
225 self.coverdir = QtGui.QLineEdit(self.settings.value('coverdir').toString())
226 self.covername = QtGui.QLineEdit(self.settings.value('covername').toString())
228 self.setLayout(QtGui.QVBoxLayout())
229 self._add_widget(self.actions[0], 'Action 0')
230 self._add_widget(self.actions[1], 'Action 1')
231 self._add_widget(self.coverdir, 'Cover directory',
232 'Where should %s store covers.\n'
233 '$musicdir will be expanded to path to MPD music library\n'
234 '$songdir will be expanded to path to the song (relative to $musicdir'
235 %APPNAME)
236 self._add_widget(self.covername, 'Cover filename', 'Filename for %s cover files.'%APPNAME)
237 self.settings.endGroup()
239 def save_settings(self):
240 self.settings.beginGroup(self.plugin.getName())
241 self.settings.setValue('action0', QVariant(self.actions[0].currentIndex()))
242 self.settings.setValue('action1', QVariant(self.actions[1].currentIndex()))
243 self.settings.setValue('coverdir', QVariant(self.coverdir.text()))
244 self.settings.setValue('covername', QVariant(self.covername.text()))
245 self.settings.endGroup()
246 self.plugin.o.refresh()
248 def get_settings_widget(self):
249 return self.SettingsWidgetAlbumCover(self)
252 # This is the amazon cover fetcher using their webservice api
253 # Thank you, http://www.semicomplete.com/scripts/albumcover.py
254 import re
255 import urllib
257 AMAZON_AWS_ID = "0K4RZZKHSB5N2XYJWF02"
259 class AmazonAlbumImage(object):
260 awsurl = "http://ecs.amazonaws.com/onca/xml"
261 def __init__(self, artist, album):
262 self.artist = artist
263 self.album = album
265 def fetch(self):
266 url = self._GetResultURL(self._SearchAmazon())
267 if not url:
268 return None
269 img_re = re.compile(r'''registerImage\("original_image", "([^"]+)"''')
270 try:
271 prod_data = urllib.urlopen(url).read()
272 except:
273 self.important("timeout opening %s"%(url))
274 return None
275 m = img_re.search(prod_data)
276 if not m:
277 return None
278 img_url = m.group(1)
279 return img_url
281 def _SearchAmazon(self):
282 data = {
283 "Service": "AWSECommerceService",
284 "Version": "2005-03-23",
285 "Operation": "ItemSearch",
286 "ContentType": "text/xml",
287 "SubscriptionId": AMAZON_AWS_ID,
288 "SearchIndex": "Music",
289 "ResponseGroup": "Small",
292 data["Artist"] = self.artist
293 data["Keywords"] = self.album
295 fd = urllib.urlopen("%s?%s" % (self.awsurl, urllib.urlencode(data)))
296 return fd.read()
299 def _GetResultURL(self, xmldata):
300 if not xmldata:
301 return None
302 url_re = re.compile(r"<DetailPageURL>([^<]+)</DetailPageURL>")
303 m = url_re.search(xmldata)
304 return m and m.group(1)