from clPlugin import * from traceback import print_exc from thread import start_new_thread import format import os import shutil # FETCH MODES AC_NO_FETCH='0' AC_FETCH_LOCAL_DIR='1' AC_FETCH_INTERNET='2' ALBUMCOVER_GFX_EXTS_DEFAULT='jpg,jpeg,bmp,gif,png' ALBUMCOVER_FILES_DEFAULT='cover,album' ALBUMCOVER_DOWNLOADTO_DEFAULT='$dirname($file)/$cover' ALBUMCOVER_FETCH0_DEFAULT=1 ALBUMCOVER_FETCH1_DEFAULT=1 class wgAlbumCover(QtGui.QWidget): " container for the image" img=None imgLoaded=False p=None # plugin acFormat=None def __init__(self, p, parent=None): QtGui.QWidget.__init__(self,parent) self.p=p self.img=QtGui.QImage() self.setMinimumSize(64,64) def mousePressEvent(self, event): if event.button()==QtCore.Qt.RightButton: song=monty.getCurrentSong() file=QtGui.QFileDialog.getOpenFileName(self , "Select album cover for %s - %s"%(song.getArtist(), song.getAlbum()) , "" , "Images ("+" ".join(map(lambda ext: "*.%s"%(ext), ALBUMCOVER_GFX_EXTS_DEFAULT.split(',')))+")" ) if file: cur=self.getLocalACPath(song, probe=True) # remove the previous cover try: if cur: self.p.extended("removing %s"%(cur)) os.remove(cur) else: cur=self.getLocalACPath(song, probe=False) self.p.extended("copy %s to %s"%(file, cur)) shutil.copy(file, cur) except Exception, e: self.p.normal("failed to set new cover: %s"%(str(e))) else: return self.refresh() def getIMG(self): if self.imgLoaded: return self.img return None def paintEvent(self, event): l=min(self.width(),self.height()) p=QtGui.QPainter(self) rect=QtCore.QRectF((self.width()-l)/2,0,l,l) p.drawImage(rect,self.img) def refresh(self): self.p.extended("refreshing cover") song=monty.getCurrentSong() try: song._data['file'] except: self.img.load('') self.update() return start_new_thread(self.fetchCover, (song,)) def fetchCover(self, song): """Fetch cover (from internet or local dir)""" # set default cover self.imgLoaded=False self.img.load('gfx/no-cd-cover.png') self.acFormat=format.compile(self.p.getSetting('downloadto')) for i in [0, 1]: src=self.p.getSetting('fetch%i'%(i)) if src!=AC_NO_FETCH and self.fetchCoverSrc(song, src): # ACK! self.imgLoaded=True break self.update() def getLocalACPath(self, song, probe): """Get the local path of an albumcover. If $probe, then try covers*exts for existing file.""" # fetch gfx extensions exts=self.p.getSetting('gfx_exts').split(',') exts=map(lambda ext: ext.strip(), exts) # fetch cover album titles covers=self.p.getSetting('files').split(',') covers=map(lambda title: title.strip(), covers) params={'music_dir': mpdSettings.get('music_directory'), 'cover':'%s.%s'%(covers[0], exts[0])} if probe: self.p.debug("probing ...") for cover in covers: for ext in exts: params['cover']='%s.%s'%(cover, ext) path=self.acFormat(format.params(song, params)) self.p.debug(" path: %s"%(path)) fInfo=QtCore.QFileInfo(path) if fInfo.exists(): self.p.debug(" OK!") return path self.p.debug("done probing: no matching albumcover found") return None else: self.p.debug("no probing") path=self.acFormat(format.params(song, params)) self.p.debug(" path: %s"%(path)) return path def fetchCoverSrc(self, song, src): """Fetch the album cover for $song from $src.""" if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]: print "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() self.p.extended("fetch from Amazon") if not coverURL: self.p.normal("not found on Amazon") return False # read the url, i.e. retrieve image data img=urllib.urlopen(coverURL) # where do we save to? file=self.getLocalACPath(song, False) # open file, and write the read of img! f=open(file,'wb') f.write(img.read()) f.close() # load image; should work now! self.img.load(file) return True except: self.p.normal("failed to download cover from Amazon") print_exc() return False file=self.getLocalACPath(song, True) if file: try: self.img.load(file) self.p.extended("cover set!") return True except: self.p.normal("failed to load %s"%(path)) class pluginAlbumCover(Plugin): o=None def __init__(self, winMain): Plugin.__init__(self, winMain, 'AlbumCover') self.addMontyListener('onSongChange', self.onEvent) self.addMontyListener('onReady', self.onEvent) self.addMontyListener('onDisconnect', self.onEvent) self.addMontyListener('onStateChange', self.onEvent) def _load(self): self.o=wgAlbumCover(self, None) 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) def onEvent(self, params): self.o.refresh() def _getSettings(self): ret=[] nums=['first', 'second'] actions=[QtGui.QComboBox(), QtGui.QComboBox()] for i,action in enumerate(actions): setting='fetch%i'%(i) action.addItem("On the %s action, Monty rested."%(nums[i])) action.addItem("Local dir") action.addItem("Internet") action.setCurrentIndex(int(self.getSetting(setting, str(i+1)))) ret.append([setting, 'Action %i'%(i+1), 'What to do on the %s step.'%(nums[i]), action]) ret.append(['downloadto', 'Local dir', 'Specifies where to save album covers fetched from internet to.\nPossible tags: music_dir, cover, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))]) ret.append(['files', 'Album cover files', 'Comma separated list of titles that are to be considered album covers. E.g. cover,album.', QtGui.QLineEdit(self.getSetting('files'))]) return ret def afterSaveSettings(self): self.o.refresh() # 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 try: fd = urllib.urlopen("%s?%s" % (self.awsurl, urllib.urlencode(data))) return fd.read() except: # this is very probable a timeout exception self.important("timeout openening %s"%(self.awsurl)) return None 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)