diff options
Diffstat (limited to 'nephilim/plugins/AlbumCover.py')
-rw-r--r-- | nephilim/plugins/AlbumCover.py | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/nephilim/plugins/AlbumCover.py b/nephilim/plugins/AlbumCover.py new file mode 100644 index 0000000..426df96 --- /dev/null +++ b/nephilim/plugins/AlbumCover.py @@ -0,0 +1,304 @@ +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.mpclient.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, params = None): + logging.info("refreshing cover") + song = self.p.mpclient.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.mpclient.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_mpclient_$artist_$album', + 'action0' : 1, 'action1' : 1} + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'AlbumCover') + + def _load(self): + self.o = wgAlbumCover(self, None) + self.mpclient.add_listener('onSongChange' , self.o.refresh) + self.mpclient.add_listener('onReady' , self.o.refresh) + self.mpclient.add_listener('onDisconnect' , self.o.refresh) + self.mpclient.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"<DetailPageURL>([^<]+)</DetailPageURL>") + m = url_re.search(xmldata) + return m and m.group(1) |