song: raise KeyError instead of returning empty string
[nephilim.git] / nephilim / song.py
blob6cf0a6b8d840a8bec1ea2631b314585035bd1152
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
21 from common import sec2min
23 class Song(dict):
24 """Song provides a dictionary-like wrapper around song metadata.
25 Its instances _shouldn't_ be stored, use SongRef or PlaylistEntryRef for that.
26 Access '?tag' to get always at least an empty string. """
28 def __init__(self, data):
29 for tag in data:
30 if isinstance(data[tag], list):
31 data[tag] = ','.join(data[tag])
33 dict.__init__(self, data)
35 def __eq__(self, other):
36 if not isinstance(other, Song):
37 return NotImplemented
38 return self['file'] == other['file']
40 def __ne__(self, other):
41 if not isinstance(other, Song):
42 return NotImplemented
43 return self['file'] != other['file']
45 def __getitem__(self, key):
46 try:
47 return dict.__getitem__(self, key)
48 except KeyError:
49 if key and key[0] == '?':
50 key = key[1:]
51 return self[key] if key in self else ''
52 if key == 'tracknum':
53 try:
54 return int(self['track'])
55 except ValueError:
56 try:
57 return int(self['track'].split('/')[0])
58 except ValueError:
59 return 0
60 elif key == 'length':
61 return sec2min(int(self['time']))
62 elif key == 'id':
63 return '-1'
64 elif key == 'title':
65 return self['file']
66 elif key == 'albumartist':
67 return self['artist']
68 elif key == 'songdir':
69 return os.path.dirname(self['file'])
71 raise KeyError
73 def __contains__(self, item):
74 if dict.__contains__(self, item):
75 return True
76 try:
77 self[item]
78 return True
79 except KeyError:
80 return False
82 def __nonzero__(self):
83 return bool(self['file'])
85 def expand_tags(self, str):
86 """Expands tags in form $tag in str."""
87 ret = str
88 ret = ret.replace('$title', self['title']) #to ensure that it is set to at least filename
89 for tag in self.keys() + ['tracknum', 'length', 'id']:
90 ret = ret.replace('$' + tag, unicode(self[tag]))
91 ret = ret.replace('$songdir', os.path.dirname(self['file']))
92 return ret
94 class SongRef:
95 """SongRef stores only a reference to the song (uri) instead
96 of full metadata to conserve memory. Song's tags can be accessed
97 as in Song, but it requires a call to MPD for each tag. """
98 path = None
99 mpclient = None
101 def __init__(self, mpclient, path):
102 self.mpclient = mpclient
103 self.path = path
105 def __getitem__(self, key):
106 return self.song()[key]
108 def __nonzero__(self):
109 return bool(self.path)
111 def song(self):
112 try:
113 return Song(self.mpclient.find('file', self.path)[0])
114 except IndexError:
115 return Song({})
117 class PlaylistEntryRef:
118 """This class stores a reference to a playlist entry instead of full
119 metadata to conserve memory. Song's tags can be accessed
120 as in Song, but it requires a call to MPD for each tag. """
121 plid = None
122 mpclient = None
124 def __init__(self, mpclient, plid):
125 self.mpclient = mpclient
126 self.plid = plid
128 def __getitem__(self, key):
129 return self.plid if key == 'id' else self.song()[key]
131 def __nonzero__(self):
132 return self.plid != '-1'
134 def song(self):
135 try:
136 return self.mpclient.playlistid(self.plid)
137 except IndexError:
138 return Song({})