Tentative fix for LRUcache bug (with Python 2.7.11?), reported by
[pyTivo/wmcbrine.git] / mutagen / easymp4.py
blob49ba782e82c033b12daf0f17d97673b4384e819d
1 import mutagen.mp4
3 from mutagen import Metadata
4 from mutagen._util import DictMixin, dict_match, utf8
5 from mutagen.mp4 import MP4, MP4Tags, error, delete
7 __all__ = ["EasyMP4Tags", "EasyMP4", "delete", "error"]
9 class EasyMP4KeyError(error, KeyError, ValueError):
10 pass
12 class EasyMP4Tags(DictMixin, Metadata):
13 """A file with MPEG-4 iTunes metadata.
15 Like Vorbis comments, EasyMP4Tags keys are case-insensitive ASCII
16 strings, and values are a list of Unicode strings (and these lists
17 are always of length 0 or 1). If you need access to the full MP4
18 metadata feature set, you should use MP4, not EasyMP4.
19 """
21 Set = {}
22 Get = {}
23 Delete = {}
24 List = {}
26 def __init__(self, *args, **kwargs):
27 self.__mp4 = MP4Tags(*args, **kwargs)
28 self.load = self.__mp4.load
29 self.save = self.__mp4.save
30 self.delete = self.__mp4.delete
32 filename = property(lambda s: s.__mp4.filename,
33 lambda s, fn: setattr(s.__mp4, 'filename', fn))
35 def RegisterKey(cls, key,
36 getter=None, setter=None, deleter=None, lister=None):
37 """Register a new key mapping.
39 A key mapping is four functions, a getter, setter, deleter,
40 and lister. The key may be either a string or a glob pattern.
42 The getter, deleted, and lister receive an MP4Tags instance
43 and the requested key name. The setter also receives the
44 desired value, which will be a list of strings.
46 The getter, setter, and deleter are used to implement __getitem__,
47 __setitem__, and __delitem__.
49 The lister is used to implement keys(). It should return a
50 list of keys that are actually in the MP4 instance, provided
51 by its associated getter.
52 """
53 key = key.lower()
54 if getter is not None:
55 cls.Get[key] = getter
56 if setter is not None:
57 cls.Set[key] = setter
58 if deleter is not None:
59 cls.Delete[key] = deleter
60 if lister is not None:
61 cls.List[key] = lister
62 RegisterKey = classmethod(RegisterKey)
64 def RegisterTextKey(cls, key, atomid):
65 """Register a text key.
67 If the key you need to register is a simple one-to-one mapping
68 of MP4 atom name to EasyMP4Tags key, then you can use this
69 function:
70 EasyMP4Tags.RegisterTextKey("artist", "\xa9ART")
71 """
72 def getter(tags, key):
73 return tags[atomid]
75 def setter(tags, key, value):
76 tags[atomid] = value
78 def deleter(tags, key):
79 del(tags[atomid])
81 cls.RegisterKey(key, getter, setter, deleter)
82 RegisterTextKey = classmethod(RegisterTextKey)
84 def RegisterIntKey(cls, key, atomid, min_value=0, max_value=2**16-1):
85 """Register a scalar integer key.
86 """
88 def getter(tags, key):
89 return map(unicode, tags[atomid])
91 def setter(tags, key, value):
92 clamp = lambda x: int(min(max(min_value, x), max_value))
93 tags[atomid] = map(clamp, map(int, value))
95 def deleter(tags, key):
96 del(tags[atomid])
98 cls.RegisterKey(key, getter, setter, deleter)
99 RegisterIntKey = classmethod(RegisterIntKey)
101 def RegisterIntPairKey(cls, key, atomid, min_value=0, max_value=2**16-1):
102 def getter(tags, key):
103 ret = []
104 for (track, total) in tags[atomid]:
105 if total:
106 ret.append(u"%d/%d" % (track, total))
107 else:
108 ret.append(unicode(track))
109 return ret
111 def setter(tags, key, value):
112 clamp = lambda x: int(min(max(min_value, x), max_value))
113 data = []
114 for v in value:
115 try:
116 tracks, total = v.split("/")
117 tracks = clamp(int(tracks))
118 total = clamp(int(total))
119 except (ValueError, TypeError):
120 tracks = clamp(int(v))
121 total = min_value
122 data.append((tracks, total))
123 tags[atomid] = data
125 def deleter(tags, key):
126 del(tags[atomid])
128 cls.RegisterKey(key, getter, setter, deleter)
129 RegisterIntPairKey = classmethod(RegisterIntPairKey)
131 def RegisterFreeformKey(cls, key, name, mean="com.apple.iTunes"):
132 """Register a text key.
134 If the key you need to register is a simple one-to-one mapping
135 of MP4 freeform atom (----) and name to EasyMP4Tags key, then
136 you can use this function:
137 EasyMP4Tags.RegisterFreeformKey(
138 "musicbrainz_artistid", "MusicBrainz Artist Id")
140 atomid = "----:%s:%s" % (mean, name)
142 def getter(tags, key):
143 return [s.decode("utf-8", "replace") for s in tags[atomid]]
145 def setter(tags, key, value):
146 tags[atomid] = map(utf8, value)
148 def deleter(tags, key):
149 del(tags[atomid])
151 cls.RegisterKey(key, getter, setter, deleter)
152 RegisterFreeformKey = classmethod(RegisterFreeformKey)
154 def __getitem__(self, key):
155 key = key.lower()
156 func = dict_match(self.Get, key)
157 if func is not None:
158 return func(self.__mp4, key)
159 else:
160 raise EasyMP4KeyError("%r is not a valid key" % key)
162 def __setitem__(self, key, value):
163 key = key.lower()
164 if isinstance(value, basestring):
165 value = [value]
166 func = dict_match(self.Set, key)
167 if func is not None:
168 return func(self.__mp4, key, value)
169 else:
170 raise EasyMP4KeyError("%r is not a valid key" % key)
172 def __delitem__(self, key):
173 key = key.lower()
174 func = dict_match(self.Delete, key)
175 if func is not None:
176 return func(self.__mp4, key)
177 else:
178 raise EasyMP4KeyError("%r is not a valid key" % key)
180 def keys(self):
181 keys = []
182 for key in self.Get.keys():
183 if key in self.List:
184 keys.extend(self.List[key](self.__mp4, key))
185 elif key in self:
186 keys.append(key)
187 return keys
189 def pprint(self):
190 """Print tag key=value pairs."""
191 strings = []
192 for key in sorted(self.keys()):
193 values = self[key]
194 for value in values:
195 strings.append("%s=%s" % (key, value))
196 return "\n".join(strings)
198 for atomid, key in {
199 '\xa9nam': 'title',
200 '\xa9alb': 'album',
201 '\xa9ART': 'artist',
202 'aART': 'albumartist',
203 '\xa9day': 'date',
204 '\xa9cmt': 'comment',
205 'desc': 'description',
206 '\xa9grp': 'grouping',
207 '\xa9gen': 'genre',
208 'cprt': 'copyright',
209 'soal': 'albumsort',
210 'soaa': 'albumartistsort',
211 'soar': 'artistsort',
212 'sonm': 'titlesort',
213 'soco': 'composersort',
214 }.items():
215 EasyMP4Tags.RegisterTextKey(key, atomid)
217 for name, key in {
218 'MusicBrainz Artist Id': 'musicbrainz_artistid',
219 'MusicBrainz Track Id': 'musicbrainz_trackid',
220 'MusicBrainz Album Id': 'musicbrainz_albumid',
221 'MusicBrainz Album Artist Id': 'musicbrainz_albumartistid',
222 'MusicIP PUID': 'musicip_puid',
223 'MusicBrainz Album Status': 'musicbrainz_albumstatus',
224 'MusicBrainz Album Type': 'musicbrainz_albumtype',
225 'MusicBrainz Release Country': 'releasecountry',
226 }.items():
227 EasyMP4Tags.RegisterFreeformKey(key, name)
229 for name, key in {
230 "tmpo": "bpm",
231 }.items():
232 EasyMP4Tags.RegisterIntKey(key, name)
234 for name, key in {
235 "trkn": "tracknumber",
236 "disk": "discnumber",
237 }.items():
238 EasyMP4Tags.RegisterIntPairKey(key, name)
240 class EasyMP4(MP4):
241 """Like MP4, but uses EasyMP4Tags for tags."""
242 MP4Tags = EasyMP4Tags
244 Get = EasyMP4Tags.Get
245 Set = EasyMP4Tags.Set
246 Delete = EasyMP4Tags.Delete
247 List = EasyMP4Tags.List
248 RegisterTextKey = EasyMP4Tags.RegisterTextKey
249 RegisterKey = EasyMP4Tags.RegisterKey