summaryrefslogtreecommitdiff
path: root/nephilim/plugins/AlbumCover.py
diff options
context:
space:
mode:
Diffstat (limited to 'nephilim/plugins/AlbumCover.py')
-rw-r--r--nephilim/plugins/AlbumCover.py304
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)