update TODO
[nephilim.git] / nephilim / song.py
blob0cf3b5cbdbb076f98c28597a04a7b2aceb68b127
2 # Copyright (C) 2009 Anton Khirnov <wyskas@gmail.com>
4 # Nephilim is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # Nephilim is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Nephilim. If not, see <http://www.gnu.org/licenses/>.
18 from PyQt4 import QtCore
19 import os
20 from string import Template
22 from common import sec2min
24 class Song(dict):
25 """Song provides a dictionary-like wrapper around song metadata.
26 Its instances _shouldn't_ be stored, use SongRef or PlaylistEntryRef for that.
27 Access '?tag' to get always at least an empty string. """
29 def __init__(self, data = {}):
30 dict.__init__(self)
31 for key in data:
32 dict.__setitem__(self, key.lower(), data[key])
33 if not 'file' in self:
34 self['file'] = ''
36 def __eq__(self, other):
37 if not isinstance(other, Song):
38 return NotImplemented
39 return self['file'] == other['file']
41 def __ne__(self, other):
42 if not isinstance(other, Song):
43 return NotImplemented
44 return self['file'] != other['file']
46 def __getitem__(self, key):
47 key = key.lower()
48 try:
49 return dict.__getitem__(self, key)
50 except KeyError:
51 if key and key[0] == '?':
52 key = key[1:]
53 return self[key] if key in self else ''
54 if key == 'tracknum':
55 try:
56 return int(self['track'])
57 except ValueError:
58 try:
59 return int(self['track'].split('/')[0])
60 except ValueError:
61 return 0
62 elif key == 'length':
63 return sec2min(int(self['time']))
64 elif key == 'id':
65 return '-1'
66 elif key == 'title':
67 return self['file']
68 elif key == 'albumartist':
69 return self['artist']
70 elif key == 'songdir':
71 return os.path.dirname(self['file'])
73 raise KeyError
75 def __contains__(self, item):
76 if dict.__contains__(self, item.lower()):
77 return True
78 try:
79 self[item]
80 return True
81 except KeyError:
82 return False
84 def __nonzero__(self):
85 return bool(self['file'])
87 def expand_tags(self, s):
88 """Expands tags in form ${tag} in string s."""
89 ret = Template(s)
90 ret = ret.safe_substitute(self)
91 return ret
93 class SongRef:
94 """SongRef stores only a reference to the song (uri) instead
95 of full metadata to conserve memory. Song's tags can be accessed
96 as in Song, but it requires a call to MPD for each tag. """
97 path = None
98 mpclient = None
100 def __init__(self, mpclient, path):
101 self.mpclient = mpclient
102 self.path = path
104 def __getitem__(self, key):
105 return self.song()[key]
107 def __nonzero__(self):
108 return bool(self.path)
110 def song(self):
111 return list(self.mpclient.find_sync('file', self.path))[0]
113 class PlaylistEntryRef:
114 """This class stores a reference to a playlist entry instead of full
115 metadata to conserve memory. Song's tags can be accessed
116 as in Song, but it requires a call to MPD for each tag. """
117 plid = None
118 mpclient = None
120 def __init__(self, mpclient, plid):
121 self.mpclient = mpclient
122 self.plid = plid
124 def __getitem__(self, key):
125 return self.plid if key == 'id' else self.song()[key]
127 def __nonzero__(self):
128 return self.plid != '-1'
130 def song(self):
131 return self.mpclient.get_plist_song(self.plid)