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):
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.
26 def __init__(self
, *args
, **kwargs
):
27 self
.__mp
4 = MP4Tags(*args
, **kwargs
)
28 self
.load
= self
.__mp
4.load
29 self
.save
= self
.__mp
4.save
30 self
.delete
= self
.__mp
4.delete
32 filename
= property(lambda s
: s
.__mp
4.filename
,
33 lambda s
, fn
: setattr(s
.__mp
4, '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.
54 if getter
is not None:
56 if setter
is not None:
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
70 EasyMP4Tags.RegisterTextKey("artist", "\xa9ART")
72 def getter(tags
, key
):
75 def setter(tags
, key
, value
):
78 def deleter(tags
, key
):
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.
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
):
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
):
104 for (track
, total
) in tags
[atomid
]:
106 ret
.append(u
"%d/%d" % (track
, total
))
108 ret
.append(unicode(track
))
111 def setter(tags
, key
, value
):
112 clamp
= lambda x
: int(min(max(min_value
, x
), max_value
))
116 tracks
, total
= v
.split("/")
117 tracks
= clamp(int(tracks
))
118 total
= clamp(int(total
))
119 except (ValueError, TypeError):
120 tracks
= clamp(int(v
))
122 data
.append((tracks
, total
))
125 def deleter(tags
, key
):
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
):
151 cls
.RegisterKey(key
, getter
, setter
, deleter
)
152 RegisterFreeformKey
= classmethod(RegisterFreeformKey
)
154 def __getitem__(self
, key
):
156 func
= dict_match(self
.Get
, key
)
158 return func(self
.__mp
4, key
)
160 raise EasyMP4KeyError("%r is not a valid key" % key
)
162 def __setitem__(self
, key
, value
):
164 if isinstance(value
, basestring
):
166 func
= dict_match(self
.Set
, key
)
168 return func(self
.__mp
4, key
, value
)
170 raise EasyMP4KeyError("%r is not a valid key" % key
)
172 def __delitem__(self
, key
):
174 func
= dict_match(self
.Delete
, key
)
176 return func(self
.__mp
4, key
)
178 raise EasyMP4KeyError("%r is not a valid key" % key
)
182 for key
in self
.Get
.keys():
184 keys
.extend(self
.List
[key
](self
.__mp
4, key
))
190 """Print tag key=value pairs."""
192 for key
in sorted(self
.keys()):
195 strings
.append("%s=%s" % (key
, value
))
196 return "\n".join(strings
)
202 'aART': 'albumartist',
204 '\xa9cmt': 'comment',
205 'desc': 'description',
206 '\xa9grp': 'grouping',
210 'soaa': 'albumartistsort',
211 'soar': 'artistsort',
213 'soco': 'composersort',
215 EasyMP4Tags
.RegisterTextKey(key
, atomid
)
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',
227 EasyMP4Tags
.RegisterFreeformKey(key
, name
)
232 EasyMP4Tags
.RegisterIntKey(key
, name
)
235 "trkn": "tracknumber",
236 "disk": "discnumber",
238 EasyMP4Tags
.RegisterIntPairKey(key
, name
)
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