common: split MetadataFetcher into its own file
[nephilim.git] / nephilim / song.py
blobeb5fedb2585dcfed47da1270a378d0943aaec083
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 """
26 Song provides a dictionary-like wrapper around song metadata.
27 Its instances are generated by MPClient, don't instantiate it
28 directly.
29 Its instances _shouldn't_ be stored, use SongRef or PlaylistEntryRef for that.
30 Access '?tag' to get always at least an empty string.
31 """
33 #### PUBLIC ####
34 def __init__(self, data = {}):
35 dict.__init__(self)
36 for key in data:
37 dict.__setitem__(self, key.lower(), data[key])
38 if not 'file' in self:
39 self['file'] = ''
40 def __eq__(self, other):
41 if not isinstance(other, Song):
42 return NotImplemented
43 return self['file'] == other['file']
44 def __ne__(self, other):
45 if not isinstance(other, Song):
46 return NotImplemented
47 return self['file'] != other['file']
48 def __getitem__(self, key):
49 key = key.lower()
50 try:
51 return dict.__getitem__(self, key)
52 except KeyError:
53 if key and key[0] == '?':
54 key = key[1:]
55 return self[key] if key in self else ''
56 if key == 'tracknum':
57 try:
58 return int(self['track'])
59 except ValueError:
60 try:
61 return int(self['track'].split('/')[0])
62 except ValueError:
63 return 0
64 elif key == 'length':
65 return sec2min(int(self['time']))
66 elif key == 'id':
67 return '-1'
68 elif key == 'title':
69 return self['file']
70 elif key == 'albumartist':
71 return self['artist']
72 elif key == 'songdir':
73 return os.path.dirname(self['file'])
75 raise KeyError
76 def __contains__(self, item):
77 if dict.__contains__(self, item.lower()):
78 return True
79 try:
80 self[item]
81 return True
82 except KeyError:
83 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)