diff options
author | Anton Khirnov <wyskas@gmail.com> | 2008-12-17 20:48:52 +0100 |
---|---|---|
committer | Anton Khirnov <wyskas@gmail.com> | 2008-12-17 20:48:52 +0100 |
commit | 4942e2f39fc9dc61745c952f0ddef6804351398f (patch) | |
tree | 20b28f22ca8b9290d690aad2c1653a25d51a75ad /plugins | |
parent | 6ff27d9b54cb75d041bd18589a1c6b5f606d51c3 (diff) |
get rid of tabs
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/AlbumCover.py | 462 | ||||
-rw-r--r-- | plugins/Library.py | 120 | ||||
-rw-r--r-- | plugins/Logger.py | 110 | ||||
-rw-r--r-- | plugins/Lyrics.py | 494 | ||||
-rw-r--r-- | plugins/MPD.py | 34 | ||||
-rw-r--r-- | plugins/Notify.py | 288 | ||||
-rw-r--r-- | plugins/PlayControl.py | 682 | ||||
-rw-r--r-- | plugins/Playlist.py | 136 | ||||
-rw-r--r-- | plugins/Scrobbler.py | 698 | ||||
-rw-r--r-- | plugins/Shortcuts.py | 150 | ||||
-rw-r--r-- | plugins/SongStatus.py | 136 | ||||
-rw-r--r-- | plugins/Systray.py | 202 | ||||
-rw-r--r-- | plugins/Tabs.py | 276 | ||||
-rw-r--r-- | plugins/__init__.py | 112 |
14 files changed, 1950 insertions, 1950 deletions
diff --git a/plugins/AlbumCover.py b/plugins/AlbumCover.py index cb67f51..d2952ca 100644 --- a/plugins/AlbumCover.py +++ b/plugins/AlbumCover.py @@ -17,207 +17,207 @@ 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 + " 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 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 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() + + 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)) + # 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" + 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 getWidget(self): + return self.o - def _getDockWidget(self): - return self._createDock(self.o) + def _getDockWidget(self): + return self._createDock(self.o) - def onEvent(self, params): - self.o.refresh() + 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]) + 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() + 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 @@ -228,48 +228,48 @@ 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 + 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 _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) + 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) diff --git a/plugins/Library.py b/plugins/Library.py index b1d86e9..bcd4ae7 100644 --- a/plugins/Library.py +++ b/plugins/Library.py @@ -6,69 +6,69 @@ from wgPlaylist import Playlist from wgSongList import clrRowSel LIBRARY_MODES_DEFAULT='$artist\n'\ - '$artist/$date - $album\n'\ - '$artist - $album\n'\ - '$album ($artist)\n'\ - '$genre\n'\ - '$genre/$artist\n'\ - '$genre/$artist - $album\n' + '$artist/$date - $album\n'\ + '$artist - $album\n'\ + '$album ($artist)\n'\ + '$genre\n'\ + '$genre/$artist\n'\ + '$genre/$artist - $album\n' class pluginLibrary(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Library') - def _load(self): - self.o=Playlist(self.winMain, self, ['song'], 'Library' - , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n')) - def _unload(self): - self.o=None - def getInfo(self): - return "List showing all the songs allowing filtering and grouping." - - def getList(self): - return self.o - - def _getDockWidget(self): - return self._createDock(self.o) - - def onDoubleClick(self): - self.addLibrarySelToPlaylist() + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Library') + def _load(self): + self.o=Playlist(self.winMain, self, ['song'], 'Library' + , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n')) + def _unload(self): + self.o=None + def getInfo(self): + return "List showing all the songs allowing filtering and grouping." + + def getList(self): + return self.o + + def _getDockWidget(self): + return self._createDock(self.o) + + def onDoubleClick(self): + self.addLibrarySelToPlaylist() - def onKeyPress(self, event): - # Add selection, or entire library to playlist using ENTER-key. - if event.key()==QtCore.Qt.Key_Enter or event.key()==QtCore.Qt.Key_Return: - self.addLibrarySelToPlaylist() - return QtGui.QWidget.keyPressEvent(self.o, event) + def onKeyPress(self, event): + # Add selection, or entire library to playlist using ENTER-key. + if event.key()==QtCore.Qt.Key_Enter or event.key()==QtCore.Qt.Key_Return: + self.addLibrarySelToPlaylist() + return QtGui.QWidget.keyPressEvent(self.o, event) - def addLibrarySelToPlaylist(self): - """Add the library selection to the playlist.""" - songs=self.o.selectedSongs() - self.setStatus('Adding '+str(len(songs))+' songs to library ...') - doEvents() + def addLibrarySelToPlaylist(self): + """Add the library selection to the playlist.""" + songs=self.o.selectedSongs() + self.setStatus('Adding '+str(len(songs))+' songs to library ...') + doEvents() - # add filepaths of selected songs to path - paths=map(lambda song: unicode(song.getFilepath()), songs) - # add in chunks of 1000 - CHUNK_SIZE=1000 - start=0 - while start<len(paths): - if start+CHUNK_SIZE<len(paths): - end=start+CHUNK_SIZE - else: - end=len(paths) - self.setStatus('Adding '+str(len(songs))+' songs to library: %i%%'%(int(100*start/len(paths)))) - doEvents() - monty.addToPlaylist(paths[start:end]) - start+=CHUNK_SIZE + # add filepaths of selected songs to path + paths=map(lambda song: unicode(song.getFilepath()), songs) + # add in chunks of 1000 + CHUNK_SIZE=1000 + start=0 + while start<len(paths): + if start+CHUNK_SIZE<len(paths): + end=start+CHUNK_SIZE + else: + end=len(paths) + self.setStatus('Adding '+str(len(songs))+' songs to library: %i%%'%(int(100*start/len(paths)))) + doEvents() + monty.addToPlaylist(paths[start:end]) + start+=CHUNK_SIZE - self.setStatus('') - doEvents() - self.getWinMain().fillPlaylist() + self.setStatus('') + doEvents() + self.getWinMain().fillPlaylist() - def _getSettings(self): - modes=QtGui.QTextEdit() - modes.insertPlainText(self.getSetting('modes')) - return [ - ['modes', 'Modes', 'Sets the available modes.', modes], - ] - def afterSaveSettings(self): - self.o.setModes(self.getSetting('modes').split('\n')) + def _getSettings(self): + modes=QtGui.QTextEdit() + modes.insertPlainText(self.getSetting('modes')) + return [ + ['modes', 'Modes', 'Sets the available modes.', modes], + ] + def afterSaveSettings(self): + self.o.setModes(self.getSetting('modes').split('\n')) diff --git a/plugins/Logger.py b/plugins/Logger.py index 575fd5b..4e8ce6b 100644 --- a/plugins/Logger.py +++ b/plugins/Logger.py @@ -6,62 +6,62 @@ from misc import Button LOGGER_LEVEL_DEFAULT=log.LOG_NORMAL class wgLogger(QtGui.QWidget): - """Shows log information""" - " list containing the log" - log=None - btnClear=None - p=None - def __init__(self, p, parent=None): - QtGui.QWidget.__init__(self, parent) - self.p=p - - self.log=QtGui.QListWidget(self) - self.btnClear=Button("Clear", self.clear) - self.cmbLevel=QtGui.QComboBox(self) - - self.cmbLevel.addItem("Nothing") - self.cmbLevel.addItem("Important") - self.cmbLevel.addItem("Normal") - self.cmbLevel.addItem("Extended") - self.cmbLevel.addItem("Debug") - - self.cmbLevel.setCurrentIndex(int(self.p.getSetting("level"))) - self.onCmbLevelChanged(self.cmbLevel.currentIndex()) + """Shows log information""" + " list containing the log" + log=None + btnClear=None + p=None + def __init__(self, p, parent=None): + QtGui.QWidget.__init__(self, parent) + self.p=p + + self.log=QtGui.QListWidget(self) + self.btnClear=Button("Clear", self.clear) + self.cmbLevel=QtGui.QComboBox(self) + + self.cmbLevel.addItem("Nothing") + self.cmbLevel.addItem("Important") + self.cmbLevel.addItem("Normal") + self.cmbLevel.addItem("Extended") + self.cmbLevel.addItem("Debug") + + self.cmbLevel.setCurrentIndex(int(self.p.getSetting("level"))) + self.onCmbLevelChanged(self.cmbLevel.currentIndex()) - layout=QtGui.QVBoxLayout() - layout2=QtGui.QHBoxLayout() - self.setLayout(layout) + layout=QtGui.QVBoxLayout() + layout2=QtGui.QHBoxLayout() + self.setLayout(layout) - layout2.addWidget(self.cmbLevel) - layout2.addWidget(self.btnClear) - layout.addLayout(layout2) - layout.addWidget(self.log) - - self.connect(self.cmbLevel, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbLevelChanged) - - def onCmbLevelChanged(self, newval): - log.setLevel(newval) - log.extended("Switching loglevel to %s"%(str(newval))) - settings.set("logger.level", newval) - def writer(self, item, level): - self.log.addItem(item) - self.log.setCurrentRow(self.log.count()-1) - #self.log.scrollToItem(self.log.item(self.log.count())) - def clear(self): - self.log.clear() + layout2.addWidget(self.cmbLevel) + layout2.addWidget(self.btnClear) + layout.addLayout(layout2) + layout.addWidget(self.log) + + self.connect(self.cmbLevel, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbLevelChanged) + + def onCmbLevelChanged(self, newval): + log.setLevel(newval) + log.extended("Switching loglevel to %s"%(str(newval))) + settings.set("logger.level", newval) + def writer(self, item, level): + self.log.addItem(item) + self.log.setCurrentRow(self.log.count()-1) + #self.log.scrollToItem(self.log.item(self.log.count())) + def clear(self): + self.log.clear() class pluginLogger(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Logger') - def _load(self): - self.o=wgLogger(self, None) - log.setWriter(self.o.writer) - def _unload(self): - self.o=None - log.setWriter(log._writer) - def getInfo(self): - return "Shows information from the log." - - def _getDockWidget(self): - return self._createDock(self.o) + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Logger') + def _load(self): + self.o=wgLogger(self, None) + log.setWriter(self.o.writer) + def _unload(self): + self.o=None + log.setWriter(log._writer) + def getInfo(self): + return "Shows information from the log." + + def _getDockWidget(self): + return self._createDock(self.o) diff --git a/plugins/Lyrics.py b/plugins/Lyrics.py index 2e4d31c..840ec06 100644 --- a/plugins/Lyrics.py +++ b/plugins/Lyrics.py @@ -13,273 +13,273 @@ from clMonty import monty from clPlugin import * class ResetEvent(QtCore.QEvent): - song=None - def __init__(self, song=None): - QtCore.QEvent.__init__(self,QtCore.QEvent.User) - self.song=song + song=None + def __init__(self, song=None): + QtCore.QEvent.__init__(self,QtCore.QEvent.User) + self.song=song class AddHtmlEvent(QtCore.QEvent): - html=None - def __init__(self,html): - QtCore.QEvent.__init__(self,QtCore.QEvent.User) - self.html=html + html=None + def __init__(self,html): + QtCore.QEvent.__init__(self,QtCore.QEvent.User) + self.html=html class AddTextEvent(QtCore.QEvent): - text=None - def __init__(self,text): - QtCore.QEvent.__init__(self,QtCore.QEvent.User) - self.text=text + text=None + def __init__(self,text): + QtCore.QEvent.__init__(self,QtCore.QEvent.User) + self.text=text LYRICS_ENGINE_DEFAULT='http://www.google.com/search?q=lyrics+"$artist"+"$title"' LYRICS_SITES_DEFAULT=\ - 'absolutelyrics.com <div id="realText">(.*?)</div>\n'\ - 'azlyrics.com <br><br>.*?<br><br>(.*?)<br><br>\n'\ - 'oldielyrics.com song_in_top2.*?<br>(.*?)<script\n'\ - 'lyricstime.com phone-left.gif.*?<p>(.*?)</p>\n'\ - 'lyricsfire.com class="lyric">.*?Song.*?<br>(.*?)</pre>\n'\ - 'lyricsfreak.com <div.*?id="content".*?>(.*?)<blockquote\n'\ - 'artists.letssingit.com <pre>(.*?)</pre>\n'\ - 'gugalyrics.com </h1>(.*?)<a\n'\ - 'lyricsmania.com </strong> :(.*?)[\n'\ - 'leoslyrics.com <font face=.*?size=-1>(.*?)</font>\n'\ - 'bluesforpeace.com <blockquote>(.*?)</blockquote>\n' + 'absolutelyrics.com <div id="realText">(.*?)</div>\n'\ + 'azlyrics.com <br><br>.*?<br><br>(.*?)<br><br>\n'\ + 'oldielyrics.com song_in_top2.*?<br>(.*?)<script\n'\ + 'lyricstime.com phone-left.gif.*?<p>(.*?)</p>\n'\ + 'lyricsfire.com class="lyric">.*?Song.*?<br>(.*?)</pre>\n'\ + 'lyricsfreak.com <div.*?id="content".*?>(.*?)<blockquote\n'\ + 'artists.letssingit.com <pre>(.*?)</pre>\n'\ + 'gugalyrics.com </h1>(.*?)<a\n'\ + 'lyricsmania.com </strong> :(.*?)[\n'\ + 'leoslyrics.com <font face=.*?size=-1>(.*?)</font>\n'\ + 'bluesforpeace.com <blockquote>(.*?)</blockquote>\n' LYRICS_DOWNLOADTO_DEFAULT='~/.lyrics/$artist/$artist - $title.txt' LYRICS_AUTOSCROLL_DEFAULT='1' class wgLyrics(QtGui.QWidget): - " contains the lyrics" - txtView=None # text-object - curLyrics=None # current lyrics - btnEdit=None - btnRefetch=None - btnSave=None - btnSearch=None - editMode=False - lyFormat=None - p=None # plugin - def __init__(self, p, parent=None): - QtGui.QWidget.__init__(self, parent) - self.p=p - self.curLyrics="" - self.btnEdit=Button("Edit lyrics", self.onBtnEditClick) - self.btnRefetch=Button("Refetch", self.onBtnRefetchClick) - self.btnSave=Button("Save lyrics", self.onBtnSaveClick) - self.btnSearch=Button("Search www", self.onBtnSearch) - - self.txtView=QtGui.QTextEdit(parent) - self.txtView.setReadOnly(True) - - self.setMode(False) - - layout=QtGui.QVBoxLayout() - hlayout=QtGui.QHBoxLayout() - layout.addLayout(hlayout) - hlayout.addWidget(self.btnEdit) - hlayout.addWidget(self.btnSave) - hlayout.addWidget(self.btnRefetch) - hlayout.addWidget(self.btnSearch) - layout.addWidget(self.txtView) - self.setLayout(layout) + " contains the lyrics" + txtView=None # text-object + curLyrics=None # current lyrics + btnEdit=None + btnRefetch=None + btnSave=None + btnSearch=None + editMode=False + lyFormat=None + p=None # plugin + def __init__(self, p, parent=None): + QtGui.QWidget.__init__(self, parent) + self.p=p + self.curLyrics="" + self.btnEdit=Button("Edit lyrics", self.onBtnEditClick) + self.btnRefetch=Button("Refetch", self.onBtnRefetchClick) + self.btnSave=Button("Save lyrics", self.onBtnSaveClick) + self.btnSearch=Button("Search www", self.onBtnSearch) + + self.txtView=QtGui.QTextEdit(parent) + self.txtView.setReadOnly(True) + + self.setMode(False) + + layout=QtGui.QVBoxLayout() + hlayout=QtGui.QHBoxLayout() + layout.addLayout(hlayout) + hlayout.addWidget(self.btnEdit) + hlayout.addWidget(self.btnSave) + hlayout.addWidget(self.btnRefetch) + hlayout.addWidget(self.btnSearch) + layout.addWidget(self.txtView) + self.setLayout(layout) - - def setMode(self, mode): - self.editMode=mode - self.btnSave.setVisible(mode) - self.btnEdit.setVisible(not mode) - self.txtView.setReadOnly(not mode) - - def onBtnSearch(self): - SE=self.p.getSetting('engine') - f=format.compile(SE) - SE_url=toAscii(f(format.params(monty.getCurrentSong()))) - webbrowser.open(urllib.quote(SE_url, ":/+?=")) - - def onBtnEditClick(self): - self.setMode(True) - self.txtView.setPlainText(self.curLyrics) - - def onBtnSaveClick(self): - self.setMode(False) - self.curLyrics=self.txtView.toPlainText() - file=open(self.getLyricsFilePath(monty.getCurrentSong()), 'w') - file.write(self.curLyrics) - file.close() - - # just to test if everything's still fine - self.fetchLyrics(monty.getCurrentSong()) - - def onBtnRefetchClick(self): - # force refetch - lyFName=self.getLyricsFilePath(monty.getCurrentSong()) - try: - os.remove(lyFName) - except: - pass - self.refresh() - - def refresh(self): - # if we're editing while song changes, too bad: we don't save, for the moment! - if self.editMode: - self.setMode(False) - - self.lyFormat=format.compile(self.p.getSetting('downloadto')) - song=monty.getCurrentSong() - try: - song._data['file'] - except: - self.resetTxt() - return - - self.resetTxt(song) - start_new_thread(self.fetchLyrics, (song,)) - - def customEvent(self, event): - if isinstance(event,ResetEvent): - self.resetTxt(event.song) - elif isinstance(event,AddTextEvent): - self.txtView.insertPlainText(event.text) - elif isinstance(event,AddHtmlEvent): - self.txtView.insertHtml(event.html) + + def setMode(self, mode): + self.editMode=mode + self.btnSave.setVisible(mode) + self.btnEdit.setVisible(not mode) + self.txtView.setReadOnly(not mode) + + def onBtnSearch(self): + SE=self.p.getSetting('engine') + f=format.compile(SE) + SE_url=toAscii(f(format.params(monty.getCurrentSong()))) + webbrowser.open(urllib.quote(SE_url, ":/+?=")) + + def onBtnEditClick(self): + self.setMode(True) + self.txtView.setPlainText(self.curLyrics) + + def onBtnSaveClick(self): + self.setMode(False) + self.curLyrics=self.txtView.toPlainText() + file=open(self.getLyricsFilePath(monty.getCurrentSong()), 'w') + file.write(self.curLyrics) + file.close() + + # just to test if everything's still fine + self.fetchLyrics(monty.getCurrentSong()) + + def onBtnRefetchClick(self): + # force refetch + lyFName=self.getLyricsFilePath(monty.getCurrentSong()) + try: + os.remove(lyFName) + except: + pass + self.refresh() + + def refresh(self): + # if we're editing while song changes, too bad: we don't save, for the moment! + if self.editMode: + self.setMode(False) + + self.lyFormat=format.compile(self.p.getSetting('downloadto')) + song=monty.getCurrentSong() + try: + song._data['file'] + except: + self.resetTxt() + return + + self.resetTxt(song) + start_new_thread(self.fetchLyrics, (song,)) + + def customEvent(self, event): + if isinstance(event,ResetEvent): + self.resetTxt(event.song) + elif isinstance(event,AddTextEvent): + self.txtView.insertPlainText(event.text) + elif isinstance(event,AddHtmlEvent): + self.txtView.insertHtml(event.html) - def getLyricsFilePath(self, song): - params={'music_dir': mpdSettings.get('music_directory')} - path=self.lyFormat(format.params(song, params)) - return toAscii(os.path.expanduser(path)) - - _mutex=QtCore.QMutex() - _fetchCnt=0 - def fetchLyrics(self, song): - # only allow 1 instance to look lyrics! - self._mutex.lock() - if self._fetchCnt: - self._mutex.unlock() - return - self._fetchCnt=1 - self._mutex.unlock() + def getLyricsFilePath(self, song): + params={'music_dir': mpdSettings.get('music_directory')} + path=self.lyFormat(format.params(song, params)) + return toAscii(os.path.expanduser(path)) + + _mutex=QtCore.QMutex() + _fetchCnt=0 + def fetchLyrics(self, song): + # only allow 1 instance to look lyrics! + self._mutex.lock() + if self._fetchCnt: + self._mutex.unlock() + return + self._fetchCnt=1 + self._mutex.unlock() - QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) - - lyFName=self.getLyricsFilePath(song) - self.p.debug("checking %s"%(lyFName)) - self.curLyrics="" - # does the file exist? if yes, read that one! - try: - file=open(lyFName, 'r') - self.curLyrics=file.read() - file.close() - QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics)) - self._fetchCnt=0 - self.p.extended("Fetch lyrics from file") - return - except Exception, e: - self.p.debug("fail - %s"%(str(e))) + QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) + + lyFName=self.getLyricsFilePath(song) + self.p.debug("checking %s"%(lyFName)) + self.curLyrics="" + # does the file exist? if yes, read that one! + try: + file=open(lyFName, 'r') + self.curLyrics=file.read() + file.close() + QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics)) + self._fetchCnt=0 + self.p.extended("Fetch lyrics from file") + return + except Exception, e: + self.p.debug("fail - %s"%(str(e))) - # fetch from inet - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching lyrics ...</i>')) - self.p.extended("Fetch lyrics from internet") - - lines=self.p.getSetting('sites').split('\n') - sites={} - for line in lines: - if line.strip(): - sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip() - # construct URL to search! - SE=self.p.getSetting('engine') - try: - ret=fetch(SE, sites, song, {}) - QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) - if ret: - self.p.extended("Success!") - self.curLyrics=ret[0] - # save for later use! - if lyFName: - # we can't save if the path isn't correct - try: - self.p.extended("Saving to %s"%(lyFName)) - try: - # fails when dir exists - os.makedirs(os.path.dirname(os.path.expanduser(lyFName))) - except Exception, e: - pass - file=open(lyFName, 'w') - file.write(self.curLyrics) - file.close() - except Exception, e: - # probably a wrong path! - self.p.normal("Failed to write lyrics %s"%(str(e))) - else: - self.curLyrics="" - txt="No lyrics found :'(" - self.p.extended("Lyrics not found!") - QtCore.QCoreApplication.postEvent(self, AddTextEvent(txt)) - - QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics)) - if ret: - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<br /><br /><a href="%s">%s</a>'%(ret[1],ret[1]))) + # fetch from inet + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching lyrics ...</i>')) + self.p.extended("Fetch lyrics from internet") + + lines=self.p.getSetting('sites').split('\n') + sites={} + for line in lines: + if line.strip(): + sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip() + # construct URL to search! + SE=self.p.getSetting('engine') + try: + ret=fetch(SE, sites, song, {}) + QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) + if ret: + self.p.extended("Success!") + self.curLyrics=ret[0] + # save for later use! + if lyFName: + # we can't save if the path isn't correct + try: + self.p.extended("Saving to %s"%(lyFName)) + try: + # fails when dir exists + os.makedirs(os.path.dirname(os.path.expanduser(lyFName))) + except Exception, e: + pass + file=open(lyFName, 'w') + file.write(self.curLyrics) + file.close() + except Exception, e: + # probably a wrong path! + self.p.normal("Failed to write lyrics %s"%(str(e))) + else: + self.curLyrics="" + txt="No lyrics found :'(" + self.p.extended("Lyrics not found!") + QtCore.QCoreApplication.postEvent(self, AddTextEvent(txt)) + + QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics)) + if ret: + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<br /><br /><a href="%s">%s</a>'%(ret[1],ret[1]))) - except Exception, e: - QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, error! Possible causes:'\ - '<br />no internet connection?'\ - '<br />site unavailable'\ - '<br />an error in the fetching regular expression'\ - '<br />(exception: %s)'%(str(e)))) - self._fetchCnt=0 + except Exception, e: + QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, error! Possible causes:'\ + '<br />no internet connection?'\ + '<br />site unavailable'\ + '<br />an error in the fetching regular expression'\ + '<br />(exception: %s)'%(str(e)))) + self._fetchCnt=0 - def resetTxt(self, song=None): - self.txtView.clear() - if song: - self.txtView.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\ - '<br />\n\n'%(song.getTitle(), song.getArtist())) - - def autoScroll(self, time): - t=self.txtView - max=t.verticalScrollBar().maximum() - if max<=0: - return - t.verticalScrollBar().setValue((max+t.height()/t.currentFont().pointSize())*time/monty.getCurrentSong()._data['time']) - + def resetTxt(self, song=None): + self.txtView.clear() + if song: + self.txtView.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\ + '<br />\n\n'%(song.getTitle(), song.getArtist())) + + def autoScroll(self, time): + t=self.txtView + max=t.verticalScrollBar().maximum() + if max<=0: + return + t.verticalScrollBar().setValue((max+t.height()/t.currentFont().pointSize())*time/monty.getCurrentSong()._data['time']) + class pluginLyrics(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Lyrics') - self.addMontyListener('onSongChange', self.refresh) - self.addMontyListener('onReady', self.refresh) - self.addMontyListener('onDisconnect', self.onDisconnect) - self.addMontyListener('onTimeChange', self.onTimeChange) - def _load(self): - self.o=wgLyrics(self, None) - self.o.refresh() - def _unload(self): - self.o=None - def getInfo(self): - return "Show (and fetch) the lyrics of the currently playing song." - - def _getDockWidget(self): - return self._createDock(self.o) + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Lyrics') + self.addMontyListener('onSongChange', self.refresh) + self.addMontyListener('onReady', self.refresh) + self.addMontyListener('onDisconnect', self.onDisconnect) + self.addMontyListener('onTimeChange', self.onTimeChange) + def _load(self): + self.o=wgLyrics(self, None) + self.o.refresh() + def _unload(self): + self.o=None + def getInfo(self): + return "Show (and fetch) the lyrics of the currently playing song." + + def _getDockWidget(self): + return self._createDock(self.o) - def refresh(self, params): - self.o.refresh() - def onDisconnect(self, params): - self.o.resetTxt() - def onTimeChange(self, params): - if self.getSetting("autoScroll")!="0" and self.o.editMode==False: - # could be done better, but this is just plain simple :) - self.o.autoScroll(params['newTime']) + def refresh(self, params): + self.o.refresh() + def onDisconnect(self, params): + self.o.resetTxt() + def onTimeChange(self, params): + if self.getSetting("autoScroll")!="0" and self.o.editMode==False: + # could be done better, but this is just plain simple :) + self.o.autoScroll(params['newTime']) - def _getSettings(self): - sites=QtGui.QTextEdit() - sites.insertPlainText(self.getSetting('sites')) - autoscroll=QtGui.QCheckBox("Autoscroll") - autoscroll.setCheckState(QtCore.Qt.Checked if self.getSetting("autoScroll")!=0 else QtCore.Qt.Unchecked) - return [ - ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))], - ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the lyrics.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the lyrics website, $regex-start is the regex indicating the start of the lyrics, $regex-end indicates the end. E.g. foolyrics.org <lyrics>(.*?)</lyrics>', sites], - ['downloadto', 'Target', 'Specifies where to save lyrics fetched from internet to.\nPossible tags: music_dir, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))], - ['autoScroll', 'Autoscroll', 'Check this box to scroll the contents automatically as the time advances', autoscroll], - ] - def afterSaveSettings(self): - self.o.refresh() + def _getSettings(self): + sites=QtGui.QTextEdit() + sites.insertPlainText(self.getSetting('sites')) + autoscroll=QtGui.QCheckBox("Autoscroll") + autoscroll.setCheckState(QtCore.Qt.Checked if self.getSetting("autoScroll")!=0 else QtCore.Qt.Unchecked) + return [ + ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))], + ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the lyrics.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the lyrics website, $regex-start is the regex indicating the start of the lyrics, $regex-end indicates the end. E.g. foolyrics.org <lyrics>(.*?)</lyrics>', sites], + ['downloadto', 'Target', 'Specifies where to save lyrics fetched from internet to.\nPossible tags: music_dir, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))], + ['autoScroll', 'Autoscroll', 'Check this box to scroll the contents automatically as the time advances', autoscroll], + ] + def afterSaveSettings(self): + self.o.refresh() diff --git a/plugins/MPD.py b/plugins/MPD.py index dd41230..c7e8c9c 100644 --- a/plugins/MPD.py +++ b/plugins/MPD.py @@ -8,21 +8,21 @@ MPD_HOST_DEFAULT='localhost' MPD_PORT_DEFAULT='6600' class pluginMPD(Plugin): - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'MPD') - def getInfo(self): - return "Provides an interface to the MPD settings." - - - def _getSettings(self): - return [ - ['host', 'Host', 'Host where mpd resides.', QtGui.QLineEdit(self.getSetting('host'))], - ['port', 'Port', 'Port of mpd.', QtGui.QLineEdit(self.getSetting('port'))], - ['music_directory', 'Music directory', 'Root directory where all music is located.', QtGui.QLineEdit(mpdSettings.get('music_directory')), mpdSettings], - ['', 'Update database', 'Updates the database.\nUse this if you have changed the music_directory. Updating will save all entries on the MPD tab.', Button('Update db', self.onBtnUpdateDBClick)], - ] - def onBtnUpdateDBClick(self): - self.saveSettings() - monty.updateDB([mpdSettings.get('music_directory')]) - pass + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'MPD') + def getInfo(self): + return "Provides an interface to the MPD settings." + + + def _getSettings(self): + return [ + ['host', 'Host', 'Host where mpd resides.', QtGui.QLineEdit(self.getSetting('host'))], + ['port', 'Port', 'Port of mpd.', QtGui.QLineEdit(self.getSetting('port'))], + ['music_directory', 'Music directory', 'Root directory where all music is located.', QtGui.QLineEdit(mpdSettings.get('music_directory')), mpdSettings], + ['', 'Update database', 'Updates the database.\nUse this if you have changed the music_directory. Updating will save all entries on the MPD tab.', Button('Update db', self.onBtnUpdateDBClick)], + ] + def onBtnUpdateDBClick(self): + self.saveSettings() + monty.updateDB([mpdSettings.get('music_directory')]) + pass diff --git a/plugins/Notify.py b/plugins/Notify.py index 14e35af..4c6de63 100644 --- a/plugins/Notify.py +++ b/plugins/Notify.py @@ -12,156 +12,156 @@ NOTIFY_SONGFORMAT_DEFAULT='$if($artist,$artist)$if($album, - [$album #$track])\n NOTIFY_TIMER_DEFAULT=3 class winNotify(QtGui.QWidget): - _timerID=None - resizeWindow=True - winMain=None - p=None + _timerID=None + resizeWindow=True + winMain=None + p=None - # data used for showing off - timer=None - msg=None - song=None - xtra_tags=None + # data used for showing off + timer=None + msg=None + song=None + xtra_tags=None - def __init__(self, p, winMain, parent=None): - QtGui.QWidget.__init__(self, parent) - self.p=p - self.winMain=winMain - - self.setWindowFlags(QtCore.Qt.ToolTip) - self.setWindowOpacity(0.7) - - font=QtGui.QFont() - font.setPixelSize(30) - font.setFamily('Comic Sans Ms') - self.setFont(font) - - def mousePressEvent(self, event): - self.hide() + def __init__(self, p, winMain, parent=None): + QtGui.QWidget.__init__(self, parent) + self.p=p + self.winMain=winMain + + self.setWindowFlags(QtCore.Qt.ToolTip) + self.setWindowOpacity(0.7) + + font=QtGui.QFont() + font.setPixelSize(30) + font.setFamily('Comic Sans Ms') + self.setFont(font) + + def mousePressEvent(self, event): + self.hide() - def show(self, msg, song=None, xtra_tags={}, time=3): - if not self.isVisible(): - self.setVisible(True) - self.resizeWindow=True - self.msg=format.compile(msg) - self.song=song - self.xtra_tags=xtra_tags - if self._timerID: - self.killTimer(self._timerID) - self._timerID=self.startTimer(500) - try: - self.timer=int(time)*2 - except: - self.timer=3 - self.raise_() - self.timerEvent(None) - - def hide(self): - if self._timerID: - self.killTimer(self._timerID) - self._timerID=None - self.setHidden(True) + def show(self, msg, song=None, xtra_tags={}, time=3): + if not self.isVisible(): + self.setVisible(True) + self.resizeWindow=True + self.msg=format.compile(msg) + self.song=song + self.xtra_tags=xtra_tags + if self._timerID: + self.killTimer(self._timerID) + self._timerID=self.startTimer(500) + try: + self.timer=int(time)*2 + except: + self.timer=3 + self.raise_() + self.timerEvent(None) + + def hide(self): + if self._timerID: + self.killTimer(self._timerID) + self._timerID=None + self.setHidden(True) - - def centerH(self): - screen = QtGui.QDesktopWidget().screenGeometry() - size = self.geometry() - self.move((screen.width()-size.width())/2, 100) - def paintEvent(self, event): - p=QtGui.QPainter(self) - margin=3 # margin in pixels - spacing=10 # space between album cover and text - - # determine the Rect our message must fit in - txt=self.msg(format.params(self.song, self.xtra_tags)) - rect=p.boundingRect(0,0,1,1, QtCore.Qt.AlignHCenter, txt) - - # check if 1/ albumcover plugin is loaded, and 2/ there is an - # album cover - width=0 - try: - cover=plugins.getPlugin('albumcover') - img=cover.getWidget().getIMG() - if img: - width=128 - else: - spacing=0 - except: - img=None - pass - - # do we have to resize? - if self.resizeWindow: - self.resizeWindow=False - self.resize(rect.width()+width+margin*3+spacing, max(width,rect.height())+margin*2) - self.centerH() - - # fill up with a nice color :) - p.fillRect(QtCore.QRect(0,0,self.width(),self.height()), QtGui.QBrush(QtGui.QColor(230,230,255))) - - # draw album cover if necessary - if img: - rImg=QtCore.QRectF(margin,margin,width,width) - p.drawImage(rImg, img) - - # Pen In Black ... - p.setPen(QtCore.Qt.black) - rect=p.boundingRect(width+margin+spacing,margin, - rect.width(),self.height(), QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter, txt) - p.drawText(rect, QtCore.Qt.AlignHCenter, txt) - - def timerEvent(self, event): - self.timer-=1 - if self.timer<=0: - self.hide() - self.update() - + + def centerH(self): + screen = QtGui.QDesktopWidget().screenGeometry() + size = self.geometry() + self.move((screen.width()-size.width())/2, 100) + def paintEvent(self, event): + p=QtGui.QPainter(self) + margin=3 # margin in pixels + spacing=10 # space between album cover and text + + # determine the Rect our message must fit in + txt=self.msg(format.params(self.song, self.xtra_tags)) + rect=p.boundingRect(0,0,1,1, QtCore.Qt.AlignHCenter, txt) + + # check if 1/ albumcover plugin is loaded, and 2/ there is an + # album cover + width=0 + try: + cover=plugins.getPlugin('albumcover') + img=cover.getWidget().getIMG() + if img: + width=128 + else: + spacing=0 + except: + img=None + pass + + # do we have to resize? + if self.resizeWindow: + self.resizeWindow=False + self.resize(rect.width()+width+margin*3+spacing, max(width,rect.height())+margin*2) + self.centerH() + + # fill up with a nice color :) + p.fillRect(QtCore.QRect(0,0,self.width(),self.height()), QtGui.QBrush(QtGui.QColor(230,230,255))) + + # draw album cover if necessary + if img: + rImg=QtCore.QRectF(margin,margin,width,width) + p.drawImage(rImg, img) + + # Pen In Black ... + p.setPen(QtCore.Qt.black) + rect=p.boundingRect(width+margin+spacing,margin, + rect.width(),self.height(), QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter, txt) + p.drawText(rect, QtCore.Qt.AlignHCenter, txt) + + def timerEvent(self, event): + self.timer-=1 + if self.timer<=0: + self.hide() + self.update() + class pluginNotify(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Notify') - self.addMontyListener('onSongChange', self.onSongChange) - self.addMontyListener('onReady', self.onReady) - self.addMontyListener('onDisconnect', self.onDisconnect) - self.addMontyListener('onStateChange', self.onStateChange) - self.addMontyListener('onVolumeChange', self.onVolumeChange) - - def _load(self): - self.o=winNotify(self, self.winMain) - def _unload(self): - self.o=None - def getInfo(self): - return "Show interesting events in a popup window." + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Notify') + self.addMontyListener('onSongChange', self.onSongChange) + self.addMontyListener('onReady', self.onReady) + self.addMontyListener('onDisconnect', self.onDisconnect) + self.addMontyListener('onStateChange', self.onStateChange) + self.addMontyListener('onVolumeChange', self.onVolumeChange) + + def _load(self): + self.o=winNotify(self, self.winMain) + def _unload(self): + self.o=None + def getInfo(self): + return "Show interesting events in a popup window." - def onSongChange(self, params): - self.o.show(self.getSetting('songformat').replace("\n", "\\n"), monty.getCurrentSong() - , time=self.getSetting('timer')) + def onSongChange(self, params): + self.o.show(self.getSetting('songformat').replace("\n", "\\n"), monty.getCurrentSong() + , time=self.getSetting('timer')) - def onReady(self, params): - self.o.show('montypc loaded!', monty.getCurrentSong(), time=self.getSetting('timer')) - - def onDisconnect(self, params): - self.o.show('Disconnected!', time=self.getSetting('timer')) - - def onStateChange(self, params): - self.o.show(params['newState'], monty.getCurrentSong(), time=self.getSetting('timer')) - - def onVolumeChange(self, params): - self.o.show('Volume: %i%%'%(params['newVolume']), monty.getCurrentSong(), time=self.getSetting('timer')) - - def _getSettings(self): - txt=QtGui.QTextEdit() - txt.insertPlainText(self.getSetting('songformat')) - return [ - ['songformat', 'Song format', 'How to format the current playing song.', txt], - ['timer', 'Show seconds', 'How many seconds does the notification have to be shown.', QtGui.QLineEdit(str(self.getSetting('timer')))], - ] - def afterSaveSettings(self): - try: - int(self.getSetting('timer')) - except: - self.getSetting('timer') - self.getSettingWidget('notify.timer').setText(str(NOTIFY_DEFAULT_TIMER)) - self.onSongChange(None) + def onReady(self, params): + self.o.show('montypc loaded!', monty.getCurrentSong(), time=self.getSetting('timer')) + + def onDisconnect(self, params): + self.o.show('Disconnected!', time=self.getSetting('timer')) + + def onStateChange(self, params): + self.o.show(params['newState'], monty.getCurrentSong(), time=self.getSetting('timer')) + + def onVolumeChange(self, params): + self.o.show('Volume: %i%%'%(params['newVolume']), monty.getCurrentSong(), time=self.getSetting('timer')) + + def _getSettings(self): + txt=QtGui.QTextEdit() + txt.insertPlainText(self.getSetting('songformat')) + return [ + ['songformat', 'Song format', 'How to format the current playing song.', txt], + ['timer', 'Show seconds', 'How many seconds does the notification have to be shown.', QtGui.QLineEdit(str(self.getSetting('timer')))], + ] + def afterSaveSettings(self): + try: + int(self.getSetting('timer')) + except: + self.getSetting('timer') + self.getSettingWidget('notify.timer').setText(str(NOTIFY_DEFAULT_TIMER)) + self.onSongChange(None) diff --git a/plugins/PlayControl.py b/plugins/PlayControl.py index 586527c..be7006b 100644 --- a/plugins/PlayControl.py +++ b/plugins/PlayControl.py @@ -12,15 +12,15 @@ import plugins # Note that REPEAT precedes RANDOM. E.g. if repeat # is ALBUM, and random is SONG, then it'll take a random song # from the current album ... -PC_RANDOM_NO=0 # no randomness -PC_RANDOM_SONG=1 # choose a song random from the playlist -PC_RANDOM_ALBUM=2 # choose a random album, and play that fully -PC_RANDOM_QUEUE=3 # choose next song from the queue +PC_RANDOM_NO=0 # no randomness +PC_RANDOM_SONG=1 # choose a song random from the playlist +PC_RANDOM_ALBUM=2 # choose a random album, and play that fully +PC_RANDOM_QUEUE=3 # choose next song from the queue -PC_REPEAT_NO=0 # no repeat -PC_REPEAT_SONG=1 # repeat current song -PC_REPEAT_ALBUM=2 # repeat current album -PC_REPEAT_PLAYLIST=3 # repeat playlist +PC_REPEAT_NO=0 # no repeat +PC_REPEAT_SONG=1 # repeat current song +PC_REPEAT_ALBUM=2 # repeat current album +PC_REPEAT_PLAYLIST=3 # repeat playlist PLAYCONTROL_SHUFFLE_DEFAULT=PC_RANDOM_ALBUM PLAYCONTROL_OLDSHUFFLE_DEFAULT=PLAYCONTROL_SHUFFLE_DEFAULT @@ -29,355 +29,355 @@ PLAYCONTROL_REPEAT_DEFAULT=PC_REPEAT_PLAYLIST class wgPlayControl(QtGui.QWidget): - """Displays controls for interacting with playing, like play, volume ...""" - " control buttons" - btnPlayPause=None - btnStop=None - btnPrevious=None - btnNext=None - " button to jump to current song" - btnJmpCurrent=None - " slider for current time" - slrTime=None - " slider for volume" - slrVolume=None - " indicator for volume" - svgVolume=None - " all objects in this widget" - objects=None + """Displays controls for interacting with playing, like play, volume ...""" + " control buttons" + btnPlayPause=None + btnStop=None + btnPrevious=None + btnNext=None + " button to jump to current song" + btnJmpCurrent=None + " slider for current time" + slrTime=None + " slider for volume" + slrVolume=None + " indicator for volume" + svgVolume=None + " all objects in this widget" + objects=None - cmbRepeat=None - cmbShuffle=None - p=None - - " contains the songs of the album the current song is playing. None, if the album is not set" - curAlbumSongs=None - - " queued songs: int*" - queuedSongs=[] - # what mode where we in before the queue started? - beforeQueuedMode=None + cmbRepeat=None + cmbShuffle=None + p=None + + " contains the songs of the album the current song is playing. None, if the album is not set" + curAlbumSongs=None + + " queued songs: int*" + queuedSongs=[] + # what mode where we in before the queue started? + beforeQueuedMode=None - def __init__(self, p, parent=None): - QtGui.QWidget.__init__(self, parent) - self.p=p + def __init__(self, p, parent=None): + QtGui.QWidget.__init__(self, parent) + self.p=p - class wgSvgSwitcher(QtSvg.QSvgWidget): - """Widget showing an svg-image, which, when clicked, will (un)hide an element.""" - # the element we wish to hide/show - scroller=None - def __init__(self,scroller,parent=None): - QtSvg.QSvgWidget.__init__(self,parent) - self.scroller=scroller - def mousePressEvent(self,event): - self.scroller.setVisible(not self.scroller.isVisible()) - def wheelEvent(self, event): - event.accept() - numDegrees=event.delta() / 8 - numSteps=5*numDegrees/15 - self.scroller.setValue(self.scroller.value()+numSteps) + class wgSvgSwitcher(QtSvg.QSvgWidget): + """Widget showing an svg-image, which, when clicked, will (un)hide an element.""" + # the element we wish to hide/show + scroller=None + def __init__(self,scroller,parent=None): + QtSvg.QSvgWidget.__init__(self,parent) + self.scroller=scroller + def mousePressEvent(self,event): + self.scroller.setVisible(not self.scroller.isVisible()) + def wheelEvent(self, event): + event.accept() + numDegrees=event.delta() / 8 + numSteps=5*numDegrees/15 + self.scroller.setValue(self.scroller.value()+numSteps) - self.slrTime=QtGui.QSlider(QtCore.Qt.Horizontal, self) - self.slrTime.setMinimumWidth(100) - self.slrVolume=QtGui.QSlider(QtCore.Qt.Vertical, self) - self.slrVolume.setMaximum(100) - self.slrVolume.setMinimumWidth(100) - self.slrVolume.setMaximumWidth(350) - # set to some value that'll never be chosen, that way onChange will be called automatically :) - self.slrVolume.setValue(3.141595) - self.slrVolume.setVisible(False) - self.svgVolume=wgSvgSwitcher(self.slrVolume) - self.svgVolume.setMaximumSize(64,64) - self.svgVolume.setMinimumSize(64,64) - self.btnPlayPause=Button("play", self.onBtnPlayPauseClick, 'gfx/media-playback-start.svg', True) - self.btnStop=Button("stop", self.onBtnStopClick, 'gfx/media-playback-stop.svg', True) - self.btnPrevious=Button("prev", self.onBtnPreviousClick, 'gfx/media-skip-backward.svg', True) - self.btnNext=Button("next", self.onBtnNextClick, 'gfx/media-skip-forward.svg', True) - self.btnJmpCurrent=Button("Current", self.onBtnJmpCurrentClick) + self.slrTime=QtGui.QSlider(QtCore.Qt.Horizontal, self) + self.slrTime.setMinimumWidth(100) + self.slrVolume=QtGui.QSlider(QtCore.Qt.Vertical, self) + self.slrVolume.setMaximum(100) + self.slrVolume.setMinimumWidth(100) + self.slrVolume.setMaximumWidth(350) + # set to some value that'll never be chosen, that way onChange will be called automatically :) + self.slrVolume.setValue(3.141595) + self.slrVolume.setVisible(False) + self.svgVolume=wgSvgSwitcher(self.slrVolume) + self.svgVolume.setMaximumSize(64,64) + self.svgVolume.setMinimumSize(64,64) + self.btnPlayPause=Button("play", self.onBtnPlayPauseClick, 'gfx/media-playback-start.svg', True) + self.btnStop=Button("stop", self.onBtnStopClick, 'gfx/media-playback-stop.svg', True) + self.btnPrevious=Button("prev", self.onBtnPreviousClick, 'gfx/media-skip-backward.svg', True) + self.btnNext=Button("next", self.onBtnNextClick, 'gfx/media-skip-forward.svg', True) + self.btnJmpCurrent=Button("Current", self.onBtnJmpCurrentClick) - self.cmbShuffle=QtGui.QComboBox(self) - self.cmbShuffle.addItem('Don\'t play dices') - self.cmbShuffle.addItem('Random song') - self.cmbShuffle.addItem('Random album') - self.cmbShuffle.addItem('Queue') - self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle'))) - - self.cmbRepeat=QtGui.QComboBox(self) - self.cmbRepeat.addItem('No repeat') - self.cmbRepeat.addItem('Repeat current song') - self.cmbRepeat.addItem('Repeat album') - self.cmbRepeat.addItem('Playlist') - self.cmbRepeat.setCurrentIndex(int(self.p.getSetting('repeat'))) + self.cmbShuffle=QtGui.QComboBox(self) + self.cmbShuffle.addItem('Don\'t play dices') + self.cmbShuffle.addItem('Random song') + self.cmbShuffle.addItem('Random album') + self.cmbShuffle.addItem('Queue') + self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle'))) + + self.cmbRepeat=QtGui.QComboBox(self) + self.cmbRepeat.addItem('No repeat') + self.cmbRepeat.addItem('Repeat current song') + self.cmbRepeat.addItem('Repeat album') + self.cmbRepeat.addItem('Playlist') + self.cmbRepeat.setCurrentIndex(int(self.p.getSetting('repeat'))) - self.objects=[self.slrVolume, self.slrTime, - self.btnStop, self.btnNext, self.btnPrevious] + self.objects=[self.slrVolume, self.slrTime, + self.btnStop, self.btnNext, self.btnPrevious] - layout=QtGui.QHBoxLayout(parent) - layout2=QtGui.QHBoxLayout(parent) - layoutWidget=QtGui.QVBoxLayout(parent) - layoutWidget.addLayout(layout) - layoutWidget.addLayout(layout2) - self.setLayout(layoutWidget) - - layout.addWidget(self.btnPrevious) - layout.addWidget(self.btnPlayPause) - layout.addWidget(self.btnStop) - layout.addWidget(self.btnNext) - layout.addWidget(self.slrTime) - layout.addWidget(self.slrVolume) - layout.addWidget(self.svgVolume) + layout=QtGui.QHBoxLayout(parent) + layout2=QtGui.QHBoxLayout(parent) + layoutWidget=QtGui.QVBoxLayout(parent) + layoutWidget.addLayout(layout) + layoutWidget.addLayout(layout2) + self.setLayout(layoutWidget) + + layout.addWidget(self.btnPrevious) + layout.addWidget(self.btnPlayPause) + layout.addWidget(self.btnStop) + layout.addWidget(self.btnNext) + layout.addWidget(self.slrTime) + layout.addWidget(self.slrVolume) + layout.addWidget(self.svgVolume) - layout2.addWidget(self.cmbRepeat) - layout2.addWidget(self.cmbShuffle) - layout2.addWidget(self.btnJmpCurrent) - - # queue gets loaded in _load of pluginPlayControl - self.queuedSongs=[] - self._onQueueUpdate() + layout2.addWidget(self.cmbRepeat) + layout2.addWidget(self.cmbShuffle) + layout2.addWidget(self.btnJmpCurrent) + + # queue gets loaded in _load of pluginPlayControl + self.queuedSongs=[] + self._onQueueUpdate() - self.connect(self.slrVolume, QtCore.SIGNAL('valueChanged(int)'),self.onVolumeSliderChange) - self.connect(self.slrTime, QtCore.SIGNAL('sliderReleased()'),self.onTimeSliderChange) - - self.connect(self.cmbRepeat, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbRepeatChanged) - self.connect(self.cmbShuffle, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbShuffleChanged) - - def addSongsToQueue(self, songs): - self.queuedSongs.extend(songs) - self._onQueueUpdate() - if self.cmbShuffle.currentIndex()!=PC_RANDOM_QUEUE: - self.cmbShuffle.setCurrentIndex(PC_RANDOM_QUEUE) - - def _onQueueUpdate(self): - """This method gets called whenever the queue is updated""" - self.cmbShuffle.setItemText(PC_RANDOM_QUEUE, "Queue (%i)"%(len(self.queuedSongs))) - - def onBtnJmpCurrentClick(self): - plugins.getPlugin("Playlist").getPlaylist().ensureVisible(monty.getCurrentSong().getID()) - def onStateChange(self, params): - newState=monty.getStatus()['state'] + self.connect(self.slrVolume, QtCore.SIGNAL('valueChanged(int)'),self.onVolumeSliderChange) + self.connect(self.slrTime, QtCore.SIGNAL('sliderReleased()'),self.onTimeSliderChange) + + self.connect(self.cmbRepeat, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbRepeatChanged) + self.connect(self.cmbShuffle, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbShuffleChanged) + + def addSongsToQueue(self, songs): + self.queuedSongs.extend(songs) + self._onQueueUpdate() + if self.cmbShuffle.currentIndex()!=PC_RANDOM_QUEUE: + self.cmbShuffle.setCurrentIndex(PC_RANDOM_QUEUE) + + def _onQueueUpdate(self): + """This method gets called whenever the queue is updated""" + self.cmbShuffle.setItemText(PC_RANDOM_QUEUE, "Queue (%i)"%(len(self.queuedSongs))) + + def onBtnJmpCurrentClick(self): + plugins.getPlugin("Playlist").getPlaylist().ensureVisible(monty.getCurrentSong().getID()) + def onStateChange(self, params): + newState=monty.getStatus()['state'] - map(lambda o: o.setEnabled(newState!='stop'), self.objects) + map(lambda o: o.setEnabled(newState!='stop'), self.objects) - if newState=='play': - self.btnPlayPause.changeIcon('gfx/media-playback-pause.svg') - self.btnPlayPause.setText('pauze') - elif newState=='pause' or newState=='stop': - self.btnPlayPause.changeIcon('gfx/media-playback-start.svg') - self.btnPlayPause.setText('play') - def onVolumeChange(self, params): - self.slrVolume.setValue(params['newVolume']) - def onDisconnect(self, params): - map(lambda o: o.setEnabled(False), self.objects) - def onTimeChange(self, params): - if not self.slrTime.isSliderDown(): - self.slrTime.setValue(params['newTime']) - def onSongChange(self, params): - try: - self.slrTime.setMaximum(monty.getStatus()['length']) - self.slrTime.setEnabled(True) - except: - pass - # look in another thread for the songs in the current album! - params=() - start_new_thread(self.findAlbumSongs, params) + if newState=='play': + self.btnPlayPause.changeIcon('gfx/media-playback-pause.svg') + self.btnPlayPause.setText('pauze') + elif newState=='pause' or newState=='stop': + self.btnPlayPause.changeIcon('gfx/media-playback-start.svg') + self.btnPlayPause.setText('play') + def onVolumeChange(self, params): + self.slrVolume.setValue(params['newVolume']) + def onDisconnect(self, params): + map(lambda o: o.setEnabled(False), self.objects) + def onTimeChange(self, params): + if not self.slrTime.isSliderDown(): + self.slrTime.setValue(params['newTime']) + def onSongChange(self, params): + try: + self.slrTime.setMaximum(monty.getStatus()['length']) + self.slrTime.setEnabled(True) + except: + pass + # look in another thread for the songs in the current album! + params=() + start_new_thread(self.findAlbumSongs, params) - def beforeSongChange(self, params): - nextID=None - song=monty.getCurrentSong() - # decide here what next song to play! - repeat=self.cmbRepeat.currentIndex() - random=self.cmbShuffle.currentIndex() - # is the current song the last of the album? - try: - eofAlbum=int(song.getTrack())==int(self.curAlbumSongs[-1].getTrack()) - except: - eofAlbum=False - if repeat==PC_REPEAT_NO: - # no repeat, nothing to see here! Pass on! - pass - elif repeat==PC_REPEAT_SONG: - # we must repeat the previous song! - nextID=params['curSongID'] - elif repeat==PC_REPEAT_ALBUM: - # check if we are at the last track, if it is, we must start a new! - if eofAlbum: - nextID=self.curAlbumSongs[0].getID() - elif repeat==PC_REPEAT_PLAYLIST: - # repeating the playlist is handled by monty itself; - # it is set in onCmbRepeatChanged. - pass - - if repeat!=PC_REPEAT_SONG: - if random==PC_RANDOM_QUEUE: - # we must check here for the queue, because, if the queue is empty, we must - # choose the next one according the method! - # pick the next song from the queue. Simple as that :) - if len(self.queuedSongs): - # pick the front - nextID=self.queuedSongs.pop(0) - self._onQueueUpdate() - else: - # no songs anymore, so we must restore the old mode! - # We should never arrive here though ... - self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle'))) - - if random==PC_RANDOM_NO: - # just follow our leader Monty. - pass - elif random==PC_RANDOM_SONG: - # pick a random song! This depends on what repeat-mode we're in. - if repeat==PC_REPEAT_NO or repeat==PC_REPEAT_PLAYLIST: - # we don't repeat anything, so we can just let monty pick the - # next random one! - pass - elif repeat==PC_REPEAT_ALBUM and self.curAlbumSongs: - # pick random song from current album - nextID=self.curAlbumSongs[randint(0,len(self.curAlbumSongs)-1)].getID() - elif random==PC_RANDOM_ALBUM: - # pick a random album! This means, we pick the first song of a random - # album. - if eofAlbum and (repeat==PC_REPEAT_PLAYLIST or repeat==PC_REPEAT_NO): - # all first songs of an album - albums=filter(lambda s: s.getAlbum() and s.getTrack()==1, monty.listPlaylist()) - nextID=albums[randint(0,len(albums)-1)].getID() - else: - # we're not at end of album, so we fetch the next id - # We must do this, because albums are not necesseraly in the same order - for i in xrange(len(self.curAlbumSongs)): - if self.curAlbumSongs[i].getID()==song.getID(): - nextID=self.curAlbumSongs[i+1].getID() - break - - if random==PC_RANDOM_QUEUE: - # now here reset the mode to the previous, if needed - if len(self.queuedSongs)==0: - self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle'))) + def beforeSongChange(self, params): + nextID=None + song=monty.getCurrentSong() + # decide here what next song to play! + repeat=self.cmbRepeat.currentIndex() + random=self.cmbShuffle.currentIndex() + # is the current song the last of the album? + try: + eofAlbum=int(song.getTrack())==int(self.curAlbumSongs[-1].getTrack()) + except: + eofAlbum=False + if repeat==PC_REPEAT_NO: + # no repeat, nothing to see here! Pass on! + pass + elif repeat==PC_REPEAT_SONG: + # we must repeat the previous song! + nextID=params['curSongID'] + elif repeat==PC_REPEAT_ALBUM: + # check if we are at the last track, if it is, we must start a new! + if eofAlbum: + nextID=self.curAlbumSongs[0].getID() + elif repeat==PC_REPEAT_PLAYLIST: + # repeating the playlist is handled by monty itself; + # it is set in onCmbRepeatChanged. + pass + + if repeat!=PC_REPEAT_SONG: + if random==PC_RANDOM_QUEUE: + # we must check here for the queue, because, if the queue is empty, we must + # choose the next one according the method! + # pick the next song from the queue. Simple as that :) + if len(self.queuedSongs): + # pick the front + nextID=self.queuedSongs.pop(0) + self._onQueueUpdate() + else: + # no songs anymore, so we must restore the old mode! + # We should never arrive here though ... + self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle'))) + + if random==PC_RANDOM_NO: + # just follow our leader Monty. + pass + elif random==PC_RANDOM_SONG: + # pick a random song! This depends on what repeat-mode we're in. + if repeat==PC_REPEAT_NO or repeat==PC_REPEAT_PLAYLIST: + # we don't repeat anything, so we can just let monty pick the + # next random one! + pass + elif repeat==PC_REPEAT_ALBUM and self.curAlbumSongs: + # pick random song from current album + nextID=self.curAlbumSongs[randint(0,len(self.curAlbumSongs)-1)].getID() + elif random==PC_RANDOM_ALBUM: + # pick a random album! This means, we pick the first song of a random + # album. + if eofAlbum and (repeat==PC_REPEAT_PLAYLIST or repeat==PC_REPEAT_NO): + # all first songs of an album + albums=filter(lambda s: s.getAlbum() and s.getTrack()==1, monty.listPlaylist()) + nextID=albums[randint(0,len(albums)-1)].getID() + else: + # we're not at end of album, so we fetch the next id + # We must do this, because albums are not necesseraly in the same order + for i in xrange(len(self.curAlbumSongs)): + if self.curAlbumSongs[i].getID()==song.getID(): + nextID=self.curAlbumSongs[i+1].getID() + break + + if random==PC_RANDOM_QUEUE: + # now here reset the mode to the previous, if needed + if len(self.queuedSongs)==0: + self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle'))) - if nextID!=None: - monty.play(nextID) - - def getCurAlbumSongs(self): - return self.curAlbumSongs - def findAlbumSongs(self): - """This method looks for the songs in the album of current playing song.""" - song=monty.getCurrentSong() - if self.curAlbumSongs and clSong.isSameAlbum(song, self.curAlbumSongs[0]): - return - self.curAlbumSongs=None - if not song or not song.getAlbum(): - return - self.curAlbumSongs=filter(lambda s: clSong.isSameAlbum(s, song), monty.listPlaylist()) - self.curAlbumSongs=sorted(self.curAlbumSongs, lambda l,r: numeric_compare(l.getTrack(), r.getTrack())) + if nextID!=None: + monty.play(nextID) + + def getCurAlbumSongs(self): + return self.curAlbumSongs + def findAlbumSongs(self): + """This method looks for the songs in the album of current playing song.""" + song=monty.getCurrentSong() + if self.curAlbumSongs and clSong.isSameAlbum(song, self.curAlbumSongs[0]): + return + self.curAlbumSongs=None + if not song or not song.getAlbum(): + return + self.curAlbumSongs=filter(lambda s: clSong.isSameAlbum(s, song), monty.listPlaylist()) + self.curAlbumSongs=sorted(self.curAlbumSongs, lambda l,r: numeric_compare(l.getTrack(), r.getTrack())) - def onBtnPlayPauseClick(self): - status=monty.getStatus() - if status['state']=='play': - monty.pause() - self.p.extended("Toggling playback") - elif status['state']=='stop': - monty.play(None) - self.p.extended("Pausing playback") - else: - monty.resume() - def onBtnStopClick(self): - monty.stop() - self.p.extended("Stopping playback") - def onBtnPreviousClick(self): - monty.previous() - self.p.extended("Playing previous") - def onBtnNextClick(self): - monty.next() - self.p.extended("Playing next") - def onTimeSliderChange(self): - monty.seek(self.slrTime.value()) - def onVolumeSliderChange(self): - v=self.slrVolume.value() - monty.setVolume(v) - if v<=1: - mode='mute' - else: - mode=('0', 'min', 'med', 'max')[int(3*v/100)] - self.svgVolume.load('gfx/stock_volume-%s.svg'%(mode)) - - def onCmbRepeatChanged(self, newval): - self.p.setSetting('repeat', newval) - if newval==PC_REPEAT_PLAYLIST: - monty.repeat(1) - else: - monty.repeat(0) - def onCmbShuffleChanged(self, newval): - if newval==PC_RANDOM_QUEUE: - # must do some extra's if moving to queued-mode - if len(self.queuedSongs): - self.p.setSetting('oldshuffle', self.p.getSetting('shuffle')) - else: - self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle'))) - return - else: - # clear the queued songs when switching - self.queuedSongs=[] - self._onQueueUpdate() + def onBtnPlayPauseClick(self): + status=monty.getStatus() + if status['state']=='play': + monty.pause() + self.p.extended("Toggling playback") + elif status['state']=='stop': + monty.play(None) + self.p.extended("Pausing playback") + else: + monty.resume() + def onBtnStopClick(self): + monty.stop() + self.p.extended("Stopping playback") + def onBtnPreviousClick(self): + monty.previous() + self.p.extended("Playing previous") + def onBtnNextClick(self): + monty.next() + self.p.extended("Playing next") + def onTimeSliderChange(self): + monty.seek(self.slrTime.value()) + def onVolumeSliderChange(self): + v=self.slrVolume.value() + monty.setVolume(v) + if v<=1: + mode='mute' + else: + mode=('0', 'min', 'med', 'max')[int(3*v/100)] + self.svgVolume.load('gfx/stock_volume-%s.svg'%(mode)) + + def onCmbRepeatChanged(self, newval): + self.p.setSetting('repeat', newval) + if newval==PC_REPEAT_PLAYLIST: + monty.repeat(1) + else: + monty.repeat(0) + def onCmbShuffleChanged(self, newval): + if newval==PC_RANDOM_QUEUE: + # must do some extra's if moving to queued-mode + if len(self.queuedSongs): + self.p.setSetting('oldshuffle', self.p.getSetting('shuffle')) + else: + self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle'))) + return + else: + # clear the queued songs when switching + self.queuedSongs=[] + self._onQueueUpdate() - self.p.setSetting('shuffle', newval) - if newval==PC_RANDOM_SONG: - monty.random(1) - else: - monty.random(0) + self.p.setSetting('shuffle', newval) + if newval==PC_RANDOM_SONG: + monty.random(1) + else: + monty.random(0) - # save and load the queue - def saveQueue(self): - # save the ids as a list of space-separated numbers - self.p.extended("saving queue") - self.p.setSetting('queue', str(self.queuedSongs)[1:-1].replace(',', '')) - def loadQueue(self): - # just read all the numbers! - self.p.extended("loading queue") - self.queuedSongs=[] - i=0 - ids=self.p.getSetting('queue').split(' ') - for id in ids: - try: - self.queuedSongs.append(int(id)) - except: - pass - self._onQueueUpdate() + # save and load the queue + def saveQueue(self): + # save the ids as a list of space-separated numbers + self.p.extended("saving queue") + self.p.setSetting('queue', str(self.queuedSongs)[1:-1].replace(',', '')) + def loadQueue(self): + # just read all the numbers! + self.p.extended("loading queue") + self.queuedSongs=[] + i=0 + ids=self.p.getSetting('queue').split(' ') + for id in ids: + try: + self.queuedSongs.append(int(id)) + except: + pass + self._onQueueUpdate() class pluginPlayControl(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'PlayControl') - self.addMontyListener('onStateChange', self.onStateChange) - self.addMontyListener('beforeSongChange', self.beforeSongChange) - self.addMontyListener('onSongChange', self.onSongChange) - self.addMontyListener('onVolumeChange', self.onVolumeChange) - self.addMontyListener('onReady', self.onStateChange) - self.addMontyListener('onDisconnect', self.onDisconnect) - self.addMontyListener('onTimeChange', self.onTimeChange) - def _load(self): - self.o=wgPlayControl(self, None) - self.o.loadQueue() - def _unload(self): - self.o.saveQueue() - self.o=None - def getInfo(self): - return "Have total control over the playing!" - - def addSongsToQueue(self, songs): - return self.o.addSongsToQueue(songs) - - def onStateChange(self, params): - self.o.onStateChange(params) - def beforeSongChange(self, params): - self.o.beforeSongChange(params) - def onSongChange(self, params): - self.o.onSongChange(params) - def onVolumeChange(self, params): - self.o.onVolumeChange(params) - def onDisconnect(self, params): - self.o.onDisconnect(params) - def onTimeChange(self, params): - self.o.onTimeChange(params) - - def _getDockWidget(self): - return self._createDock(self.o) + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'PlayControl') + self.addMontyListener('onStateChange', self.onStateChange) + self.addMontyListener('beforeSongChange', self.beforeSongChange) + self.addMontyListener('onSongChange', self.onSongChange) + self.addMontyListener('onVolumeChange', self.onVolumeChange) + self.addMontyListener('onReady', self.onStateChange) + self.addMontyListener('onDisconnect', self.onDisconnect) + self.addMontyListener('onTimeChange', self.onTimeChange) + def _load(self): + self.o=wgPlayControl(self, None) + self.o.loadQueue() + def _unload(self): + self.o.saveQueue() + self.o=None + def getInfo(self): + return "Have total control over the playing!" + + def addSongsToQueue(self, songs): + return self.o.addSongsToQueue(songs) + + def onStateChange(self, params): + self.o.onStateChange(params) + def beforeSongChange(self, params): + self.o.beforeSongChange(params) + def onSongChange(self, params): + self.o.onSongChange(params) + def onVolumeChange(self, params): + self.o.onVolumeChange(params) + def onDisconnect(self, params): + self.o.onDisconnect(params) + def onTimeChange(self, params): + self.o.onTimeChange(params) + + def _getDockWidget(self): + return self._createDock(self.o) diff --git a/plugins/Playlist.py b/plugins/Playlist.py index 3cb812b..811441d 100644 --- a/plugins/Playlist.py +++ b/plugins/Playlist.py @@ -7,85 +7,85 @@ from wgSongList import clrRowSel import plugins PLAYLIST_MODES_DEFAULT='$artist\n'\ - '$artist/$date - $album\n'\ - '$artist - $album\n'\ - '$album ($artist)\n'\ - '$genre\n'\ - '$genre/$artist\n'\ - '$genre/$artist - $album\n' + '$artist/$date - $album\n'\ + '$artist - $album\n'\ + '$album ($artist)\n'\ + '$genre\n'\ + '$genre/$artist\n'\ + '$genre/$artist - $album\n' # Dependencies: # playcontrol class pluginPlaylist(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Playlist') - self.addMontyListener('onSongChange', self.onSongChange) - def _load(self): - self.o=Playlist(self.winMain, self, ['artist', 'title', 'track', 'album'], 'Playlist' - , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n')) - def _unload(self): - self.o=None - def getPlaylist(self): - return self.o - def getInfo(self): - return "The playlist showing the songs that will be played." - - def getList(self): - return self.o + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Playlist') + self.addMontyListener('onSongChange', self.onSongChange) + def _load(self): + self.o=Playlist(self.winMain, self, ['artist', 'title', 'track', 'album'], 'Playlist' + , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n')) + def _unload(self): + self.o=None + def getPlaylist(self): + return self.o + def getInfo(self): + return "The playlist showing the songs that will be played." + + def getList(self): + return self.o - def _getDockWidget(self): - return self._createDock(self.o) + def _getDockWidget(self): + return self._createDock(self.o) - def onDoubleClick(self): - monty.play(self.o.getSelItemID()) + def onDoubleClick(self): + monty.play(self.o.getSelItemID()) - def onKeyPress(self, event): - if event.matches(QtGui.QKeySequence.Delete): - # remove selection from playlist using DELETE-key - ids=self.o.selectedIds() - self.setStatus('Deleting '+str(len(ids))+' songs from playlist ...') - doEvents() + def onKeyPress(self, event): + if event.matches(QtGui.QKeySequence.Delete): + # remove selection from playlist using DELETE-key + ids=self.o.selectedIds() + self.setStatus('Deleting '+str(len(ids))+' songs from playlist ...') + doEvents() - monty.deleteFromPlaylist(ids) + monty.deleteFromPlaylist(ids) - self.setStatus('') - doEvents() + self.setStatus('') + doEvents() - self.getWinMain().fillPlaylist() - elif event.key()==QtCore.Qt.Key_Q: - # queue selected songs - # Hoho, this one needs the playcontrol plugin! - plugins.getPlugin('playcontrol').addSongsToQueue(self.o.selectedIds()) - return QtGui.QWidget.keyPressEvent(self.o, event) - - def onSongChange(self, params): - lst=self.o - lst.colorID(int(params['newSongID']), clrRowSel) + self.getWinMain().fillPlaylist() + elif event.key()==QtCore.Qt.Key_Q: + # queue selected songs + # Hoho, this one needs the playcontrol plugin! + plugins.getPlugin('playcontrol').addSongsToQueue(self.o.selectedIds()) + return QtGui.QWidget.keyPressEvent(self.o, event) + + def onSongChange(self, params): + lst=self.o + lst.colorID(int(params['newSongID']), clrRowSel) - if params['newSongID']!=-1: - lst.ensureVisible(params['newSongID']) + if params['newSongID']!=-1: + lst.ensureVisible(params['newSongID']) - _rowColorModifier=0 - _rowColorAdder=1 - def timerEvent(self, event): - curSong=monty.getCurrentSong() - if curSong: - lst=self.lstPlaylist - # color current playing song - lst.colorID(curSong.getID(), - QtGui.QColor(140-self._rowColorModifier*5,140-self._rowColorModifier*5,180)) + _rowColorModifier=0 + _rowColorAdder=1 + def timerEvent(self, event): + curSong=monty.getCurrentSong() + if curSong: + lst=self.lstPlaylist + # color current playing song + lst.colorID(curSong.getID(), + QtGui.QColor(140-self._rowColorModifier*5,140-self._rowColorModifier*5,180)) - # make sure color changes nicely over time - self._rowColorModifier=self._rowColorModifier+self._rowColorAdder - if abs(self._rowColorModifier)>4: - self._rowColorAdder=-1*self._rowColorAdder + # make sure color changes nicely over time + self._rowColorModifier=self._rowColorModifier+self._rowColorAdder + if abs(self._rowColorModifier)>4: + self._rowColorAdder=-1*self._rowColorAdder - def _getSettings(self): - modes=QtGui.QTextEdit() - modes.insertPlainText(self.getSetting('modes')) - return [ - ['modes', 'Modes', 'Sets the available modes.', modes], - ] - def afterSaveSettings(self): - self.o.setModes(self.getSetting('modes').split('\n')) + def _getSettings(self): + modes=QtGui.QTextEdit() + modes.insertPlainText(self.getSetting('modes')) + return [ + ['modes', 'Modes', 'Sets the available modes.', modes], + ] + def afterSaveSettings(self): + self.o.setModes(self.getSetting('modes').split('\n')) diff --git a/plugins/Scrobbler.py b/plugins/Scrobbler.py index bb2321f..ced3964 100644 --- a/plugins/Scrobbler.py +++ b/plugins/Scrobbler.py @@ -9,90 +9,90 @@ SCROBBLER_PASSWORD_DEFAULT='' # TODO cached failed submissions class pluginScrobbler(Plugin): - submitted=False - time=None - loggedIn=False - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Scrobbler') - self.addMontyListener('onSongChange', self.onSongChange) - self.addMontyListener('onTimeChange', self.onTimeChange) - - def _load(self): - self._login() - - def _login(self): - self.submitting=False - self.loggedIn=False - if self._username(): - self.normal("logging in %s"%(self._username())) - try: - login(self._username(), self._password()) - self.loggedIn=True - except Exception, e: - self.normal("failed to login: "+str(e)) - else: - self.debug("no username provided, not logging in") - - def _username(self): - return self.getSetting('username') - def _password(self): - return self.getSetting('password') - def onTimeChange(self, params): - if self.submitted==False and self.loggedIn: - song=monty.getCurrentSong() - if song.getTag('time')>30: - if int(params['newTime'])>int(song.getTag('time'))/2 \ - or int(params['newTime'])>240: - if not self.time: - self.onSongChange(None) - self.normal("submitting song") - submit(song.getArtist(), song.getTitle(), self.time, 'P', '', song.getTag('time'), song.getAlbum(), song.getTrack(), False) - self.debug("flushing ...") - try: - flush() - self.submitted=True - except Exception, e: - self.important("failed to submit song1 - %s"%(e)) - self.extended("Logging in ...") - self._login() - try: - flush() - self.submitted=True - except Exception, e: - self.important("failed to submit song2 - %s"%(e)) - if self.submitted: - self.debug("flushed") - else: - self.important("failed to submit the song!") - - def onSongChange(self, params): - if self.loggedIn==False: - return - self.time=int(time.mktime(datetime.utcnow().timetuple())) - self.submitted=False - song=monty.getCurrentSong() - # for loop: in case of SessionError, we need to retry once ... - for i in [0, 1]: - try: - self.extended("submitting now playing") - now_playing(song.getArtist(), song.getTitle(), song.getAlbum(), "", song.getTrack()) - self.debug("submitted") - break - except AuthError, e: - self.important("failed to submit playing song - %s"%(e)) - break - except SessionError, e: - self.normal("session error") - self._login() - def _getSettings(self): - return [ - ['username', 'Username', 'Username to submit to last.fm.', QtGui.QLineEdit(self._username())], - ['password', 'Password', 'Password to user to submit. Note that the password is stored *unencrypted* to file.', QtGui.QLineEdit(self._password())], - ] - def afterSaveSettings(self): - self._login() - def getInfo(self): - return "Submits tracks to last.fm" + submitted=False + time=None + loggedIn=False + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Scrobbler') + self.addMontyListener('onSongChange', self.onSongChange) + self.addMontyListener('onTimeChange', self.onTimeChange) + + def _load(self): + self._login() + + def _login(self): + self.submitting=False + self.loggedIn=False + if self._username(): + self.normal("logging in %s"%(self._username())) + try: + login(self._username(), self._password()) + self.loggedIn=True + except Exception, e: + self.normal("failed to login: "+str(e)) + else: + self.debug("no username provided, not logging in") + + def _username(self): + return self.getSetting('username') + def _password(self): + return self.getSetting('password') + def onTimeChange(self, params): + if self.submitted==False and self.loggedIn: + song=monty.getCurrentSong() + if song.getTag('time')>30: + if int(params['newTime'])>int(song.getTag('time'))/2 \ + or int(params['newTime'])>240: + if not self.time: + self.onSongChange(None) + self.normal("submitting song") + submit(song.getArtist(), song.getTitle(), self.time, 'P', '', song.getTag('time'), song.getAlbum(), song.getTrack(), False) + self.debug("flushing ...") + try: + flush() + self.submitted=True + except Exception, e: + self.important("failed to submit song1 - %s"%(e)) + self.extended("Logging in ...") + self._login() + try: + flush() + self.submitted=True + except Exception, e: + self.important("failed to submit song2 - %s"%(e)) + if self.submitted: + self.debug("flushed") + else: + self.important("failed to submit the song!") + + def onSongChange(self, params): + if self.loggedIn==False: + return + self.time=int(time.mktime(datetime.utcnow().timetuple())) + self.submitted=False + song=monty.getCurrentSong() + # for loop: in case of SessionError, we need to retry once ... + for i in [0, 1]: + try: + self.extended("submitting now playing") + now_playing(song.getArtist(), song.getTitle(), song.getAlbum(), "", song.getTrack()) + self.debug("submitted") + break + except AuthError, e: + self.important("failed to submit playing song - %s"%(e)) + break + except SessionError, e: + self.normal("session error") + self._login() + def _getSettings(self): + return [ + ['username', 'Username', 'Username to submit to last.fm.', QtGui.QLineEdit(self._username())], + ['password', 'Password', 'Password to user to submit. Note that the password is stored *unencrypted* to file.', QtGui.QLineEdit(self._password())], + ] + def afterSaveSettings(self): + self._login() + def getInfo(self): + return "Submits tracks to last.fm" @@ -110,293 +110,293 @@ from datetime import datetime, timedelta from md5 import md5 SESSION_ID = None -POST_URL = None -NOW_URL = None +POST_URL = None +NOW_URL = None HARD_FAILS = 0 -LAST_HS = None # Last handshake time -HS_DELAY = 0 # wait this many seconds until next handshake +LAST_HS = None # Last handshake time +HS_DELAY = 0 # wait this many seconds until next handshake SUBMIT_CACHE = [] -MAX_CACHE = 5 # keep only this many songs in the cache +MAX_CACHE = 5 # keep only this many songs in the cache PROTOCOL_VERSION = '1.2' class BackendError(Exception): - "Raised if the AS backend does something funny" - pass + "Raised if the AS backend does something funny" + pass class AuthError(Exception): - "Raised on authencitation errors" - pass + "Raised on authencitation errors" + pass class PostError(Exception): - "Raised if something goes wrong when posting data to AS" - pass + "Raised if something goes wrong when posting data to AS" + pass class SessionError(Exception): - "Raised when problems with the session exist" - pass + "Raised when problems with the session exist" + pass class ProtocolError(Exception): - "Raised on general Protocol errors" - pass + "Raised on general Protocol errors" + pass def login( user, password, client=('tst', '1.0') ): - """Authencitate with AS (The Handshake) - - @param user: The username - @param password: The password - @param client: Client information (see http://www.audioscrobbler.net/development/protocol/ for more info) - @type client: Tuple: (client-id, client-version)""" - global LAST_HS, SESSION_ID, POST_URL, NOW_URL, HARD_FAILS, HS_DELAY, PROTOCOL_VERSION - - if LAST_HS is not None: - next_allowed_hs = LAST_HS + timedelta(seconds=HS_DELAY) - if datetime.now() < next_allowed_hs: - delta = next_allowed_hs - datetime.now() - raise ProtocolError("""Please wait another %d seconds until next handshake (login) attempt.""" % delta.seconds) - - LAST_HS = datetime.now() - - tstamp = int(mktime(datetime.now().timetuple())) - url = "http://post.audioscrobbler.com/" - pwhash = md5(password).hexdigest() - token = md5( "%s%d" % (pwhash, int(tstamp))).hexdigest() - values = { - 'hs': 'true', - 'p' : PROTOCOL_VERSION, - 'c': client[0], - 'v': client[1], - 'u': user, - 't': tstamp, - 'a': token - } - data = urllib.urlencode(values) - req = urllib2.Request("%s?%s" % (url, data) ) - response = urllib2.urlopen(req) - result = response.read() - lines = result.split('\n') - - - if lines[0] == 'BADAUTH': - raise AuthError('Bad username/password') - - elif lines[0] == 'BANNED': - raise Exception('''This client-version was banned by Audioscrobbler. Please contact the author of this module!''') - - elif lines[0] == 'BADTIME': - raise ValueError('''Your system time is out of sync with Audioscrobbler.Consider using an NTP-client to keep you system time in sync.''') - - elif lines[0].startswith('FAILED'): - handle_hard_error() - raise BackendError("Authencitation with AS failed. Reason: %s" % - lines[0]) - - elif lines[0] == 'OK': - # wooooooohooooooo. We made it! - SESSION_ID = lines[1] - NOW_URL = lines[2] - POST_URL = lines[3] - HARD_FAILS = 0 - - else: - # some hard error - handle_hard_error() + """Authencitate with AS (The Handshake) + + @param user: The username + @param password: The password + @param client: Client information (see http://www.audioscrobbler.net/development/protocol/ for more info) + @type client: Tuple: (client-id, client-version)""" + global LAST_HS, SESSION_ID, POST_URL, NOW_URL, HARD_FAILS, HS_DELAY, PROTOCOL_VERSION + + if LAST_HS is not None: + next_allowed_hs = LAST_HS + timedelta(seconds=HS_DELAY) + if datetime.now() < next_allowed_hs: + delta = next_allowed_hs - datetime.now() + raise ProtocolError("""Please wait another %d seconds until next handshake (login) attempt.""" % delta.seconds) + + LAST_HS = datetime.now() + + tstamp = int(mktime(datetime.now().timetuple())) + url = "http://post.audioscrobbler.com/" + pwhash = md5(password).hexdigest() + token = md5( "%s%d" % (pwhash, int(tstamp))).hexdigest() + values = { + 'hs': 'true', + 'p' : PROTOCOL_VERSION, + 'c': client[0], + 'v': client[1], + 'u': user, + 't': tstamp, + 'a': token + } + data = urllib.urlencode(values) + req = urllib2.Request("%s?%s" % (url, data) ) + response = urllib2.urlopen(req) + result = response.read() + lines = result.split('\n') + + + if lines[0] == 'BADAUTH': + raise AuthError('Bad username/password') + + elif lines[0] == 'BANNED': + raise Exception('''This client-version was banned by Audioscrobbler. Please contact the author of this module!''') + + elif lines[0] == 'BADTIME': + raise ValueError('''Your system time is out of sync with Audioscrobbler.Consider using an NTP-client to keep you system time in sync.''') + + elif lines[0].startswith('FAILED'): + handle_hard_error() + raise BackendError("Authencitation with AS failed. Reason: %s" % + lines[0]) + + elif lines[0] == 'OK': + # wooooooohooooooo. We made it! + SESSION_ID = lines[1] + NOW_URL = lines[2] + POST_URL = lines[3] + HARD_FAILS = 0 + + else: + # some hard error + handle_hard_error() def handle_hard_error(): - "Handles hard errors." - global SESSION_ID, HARD_FAILS, HS_DELAY + "Handles hard errors." + global SESSION_ID, HARD_FAILS, HS_DELAY - if HS_DELAY == 0: - HS_DELAY = 60 - elif HS_DELAY < 120*60: - HS_DELAY *= 2 - if HS_DELAY > 120*60: - HS_DELAY = 120*60 + if HS_DELAY == 0: + HS_DELAY = 60 + elif HS_DELAY < 120*60: + HS_DELAY *= 2 + if HS_DELAY > 120*60: + HS_DELAY = 120*60 - HARD_FAILS += 1 - if HARD_FAILS == 3: - SESSION_ID = None + HARD_FAILS += 1 + if HARD_FAILS == 3: + SESSION_ID = None def now_playing( artist, track, album="", length="", trackno="", mbid="" ): - """Tells audioscrobbler what is currently running in your player. This won't - affect the user-profile on last.fm. To do submissions, use the "submit" - method - - @param artist: The artist name - @param track: The track name - @param album: The album name - @param length: The song length in seconds - @param trackno: The track number - @param mbid: The MusicBrainz Track ID - @return: True on success, False on failure""" - - global SESSION_ID, NOW_URL - - if SESSION_ID is None: - raise AuthError("Please 'login()' first. (No session available)") - - if POST_URL is None: - raise PostError("Unable to post data. Post URL was empty!") - - if length != "" and type(length) != type(1): - raise TypeError("length should be of type int") - - if trackno != "" and type(trackno) != type(1): - raise TypeError("trackno should be of type int") - - values = {'s': SESSION_ID, - 'a': unicode(artist).encode('utf-8'), - 't': unicode(track).encode('utf-8'), - 'b': unicode(album).encode('utf-8'), - 'l': length, - 'n': trackno, - 'm': mbid } - - data = urllib.urlencode(values) - req = urllib2.Request(NOW_URL, data) - response = urllib2.urlopen(req) - result = response.read() - - if result.strip() == "OK": - return True - elif result.strip() == "BADSESSION" : - raise SessionError('Invalid session') - else: - return False + """Tells audioscrobbler what is currently running in your player. This won't + affect the user-profile on last.fm. To do submissions, use the "submit" + method + + @param artist: The artist name + @param track: The track name + @param album: The album name + @param length: The song length in seconds + @param trackno: The track number + @param mbid: The MusicBrainz Track ID + @return: True on success, False on failure""" + + global SESSION_ID, NOW_URL + + if SESSION_ID is None: + raise AuthError("Please 'login()' first. (No session available)") + + if POST_URL is None: + raise PostError("Unable to post data. Post URL was empty!") + + if length != "" and type(length) != type(1): + raise TypeError("length should be of type int") + + if trackno != "" and type(trackno) != type(1): + raise TypeError("trackno should be of type int") + + values = {'s': SESSION_ID, + 'a': unicode(artist).encode('utf-8'), + 't': unicode(track).encode('utf-8'), + 'b': unicode(album).encode('utf-8'), + 'l': length, + 'n': trackno, + 'm': mbid } + + data = urllib.urlencode(values) + req = urllib2.Request(NOW_URL, data) + response = urllib2.urlopen(req) + result = response.read() + + if result.strip() == "OK": + return True + elif result.strip() == "BADSESSION" : + raise SessionError('Invalid session') + else: + return False def submit(artist, track, time, source='P', rating="", length="", album="", - trackno="", mbid="", autoflush=False): - """Append a song to the submission cache. Use 'flush()' to send the cache to - AS. You can also set "autoflush" to True. - - From the Audioscrobbler protocol docs: - --------------------------------------------------------------------------- - - The client should monitor the user's interaction with the music playing - service to whatever extent the service allows. In order to qualify for - submission all of the following criteria must be met: - - 1. The track must be submitted once it has finished playing. Whether it has - finished playing naturally or has been manually stopped by the user is - irrelevant. - 2. The track must have been played for a duration of at least 240 seconds or - half the track's total length, whichever comes first. Skipping or pausing - the track is irrelevant as long as the appropriate amount has been played. - 3. The total playback time for the track must be more than 30 seconds. Do - not submit tracks shorter than this. - 4. Unless the client has been specially configured, it should not attempt to - interpret filename information to obtain metadata instead of tags (ID3, - etc). - - @param artist: Artist name - @param track: Track name - @param time: Time the track *started* playing in the UTC timezone (see - datetime.utcnow()). - - Example: int(time.mktime(datetime.utcnow())) - @param source: Source of the track. One of: - 'P': Chosen by the user - 'R': Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1) - 'E': Personalised recommendation except Last.fm (e.g. - Pandora, Launchcast) - 'L': Last.fm (any mode). In this case, the 5-digit Last.fm - recommendation key must be appended to this source ID to - prove the validity of the submission (for example, - "L1b48a"). - 'U': Source unknown - @param rating: The rating of the song. One of: - 'L': Love (on any mode if the user has manually loved the - track) - 'B': Ban (only if source=L) - 'S': Skip (only if source=L) - '': Not applicable - @param length: The song length in seconds - @param album: The album name - @param trackno:The track number - @param mbid: MusicBrainz Track ID - @param autoflush: Automatically flush the cache to AS? - """ - - global SUBMIT_CACHE, MAX_CACHE - - source = source.upper() - rating = rating.upper() - - if source == 'L' and (rating == 'B' or rating == 'S'): - raise ProtocolError("""You can only use rating 'B' or 'S' on source 'L'.See the docs!""") - - if source == 'P' and length == '': - raise ProtocolError("""Song length must be specified when using 'P' as source!""") - - if type(time) != type(1): - raise ValueError("""The time parameter must be of type int (unix timestamp). Instead it was %s""" % time) - - SUBMIT_CACHE.append( - { 'a': unicode(artist).encode('utf-8'), - 't': unicode(track).encode('utf-8'), - 'i': time, - 'o': source, - 'r': rating, - 'l': length, - 'b': unicode(album).encode('utf-8'), - 'n': trackno, - 'm': mbid - } - ) - - if autoflush or len(SUBMIT_CACHE) >= MAX_CACHE: - flush() + trackno="", mbid="", autoflush=False): + """Append a song to the submission cache. Use 'flush()' to send the cache to + AS. You can also set "autoflush" to True. + + From the Audioscrobbler protocol docs: + --------------------------------------------------------------------------- + + The client should monitor the user's interaction with the music playing + service to whatever extent the service allows. In order to qualify for + submission all of the following criteria must be met: + + 1. The track must be submitted once it has finished playing. Whether it has + finished playing naturally or has been manually stopped by the user is + irrelevant. + 2. The track must have been played for a duration of at least 240 seconds or + half the track's total length, whichever comes first. Skipping or pausing + the track is irrelevant as long as the appropriate amount has been played. + 3. The total playback time for the track must be more than 30 seconds. Do + not submit tracks shorter than this. + 4. Unless the client has been specially configured, it should not attempt to + interpret filename information to obtain metadata instead of tags (ID3, + etc). + + @param artist: Artist name + @param track: Track name + @param time: Time the track *started* playing in the UTC timezone (see + datetime.utcnow()). + + Example: int(time.mktime(datetime.utcnow())) + @param source: Source of the track. One of: + 'P': Chosen by the user + 'R': Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1) + 'E': Personalised recommendation except Last.fm (e.g. + Pandora, Launchcast) + 'L': Last.fm (any mode). In this case, the 5-digit Last.fm + recommendation key must be appended to this source ID to + prove the validity of the submission (for example, + "L1b48a"). + 'U': Source unknown + @param rating: The rating of the song. One of: + 'L': Love (on any mode if the user has manually loved the + track) + 'B': Ban (only if source=L) + 'S': Skip (only if source=L) + '': Not applicable + @param length: The song length in seconds + @param album: The album name + @param trackno:The track number + @param mbid: MusicBrainz Track ID + @param autoflush: Automatically flush the cache to AS? + """ + + global SUBMIT_CACHE, MAX_CACHE + + source = source.upper() + rating = rating.upper() + + if source == 'L' and (rating == 'B' or rating == 'S'): + raise ProtocolError("""You can only use rating 'B' or 'S' on source 'L'.See the docs!""") + + if source == 'P' and length == '': + raise ProtocolError("""Song length must be specified when using 'P' as source!""") + + if type(time) != type(1): + raise ValueError("""The time parameter must be of type int (unix timestamp). Instead it was %s""" % time) + + SUBMIT_CACHE.append( + { 'a': unicode(artist).encode('utf-8'), + 't': unicode(track).encode('utf-8'), + 'i': time, + 'o': source, + 'r': rating, + 'l': length, + 'b': unicode(album).encode('utf-8'), + 'n': trackno, + 'm': mbid + } + ) + + if autoflush or len(SUBMIT_CACHE) >= MAX_CACHE: + flush() def flush(): - "Sends the cached songs to AS." - global SUBMIT_CACHE - - values = {} - - for i, item in enumerate(SUBMIT_CACHE): - for key in item: - values[key + "[%d]" % i] = item[key] - - values['s'] = SESSION_ID - - data = urllib.urlencode(values) - req = urllib2.Request(POST_URL, data) - response = urllib2.urlopen(req) - result = response.read() - lines = result.split('\n') - - if lines[0] == "OK": - SUBMIT_CACHE = [] - return True - elif lines[0] == "BADSESSION" : - raise SessionError('Invalid session') - elif lines[0].startswith('FAILED'): - handle_hard_error() - raise BackendError("Authencitation with AS failed. Reason: %s" % - lines[0]) - else: - # some hard error - handle_hard_error() - return False + "Sends the cached songs to AS." + global SUBMIT_CACHE + + values = {} + + for i, item in enumerate(SUBMIT_CACHE): + for key in item: + values[key + "[%d]" % i] = item[key] + + values['s'] = SESSION_ID + + data = urllib.urlencode(values) + req = urllib2.Request(POST_URL, data) + response = urllib2.urlopen(req) + result = response.read() + lines = result.split('\n') + + if lines[0] == "OK": + SUBMIT_CACHE = [] + return True + elif lines[0] == "BADSESSION" : + raise SessionError('Invalid session') + elif lines[0].startswith('FAILED'): + handle_hard_error() + raise BackendError("Authencitation with AS failed. Reason: %s" % + lines[0]) + else: + # some hard error + handle_hard_error() + return False if __name__ == "__main__": - login( 'user', 'password' ) - submit( - 'De/Vision', - 'Scars', - 1192374052, - source='P', - length=3*60+44 - ) - submit( - 'Spineshank', - 'Beginning of the End', - 1192374052+(5*60), - source='P', - length=3*60+32 - ) - submit( - 'Dry Cell', - 'Body Crumbles', - 1192374052+(10*60), - source='P', - length=3*60+3 - ) - print flush() + login( 'user', 'password' ) + submit( + 'De/Vision', + 'Scars', + 1192374052, + source='P', + length=3*60+44 + ) + submit( + 'Spineshank', + 'Beginning of the End', + 1192374052+(5*60), + source='P', + length=3*60+32 + ) + submit( + 'Dry Cell', + 'Body Crumbles', + 1192374052+(10*60), + source='P', + length=3*60+3 + ) + print flush() diff --git a/plugins/Shortcuts.py b/plugins/Shortcuts.py index baa462f..e76078f 100644 --- a/plugins/Shortcuts.py +++ b/plugins/Shortcuts.py @@ -6,81 +6,81 @@ from misc import * import format class pluginShortcuts(Plugin): - keys=None - col=None - actionPrefix=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Shortcuts') + keys=None + col=None + actionPrefix=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Shortcuts') - def _load(self): - winMain=self.getWinMain() - # Note people wanting to implement global shortcuts in KDE4 (and sniffing through - # this code): one needs to have a KApplication running, else shortcuts will fail - self.col=kdeui.KActionCollection(None) - self.col.addAssociatedWidget(winMain) - self.keys=[ - ['toggleplay', QtCore.Qt.META+QtCore.Qt.Key_Home, self.togglePlay], - ['volumeup', QtCore.Qt.META+QtCore.Qt.Key_PageDown, lambda b: monty.setVolume(monty.getVolume()-5)], - ['volumedown', QtCore.Qt.META+QtCore.Qt.Key_PageUp, lambda b: monty.setVolume(monty.getVolume()+5)], - ['playnext', QtCore.Qt.META+QtCore.Qt.Key_Right, monty.next], - ['playprevious', QtCore.Qt.META+QtCore.Qt.Key_Left, monty.previous], - ['showosd', QtCore.Qt.META+QtCore.Qt.Key_O, self.showOSD], - ['togglewin', QtCore.Qt.META+QtCore.Qt.Key_P, self.toggleWinMain], - ] - # Note: don't use any non-alphanumerics in the prefix, as else it won't work - self.actionPrefix="shortcuts" - - for entry in self.keys: - name=entry[0] - key=entry[1] - callback=entry[2] - - self.debug("%s - %s"%(name, QtGui.QKeySequence(key).toString())) - - action=kdeui.KAction(winMain) # winMain needed - action.setText(name) - action.setObjectName(name) - action.setGlobalShortcut(kdeui.KShortcut(key)) - QtCore.QObject.connect(action, QtCore.SIGNAL('triggered(bool)'), callback) - self.col.addAction("%s%s"%(self.actionPrefix, action.objectName()), action) - def _unload(self): - actions=self.col.actions() - for action in actions: - try: - if action.objectName()[0:len(self.actionPrefix)]==self.actionPrefix: - self.debug("removing %s"%(action.objectName())) - self.col.removeAction(action) - except Exception, e: - self.important(str(e)) - - def getInfo(self): - return "Shortcuts for mpd." + def _load(self): + winMain=self.getWinMain() + # Note people wanting to implement global shortcuts in KDE4 (and sniffing through + # this code): one needs to have a KApplication running, else shortcuts will fail + self.col=kdeui.KActionCollection(None) + self.col.addAssociatedWidget(winMain) + self.keys=[ + ['toggleplay', QtCore.Qt.META+QtCore.Qt.Key_Home, self.togglePlay], + ['volumeup', QtCore.Qt.META+QtCore.Qt.Key_PageDown, lambda b: monty.setVolume(monty.getVolume()-5)], + ['volumedown', QtCore.Qt.META+QtCore.Qt.Key_PageUp, lambda b: monty.setVolume(monty.getVolume()+5)], + ['playnext', QtCore.Qt.META+QtCore.Qt.Key_Right, monty.next], + ['playprevious', QtCore.Qt.META+QtCore.Qt.Key_Left, monty.previous], + ['showosd', QtCore.Qt.META+QtCore.Qt.Key_O, self.showOSD], + ['togglewin', QtCore.Qt.META+QtCore.Qt.Key_P, self.toggleWinMain], + ] + # Note: don't use any non-alphanumerics in the prefix, as else it won't work + self.actionPrefix="shortcuts" + + for entry in self.keys: + name=entry[0] + key=entry[1] + callback=entry[2] + + self.debug("%s - %s"%(name, QtGui.QKeySequence(key).toString())) + + action=kdeui.KAction(winMain) # winMain needed + action.setText(name) + action.setObjectName(name) + action.setGlobalShortcut(kdeui.KShortcut(key)) + QtCore.QObject.connect(action, QtCore.SIGNAL('triggered(bool)'), callback) + self.col.addAction("%s%s"%(self.actionPrefix, action.objectName()), action) + def _unload(self): + actions=self.col.actions() + for action in actions: + try: + if action.objectName()[0:len(self.actionPrefix)]==self.actionPrefix: + self.debug("removing %s"%(action.objectName())) + self.col.removeAction(action) + except Exception, e: + self.important(str(e)) + + def getInfo(self): + return "Shortcuts for mpd." - def showOSD(self, b): - plugins.getPlugin('notify').onSongChange(None) + def showOSD(self, b): + plugins.getPlugin('notify').onSongChange(None) - def togglePlay(self, btns=None, mods=None): - if monty.isPlaying(): - monty.pause() - else: - monty.resume() - - def toggleWinMain(self, b): - w=self.getWinMain() - if w.isVisible(): - w.setVisible(False) - else: - w.setVisible(True) - - def _getSettings(self): - txt=QtGui.QTextEdit() - txt.setReadOnly(True) - txt.insertPlainText("Keybindings (read-only)\n") - for entry in self.keys: - name=entry[0] - key=entry[1] - - txt.insertPlainText("%s\t%s\n"%(name, QtGui.QKeySequence(key).toString())) - return [ - ['', 'Keybindings', 'Current keybindings', txt], - ] + def togglePlay(self, btns=None, mods=None): + if monty.isPlaying(): + monty.pause() + else: + monty.resume() + + def toggleWinMain(self, b): + w=self.getWinMain() + if w.isVisible(): + w.setVisible(False) + else: + w.setVisible(True) + + def _getSettings(self): + txt=QtGui.QTextEdit() + txt.setReadOnly(True) + txt.insertPlainText("Keybindings (read-only)\n") + for entry in self.keys: + name=entry[0] + key=entry[1] + + txt.insertPlainText("%s\t%s\n"%(name, QtGui.QKeySequence(key).toString())) + return [ + ['', 'Keybindings', 'Current keybindings', txt], + ] diff --git a/plugins/SongStatus.py b/plugins/SongStatus.py index 052e99b..71aa6e4 100644 --- a/plugins/SongStatus.py +++ b/plugins/SongStatus.py @@ -12,79 +12,79 @@ SONGSTATUS_FORMAT_DEFAULT='<font size="4">now $state</font>'\ '$if($length,<br /><font size="4">$time/$length</font>)' class wgSongStatus(QtGui.QWidget): - """Displays the status of the current song, if playing.""" - " label containing the info" - lblInfo=None - format=None - p=None - def __init__(self, p, parent=None): - QtGui.QWidget.__init__(self, parent) - self.p=p + """Displays the status of the current song, if playing.""" + " label containing the info" + lblInfo=None + format=None + p=None + def __init__(self, p, parent=None): + QtGui.QWidget.__init__(self, parent) + self.p=p - self.lblInfo=QtGui.QLabel() - self.setMinimumWidth(400) - - layout=QtGui.QHBoxLayout() - self.setLayout(layout) + self.lblInfo=QtGui.QLabel() + self.setMinimumWidth(400) + + layout=QtGui.QHBoxLayout() + self.setLayout(layout) - layout.addWidget(self.lblInfo) - self.updateFormat() + layout.addWidget(self.lblInfo) + self.updateFormat() - def update(self, params): - status=monty.getStatus() - song=monty.getCurrentSong() + def update(self, params): + status=monty.getStatus() + song=monty.getCurrentSong() - values={'state':''} - try: - values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']] - if 'time' in status: - values['length']=sec2min(status['length']) - values['time']=sec2min(status['time']) - except: - pass - - if song: - self.lblInfo.setText(self.format(format.params(song, values))) - - def updateFormat(self): - try: - self.format=format.compile(self.p.getSetting('format')) - except Exception, e: - self.format=lambda p: "Invalid format: %s"%(e) + values={'state':''} + try: + values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']] + if 'time' in status: + values['length']=sec2min(status['length']) + values['time']=sec2min(status['time']) + except: + pass + + if song: + self.lblInfo.setText(self.format(format.params(song, values))) + + def updateFormat(self): + try: + self.format=format.compile(self.p.getSetting('format')) + except Exception, e: + self.format=lambda p: "Invalid format: %s"%(e) - def text(self): - return self.lblInfo.text() + def text(self): + return self.lblInfo.text() class pluginSongStatus(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'SongStatus') - self.addMontyListener('onSongChange', self.update) - self.addMontyListener('onTimeChange', self.update) - self.addMontyListener('onStateChange', self.update) - self.addMontyListener('onConnect', self.update) - self.addMontyListener('onDisconnect', self.update) - - def _load(self): - self.o=wgSongStatus(self, None) - self.update(None) - def _unload(self): - self.o=None - def getInfo(self): - return "Show information about the current song." - - def update(self, params): - self.o.update(params) - - def _getDockWidget(self): - return self._createDock(self.o) + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'SongStatus') + self.addMontyListener('onSongChange', self.update) + self.addMontyListener('onTimeChange', self.update) + self.addMontyListener('onStateChange', self.update) + self.addMontyListener('onConnect', self.update) + self.addMontyListener('onDisconnect', self.update) + + def _load(self): + self.o=wgSongStatus(self, None) + self.update(None) + def _unload(self): + self.o=None + def getInfo(self): + return "Show information about the current song." + + def update(self, params): + self.o.update(params) + + def _getDockWidget(self): + return self._createDock(self.o) - def _getSettings(self): - format=QtGui.QTextEdit() - format.insertPlainText(self.getSetting('format')) - return [ - ['format', 'Format', 'Format of the song status. Possible tags: $title, $artist, $album, $track, $time, $length, $state', format] - ] - def afterSaveSettings(self): - self.o.updateFormat() - self.o.update(None) + def _getSettings(self): + format=QtGui.QTextEdit() + format.insertPlainText(self.getSetting('format')) + return [ + ['format', 'Format', 'Format of the song status. Possible tags: $title, $artist, $album, $track, $time, $length, $state', format] + ] + def afterSaveSettings(self): + self.o.updateFormat() + self.o.update(None) diff --git a/plugins/Systray.py b/plugins/Systray.py index 75d2267..b933225 100644 --- a/plugins/Systray.py +++ b/plugins/Systray.py @@ -7,107 +7,107 @@ import format SYSTRAY_FORMAT="$if($title,$title)$if($artist, by $artist) - [$album # $track] ($time/$length)" class pluginSystray(Plugin): - o=None - format=None - eventObj=None - time=None # indicator of current time [0..64] - appIcon=None - pixmap=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Systray') - self.addMontyListener('onSongChange', self.update) - self.addMontyListener('onReady', self.update) - self.addMontyListener('onConnect', self.update) - self.addMontyListener('onDisconnect', self.update) - # TODO only update this when necessary, i.e. mouse-hover etc - self.addMontyListener('onTimeChange', self.update) - self.appIcon=appIcon - + o=None + format=None + eventObj=None + time=None # indicator of current time [0..64] + appIcon=None + pixmap=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Systray') + self.addMontyListener('onSongChange', self.update) + self.addMontyListener('onReady', self.update) + self.addMontyListener('onConnect', self.update) + self.addMontyListener('onDisconnect', self.update) + # TODO only update this when necessary, i.e. mouse-hover etc + self.addMontyListener('onTimeChange', self.update) + self.appIcon=appIcon + - def _load(self): - self.format=format.compile(SYSTRAY_FORMAT) - class SystrayWheelEventObject(QtCore.QObject): - """This class listens for systray-wheel events""" - def eventFilter(self, object, event): - if type(event)==QtGui.QWheelEvent: - numDegrees=event.delta() / 8 - numSteps=5*numDegrees/15 - monty.setVolume(int(monty.getStatus()['volume'])+numSteps) - event.accept() - return True - return False - - self.o=QtGui.QSystemTrayIcon(appIcon, self.winMain) - self.eventObj=SystrayWheelEventObject() - self.o.installEventFilter(self.eventObj) - self.winMain.connect(self.o, QtCore.SIGNAL('activated (QSystemTrayIcon::ActivationReason)') - , self.onSysTrayClick) - self.o.show() - self.update(None) - - def _unload(self): - self.o.hide() - self.o.setIcon(QtGui.QIcon(None)) - self.o=None - self.winMain._wheelEvent=None - def getInfo(self): - return "Display the montypc icon in the systray." - - def update(self, params): - status=monty.getStatus() - song=monty.getCurrentSong() + def _load(self): + self.format=format.compile(SYSTRAY_FORMAT) + class SystrayWheelEventObject(QtCore.QObject): + """This class listens for systray-wheel events""" + def eventFilter(self, object, event): + if type(event)==QtGui.QWheelEvent: + numDegrees=event.delta() / 8 + numSteps=5*numDegrees/15 + monty.setVolume(int(monty.getStatus()['volume'])+numSteps) + event.accept() + return True + return False + + self.o=QtGui.QSystemTrayIcon(appIcon, self.winMain) + self.eventObj=SystrayWheelEventObject() + self.o.installEventFilter(self.eventObj) + self.winMain.connect(self.o, QtCore.SIGNAL('activated (QSystemTrayIcon::ActivationReason)') + , self.onSysTrayClick) + self.o.show() + self.update(None) + + def _unload(self): + self.o.hide() + self.o.setIcon(QtGui.QIcon(None)) + self.o=None + self.winMain._wheelEvent=None + def getInfo(self): + return "Display the montypc icon in the systray." + + def update(self, params): + status=monty.getStatus() + song=monty.getCurrentSong() - values={'state':''} - try: - values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']] - if 'time' in status: - values['length']=sec2min(status['length']) - values['time']=sec2min(status['time']) - except: - pass - - if song: - self.o.setToolTip(self.format(format.params(song, values))) - else: - self.o.setToolTip("montypc not playing") - - try: - curTime=(64*status['time'])/status['length'] - except: - curTime=-1 - if self.time!=curTime: - self.time=curTime - # redraw the systray icon - self.pixmap=self.appIcon.pixmap(64,64) - painter=QtGui.QPainter(self.pixmap) - painter.fillRect(1, curTime, 63, 64, QtGui.QBrush(QtCore.Qt.white)) - appIcon.paint(painter, 1, 0, 63, 64) - self.o.setIcon(QtGui.QIcon(self.pixmap)) - - elif not song: - self.time=None - self.o.setIcon(appIcon) + values={'state':''} + try: + values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']] + if 'time' in status: + values['length']=sec2min(status['length']) + values['time']=sec2min(status['time']) + except: + pass + + if song: + self.o.setToolTip(self.format(format.params(song, values))) + else: + self.o.setToolTip("montypc not playing") + + try: + curTime=(64*status['time'])/status['length'] + except: + curTime=-1 + if self.time!=curTime: + self.time=curTime + # redraw the systray icon + self.pixmap=self.appIcon.pixmap(64,64) + painter=QtGui.QPainter(self.pixmap) + painter.fillRect(1, curTime, 63, 64, QtGui.QBrush(QtCore.Qt.white)) + appIcon.paint(painter, 1, 0, 63, 64) + self.o.setIcon(QtGui.QIcon(self.pixmap)) + + elif not song: + self.time=None + self.o.setIcon(appIcon) - - def onSysTrayClick(self, reason): - if reason==QtGui.QSystemTrayIcon.Trigger \ - or reason==QtGui.QSystemTrayIcon.Context: - w=self.getWinMain() - # left mouse button - if w.isVisible(): - settings.setIntTuple('winMain.pos', w.x(), w.y()) - w.setVisible(False) - else: - w.setVisible(True) - try: - x,y=settings.getIntTuple('winMain.pos') - except: - x,y=0,0 - w.move(x, y) - elif reason==QtGui.QSystemTrayIcon.MiddleClick: - # middle mouse button - if monty.isPlaying(): - monty.pause() - else: - monty.resume() - + + def onSysTrayClick(self, reason): + if reason==QtGui.QSystemTrayIcon.Trigger \ + or reason==QtGui.QSystemTrayIcon.Context: + w=self.getWinMain() + # left mouse button + if w.isVisible(): + settings.setIntTuple('winMain.pos', w.x(), w.y()) + w.setVisible(False) + else: + w.setVisible(True) + try: + x,y=settings.getIntTuple('winMain.pos') + except: + x,y=0,0 + w.move(x, y) + elif reason==QtGui.QSystemTrayIcon.MiddleClick: + # middle mouse button + if monty.isPlaying(): + monty.pause() + else: + monty.resume() + diff --git a/plugins/Tabs.py b/plugins/Tabs.py index 811096a..8914a4b 100644 --- a/plugins/Tabs.py +++ b/plugins/Tabs.py @@ -9,157 +9,157 @@ from clMonty import monty from clPlugin import * class ResetEvent(QtCore.QEvent): - song=None - def __init__(self, song=None): - QtCore.QEvent.__init__(self,QtCore.QEvent.User) - self.song=song + song=None + def __init__(self, song=None): + QtCore.QEvent.__init__(self,QtCore.QEvent.User) + self.song=song class AddHtmlEvent(QtCore.QEvent): - html=None - def __init__(self,html): - QtCore.QEvent.__init__(self,QtCore.QEvent.User) - self.html=html + html=None + def __init__(self,html): + QtCore.QEvent.__init__(self,QtCore.QEvent.User) + self.html=html TABS_DIR_DEFAULT='/jammin' TABS_ENGINE_DEFAULT='http://www.google.com/search?q=tabs|chords+"$artist"+"$title"' -TABS_SITES_DEFAULT='azchords.com <pre>(.*?)</pre>\n'\ - 'fretplay.com <P CLASS="tabs">(.*?)</P>\n'\ - 'guitaretab.com <pre style="COLOR: #000000; FONT-SIZE: 11px;">(.*)?</pre>'\ +TABS_SITES_DEFAULT='azchords.com <pre>(.*?)</pre>\n'\ + 'fretplay.com <P CLASS="tabs">(.*?)</P>\n'\ + 'guitaretab.com <pre style="COLOR: #000000; FONT-SIZE: 11px;">(.*)?</pre>'\ class wgTabs(QtGui.QWidget): - " contains the tabs" - txt=None - p=None # plugin - def __init__(self, p, parent=None): - QtGui.QWidget.__init__(self, parent) - self.p=p - self.txt=QtGui.QTextEdit(parent) - self.txt.setReadOnly(True) - - layout=QtGui.QVBoxLayout() - layout.addWidget(self.txt) - self.setLayout(layout) + " contains the tabs" + txt=None + p=None # plugin + def __init__(self, p, parent=None): + QtGui.QWidget.__init__(self, parent) + self.p=p + self.txt=QtGui.QTextEdit(parent) + self.txt.setReadOnly(True) + + layout=QtGui.QVBoxLayout() + layout.addWidget(self.txt) + self.setLayout(layout) - - def refresh(self): - song=monty.getCurrentSong() - try: - song._data['file'] - except: - self.resetTxt() - return - - self.resetTxt(song) - start_new_thread(self.fetchTabs, (song,)) - - def customEvent(self, event): - if isinstance(event,ResetEvent): - self.resetTxt(event.song) - elif isinstance(event,AddHtmlEvent): - self.txt.insertHtml(event.html) + + def refresh(self): + song=monty.getCurrentSong() + try: + song._data['file'] + except: + self.resetTxt() + return + + self.resetTxt(song) + start_new_thread(self.fetchTabs, (song,)) + + def customEvent(self, event): + if isinstance(event,ResetEvent): + self.resetTxt(event.song) + elif isinstance(event,AddHtmlEvent): + self.txt.insertHtml(event.html) - _mutex=QtCore.QMutex() - _fetchCnt=0 - def fetchTabs(self, song): - # only allow 1 instance to look tabs! - self._mutex.lock() - if self._fetchCnt: - self._mutex.unlock() - return - self._fetchCnt=1 - self._mutex.unlock() + _mutex=QtCore.QMutex() + _fetchCnt=0 + def fetchTabs(self, song): + # only allow 1 instance to look tabs! + self._mutex.lock() + if self._fetchCnt: + self._mutex.unlock() + return + self._fetchCnt=1 + self._mutex.unlock() - QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) - - # save the data to file! - save_dir=self.p.getSetting('dir') - fInfo=QtCore.QFileInfo(save_dir) - if fInfo.isDir(): - tabsFName=toAscii('%s/%s - %s.txt'%(save_dir,song.getArtist(),song.getTitle())) - else: - tabsFName=None - # does the file exist? if yes, read that one! - try: - # we have it: load, and return! - file=open(tabsFName, 'r') - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(file.read())) - file.close() - self._fetchCnt=0 - return - except: - pass + QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) + + # save the data to file! + save_dir=self.p.getSetting('dir') + fInfo=QtCore.QFileInfo(save_dir) + if fInfo.isDir(): + tabsFName=toAscii('%s/%s - %s.txt'%(save_dir,song.getArtist(),song.getTitle())) + else: + tabsFName=None + # does the file exist? if yes, read that one! + try: + # we have it: load, and return! + file=open(tabsFName, 'r') + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(file.read())) + file.close() + self._fetchCnt=0 + return + except: + pass - # fetch from inet - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching tabs ...</i>')) - - lines=self.p.getSetting('sites').split('\n') - sites={} - for line in lines: - if line.strip(): - sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip() - # construct URL to search! - SE=self.p.getSetting('engine') - try: - ret=fetch(SE, sites, song, {}, stripHTML=False) - if ret: - txt='<pre>%s<br /><br /><a href="%s">%s</a></pre>'%(ret[0],ret[1],ret[1]) - # save for later use! - if tabsFName: - # we can't save if the path isn't correct - try: - file=open(tabsFName, 'w') - file.write(ret[0]) - file.close() - except: - pass - else: - txt="No tabs found :'(" + # fetch from inet + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching tabs ...</i>')) + + lines=self.p.getSetting('sites').split('\n') + sites={} + for line in lines: + if line.strip(): + sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip() + # construct URL to search! + SE=self.p.getSetting('engine') + try: + ret=fetch(SE, sites, song, {}, stripHTML=False) + if ret: + txt='<pre>%s<br /><br /><a href="%s">%s</a></pre>'%(ret[0],ret[1],ret[1]) + # save for later use! + if tabsFName: + # we can't save if the path isn't correct + try: + file=open(tabsFName, 'w') + file.write(ret[0]) + file.close() + except: + pass + else: + txt="No tabs found :'(" - QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(txt)) - except: - print_exc() - QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) - QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, site unavailable!'\ - '<br />You have an internet connection?')) - self._fetchCnt=0 + QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(txt)) + except: + print_exc() + QtCore.QCoreApplication.postEvent(self, ResetEvent(song)) + QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, site unavailable!'\ + '<br />You have an internet connection?')) + self._fetchCnt=0 - def resetTxt(self, song=None): - self.txt.clear() - if song: - self.txt.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\ - '<br />\n\n'%(song.getTitle(), song.getArtist())) + def resetTxt(self, song=None): + self.txt.clear() + if song: + self.txt.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\ + '<br />\n\n'%(song.getTitle(), song.getArtist())) class pluginTabs(Plugin): - o=None - def __init__(self, winMain): - Plugin.__init__(self, winMain, 'Tabs') - self.addMontyListener('onSongChange', self.refresh) - self.addMontyListener('onReady', self.refresh) - self.addMontyListener('onDisconnect', self.onDisconnect) - def _load(self): - self.o=wgTabs(self, None) - self.refresh(None) - def _unload(self): - self.o=None - def getInfo(self): - return "Show (and fetch) the tabs of the currently playing song." - - def _getDockWidget(self): - return self._createDock(self.o) - - def refresh(self, params): - self.o.refresh() - def onDisconnect(self, params): - self.o.resetTxt() + o=None + def __init__(self, winMain): + Plugin.__init__(self, winMain, 'Tabs') + self.addMontyListener('onSongChange', self.refresh) + self.addMontyListener('onReady', self.refresh) + self.addMontyListener('onDisconnect', self.onDisconnect) + def _load(self): + self.o=wgTabs(self, None) + self.refresh(None) + def _unload(self): + self.o=None + def getInfo(self): + return "Show (and fetch) the tabs of the currently playing song." + + def _getDockWidget(self): + return self._createDock(self.o) + + def refresh(self, params): + self.o.refresh() + def onDisconnect(self, params): + self.o.resetTxt() - def _getSettings(self): - sites=QtGui.QTextEdit() - sites.insertPlainText(self.getSetting('sites')) - return [ - ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))], - ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the tabs.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the tabs website, $regex-start is the regex indicating the start of the tabs, $regex-end indicates the end. E.g. footabs.org <tabs>(.*?)</tabs>', sites], - ['dir', 'Tabs directory', 'Directory where tabs should be stored and retrieved.', QtGui.QLineEdit(self.getSetting('dir'))], - ] - def afterSaveSettings(self): - self.o.onSongChange(None) + def _getSettings(self): + sites=QtGui.QTextEdit() + sites.insertPlainText(self.getSetting('sites')) + return [ + ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))], + ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the tabs.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the tabs website, $regex-start is the regex indicating the start of the tabs, $regex-end indicates the end. E.g. footabs.org <tabs>(.*?)</tabs>', sites], + ['dir', 'Tabs directory', 'Directory where tabs should be stored and retrieved.', QtGui.QLineEdit(self.getSetting('dir'))], + ] + def afterSaveSettings(self): + self.o.onSongChange(None) diff --git a/plugins/__init__.py b/plugins/__init__.py index aa940bc..0673752 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -11,69 +11,69 @@ PLUGIN_MSG=3 def loadPlugins(): - """(Re)load all modules in the plugins directory.""" - global _plugins - _plugins={} - for file in os.listdir('plugins'): - if file[-3:]=='.py' and file!='__init__.py': - name=file[:-3] # name without ext - mod='plugins.%s'%(name) # mod name - className='plugin%s'%(name) # classname - - _plugins[className.lower()]=[mod, className, None, None] - loadPlugin(className, None) + """(Re)load all modules in the plugins directory.""" + global _plugins + _plugins={} + for file in os.listdir('plugins'): + if file[-3:]=='.py' and file!='__init__.py': + name=file[:-3] # name without ext + mod='plugins.%s'%(name) # mod name + className='plugin%s'%(name) # classname + + _plugins[className.lower()]=[mod, className, None, None] + loadPlugin(className, None) def getPlugin(name): - global _plugins - try: - return _plugins[name.lower()][PLUGIN_INSTANCE] - except: - try: - return _plugins["plugin%s"%(name.lower())][PLUGIN_INSTANCE] - except: - return None - + global _plugins + try: + return _plugins[name.lower()][PLUGIN_INSTANCE] + except: + try: + return _plugins["plugin%s"%(name.lower())][PLUGIN_INSTANCE] + except: + return None + def loadPlugin(className, parent): - """Constructs a plugin.""" - global _plugins - entry=_plugins[className.lower()] - mod=entry[PLUGIN_MODULE] - # ensure we get the latest version - try: - try: - sys.modules[mod] - reimport=True - except: - reimport=False - - if reimport: - reload(sys.modules[mod]) - else: - module=__import__(mod, globals(), locals(), className, -1) - - except Exception, e: - _plugins[className.lower()][PLUGIN_MSG]=str(e) - _plugins[className.lower()][PLUGIN_INSTANCE]=None - log.important("Failed to load plugin %s: %s %s"%(className, str(type(e)), str(e))) - return None - - module=sys.modules[mod] - _plugins[className.lower()][PLUGIN_MSG]=None - - if parent: - # instantiate the plugin - _plugins[className.lower()][PLUGIN_INSTANCE]=module.__dict__[className](parent) - else: - _plugins[className.lower()][PLUGIN_INSTANCE]=None - return _plugins[className.lower()][PLUGIN_INSTANCE] - + """Constructs a plugin.""" + global _plugins + entry=_plugins[className.lower()] + mod=entry[PLUGIN_MODULE] + # ensure we get the latest version + try: + try: + sys.modules[mod] + reimport=True + except: + reimport=False + + if reimport: + reload(sys.modules[mod]) + else: + module=__import__(mod, globals(), locals(), className, -1) + + except Exception, e: + _plugins[className.lower()][PLUGIN_MSG]=str(e) + _plugins[className.lower()][PLUGIN_INSTANCE]=None + log.important("Failed to load plugin %s: %s %s"%(className, str(type(e)), str(e))) + return None + + module=sys.modules[mod] + _plugins[className.lower()][PLUGIN_MSG]=None + + if parent: + # instantiate the plugin + _plugins[className.lower()][PLUGIN_INSTANCE]=module.__dict__[className](parent) + else: + _plugins[className.lower()][PLUGIN_INSTANCE]=None + return _plugins[className.lower()][PLUGIN_INSTANCE] + def listPlugins(): - """Get the list of plugins available as { className => [mod, className, instance, msg] }.""" - global _plugins - return _plugins + """Get the list of plugins available as { className => [mod, className, instance, msg] }.""" + global _plugins + return _plugins loadPlugins() |