576
739
idata = f.read(10)
577
try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata)
578
except struct.error: id3, insize = '', -1
741
id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata)
579
744
insize = BitPaddedInt(insize)
580
745
if id3 == 'ID3' and insize >= 0:
581
746
delete_bytes(f, insize + 10, 0)
583
class BitPaddedInt(int):
584
def __new__(cls, value, bits=7, bigendian=True):
585
"Strips 8-bits bits out of every byte"
587
if isinstance(value, (int, long)):
590
bytes.append(value & ((1<<bits)-1))
592
if isinstance(value, str):
593
bytes = [ord(byte) & mask for byte in value]
594
if bigendian: bytes.reverse()
596
for shift, byte in zip(range(0, len(bytes)*bits, bits), bytes):
597
numeric_value += byte << shift
598
if isinstance(numeric_value, long):
599
self = long.__new__(BitPaddedLong, numeric_value)
601
self = int.__new__(BitPaddedInt, numeric_value)
603
self.bigendian = bigendian
606
def as_str(value, bits=7, bigendian=True, width=4):
607
bits = getattr(value, 'bits', bits)
608
bigendian = getattr(value, 'bigendian', bigendian)
613
bytes.append(value & mask)
614
value = value >> bits
615
# PCNT and POPM use growing integers of at least 4 bytes as counters.
616
if width == -1: width = max(4, len(bytes))
617
if len(bytes) > width:
618
raise ValueError, 'Value too wide (%d bytes)' % len(bytes)
619
else: bytes.extend([0] * (width-len(bytes)))
620
if bigendian: bytes.reverse()
621
return ''.join(map(chr, bytes))
622
to_str = staticmethod(as_str)
624
class BitPaddedLong(long):
625
def as_str(value, bits=7, bigendian=True, width=4):
626
return BitPaddedInt.to_str(value, bits, bigendian, width)
627
to_str = staticmethod(as_str)
629
class unsynch(object):
633
append = output.append
639
if val >= '\xE0': raise ValueError('invalid sync-safe string')
640
elif val != '\x00': append(val)
642
if not safe: raise ValueError('string ended unsafe')
643
return ''.join(output)
644
decode = staticmethod(decode)
649
append = output.append
653
if val == '\xFF': safe = False
654
elif val == '\x00' or val >= '\xE0':
661
if not safe: append('\x00')
662
return ''.join(output)
663
encode = staticmethod(encode)
666
def __init__(self, name): self.name = name
667
def __hash__(self): raise TypeError("Spec objects are unhashable")
669
class ByteSpec(Spec):
670
def read(self, frame, data): return ord(data[0]), data[1:]
671
def write(self, frame, value): return chr(value)
672
def validate(self, frame, value): return value
674
class IntegerSpec(Spec):
675
def read(self, frame, data):
676
return int(BitPaddedInt(data, bits=8)), ''
677
def write(self, frame, value):
678
return BitPaddedInt.to_str(value, bits=8, width=-1)
679
def validate(self, frame, value):
682
class SizedIntegerSpec(Spec):
683
def __init__(self, name, size):
684
self.name, self.__sz = name, size
685
def read(self, frame, data):
686
return int(BitPaddedInt(data[:self.__sz], bits=8)), data[self.__sz:]
687
def write(self, frame, value):
688
return BitPaddedInt.to_str(value, bits=8, width=self.__sz)
689
def validate(self, frame, value):
692
class EncodingSpec(ByteSpec):
693
def read(self, frame, data):
694
enc, data = super(EncodingSpec, self).read(frame, data)
695
if enc < 16: return enc, data
696
else: return 0, chr(enc)+data
698
def validate(self, frame, value):
699
if 0 <= value <= 3: return value
700
if value is None: return None
701
raise ValueError, 'Invalid Encoding: %r' % value
703
class StringSpec(Spec):
704
def __init__(self, name, length):
705
super(StringSpec, self).__init__(name)
707
def read(s, frame, data): return data[:s.len], data[s.len:]
708
def write(s, frame, value):
709
if value is None: return '\x00' * s.len
710
else: return (str(value) + '\x00' * s.len)[:s.len]
711
def validate(s, frame, value):
712
if value is None: return None
713
if isinstance(value, basestring) and len(value) == s.len: return value
714
raise ValueError, 'Invalid StringSpec[%d] data: %r' % (s.len, value)
716
class BinaryDataSpec(Spec):
717
def read(self, frame, data): return data, ''
718
def write(self, frame, value): return str(value)
719
def validate(self, frame, value): return str(value)
721
class EncodedTextSpec(Spec):
722
# Okay, seriously. This is private and defined explicitly and
723
# completely by the ID3 specification. You can't just add
724
# encodings here however you want.
725
_encodings = ( ('latin1', '\x00'), ('utf16', '\x00\x00'),
726
('utf_16_be', '\x00\x00'), ('utf8', '\x00') )
728
def read(self, frame, data):
729
enc, term = self._encodings[frame.encoding]
733
data, ret = data.split(term, 1)
738
offset = data.index(term, offset+1)
739
if offset & 1: continue
740
data, ret = data[0:offset], data[offset+2:]; break
741
except ValueError: pass
743
if len(data) < len(term): return u'', ret
744
return data.decode(enc), ret
746
def write(self, frame, value):
747
enc, term = self._encodings[frame.encoding]
748
return value.encode(enc) + term
750
def validate(self, frame, value): return unicode(value)
752
class MultiSpec(Spec):
753
def __init__(self, name, *specs, **kw):
754
super(MultiSpec, self).__init__(name)
756
self.sep = kw.get('sep')
758
def read(self, frame, data):
762
for spec in self.specs:
763
value, data = spec.read(frame, data)
765
if len(self.specs) != 1: values.append(record)
766
else: values.append(record[0])
769
def write(self, frame, value):
771
if len(self.specs) == 1:
773
data.append(self.specs[0].write(frame, v))
776
for v, s in zip(record, self.specs):
777
data.append(s.write(frame, v))
780
def validate(self, frame, value):
781
if value is None: return []
782
if self.sep and isinstance(value, basestring):
783
value = value.split(self.sep)
784
if isinstance(value, list):
785
if len(self.specs) == 1:
786
return [self.specs[0].validate(frame, v) for v in value]
789
[s.validate(frame, v) for (v,s) in zip(val, self.specs)]
791
raise ValueError, 'Invalid MultiSpec data: %r' % value
793
class EncodedNumericTextSpec(EncodedTextSpec): pass
794
class EncodedNumericPartTextSpec(EncodedTextSpec): pass
796
class Latin1TextSpec(EncodedTextSpec):
797
def read(self, frame, data):
798
if '\x00' in data: data, ret = data.split('\x00',1)
800
return data.decode('latin1'), ret
802
def write(self, data, value):
803
return value.encode('latin1') + '\x00'
805
def validate(self, frame, value): return unicode(value)
807
class ID3TimeStamp(object):
808
"""A time stamp in ID3v2 format.
810
This is a restricted form of the ISO 8601 standard; time stamps
813
Or some partial form (YYYY-MM-DD HH, YYYY, etc.).
815
The 'text' attribute contains the raw text data of the time stamp.
819
def __init__(self, text):
820
if isinstance(text, ID3TimeStamp): text = text.text
823
__formats = ['%04d'] + ['%02d'] * 5
824
__seps = ['-', '-', ' ', ':', ':', 'x']
826
parts = [self.year, self.month, self.day,
827
self.hour, self.minute, self.second]
829
for i, part in enumerate(iter(iter(parts).next, None)):
830
pieces.append(self.__formats[i]%part + self.__seps[i])
831
return u''.join(pieces)[:-1]
833
def set_text(self, text, splitre=re.compile('[-T:/.]|\s+')):
834
year, month, day, hour, minute, second = \
835
splitre.split(text + ':::::')[:6]
836
for a in 'year month day hour minute second'.split():
837
try: v = int(locals()[a])
838
except ValueError: v = None
841
text = property(get_text, set_text, doc="ID3v2.4 date and time.")
843
def __str__(self): return self.text
844
def __repr__(self): return repr(self.text)
845
def __cmp__(self, other): return cmp(self.text, other.text)
846
__hash__ = object.__hash__
847
def encode(self, *args): return self.text.encode(*args)
849
class TimeStampSpec(EncodedTextSpec):
850
def read(self, frame, data):
851
value, data = super(TimeStampSpec, self).read(frame, data)
852
return self.validate(frame, value), data
854
def write(self, frame, data):
855
return super(TimeStampSpec, self).write(frame,
856
data.text.replace(' ', 'T'))
858
def validate(self, frame, value):
859
try: return ID3TimeStamp(value)
860
except TypeError: raise ValueError, "Invalid ID3TimeStamp: %r" % value
862
class ChannelSpec(ByteSpec):
863
(OTHER, MASTER, FRONTRIGHT, FRONTLEFT, BACKRIGHT, BACKLEFT, FRONTCENTRE,
864
BACKCENTRE, SUBWOOFER) = range(9)
866
class VolumeAdjustmentSpec(Spec):
867
def read(self, frame, data):
868
value, = unpack('>h', data[0:2])
869
return value/512.0, data[2:]
871
def write(self, frame, value):
872
return pack('>h', int(round(value * 512)))
874
def validate(self, frame, value): return value
876
class VolumePeakSpec(Spec):
877
def read(self, frame, data):
878
# http://bugs.xmms.org/attachment.cgi?id=113&action=view
881
bytes = min(4, (bits + 7) >> 3)
882
# not enough frame data
883
if bytes + 1 > len(data): raise ID3JunkFrameError
884
shift = ((8 - (bits & 7)) & 7) + (4 - bytes) * 8
885
for i in range(1, bytes+1):
889
return (float(peak) / (2**31-1)), data[1+bytes:]
891
def write(self, frame, value):
892
# always write as 16 bits for sanity.
893
return "\x10" + pack('>H', int(round(value * 32768)))
895
def validate(self, frame, value): return value
897
class SynchronizedTextSpec(EncodedTextSpec):
898
def read(self, frame, data):
900
encoding, term = self._encodings[frame.encoding]
904
value_idx = data.index(term)
906
raise ID3JunkFrameError
907
value = data[:value_idx].decode(encoding)
908
if len(data) < value_idx + l + 4:
909
raise ID3JunkFrameError
910
time, = struct.unpack(">I", data[value_idx+l:value_idx+l+4])
911
texts.append((value, time))
912
data = data[value_idx+l+4:]
915
def write(self, frame, value):
917
encoding, term = self._encodings[frame.encoding]
918
for text, time in frame.text:
919
text = text.encode(encoding) + term
920
data.append(text + struct.pack(">I", time))
923
def validate(self, frame, value):
926
class KeyEventSpec(Spec):
927
def read(self, frame, data):
929
while len(data) >= 5:
930
events.append(struct.unpack(">bI", data[:5]))
934
def write(self, frame, value):
935
return "".join([struct.pack(">bI", *event) for event in value])
937
def validate(self, frame, value):
940
class VolumeAdjustmentsSpec(Spec):
941
# Not to be confused with VolumeAdjustmentSpec.
942
def read(self, frame, data):
944
while len(data) >= 4:
945
freq, adj = struct.unpack(">Hh", data[:4])
949
adjustments[freq] = adj
950
adjustments = adjustments.items()
952
return adjustments, data
954
def write(self, frame, value):
956
return "".join([struct.pack(">Hh", int(freq * 2), int(adj * 512))
957
for (freq, adj) in value])
959
def validate(self, frame, value):
962
class ASPIIndexSpec(Spec):
963
def read(self, frame, data):
971
warn("invalid bit count in ASPI (%d)" % frame.b, ID3Warning)
974
indexes = data[:frame.N * size]
975
data = data[frame.N * size:]
976
return list(struct.unpack(">" + format * frame.N, indexes)), data
978
def write(self, frame, values):
979
if frame.b == 16: format = "H"
980
elif frame.b == 8: format = "B"
981
else: raise ValueError("frame.b must be 8 or 16")
982
return struct.pack(">" + format * frame.N, *values)
984
def validate(self, frame, values):
988
"""Fundamental unit of ID3 data.
990
ID3 tags are split into frames. Each frame has a potentially
991
different structure, and so this base class is not very featureful.
994
FLAG23_ALTERTAG = 0x8000
995
FLAG23_ALTERFILE = 0x4000
996
FLAG23_READONLY = 0x2000
997
FLAG23_COMPRESS = 0x0080
998
FLAG23_ENCRYPT = 0x0040
999
FLAG23_GROUP = 0x0020
1001
FLAG24_ALTERTAG = 0x4000
1002
FLAG24_ALTERFILE = 0x2000
1003
FLAG24_READONLY = 0x1000
1004
FLAG24_GROUPID = 0x0040
1005
FLAG24_COMPRESS = 0x0008
1006
FLAG24_ENCRYPT = 0x0004
1007
FLAG24_UNSYNCH = 0x0002
1008
FLAG24_DATALEN = 0x0001
1011
def __init__(self, *args, **kwargs):
1012
if len(args)==1 and len(kwargs)==0 and isinstance(args[0], type(self)):
1014
for checker in self._framespec:
1015
val = checker.validate(self, getattr(other, checker.name))
1016
setattr(self, checker.name, val)
1018
for checker, val in zip(self._framespec, args):
1019
setattr(self, checker.name, checker.validate(self, val))
1020
for checker in self._framespec[len(args):]:
1021
validated = checker.validate(
1022
self, kwargs.get(checker.name, None))
1023
setattr(self, checker.name, validated)
1026
lambda s: s.FrameID,
1027
doc="an internal key used to ensure frame uniqueness in a tag")
1029
lambda s: type(s).__name__,
1030
doc="ID3v2 three or four character frame ID")
1033
"""Python representation of a frame.
1035
The string returned is a valid Python expression to construct
1036
a copy of this frame.
1039
for attr in self._framespec:
1040
kw.append('%s=%r' % (attr.name, getattr(self, attr.name)))
1041
return '%s(%s)' % (type(self).__name__, ', '.join(kw))
1043
def _readData(self, data):
1045
for reader in self._framespec:
1047
try: value, data = reader.read(self, data)
1048
except UnicodeDecodeError:
1049
raise ID3JunkFrameError
1050
else: raise ID3JunkFrameError
1051
setattr(self, reader.name, value)
1052
if data.strip('\x00'):
1053
warn('Leftover data: %s: %r (from %r)' % (
1054
type(self).__name__, data, odata),
1057
def _writeData(self):
1059
for writer in self._framespec:
1060
data.append(writer.write(self, getattr(self, writer.name)))
1061
return ''.join(data)
1064
"""Return a human-readable representation of the frame."""
1065
return "%s=%s" % (type(self).__name__, self._pprint())
1068
return "[unrepresentable data]"
1070
def fromData(cls, id3, tflags, data):
1071
"""Construct this ID3 frame from raw string data."""
1073
if (2,4,0) <= id3.version:
1074
if tflags & (Frame.FLAG24_COMPRESS | Frame.FLAG24_DATALEN):
1075
# The data length int is syncsafe in 2.4 (but not 2.3).
1076
# However, we don't actually need the data length int,
1077
# except to work around a QL 0.12 bug, and in that case
1078
# all we need are the raw bytes.
1079
datalen_bytes = data[:4]
1081
if tflags & Frame.FLAG24_UNSYNCH or id3.f_unsynch:
1082
try: data = unsynch.decode(data)
1083
except ValueError, err:
1085
raise ID3BadUnsynchData, '%s: %r' % (err, data)
1086
if tflags & Frame.FLAG24_ENCRYPT:
1087
raise ID3EncryptionUnsupportedError
1088
if tflags & Frame.FLAG24_COMPRESS:
1089
try: data = data.decode('zlib')
1090
except zlibError, err:
1091
# the initial mutagen that went out with QL 0.12 did not
1092
# write the 4 bytes of uncompressed size. Compensate.
1093
data = datalen_bytes + data
1094
try: data = data.decode('zlib')
1095
except zlibError, err:
1097
raise ID3BadCompressedData, '%s: %r' % (err, data)
1099
elif (2,3,0) <= id3.version:
1100
if tflags & Frame.FLAG23_COMPRESS:
1101
usize, = unpack('>L', data[:4])
1103
if tflags & Frame.FLAG23_ENCRYPT:
1104
raise ID3EncryptionUnsupportedError
1105
if tflags & Frame.FLAG23_COMPRESS:
1106
try: data = data.decode('zlib')
1107
except zlibError, err:
1109
raise ID3BadCompressedData, '%s: %r' % (err, data)
1112
frame._rawdata = data
1113
frame._flags = tflags
1114
frame._readData(data)
1116
fromData = classmethod(fromData)
1119
raise TypeError("Frame objects are unhashable")
1121
class FrameOpt(Frame):
1122
"""A frame with optional parts.
1124
Some ID3 frames have optional data; this class extends Frame to
1125
provide support for those parts.
1129
def __init__(self, *args, **kwargs):
1130
super(FrameOpt, self).__init__(*args, **kwargs)
1131
for spec in self._optionalspec:
1132
if spec.name in kwargs:
1133
validated = spec.validate(self, kwargs[spec.name])
1134
setattr(self, spec.name, validated)
1137
def _readData(self, data):
1139
for reader in self._framespec:
1140
if len(data): value, data = reader.read(self, data)
1141
else: raise ID3JunkFrameError
1142
setattr(self, reader.name, value)
1144
for reader in self._optionalspec:
1145
if len(data): value, data = reader.read(self, data)
1147
setattr(self, reader.name, value)
1148
if data.strip('\x00'):
1149
warn('Leftover data: %s: %r (from %r)' % (
1150
type(self).__name__, data, odata),
1153
def _writeData(self):
1155
for writer in self._framespec:
1156
data.append(writer.write(self, getattr(self, writer.name)))
1157
for writer in self._optionalspec:
1158
try: data.append(writer.write(self, getattr(self, writer.name)))
1159
except AttributeError: break
1160
return ''.join(data)
1164
for attr in self._framespec:
1165
kw.append('%s=%r' % (attr.name, getattr(self, attr.name)))
1166
for attr in self._optionalspec:
1167
if hasattr(self, attr.name):
1168
kw.append('%s=%r' % (attr.name, getattr(self, attr.name)))
1169
return '%s(%s)' % (type(self).__name__, ', '.join(kw))
1172
class TextFrame(Frame):
1175
Text frames support casts to unicode or str objects, as well as
1176
list-like indexing, extend, and append.
1178
Iterating over a TextFrame iterates over its strings, not its
1181
Text frames have a 'text' attribute which is the list of strings,
1182
and an 'encoding' attribute; 0 for ISO-8859 1, 1 UTF-16, 2 for
1183
UTF-16BE, and 3 for UTF-8. If you don't want to worry about
1184
encodings, just set it to 3.
1187
_framespec = [ EncodingSpec('encoding'),
1188
MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000') ]
1189
def __str__(self): return self.__unicode__().encode('utf-8')
1190
def __unicode__(self): return u'\u0000'.join(self.text)
1191
def __eq__(self, other):
1192
if isinstance(other, str): return str(self) == other
1193
elif isinstance(other, unicode): return unicode(self) == other
1194
return self.text == other
1195
__hash__ = Frame.__hash__
1196
def __getitem__(self, item): return self.text[item]
1197
def __iter__(self): return iter(self.text)
1198
def append(self, value): return self.text.append(value)
1199
def extend(self, value): return self.text.extend(value)
1200
def _pprint(self): return " / ".join(self.text)
1202
class NumericTextFrame(TextFrame):
1203
"""Numerical text strings.
1205
The numeric value of these frames can be gotten with unary plus, e.g.
1206
frame = TLEN('12345')
1210
_framespec = [ EncodingSpec('encoding'),
1211
MultiSpec('text', EncodedNumericTextSpec('text'), sep=u'\u0000') ]
1214
"""Return the numerical value of the string."""
1215
return int(self.text[0])
1217
class NumericPartTextFrame(TextFrame):
1218
"""Multivalue numerical text strings.
1220
These strings indicate 'part (e.g. track) X of Y', and unary plus
1221
returns the first value:
1222
frame = TRCK('4/15')
1223
track = +frame # track == 4
1226
_framespec = [ EncodingSpec('encoding'),
1227
MultiSpec('text', EncodedNumericPartTextSpec('text'), sep=u'\u0000') ]
1229
return int(self.text[0].split("/")[0])
1231
class TimeStampTextFrame(TextFrame):
1232
"""A list of time stamps.
1234
The 'text' attribute in this frame is a list of ID3TimeStamp
1235
objects, not a list of strings.
1238
_framespec = [ EncodingSpec('encoding'),
1239
MultiSpec('text', TimeStampSpec('stamp'), sep=u',') ]
1240
def __str__(self): return self.__unicode__().encode('utf-8')
1241
def __unicode__(self): return ','.join([stamp.text for stamp in self.text])
1243
return " / ".join([stamp.text for stamp in self.text])
1245
class UrlFrame(Frame):
1246
"""A frame containing a URL string.
1248
The ID3 specification is silent about IRIs and normalized URL
1249
forms. Mutagen assumes all URLs in files are encoded as Latin 1,
1250
but string conversion of this frame returns a UTF-8 representation
1251
for compatibility with other string conversions.
1253
The only sane way to handle URLs in MP3s is to restrict them to
1257
_framespec = [ Latin1TextSpec('url') ]
1258
def __str__(self): return self.url.encode('utf-8')
1259
def __unicode__(self): return self.url
1260
def __eq__(self, other): return self.url == other
1261
__hash__ = Frame.__hash__
1262
def _pprint(self): return self.url
1264
class UrlFrameU(UrlFrame):
1265
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.url))
1267
class TALB(TextFrame): "Album"
1268
class TBPM(NumericTextFrame): "Beats per minute"
1269
class TCOM(TextFrame): "Composer"
1271
class TCON(TextFrame):
1272
"""Content type (Genre)
1274
ID3 has several ways genres can be represented; for convenience,
1275
use the 'genres' property rather than the 'text' attribute.
1278
from mutagen._constants import GENRES
1280
def __get_genres(self):
1283
genre_re = re.compile(r"((?:\((?P<id>[0-9]+|RX|CR)\))*)(?P<str>.+)?")
1284
for value in self.text:
1286
try: genres.append(self.GENRES[int(value)])
1287
except IndexError: genres.append(u"Unknown")
1288
elif value == "CR": genres.append(u"Cover")
1289
elif value == "RX": genres.append(u"Remix")
1292
genreid, dummy, genrename = genre_re.match(value).groups()
1295
for gid in genreid[1:-1].split(")("):
1296
if gid.isdigit() and int(gid) < len(self.GENRES):
1297
gid = unicode(self.GENRES[int(gid)])
1298
newgenres.append(gid)
1299
elif gid == "CR": newgenres.append(u"Cover")
1300
elif gid == "RX": newgenres.append(u"Remix")
1301
else: newgenres.append(u"Unknown")
1304
# "Unescaping" the first parenthesis
1305
if genrename.startswith("(("): genrename = genrename[1:]
1306
if genrename not in newgenres: newgenres.append(genrename)
1308
genres.extend(newgenres)
1312
def __set_genres(self, genres):
1313
if isinstance(genres, basestring): genres = [genres]
1314
self.text = map(self.__decode, genres)
1316
def __decode(self, value):
1317
if isinstance(value, str):
1318
enc = EncodedTextSpec._encodings[self.encoding][0]
1319
return value.decode(enc)
1322
genres = property(__get_genres, __set_genres, None,
1323
"A list of genres parsed from the raw text data.")
1326
return " / ".join(self.genres)
1328
class TCOP(TextFrame): "Copyright (c)"
1329
class TCMP(NumericTextFrame): "iTunes Compilation Flag"
1330
class TDAT(TextFrame): "Date of recording (DDMM)"
1331
class TDEN(TimeStampTextFrame): "Encoding Time"
1332
class TDOR(TimeStampTextFrame): "Original Release Time"
1333
class TDLY(NumericTextFrame): "Audio Delay (ms)"
1334
class TDRC(TimeStampTextFrame): "Recording Time"
1335
class TDRL(TimeStampTextFrame): "Release Time"
1336
class TDTG(TimeStampTextFrame): "Tagging Time"
1337
class TENC(TextFrame): "Encoder"
1338
class TEXT(TextFrame): "Lyricist"
1339
class TFLT(TextFrame): "File type"
1340
class TIME(TextFrame): "Time of recording (HHMM)"
1341
class TIT1(TextFrame): "Content group description"
1342
class TIT2(TextFrame): "Title"
1343
class TIT3(TextFrame): "Subtitle/Description refinement"
1344
class TKEY(TextFrame): "Starting Key"
1345
class TLAN(TextFrame): "Audio Languages"
1346
class TLEN(NumericTextFrame): "Audio Length (ms)"
1347
class TMED(TextFrame): "Source Media Type"
1348
class TMOO(TextFrame): "Mood"
1349
class TOAL(TextFrame): "Original Album"
1350
class TOFN(TextFrame): "Original Filename"
1351
class TOLY(TextFrame): "Original Lyricist"
1352
class TOPE(TextFrame): "Original Artist/Performer"
1353
class TORY(NumericTextFrame): "Original Release Year"
1354
class TOWN(TextFrame): "Owner/Licensee"
1355
class TPE1(TextFrame): "Lead Artist/Performer/Soloist/Group"
1356
class TPE2(TextFrame): "Band/Orchestra/Accompaniment"
1357
class TPE3(TextFrame): "Conductor"
1358
class TPE4(TextFrame): "Interpreter/Remixer/Modifier"
1359
class TPOS(NumericPartTextFrame): "Part of set"
1360
class TPRO(TextFrame): "Produced (P)"
1361
class TPUB(TextFrame): "Publisher"
1362
class TRCK(NumericPartTextFrame): "Track Number"
1363
class TRDA(TextFrame): "Recording Dates"
1364
class TRSN(TextFrame): "Internet Radio Station Name"
1365
class TRSO(TextFrame): "Internet Radio Station Owner"
1366
class TSIZ(NumericTextFrame): "Size of audio data (bytes)"
1367
class TSO2(TextFrame): "iTunes Album Artist Sort"
1368
class TSOA(TextFrame): "Album Sort Order key"
1369
class TSOC(TextFrame): "iTunes Composer Sort"
1370
class TSOP(TextFrame): "Perfomer Sort Order key"
1371
class TSOT(TextFrame): "Title Sort Order key"
1372
class TSRC(TextFrame): "International Standard Recording Code (ISRC)"
1373
class TSSE(TextFrame): "Encoder settings"
1374
class TSST(TextFrame): "Set Subtitle"
1375
class TYER(NumericTextFrame): "Year of recording"
1377
class TXXX(TextFrame):
1378
"""User-defined text data.
1380
TXXX frames have a 'desc' attribute which is set to any Unicode
1381
value (though the encoding of the text and the description must be
1382
the same). Many taggers use this frame to store freeform keys.
1384
_framespec = [ EncodingSpec('encoding'), EncodedTextSpec('desc'),
1385
MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000') ]
1386
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1387
def _pprint(self): return "%s=%s" % (self.desc, " / ".join(self.text))
1389
class WCOM(UrlFrameU): "Commercial Information"
1390
class WCOP(UrlFrame): "Copyright Information"
1391
class WOAF(UrlFrame): "Official File Information"
1392
class WOAR(UrlFrameU): "Official Artist/Performer Information"
1393
class WOAS(UrlFrame): "Official Source Information"
1394
class WORS(UrlFrame): "Official Internet Radio Information"
1395
class WPAY(UrlFrame): "Payment Information"
1396
class WPUB(UrlFrame): "Official Publisher Information"
1398
class WXXX(UrlFrame):
1399
"""User-defined URL data.
1401
Like TXXX, this has a freeform description associated with it.
1403
_framespec = [ EncodingSpec('encoding'), EncodedTextSpec('desc'),
1404
Latin1TextSpec('url') ]
1405
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1407
class PairedTextFrame(Frame):
1408
"""Paired text strings.
1410
Some ID3 frames pair text strings, to associate names with a more
1411
specific involvement in the song. The 'people' attribute of these
1412
frames contains a list of pairs:
1413
[['trumpet', 'Miles Davis'], ['bass', 'Paul Chambers']]
1415
Like text frames, these frames also have an encoding attribute.
1418
_framespec = [ EncodingSpec('encoding'), MultiSpec('people',
1419
EncodedTextSpec('involvement'), EncodedTextSpec('person')) ]
1420
def __eq__(self, other):
1421
return self.people == other
1422
__hash__ = Frame.__hash__
1424
class TIPL(PairedTextFrame): "Involved People List"
1425
class TMCL(PairedTextFrame): "Musicians Credits List"
1426
class IPLS(TIPL): "Involved People List"
1428
class BinaryFrame(Frame):
1431
The 'data' attribute contains the raw byte string.
1433
_framespec = [ BinaryDataSpec('data') ]
1434
def __eq__(self, other): return self.data == other
1435
__hash__ = Frame.__hash__
1437
class MCDI(BinaryFrame): "Binary dump of CD's TOC"
1440
"""Event timing codes."""
1441
_framespec = [ ByteSpec("format"), KeyEventSpec("events") ]
1442
def __eq__(self, other): return self.events == other
1443
__hash__ = Frame.__hash__
1446
"""MPEG location lookup table.
1448
This frame's attributes may be changed in the future based on
1449
feedback from real-world use.
1451
_framespec = [ SizedIntegerSpec('frames', 2),
1452
SizedIntegerSpec('bytes', 3),
1453
SizedIntegerSpec('milliseconds', 3),
1454
ByteSpec('bits_for_bytes'),
1455
ByteSpec('bits_for_milliseconds'),
1456
BinaryDataSpec('data') ]
1457
def __eq__(self, other): return self.data == other
1458
__hash__ = Frame.__hash__
1461
"""Synchronised tempo codes.
1463
This frame's attributes may be changed in the future based on
1464
feedback from real-world use.
1466
_framespec = [ ByteSpec("format"), BinaryDataSpec("data") ]
1467
def __eq__(self, other): return self.data == other
1468
__hash__ = Frame.__hash__
1471
"""Unsynchronised lyrics/text transcription.
1473
Lyrics have a three letter ISO language code ('lang'), a
1474
description ('desc'), and a block of plain text ('text').
1477
_framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1478
EncodedTextSpec('desc'), EncodedTextSpec('text') ]
1479
HashKey = property(lambda s: '%s:%s:%r' % (s.FrameID, s.desc, s.lang))
1481
def __str__(self): return self.text.encode('utf-8')
1482
def __unicode__(self): return self.text
1483
def __eq__(self, other): return self.text == other
1484
__hash__ = Frame.__hash__
1487
"""Synchronised lyrics/text."""
1489
_framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1490
ByteSpec('format'), ByteSpec('type'), EncodedTextSpec('desc'),
1491
SynchronizedTextSpec('text') ]
1492
HashKey = property(lambda s: '%s:%s:%r' % (s.FrameID, s.desc, s.lang))
1494
def __eq__(self, other):
1495
return str(self) == other
1496
__hash__ = Frame.__hash__
1499
return "".join([text for (text, time) in self.text]).encode('utf-8')
1501
class COMM(TextFrame):
1504
User comment frames have a descrption, like TXXX, and also a three
1505
letter ISO language code in the 'lang' attribute.
1507
_framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1508
EncodedTextSpec('desc'),
1509
MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000') ]
1510
HashKey = property(lambda s: '%s:%s:%r' % (s.FrameID, s.desc, s.lang))
1511
def _pprint(self): return "%s=%r=%s" % (
1512
self.desc, self.lang, " / ".join(self.text))
1515
"""Relative volume adjustment (2).
1517
This frame is used to implemented volume scaling, and in
1518
particular, normalization using ReplayGain.
1521
desc -- description or context of this adjustment
1522
channel -- audio channel to adjust (master is 1)
1523
gain -- a + or - dB gain relative to some reference level
1524
peak -- peak of the audio as a floating point number, [0, 1]
1526
When storing ReplayGain tags, use descriptions of 'album' and
1527
'track' on channel 1.
1530
_framespec = [ Latin1TextSpec('desc'), ChannelSpec('channel'),
1531
VolumeAdjustmentSpec('gain'), VolumePeakSpec('peak') ]
1532
_channels = ["Other", "Master volume", "Front right", "Front left",
1533
"Back right", "Back left", "Front centre", "Back centre",
1535
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1537
def __eq__(self, other):
1538
return ((str(self) == other) or
1539
(self.desc == other.desc and
1540
self.channel == other.channel and
1541
self.gain == other.gain and
1542
self.peak == other.peak))
1543
__hash__ = Frame.__hash__
1546
return "%s: %+0.4f dB/%0.4f" % (
1547
self._channels[self.channel], self.gain, self.peak)
1550
"""Equalisation (2).
1553
method -- interpolation method (0 = band, 1 = linear)
1554
desc -- identifying description
1555
adjustments -- list of (frequency, vol_adjustment) pairs
1557
_framespec = [ ByteSpec("method"), Latin1TextSpec("desc"),
1558
VolumeAdjustmentsSpec("adjustments") ]
1559
def __eq__(self, other): return self.adjustments == other
1560
__hash__ = Frame.__hash__
1561
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1563
# class RVAD: unsupported
1564
# class EQUA: unsupported
1568
_framespec = [ SizedIntegerSpec('left', 2), SizedIntegerSpec('right', 2),
1569
ByteSpec('bounce_left'), ByteSpec('bounce_right'),
1570
ByteSpec('feedback_ltl'), ByteSpec('feedback_ltr'),
1571
ByteSpec('feedback_rtr'), ByteSpec('feedback_rtl'),
1572
ByteSpec('premix_ltr'), ByteSpec('premix_rtl') ]
1574
def __eq__(self, other): return (self.left, self.right) == other
1575
__hash__ = Frame.__hash__
1578
"""Attached (or linked) Picture.
1581
encoding -- text encoding for the description
1582
mime -- a MIME type (e.g. image/jpeg) or '-->' if the data is a URI
1583
type -- the source of the image (3 is the album front cover)
1584
desc -- a text description of the image
1585
data -- raw image data, as a byte string
1587
Mutagen will automatically compress large images when saving tags.
1589
_framespec = [ EncodingSpec('encoding'), Latin1TextSpec('mime'),
1590
ByteSpec('type'), EncodedTextSpec('desc'), BinaryDataSpec('data') ]
1591
def __eq__(self, other): return self.data == other
1592
__hash__ = Frame.__hash__
1593
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1595
return "%s (%s, %d bytes)" % (
1596
self.desc, self.mime, len(self.data))
1601
The 'count' attribute contains the (recorded) number of times this
1602
file has been played.
1604
This frame is basically obsoleted by POPM.
1606
_framespec = [ IntegerSpec('count') ]
1608
def __eq__(self, other): return self.count == other
1609
__hash__ = Frame.__hash__
1610
def __pos__(self): return self.count
1611
def _pprint(self): return unicode(self.count)
1613
class POPM(FrameOpt):
1616
This frame keys a rating (out of 255) and a play count to an email
1620
email -- email this POPM frame is for
1621
rating -- rating from 0 to 255
1622
count -- number of times the files has been played (optional)
1624
_framespec = [ Latin1TextSpec('email'), ByteSpec('rating') ]
1625
_optionalspec = [ IntegerSpec('count') ]
1627
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.email))
1629
def __eq__(self, other): return self.rating == other
1630
__hash__ = FrameOpt.__hash__
1631
def __pos__(self): return self.rating
1632
def _pprint(self): return "%s=%r %r/255" % (
1633
self.email, getattr(self, 'count', None), self.rating)
1636
"""General Encapsulated Object.
1638
A blob of binary data, that is not a picture (those go in APIC).
1641
encoding -- encoding of the description
1642
mime -- MIME type of the data or '-->' if the data is a URI
1643
filename -- suggested filename if extracted
1644
desc -- text description of the data
1645
data -- raw data, as a byte string
1647
_framespec = [ EncodingSpec('encoding'), Latin1TextSpec('mime'),
1648
EncodedTextSpec('filename'), EncodedTextSpec('desc'),
1649
BinaryDataSpec('data') ]
1650
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1652
def __eq__(self, other): return self.data == other
1653
__hash__ = Frame.__hash__
1655
class RBUF(FrameOpt):
1656
"""Recommended buffer size.
1659
size -- recommended buffer size in bytes
1660
info -- if ID3 tags may be elsewhere in the file (optional)
1661
offset -- the location of the next ID3 tag, if any
1663
Mutagen will not find the next tag itself.
1665
_framespec = [ SizedIntegerSpec('size', 3) ]
1666
_optionalspec = [ ByteSpec('info'), SizedIntegerSpec('offset', 4) ]
1668
def __eq__(self, other): return self.size == other
1669
__hash__ = FrameOpt.__hash__
1670
def __pos__(self): return self.size
1672
class AENC(FrameOpt):
1673
"""Audio encryption.
1676
owner -- key identifying this encryption type
1677
preview_start -- unencrypted data block offset
1678
preview_length -- number of unencrypted blocks
1679
data -- data required for decryption (optional)
1681
Mutagen cannot decrypt files.
1683
_framespec = [ Latin1TextSpec('owner'),
1684
SizedIntegerSpec('preview_start', 2),
1685
SizedIntegerSpec('preview_length', 2) ]
1686
_optionalspec = [ BinaryDataSpec('data') ]
1687
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.owner))
1689
def __str__(self): return self.owner.encode('utf-8')
1690
def __unicode__(self): return self.owner
1691
def __eq__(self, other): return self.owner == other
1692
__hash__ = FrameOpt.__hash__
1694
class LINK(FrameOpt):
1695
"""Linked information.
1698
frameid -- the ID of the linked frame
1699
url -- the location of the linked frame
1700
data -- further ID information for the frame
1703
_framespec = [ StringSpec('frameid', 4), Latin1TextSpec('url') ]
1704
_optionalspec = [ BinaryDataSpec('data') ]
1705
def __HashKey(self):
1707
return "%s:%s:%s:%r" % (
1708
self.FrameID, self.frameid, self.url, self.data)
1709
except AttributeError:
1710
return "%s:%s:%s" % (self.FrameID, self.frameid, self.url)
1711
HashKey = property(__HashKey)
1712
def __eq__(self, other):
1713
try: return (self.frameid, self.url, self.data) == other
1714
except AttributeError: return (self.frameid, self.url) == other
1715
__hash__ = FrameOpt.__hash__
1718
"""Position synchronisation frame
1721
format -- format of the position attribute (frames or milliseconds)
1722
position -- current position of the file
1724
_framespec = [ ByteSpec('format'), IntegerSpec('position') ]
1726
def __pos__(self): return self.position
1727
def __eq__(self, other): return self.position == other
1728
__hash__ = Frame.__hash__
1731
"""Unique file identifier.
1734
owner -- format/type of identifier
1738
_framespec = [ Latin1TextSpec('owner'), BinaryDataSpec('data') ]
1739
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.owner))
1741
if isinstance(o, UFI): return s.owner == o.owner and s.data == o.data
1742
else: return s.data == o
1743
__hash__ = Frame.__hash__
1745
isascii = ord(max(self.data)) < 128
1746
if isascii: return "%s=%s" % (self.owner, self.data)
1747
else: return "%s (%d bytes)" % (self.owner, len(self.data))
1753
encoding -- text encoding
1754
lang -- ISO three letter language code
1755
text -- licensing terms for the audio
1757
_framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1758
EncodedTextSpec('text') ]
1759
HashKey = property(lambda s: '%s:%r' % (s.FrameID, s.lang))
1761
def __str__(self): return self.text.encode('utf-8')
1762
def __unicode__(self): return self.text
1763
def __eq__(self, other): return self.text == other
1764
__hash__ = Frame.__hash__
1765
def _pprint(self): return "%r=%s" % (self.lang, self.text)
1768
"""Ownership frame."""
1769
_framespec = [ EncodingSpec('encoding'), Latin1TextSpec('price'),
1770
StringSpec('date', 8), EncodedTextSpec('seller') ]
1772
def __str__(self): return self.seller.encode('utf-8')
1773
def __unicode__(self): return self.seller
1774
def __eq__(self, other): return self.seller == other
1775
__hash__ = Frame.__hash__
1777
class COMR(FrameOpt):
1778
"""Commercial frame."""
1779
_framespec = [ EncodingSpec('encoding'), Latin1TextSpec('price'),
1780
StringSpec('valid_until', 8), Latin1TextSpec('contact'),
1781
ByteSpec('format'), EncodedTextSpec('seller'),
1782
EncodedTextSpec('desc')]
1783
_optionalspec = [ Latin1TextSpec('mime'), BinaryDataSpec('logo') ]
1784
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s._writeData()))
1785
def __eq__(self, other): return self._writeData() == other._writeData()
1786
__hash__ = FrameOpt.__hash__
1789
"""Encryption method registration.
1791
The standard does not allow multiple ENCR frames with the same owner
1792
or the same method. Mutagen only verifies that the owner is unique.
1794
_framespec = [ Latin1TextSpec('owner'), ByteSpec('method'),
1795
BinaryDataSpec('data') ]
1796
HashKey = property(lambda s: "%s:%s" % (s.FrameID, s.owner))
1797
def __str__(self): return self.data
1798
def __eq__(self, other): return self.data == other
1799
__hash__ = Frame.__hash__
1801
class GRID(FrameOpt):
1802
"""Group identification registration."""
1803
_framespec = [ Latin1TextSpec('owner'), ByteSpec('group') ]
1804
_optionalspec = [ BinaryDataSpec('data') ]
1805
HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.group))
1806
def __pos__(self): return self.group
1807
def __str__(self): return self.owner.encode('utf-8')
1808
def __unicode__(self): return self.owner
1809
def __eq__(self, other): return self.owner == other or self.group == other
1810
__hash__ = FrameOpt.__hash__
1814
"""Private frame."""
1815
_framespec = [ Latin1TextSpec('owner'), BinaryDataSpec('data') ]
1816
HashKey = property(lambda s: '%s:%s:%s' % (
1817
s.FrameID, s.owner, s.data.decode('latin1')))
1818
def __str__(self): return self.data
1819
def __eq__(self, other): return self.data == other
1821
isascii = ord(max(self.data)) < 128
1822
if isascii: return "%s=%s" % (self.owner, self.data)
1823
else: return "%s (%d bytes)" % (self.owner, len(self.data))
1824
__hash__ = Frame.__hash__
1827
"""Signature frame."""
1828
_framespec = [ ByteSpec('group'), BinaryDataSpec('sig') ]
1829
HashKey = property(lambda s: '%s:%c:%s' % (s.FrameID, s.group, s.sig))
1830
def __str__(self): return self.sig
1831
def __eq__(self, other): return self.sig == other
1832
__hash__ = Frame.__hash__
1837
Mutagen does not find tags at seek offsets.
1839
_framespec = [ IntegerSpec('offset') ]
1840
def __pos__(self): return self.offset
1841
def __eq__(self, other): return self.offset == other
1842
__hash__ = Frame.__hash__
1845
"""Audio seek point index.
1847
Attributes: S, L, N, b, and Fi. For the meaning of these, see
1848
the ID3v2.4 specification. Fi is a list of integers.
1850
_framespec = [ SizedIntegerSpec("S", 4), SizedIntegerSpec("L", 4),
1851
SizedIntegerSpec("N", 2), ByteSpec("b"),
1852
ASPIIndexSpec("Fi") ]
1853
def __eq__(self, other): return self.Fi == other
1854
__hash__ = Frame.__hash__
1856
Frames = dict([(k,v) for (k,v) in globals().items()
1857
if len(k)==4 and isinstance(v, type) and issubclass(v, Frame)])
1858
"""All supported ID3v2 frames, keyed by frame name."""
1862
class UFI(UFID): "Unique File Identifier"
1864
class TT1(TIT1): "Content group description"
1865
class TT2(TIT2): "Title"
1866
class TT3(TIT3): "Subtitle/Description refinement"
1867
class TP1(TPE1): "Lead Artist/Performer/Soloist/Group"
1868
class TP2(TPE2): "Band/Orchestra/Accompaniment"
1869
class TP3(TPE3): "Conductor"
1870
class TP4(TPE4): "Interpreter/Remixer/Modifier"
1871
class TCM(TCOM): "Composer"
1872
class TXT(TEXT): "Lyricist"
1873
class TLA(TLAN): "Audio Language(s)"
1874
class TCO(TCON): "Content Type (Genre)"
1875
class TAL(TALB): "Album"
1876
class TPA(TPOS): "Part of set"
1877
class TRK(TRCK): "Track Number"
1878
class TRC(TSRC): "International Standard Recording Code (ISRC)"
1879
class TYE(TYER): "Year of recording"
1880
class TDA(TDAT): "Date of recording (DDMM)"
1881
class TIM(TIME): "Time of recording (HHMM)"
1882
class TRD(TRDA): "Recording Dates"
1883
class TMT(TMED): "Source Media Type"
1884
class TFT(TFLT): "File Type"
1885
class TBP(TBPM): "Beats per minute"
1886
class TCP(TCMP): "iTunes Compilation Flag"
1887
class TCR(TCOP): "Copyright (C)"
1888
class TPB(TPUB): "Publisher"
1889
class TEN(TENC): "Encoder"
1890
class TSS(TSSE): "Encoder settings"
1891
class TOF(TOFN): "Original Filename"
1892
class TLE(TLEN): "Audio Length (ms)"
1893
class TSI(TSIZ): "Audio Data size (bytes)"
1894
class TDY(TDLY): "Audio Delay (ms)"
1895
class TKE(TKEY): "Starting Key"
1896
class TOT(TOAL): "Original Album"
1897
class TOA(TOPE): "Original Artist/Perfomer"
1898
class TOL(TOLY): "Original Lyricist"
1899
class TOR(TORY): "Original Release Year"
1901
class TXX(TXXX): "User-defined Text"
1903
class WAF(WOAF): "Official File Information"
1904
class WAR(WOAR): "Official Artist/Performer Information"
1905
class WAS(WOAS): "Official Source Information"
1906
class WCM(WCOM): "Commercial Information"
1907
class WCP(WCOP): "Copyright Information"
1908
class WPB(WPUB): "Official Publisher Information"
1910
class WXX(WXXX): "User-defined URL"
1912
class IPL(IPLS): "Involved people list"
1913
class MCI(MCDI): "Binary dump of CD's TOC"
1914
class ETC(ETCO): "Event timing codes"
1915
class MLL(MLLT): "MPEG location lookup table"
1916
class STC(SYTC): "Synced tempo codes"
1917
class ULT(USLT): "Unsychronised lyrics/text transcription"
1918
class SLT(SYLT): "Synchronised lyrics/text"
1919
class COM(COMM): "Comment"
1922
class REV(RVRB): "Reverb"
1924
"""Attached Picture.
1926
The 'mime' attribute of an ID3v2.2 attached picture must be either
1929
_framespec = [ EncodingSpec('encoding'), StringSpec('mime', 3),
1930
ByteSpec('type'), EncodedTextSpec('desc'), BinaryDataSpec('data') ]
1931
class GEO(GEOB): "General Encapsulated Object"
1932
class CNT(PCNT): "Play counter"
1933
class POP(POPM): "Popularimeter"
1934
class BUF(RBUF): "Recommended buffer size"
1937
"""Encrypted meta frame"""
1938
_framespec = [ Latin1TextSpec('owner'), Latin1TextSpec('desc'),
1939
BinaryDataSpec('data') ]
1940
def __eq__(self, other): return self.data == other
1941
__hash__ = Frame.__hash__
1943
class CRA(AENC): "Audio encryption"
1946
"""Linked information"""
1947
_framespec = [ StringSpec('frameid', 3), Latin1TextSpec('url') ]
1948
_optionalspec = [ BinaryDataSpec('data') ]
1950
Frames_2_2 = dict([(k,v) for (k,v) in globals().items()
1951
if len(k)==3 and isinstance(v, type) and issubclass(v, Frame)])
1953
749
# support open(filename) as interface
1956
753
# ID3v1.1 support.
1957
754
def ParseID3v1(string):
1958
755
"""Parse an ID3v1 tag, returning a list of ID3v2.4 frames."""