from PyQt4 import QtGui, QtCore from PyQt4.QtCore import QVariant from traceback import print_exc import logging import os from ..plugin import Plugin from ..misc import APPNAME, expand_tags # FETCH MODES AC_NO_FETCH = 0 AC_FETCH_LOCAL_DIR = 1 AC_FETCH_AMAZON = 2 class wgAlbumCover(QtGui.QLabel): "cover - QPixmap or None" cover = None "is there a (non-default) cover loaded?" cover_loaded = False "plugin object" plugin = None _cover_dirname = None # Directory and full filepath where cover _cover_filepath = None # for current song should be stored. _menu = None # popup menu def __init__(self, plugin): QtGui.QLabel.__init__(self) self.plugin = plugin self.setAlignment(QtCore.Qt.AlignCenter) # popup menu self._menu = QtGui.QMenu("album") refresh = self._menu.addAction('&Refresh cover.') select_file_action = self._menu.addAction('&Select cover file...') fetch_amazon_action = self._menu.addAction('Fetch from &Amazon.') view_action = self._menu.addAction('&View in a separate window.') save_action = self._menu.addAction('Save cover &as...') self.connect(refresh, QtCore.SIGNAL('triggered()'), self.refresh) self.connect(select_file_action, QtCore.SIGNAL('triggered()'), self._fetch_local_manual) self.connect(fetch_amazon_action, QtCore.SIGNAL('triggered()'), self._fetch_amazon_manual) self.connect(view_action, QtCore.SIGNAL('triggered()'), self._view_cover) self.connect(save_action, QtCore.SIGNAL('triggered()'), self._save_cover) # MPD events self.connect(self.plugin.mpclient(), QtCore.SIGNAL('song_changed'), self.refresh) self.connect(self.plugin.mpclient(), QtCore.SIGNAL('disconnected'), self.refresh) self.connect(self.plugin.mpclient(), QtCore.SIGNAL('state_changed'),self.refresh) def mousePressEvent(self, event): if event.button() == QtCore.Qt.RightButton: self._menu.popup(event.globalPos()) def set_cover(self, cover, write = False): """Set cover for current song, attempt to write it to a file if write is True and it's globally allowed.""" logging.info('Setting cover') if not cover or cover.isNull(): self.cover = None self.cover_loaded = False self.setPixmap(QtGui.QPixmap('gfx/no-cd-cover.png')) self.plugin.emit(QtCore.SIGNAL('cover_changed'), None) return self.cover = cover self.cover_loaded = True self.setPixmap(cover.scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) self.plugin.emit(QtCore.SIGNAL('cover_changed'), self.cover) logging.info('Cover set.') if (write and self.plugin.settings().value(self.plugin.name() + '/store').toBool() and self._cover_filepath): if cover.save(self._cover_filepath, 'png'): logging.info('Cover saved.') else: logging.error('Error saving cover.') def refresh(self): """Autofetch cover for currently playing song.""" logging.info("refreshing cover") song = self.plugin.mpclient().current_song() if not song: return self.set_cover(None) if QtCore.QDir.isAbsolutePath(song.filepath()): self._cover_dirname = os.path.dirname(song.filepath()) self._cover_filepath = '' elif '://' in song.filepath(): # we are streaming self._cover_dirname = '' self._cover_filepath = '' else: dirname = self.plugin.settings().value(self.plugin.name() + '/coverdir').toString() self._cover_dirname = expand_tags(dirname, (self.plugin.parent(), song)) filebase = self.plugin.settings().value(self.plugin.name() + '/covername').toString() self._cover_filepath = '%s/%s'%(self._cover_dirname, expand_tags(filebase, (self.plugin.parent(), song)).replace('/', '_')) self._fetch_cover(song) def _fetch_cover(self, song): write = False if not QtCore.QFile.exists(self._cover_filepath): for i in (0, 1): src = self.plugin.settings().value(self.plugin.name() + '/method%i'%i).toInt()[0] if src == AC_FETCH_LOCAL_DIR and self._cover_dirname: cover = self._fetch_local(song) elif src == AC_FETCH_AMAZON: cover = self._fetch_amazon(song) else: cover = QtGui.QPixmap() if cover and not cover.isNull(): write = True break else: cover = QtGui.QPixmap(self._cover_filepath) self.set_cover(cover, write) def _fetch_local_manual(self): song = self.plugin.mpclient().current_song() if not song: return file = QtGui.QFileDialog.getOpenFileName(self, 'Select album cover for %s - %s'%(song.artist(), song.album()), self._cover_dirname, '') cover = QtGui.QPixmap(file) if cover.isNull(): return self.set_cover(cover, True) self.cover_loaded = True def _fetch_local(self, song): logging.info('Trying to guess local cover name.') # guess cover name covers = ['cover', 'album', 'front'] exts = [] for ext in QtGui.QImageReader().supportedImageFormats(): exts.append('*.%s'%str(ext)) filter = [] for cover in covers: for ext in exts: filter.append('*.%s%s'%(cover,ext)) dir = QtCore.QDir(self._cover_dirname) if not dir: logging.warning('Error opening directory' + self._cover_dirname) return None dir.setNameFilters(filter) files = dir.entryList() if files: cover = QtGui.QPixmap(dir.filePath(files[0])) if not cover.isNull(): logging.info('Found a cover.') return cover # if this failed, try any supported image dir.setNameFilters(exts) files = dir.entryList() if files: return QtGui.QPixmap(dir.filePath(files[0])) logging.info('No matching cover found') return None def _fetch_amazon_manual(self): song = self.plugin.mpclient().current_song() if not song: return cover = self._fetch_amazon(song) self.set_cover(cover, True) def _fetch_amazon(self, song): if not song.artist() or not song.album(): return None # get the url from amazon WS coverURL = AmazonAlbumImage(song.artist(), song.album()).fetch() logging.info("fetching from Amazon") if not coverURL: logging.info("not found on Amazon") return None img = urllib.urlopen(coverURL) cover = QtGui.QPixmap() cover.loadFromData(img.read()) return cover def _view_cover(self): if not self.cover_loaded: return win = QtGui.QLabel(self, QtCore.Qt.Window) win.setScaledContents(True) win.setPixmap(self.cover) win.show() def _save_cover(self): if not self.cover_loaded: return cover = self.cover file = QtGui.QFileDialog.getSaveFileName(None, '', os.path.expanduser('~')) if file: if not cover.save(file): logging.error('Saving cover failed.') class AlbumCover(Plugin): o = None DEFAULTS = {'coverdir' : '$musicdir/$songdir', 'covername' : '.cover_nephilim_$artist_$album', 'method0' : 1, 'method1' : 1, 'store' : True} def _load(self): self.o = wgAlbumCover(self) def _unload(self): self.o = None def info(self): return "Display the album cover of the currently playing album." def refresh(self, params = None): self.o.refresh() def cover(self): if not self.o: return None return self.o.cover if self.o.cover_loaded else None def _get_dock_widget(self): return self._create_dock(self.o) class SettingsWidgetAlbumCover(Plugin.SettingsWidget): methods = [] coverdir = None covername = None store = None def __init__(self, plugin): Plugin.SettingsWidget.__init__(self, plugin) self.settings().beginGroup(self.plugin.name()) # fetching methods comboboxes self.methods = [QtGui.QComboBox(), QtGui.QComboBox()] for i,method in enumerate(self.methods): method.addItem('No method.') method.addItem('Local dir') method.addItem('Amazon') method.setCurrentIndex(self.settings().value('method' + str(i)).toInt()[0]) # store covers groupbox self.store = QtGui.QGroupBox('Store covers.') self.store.setToolTip('Should %s store its own copy of covers?'%APPNAME) self.store.setCheckable(True) self.store.setChecked(self.settings().value('store').toBool()) self.store.setLayout(QtGui.QGridLayout()) # paths to covers self.coverdir = QtGui.QLineEdit(self.settings().value('coverdir').toString()) self.coverdir.setToolTip('Where should %s store covers.\n' '$musicdir will be expanded to path to MPD music library (as set by user)\n' '$songdir will be expanded to path to the song (relative to $musicdir\n' 'other tags same as in covername' %APPNAME) self.covername = QtGui.QLineEdit(self.settings().value('covername').toString()) self.covername.setToolTip('Filename for %s cover files.\n' 'All tags supported by MPD will be expanded to their\n' 'values for current song, e.g. $title, $track, $artist,\n' '$album, $genre etc.'%APPNAME) self.store.layout().addWidget(QtGui.QLabel('Cover directory'), 0, 0) self.store.layout().addWidget(self.coverdir, 0, 1) self.store.layout().addWidget(QtGui.QLabel('Cover filename'), 1, 0) self.store.layout().addWidget(self.covername, 1, 1) self.setLayout(QtGui.QVBoxLayout()) self._add_widget(self.methods[0], 'Method 0', 'Method to try first.') self._add_widget(self.methods[1], 'Method 1', 'Method to try if the first one fails.') self.layout().addWidget(self.store) self.settings().endGroup() def save_settings(self): self.settings().beginGroup(self.plugin.name()) self.settings().setValue('method0', QVariant(self.methods[0].currentIndex())) self.settings().setValue('method1', QVariant(self.methods[1].currentIndex())) self.settings().setValue('coverdir', QVariant(self.coverdir.text())) self.settings().setValue('covername', QVariant(self.covername.text())) self.settings().setValue('store', QVariant(self.store.isChecked())) self.settings().endGroup() self.plugin.o.refresh() def get_settings_widget(self): return self.SettingsWidgetAlbumCover(self) # This is the amazon cover fetcher using their webservice api # Thank you, http://www.semicomplete.com/scripts/albumcover.py import re import urllib AMAZON_AWS_ID = "0K4RZZKHSB5N2XYJWF02" class AmazonAlbumImage(object): awsurl = 'http://ecs.amazonaws.com/onca/xml' def __init__(self, artist, album): self.artist = artist self.album = album def fetch(self): url = self._GetResultURL(self._SearchAmazon()) if not url: return None img_re = re.compile(r'''registerImage\("original_image", "([^"]+)"''') try: prod_data = urllib.urlopen(url).read() except: logging.warning('timeout opening %s'%(url)) return None m = img_re.search(prod_data) if not m: return None img_url = m.group(1) return img_url def _SearchAmazon(self): data = { 'Service' : 'AWSECommerceService', 'Version' : '2005-03-23', 'Operation' : 'ItemSearch', 'ContentType' : 'text/xml', 'SubscriptionId': AMAZON_AWS_ID, 'SearchIndex' : 'Music', 'ResponseGroup' : 'Small', } data['Artist'] = self.artist.encode('utf-8') data['Keywords'] = self.album.encode('utf-8') fd = urllib.urlopen('%s?%s' % (self.awsurl, urllib.urlencode(data))) return fd.read() def _GetResultURL(self, xmldata): if not xmldata: return None url_re = re.compile(r'([^<]+)') m = url_re.search(xmldata) return m and m.group(1)