summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <wyskas@gmail.com>2009-08-24 20:46:27 +0200
committerAnton Khirnov <wyskas@gmail.com>2009-08-24 20:46:27 +0200
commite4f46487145be0bbcd2aa278388a1b4b3ef78f54 (patch)
tree4e0170664bdc651c782601544572170d20090239
parente7c01e7b53f02b24e6938d945e84453537f3fa31 (diff)
song: convert to a subclass of dict.
also don't store whole song in Library and Playlist, this saves a significant amount of memory.
-rw-r--r--nephilim/common.py6
-rw-r--r--nephilim/mpclient.py14
-rw-r--r--nephilim/plugins/AlbumCover.py8
-rw-r--r--nephilim/plugins/Library.py18
-rw-r--r--nephilim/plugins/Lyrics.py10
-rw-r--r--nephilim/plugins/Playlist.py20
-rw-r--r--nephilim/plugins/Songinfo.py2
-rw-r--r--nephilim/song.py136
-rw-r--r--nephilim/winMain.py4
9 files changed, 125 insertions, 93 deletions
diff --git a/nephilim/common.py b/nephilim/common.py
index 5c12225..1249ec7 100644
--- a/nephilim/common.py
+++ b/nephilim/common.py
@@ -72,10 +72,10 @@ def expand_tags(str, expanders):
def generate_metadata_path(song, dir_tag, file_tag):
"""Generate dirname and (db files only) full file path for reading/writing metadata files
(cover, lyrics) from $tags in dir/filename."""
- if QtCore.QDir.isAbsolutePath(song.filepath()):
- dirname = os.path.dirname(song.filepath())
+ if QtCore.QDir.isAbsolutePath(song['file']):
+ dirname = os.path.dirname(song['file'])
filepath = ''
- elif '://' in song.filepath(): # we are streaming
+ elif '://' in song['file']: # we are streaming
dirname = ''
filepath = ''
else:
diff --git a/nephilim/mpclient.py b/nephilim/mpclient.py
index 35be3f5..bd137ea 100644
--- a/nephilim/mpclient.py
+++ b/nephilim/mpclient.py
@@ -21,7 +21,7 @@ import mpd
import socket
import logging
-from song import Song
+from song import Song, SongRef, PlaylistEntryRef
class MPClient(QtCore.QObject):
"""This class offers another layer above pympd, with usefull events."""
@@ -45,7 +45,7 @@ class MPClient(QtCore.QObject):
# SIGNALS
connect_changed = QtCore.pyqtSignal(bool)
db_updated = QtCore.pyqtSignal()
- song_changed = QtCore.pyqtSignal(str)
+ song_changed = QtCore.pyqtSignal(object)
time_changed = QtCore.pyqtSignal(int)
state_changed = QtCore.pyqtSignal(str)
volume_changed = QtCore.pyqtSignal(int)
@@ -87,7 +87,7 @@ class MPClient(QtCore.QObject):
self.logger.error('Don\'t have MPD read permission, diconnecting.')
return self.disconnect_mpd()
- self._update_current_song()
+ self.__update_current_song()
self._db_update = self.stats()['db_update']
self.emit(QtCore.SIGNAL('connected')) #should be removed
@@ -139,7 +139,7 @@ class MPClient(QtCore.QObject):
def status(self):
"""Get current MPD status."""
return self._status
- def playlist(self):
+ def playlistinfo(self):
"""Returns a list of songs in current playlist."""
self.logger.info('Listing current playlist.')
if not self.__check_command_ok('playlistinfo'):
@@ -368,7 +368,7 @@ class MPClient(QtCore.QObject):
return map(lambda entry: Song(entry)
, filter(lambda entry: not('directory' in entry), array)
)
- def _update_current_song(self):
+ def __update_current_song(self):
"""Update the current song."""
song = self._retrieve(self._client.currentsong)
if not song:
@@ -424,10 +424,10 @@ class MPClient(QtCore.QObject):
if not self._status:
return self.disconnect_mpd()
- self._update_current_song()
+ self.__update_current_song()
if self._status['songid'] != old_status['songid']:
- self.song_changed.emit(self._status['songid'])
+ self.song_changed.emit(PlaylistEntryRef(self, self._status['songid']))
if self._status['time'] != old_status['time']:
self.time_changed.emit(self._status['time'])
diff --git a/nephilim/plugins/AlbumCover.py b/nephilim/plugins/AlbumCover.py
index db4d6f8..01aa333 100644
--- a/nephilim/plugins/AlbumCover.py
+++ b/nephilim/plugins/AlbumCover.py
@@ -221,9 +221,9 @@ class AlbumCover(Plugin):
url = QtCore.QUrl('http://ws.audioscrobbler.com/2.0/')
url.setQueryItems([('api_key', 'c325945c67b3e8327e01e3afb7cdcf35'),
('method', 'album.getInfo'),
- ('artist', song.artist()),
- ('album', song.album()),
- ('mbid', song.tag('MUSICBRAINZ_ALBUMID'))])
+ ('artist', song['artist']),
+ ('album', song['album']),
+ ('mbid', song['MUSICBRAINZ_ALBUMID'])])
self.fetch2(song, url)
self.rep.finished.connect(self.__handle_search_res)
@@ -390,7 +390,7 @@ class AlbumCover(Plugin):
self.__abort_fetch()
file = QtGui.QFileDialog.getOpenFileName(None,
- 'Select album cover for %s - %s'%(song.artist(), song.album()),
+ 'Select album cover for %s - %s'%(song['artist'], song['album']),
self.__cover_dir, '')
if not file:
return
diff --git a/nephilim/plugins/Library.py b/nephilim/plugins/Library.py
index 74c5464..50f85a3 100644
--- a/nephilim/plugins/Library.py
+++ b/nephilim/plugins/Library.py
@@ -83,6 +83,11 @@ class LibraryWidget(QtGui.QWidget):
plugin = None
logger = None
+ class LibrarySongItem(QtGui.QStandardItem):
+ # public
+ "Song path"
+ path = None
+
class LibraryModel(QtGui.QStandardItemModel):
def fill(self, songs, mode):
self.clear()
@@ -91,7 +96,7 @@ class LibraryWidget(QtGui.QWidget):
for song in songs:
cur_item = tree
for part in mode.split('/'):
- tag = song.tag(part)
+ tag = song[part]
if isinstance(tag, list):
tag = tag[0] #FIXME hack to make songs with multiple genres work.
if not tag:
@@ -104,11 +109,10 @@ class LibraryWidget(QtGui.QWidget):
cur_item[1].appendRow(it)
cur_item[0][tag] = [{}, it]
cur_item = cur_item[0][tag]
- it = QtGui.QStandardItem('%s%02d %s'%(song.tag('disc') + '/' if song.tag('disc') else '',
- song.track() if song.track() else 0,
- song.title() if song.title() else song.filepath()))
+ it = LibraryWidget.LibrarySongItem('%s%02d %s'%(song['disc'] + '/' if 'disc' in song else '',
+ song['tracknum'], song['title']))
+ it.path = song['file']
it.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
- it.setData(QVariant(song), QtCore.Qt.UserRole)
cur_item[1].appendRow(it)
self.sort(0, QtCore.Qt.AscendingOrder)
@@ -196,8 +200,8 @@ class LibraryWidget(QtGui.QWidget):
self.plugin.mpclient.add(paths)
def item_to_playlist(self, item, add_queue):
- if not item.hasChildren():
- add_queue.append(item.data(QtCore.Qt.UserRole).toPyObject().filepath())
+ if isinstance(item, self.LibrarySongItem):
+ add_queue.append(item.path)
else:
for i in range(item.rowCount()):
self.item_to_playlist(item.child(i), add_queue)
diff --git a/nephilim/plugins/Lyrics.py b/nephilim/plugins/Lyrics.py
index 9217df8..3b6224f 100644
--- a/nephilim/plugins/Lyrics.py
+++ b/nephilim/plugins/Lyrics.py
@@ -91,7 +91,7 @@ class LyricsWidget(QtGui.QWidget):
self.__text_view.clear()
self.__label.setText('<b>%s</b> by <u>%s</u> on <u>%s</u>'\
- %(song.title(), song.artist(), song.album()))
+ %(song['title'], song['artist'], song['album']))
if lyrics:
self.logger.info('Setting new lyrics.')
self.__text_view.insertPlainText(lyrics.decode('utf-8'))
@@ -147,7 +147,7 @@ class Lyrics(Plugin):
def fetch(self, song):
url = QtCore.QUrl('http://lyricwiki.org/api.php')
- url.setQueryItems([('func', 'getArtist'), ('artist', song.artist()),
+ url.setQueryItems([('func', 'getArtist'), ('artist', song['artist']),
('fmt', 'xml')])
self.fetch2(song, url)
self.rep.finished.connect(self.__handle_artist_res)
@@ -167,7 +167,7 @@ class Lyrics(Plugin):
url = QtCore.QUrl('http://lyricwiki.org/api.php')
url.setQueryItems([('func', 'getSong'), ('artist', artist),
- ('song', self.song.title()), ('fmt', 'xml')])
+ ('song', self.song['title']), ('fmt', 'xml')])
self.rep = self.nam.get(QtNetwork.QNetworkRequest(url))
self.rep.finished.connect(self.__handle_search_res)
@@ -215,7 +215,7 @@ class Lyrics(Plugin):
def fetch(self, song):
url = QtCore.QUrl('http://www.animelyrics.com/search.php')
- url.setQueryItems([('t', 'performer'), ('q', song.artist())])
+ url.setQueryItems([('t', 'performer'), ('q', song['artist'])])
self.fetch2(song, url)
self.rep.finished.connect(self.__handle_search_res)
@@ -229,7 +229,7 @@ class Lyrics(Plugin):
url = None
for elem in tree.iterfind('.//a'):
- if ('href' in elem.attrib) and elem.text and (self.song.title() in elem.text):
+ if ('href' in elem.attrib) and elem.text and (self.song['title'] in elem.text):
url = QtCore.QUrl('http://www.animelyrics.com/%s'%elem.get('href'))
if not url:
diff --git a/nephilim/plugins/Playlist.py b/nephilim/plugins/Playlist.py
index 44d7a40..d8147bc 100644
--- a/nephilim/plugins/Playlist.py
+++ b/nephilim/plugins/Playlist.py
@@ -44,6 +44,14 @@ class PlaylistWidget(QtGui.QWidget):
plugin = None
playlist = None
+ class PlaylistSongItem(QtGui.QTreeWidgetItem):
+ # public
+ id = -1
+
+ def __init__(self, id):
+ QtGui.QTreeWidgetItem.__init__(self)
+ self.id = id
+
def __init__(self, plugin):
QtGui.QWidget.__init__(self)
self.plugin = plugin
@@ -55,7 +63,6 @@ class PlaylistWidget(QtGui.QWidget):
self.layout().setMargin(0)
self.layout().addWidget(self.playlist)
-
class Playlist(QtGui.QTreeWidget):
song = None
plugin = None
@@ -80,23 +87,22 @@ class PlaylistWidget(QtGui.QWidget):
self.plugin.settings.setValue(self.plugin.name + '/header_state', QVariant(self.header().saveState()))
def _song_activated(self, item):
- self.plugin.mpclient.play(item.data(0, QtCore.Qt.UserRole).toPyObject().id())
+ self.plugin.mpclient.play(item.id)
def fill(self):
columns = self.plugin.settings.value(self.plugin.name + '/columns').toStringList()
self.clear()
- for song in self.plugin.mpclient.playlist():
- item = QtGui.QTreeWidgetItem()
+ for song in self.plugin.mpclient.playlistinfo():
+ item = PlaylistWidget.PlaylistSongItem(song['id'])
for i in range(len(columns)):
- item.setText(i, unicode(song.tag(str(columns[i]))))
- item.setData(0, QtCore.Qt.UserRole, QVariant(song))
+ item.setText(i, unicode(song[str(columns[i])]))
self.addTopLevelItem(item)
def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Delete):
ids = []
for item in self.selectedItems():
- ids.append(item.data(0, QtCore.Qt.UserRole).toPyObject().id())
+ ids.append(item.id)
self.plugin.mpclient.delete(ids)
else:
diff --git a/nephilim/plugins/Songinfo.py b/nephilim/plugins/Songinfo.py
index cad5dd0..144033b 100644
--- a/nephilim/plugins/Songinfo.py
+++ b/nephilim/plugins/Songinfo.py
@@ -105,7 +105,7 @@ class Songinfo(Plugin):
song = self.mpclient.current_song()
for tag in self.__tags:
- metadata[tag] = song.tag(str(tag), '') if song else '' #XXX i don't like the explicit conversion to python string
+ metadata[tag] = song[str(tag)] if song else '' #XXX i don't like the explicit conversion to python string
self.o.set_metadata(metadata)
class SonginfoWidget(QtGui.QWidget):
diff --git a/nephilim/song.py b/nephilim/song.py
index 0f1a627..c9c1145 100644
--- a/nephilim/song.py
+++ b/nephilim/song.py
@@ -1,5 +1,4 @@
#
-# Copyright (C) 2008 jerous <jerous@gmail.com>
# Copyright (C) 2009 Anton Khirnov <wyskas@gmail.com>
#
# Nephilim is free software: you can redistribute it and/or modify
@@ -21,80 +20,103 @@ import os
from common import sec2min
-class Song:
- """The Song class offers an abstraction of a song."""
- _data = None
+class Song(dict):
+ """Song provides a dictionary-like wrapper around song metadata.
+ Its instances _shouldn't_ be stored, use SongRef or PlaylistEntryRef for that."""
def __init__(self, data):
- self._data = data
- if 'track' in self._data:
- try:
- self._data['track'] = int(self._data['track'])
- except ValueError:
- # OGG tracks come in #track/#total format
- try:
- self._data['track'] = int(self._data['track'].split('/')[0])
- except ValueError:
- self._data['track'] = 0
+ # decode all strings to unicode
+ for tag in data:
+ if isinstance(data[tag], str):
+ data[tag] = data[tag].decode('utf-8')
- # ensure all string-values are utf-8 encoded
- for tag in self._data.keys():
- if isinstance(self._data[tag], str):
- self._data[tag] = self._data[tag].decode('utf-8')
- if 'time' in self._data:
- self._data['time'] = int(self._data['time'])
- self._data['timems'] = '%i:%i'%(self._data['time'] / 60, self._data['time'] % 60)
- self._data['length'] = sec2min(self._data['time'])
+ dict.__init__(self, data)
def __eq__(self, other):
if not isinstance(other, Song):
return NotImplemented
- return self._data['file'] == other._data['file']
+ return self['file'] == other['file']
def __ne__(self, other):
if not isinstance(other, Song):
return NotImplemented
- return self._data['file'] != other._data['file']
+ return self['file'] != other['file']
- def id(self):
- """Get the song's playlist ID. (-1 if not in playlist)."""
- return self.tag('id', '-1')
+ def __getitem__(self, key):
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ if key == 'tracknum':
+ try:
+ return int(self['track'])
+ except ValueError:
+ try:
+ return int(self['track'].split('/')[0])
+ except ValueError:
+ return 0
+ elif key == 'length':
+ return sec2min(int(self['time']))
+ elif key == 'id':
+ return '-1'
+ elif key == 'title':
+ return self['file']
+ return ''
+
+ def __nonzero__(self):
+ return bool(self['file'])
- def title(self):
- """Get the song's title (or filename if it has no title)."""
- return self.tag('title', self._data['file'])
+ def expand_tags(self, str):
+ """Expands tags in form $tag in str."""
+ ret = str
+ ret = ret.replace('$title', self['title']) #to ensure that it is set to at least filename
+ for tag in self.keys() + ['tracknum', 'length', 'id']:
+ ret = ret.replace('$' + tag, unicode(self[tag]))
+ ret = ret.replace('$songdir', os.path.dirname(self['file']))
+ return ret
- def artist(self):
- """Get the song's artist."""
- return self.tag('artist')
- def track(self):
- """Get the song's track number."""
- return self.tag('track')
+class SongRef:
+ """SongRef stores only a reference to the song (uri) instead
+ of full metadata to conserve memory. Song's tags can be accessed
+ as in Song, but it requires a call to MPD for each tag. """
+ path = None
+ mpclient = None
- def album(self):
- """Get the album."""
- return self.tag('album')
+ def __init__(self, mpclient, path):
+ self.mpclient = mpclient
+ self.path = path
- def filepath(self):
- """Get the filepath."""
- return self._data['file']
+ def __getitem__(self, key):
+ return self.song()[key]
- def tag(self, tag, default=''):
- """Get a tag. If it doesn't exist, return default."""
- if tag in self._data:
- return self._data[tag]
- if tag=='song':
- return self.__str__()
+ def __nonzero__(self):
+ return bool(self.path)
- return default
+ def song(self):
+ try:
+ return Song(self.mpclient.find('file', self.path)[0])
+ except IndexError:
+ return Song({})
- def expand_tags(self, str):
- """Expands tags in form $tag in str."""
- ret = str
- ret = ret.replace('$title', self.title()) #to ensure that it is set to at least filename
- for tag in self._data:
- ret = ret.replace('$' + tag, unicode(self._data[tag]))
- ret = ret.replace('$songdir', os.path.dirname(self.filepath()))
- return ret
+class PlaylistEntryRef:
+ """This class stores a reference to a playlist entry instead of full
+ metadata to conserve memory. Song's tags can be accessed
+ as in Song, but it requires a call to MPD for each tag. """
+ id = None
+ mpclient = None
+
+ def __init__(self, mpclient, id):
+ self.mpclient = mpclient
+ self.id = id
+
+ def __getitem__(self, key):
+ return self.song()[key]
+
+ def __nonzero__(self):
+ return self.id != '-1'
+ def song(self):
+ try:
+ return Song(self.mpclient.playlistid(id)[0])
+ except IndexError:
+ return Song({})
diff --git a/nephilim/winMain.py b/nephilim/winMain.py
index 31ab17f..728db42 100644
--- a/nephilim/winMain.py
+++ b/nephilim/winMain.py
@@ -178,8 +178,8 @@ class winMain(QtGui.QMainWindow):
state = self.mpclient.status()['state']
state = 'playing' if state == 'play' else 'paused' if state == 'pause' else 'stopped'
if song:
- self.setWindowTitle('%s by %s - %s [%s]'%(song.title(), song.artist(), APPNAME, state))
- self.__statuslabel.setText('%s by %s on %s [%s]'%(song.title(), song.artist(),song.album(), state))
+ self.setWindowTitle('%s by %s - %s [%s]'%(song['title'], song['artist'], APPNAME, state))
+ self.__statuslabel.setText('%s by %s on %s [%s]'%(song['title'], song['artist'],song['album'], state))
else:
self.setWindowTitle(APPNAME)
self.__statuslabel.setText('')