from PyQt4 import QtGui, QtCore from PyQt4.QtCore import QVariant from traceback import print_exc import os import shutil import logging from clPlugin import Plugin from misc import ORGNAME, APPNAME # FETCH MODES AC_NO_FETCH = 0 AC_FETCH_LOCAL_DIR = 1 AC_FETCH_INTERNET = 2 class wgAlbumCover(QtGui.QLabel): " container for the image" img = None imgLoaded = False p = None cover_dirname = None cover_filepath = None menu = None def __init__(self, p, parent=None): QtGui.QWidget.__init__(self,parent) self.p=p self.setAlignment(QtCore.Qt.AlignCenter) # popup menu self.menu = QtGui.QMenu("album") select_file_action = self.menu.addAction('Select cover file...') self.connect(select_file_action, QtCore.SIGNAL('triggered()'), self.select_cover_file) fetch_amazon_action = self.menu.addAction('Fetch cover from Amazon.') self.connect(fetch_amazon_action, QtCore.SIGNAL('triggered()'), self.fetch_amazon) def mousePressEvent(self, event): if event.button()==QtCore.Qt.RightButton: self.menu.popup(event.globalPos()) def select_cover_file(self): try: song = self.p.monty.getCurrentSong() file = QtGui.QFileDialog.getOpenFileName(self , "Select album cover for %s - %s"%(song.getArtist(), song.getAlbum()) , self.cover_dirname , "" ) if file: shutil.copy(file, self.cover_filepath) else: return except IOError: logging.info("Error setting cover file.") self.refresh() def get_cover(self): if self.imgLoaded: return self.pixmap() return None def refresh(self): logging.info("refreshing cover") song = self.p.monty.getCurrentSong() if not song: self.clear() self.update() return dirname = unicode(self.p.settings.value(self.p.getName() + '/coverdir').toString()) self.cover_dirname = dirname.replace('$musicdir', self.p.settings.value('MPD/music_dir').toString()).replace('$songdir', os.path.dirname(song.getFilepath())) filebase = unicode(self.p.settings.value(self.p.getName() + '/covername').toString()) self.cover_filepath = os.path.join(self.cover_dirname, song.expand_tags(filebase).replace(os.path.sep, '_')) self.fetchCover(song) def fetchCover(self, song): """Fetch cover (from internet or local dir)""" # set default cover if not os.path.exists(self.cover_filepath): success = False for i in [0, 1]: src = self.p.settings.value(self.p.getName() + '/action%i'%i).toInt()[0] if src != AC_NO_FETCH: if self.fetchCoverSrc(song, src): success = True break if not success: self.imgLoaded = False self.setPixmap(QtGui.QPixmap('gfx/no-cd-cover.png').scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) return try: self.setPixmap(QtGui.QPixmap(self.cover_filepath).scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) self.imgLoaded = True logging.info("cover set!") except IOError: logging.warning("Error loading album cover" + self.cover_filepath) self.update() def getLocalACPath(self, song): """Get the local path of an albumcover.""" covers = ['cover', 'album', 'front'] # fetch gfx extensions exts = QtGui.QImageReader().supportedImageFormats() exts = map(lambda ext: '*.' + unicode(ext), exts) # fetch cover album titles filter = [] for cover in covers: for ext in exts: filter.append(cover.strip() + 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: return unicode(dir.filePath(files[0])) # if this failed, try any supported image dir.setNameFilters(exts) files = dir.entryList() if files: return unicode(dir.filePath(files[0])) logging.info("done probing: no matching albumcover found") return None def fetch_amazon(self): self.fetchCoverSrc(self.p.monty.getCurrentSong(), AC_FETCH_INTERNET) self.refresh() def fetchCoverSrc(self, song, src): """Fetch the album cover for $song from $src.""" if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]: logging.warning("wgAlbumCover::fetchCover - invalid source "+str(src)) return False if src == AC_FETCH_INTERNET: # look on the internetz! try: if not song.getArtist() or not song.getAlbum(): return False # get the url from amazon WS coverURL=AmazonAlbumImage(song.getArtist(), song.getAlbum()).fetch() logging.info("fetch from Amazon") if not coverURL: logging.info("not found on Amazon") return False # read the url, i.e. retrieve image data img=urllib.urlopen(coverURL) # open file, and write the read of img! f=open(self.cover_filepath,'wb') f.write(img.read()) f.close() return True except: logging.info("failed to download cover from Amazon") print_exc() return False if src == AC_FETCH_LOCAL_DIR: file=self.getLocalACPath(song) try: shutil.copy(file, self.cover_filepath) return True except: logging.info("Failed to create cover file") return False class pluginAlbumCover(Plugin): o = None DEFAULTS = {'coverdir' : '$musicdir/$songdir', 'covername' : '.cover_monty_$artist_$album', 'action0' : 1, 'action1' : 1} def __init__(self, winMain): Plugin.__init__(self, winMain, 'AlbumCover') def _load(self): self.o = wgAlbumCover(self, None) self.monty.add_listener('onSongChange' , self.o.refresh) self.monty.add_listener('onReady' , self.o.refresh) self.monty.add_listener('onDisconnect' , self.o.refresh) self.monty.add_listener('onStateChange', self.o.refresh) self.o.refresh() def _unload(self): self.o = None def getInfo(self): return "Display the album cover of the currently playing album." def getExtInfo(self): return "Displays the album cover of the currently playing album in a widget.\n" \ "This album cover can be fetched from various locations:\n" \ " local dir: the directory in which the album is located;\n" \ " internet: look on amazon for the album and corresponding cover\n" \ "Settings:\n" \ " albumcover.fetch$i: what source to fetch from on step $i. If step $i fails, move on to step $i+1;\n" \ " 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" \ " 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" def getWidget(self): return self.o def _getDockWidget(self): return self._createDock(self.o) class SettingsWidgetAlbumCover(Plugin.SettingsWidget): actions = [] coverdir = None covername = None def __init__(self, plugin): Plugin.SettingsWidget.__init__(self, plugin) self.settings.beginGroup(self.plugin.getName()) self.actions = [QtGui.QComboBox(), QtGui.QComboBox()] for i,action in enumerate(self.actions): action.addItem("No action.") action.addItem("Local dir") action.addItem("Amazon") action.setCurrentIndex(self.settings.value('action' + str(i)).toInt()[0]) self.coverdir = QtGui.QLineEdit(self.settings.value('coverdir').toString()) self.covername = QtGui.QLineEdit(self.settings.value('covername').toString()) self.setLayout(QtGui.QVBoxLayout()) self._add_widget(self.actions[0], 'Action 0') self._add_widget(self.actions[1], 'Action 1') self._add_widget(self.coverdir, 'Cover directory', 'Where should %s store covers.\n' '$musicdir will be expanded to path to MPD music library\n' '$songdir will be expanded to path to the song (relative to $musicdir' %APPNAME) self._add_widget(self.covername, 'Cover filename', 'Filename for %s cover files.'%APPNAME) self.settings.endGroup() def save_settings(self): self.settings.beginGroup(self.plugin.getName()) self.settings.setValue('action0', QVariant(self.actions[0].currentIndex())) self.settings.setValue('action1', QVariant(self.actions[1].currentIndex())) self.settings.setValue('coverdir', QVariant(self.coverdir.text())) self.settings.setValue('covername', QVariant(self.covername.text())) 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: self.important("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 data["Keywords"] = self.album 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)