audio_lang code tidy up
[pyTivo/wmcbrine/lucasnz.git] / mutagen / id3.py
blobef0343da98ada3b9b78c5ee631a5f3e6b5775fc1
1 # id3 support for mutagen
2 # Copyright (C) 2005 Michael Urman
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of version 2 of the GNU General Public License as
6 # published by the Free Software Foundation.
8 # $Id: id3.py 4285 2008-09-06 08:01:31Z piman $
10 """ID3v2 reading and writing.
12 This is based off of the following references:
13 http://www.id3.org/id3v2.4.0-structure.txt
14 http://www.id3.org/id3v2.4.0-frames.txt
15 http://www.id3.org/id3v2.3.0.html
16 http://www.id3.org/id3v2-00.txt
17 http://www.id3.org/id3v1.html
19 Its largest deviation from the above (versions 2.3 and 2.2) is that it
20 will not interpret the / characters as a separator, and will almost
21 always accept null separators to generate multi-valued text frames.
23 Because ID3 frame structure differs between frame types, each frame is
24 implemented as a different class (e.g. TIT2 as mutagen.id3.TIT2). Each
25 frame's documentation contains a list of its attributes.
27 Since this file's documentation is a little unwieldy, you are probably
28 interested in the 'ID3' class to start with.
29 """
31 __all__ = ['ID3', 'ID3FileType', 'Frames', 'Open', 'delete']
33 import struct; from struct import unpack, pack
34 from zlib import error as zlibError
35 from warnings import warn
37 import mutagen
38 from mutagen._util import insert_bytes, delete_bytes, DictProxy
40 class error(Exception): pass
41 class ID3NoHeaderError(error, ValueError): pass
42 class ID3BadUnsynchData(error, ValueError): pass
43 class ID3BadCompressedData(error, ValueError): pass
44 class ID3TagError(error, ValueError): pass
45 class ID3UnsupportedVersionError(error, NotImplementedError): pass
46 class ID3EncryptionUnsupportedError(error, NotImplementedError): pass
47 class ID3JunkFrameError(error, ValueError): pass
49 class ID3Warning(error, UserWarning): pass
51 def is_valid_frame_id(frame_id):
52 return frame_id.isalnum() and frame_id.isupper()
54 class ID3(DictProxy, mutagen.Metadata):
55 """A file with an ID3v2 tag.
57 Attributes:
58 version -- ID3 tag version as a tuple
59 unknown_frames -- raw frame data of any unknown frames found
60 size -- the total size of the ID3 tag, including the header
61 """
63 PEDANTIC = True
64 version = (2, 4, 0)
66 filename = None
67 size = 0
68 __flags = 0
69 __readbytes = 0
70 __crc = None
72 def __init__(self, *args, **kwargs):
73 self.unknown_frames = []
74 super(ID3, self).__init__(*args, **kwargs)
76 def __fullread(self, size):
77 try:
78 if size < 0:
79 raise ValueError('Requested bytes (%s) less than zero' % size)
80 if size > self.__filesize:
81 raise EOFError('Requested %#x of %#x (%s)' %
82 (long(size), long(self.__filesize), self.filename))
83 except AttributeError: pass
84 data = self.__fileobj.read(size)
85 if len(data) != size: raise EOFError
86 self.__readbytes += size
87 return data
89 def load(self, filename, known_frames=None, translate=True):
90 """Load tags from a filename.
92 Keyword arguments:
93 filename -- filename to load tag data from
94 known_frames -- dict mapping frame IDs to Frame objects
95 translate -- Update all tags to ID3v2.4 internally. Mutagen is
96 only capable of writing ID3v2.4 tags, so if you
97 intend to save, this must be true.
99 Example of loading a custom frame:
100 my_frames = dict(mutagen.id3.Frames)
101 class XMYF(Frame): ...
102 my_frames["XMYF"] = XMYF
103 mutagen.id3.ID3(filename, known_frames=my_frames)
106 from os.path import getsize
107 self.filename = filename
108 self.__known_frames = known_frames
109 self.__fileobj = file(filename, 'rb')
110 self.__filesize = getsize(filename)
111 try:
112 try:
113 self.__load_header()
114 except EOFError:
115 self.size = 0
116 raise ID3NoHeaderError("%s: too small (%d bytes)" %(
117 filename, self.__filesize))
118 except (ID3NoHeaderError, ID3UnsupportedVersionError), err:
119 self.size = 0
120 import sys
121 stack = sys.exc_info()[2]
122 try: self.__fileobj.seek(-128, 2)
123 except EnvironmentError: raise err, None, stack
124 else:
125 frames = ParseID3v1(self.__fileobj.read(128))
126 if frames is not None:
127 self.version = (1, 1)
128 map(self.add, frames.values())
129 else: raise err, None, stack
130 else:
131 frames = self.__known_frames
132 if frames is None:
133 if (2,3,0) <= self.version: frames = Frames
134 elif (2,2,0) <= self.version: frames = Frames_2_2
135 data = self.__fullread(self.size - 10)
136 for frame in self.__read_frames(data, frames=frames):
137 if isinstance(frame, Frame): self.add(frame)
138 else: self.unknown_frames.append(frame)
139 finally:
140 self.__fileobj.close()
141 del self.__fileobj
142 del self.__filesize
143 if translate:
144 self.update_to_v24()
146 def getall(self, key):
147 """Return all frames with a given name (the list may be empty).
149 This is best explained by examples:
150 id3.getall('TIT2') == [id3['TIT2']]
151 id3.getall('TTTT') == []
152 id3.getall('TXXX') == [TXXX(desc='woo', text='bar'),
153 TXXX(desc='baz', text='quuuux'), ...]
155 Since this is based on the frame's HashKey, which is
156 colon-separated, you can use it to do things like
157 getall('COMM:MusicMatch') or getall('TXXX:QuodLibet:').
159 if key in self: return [self[key]]
160 else:
161 key = key + ":"
162 return [v for s,v in self.items() if s.startswith(key)]
164 def delall(self, key):
165 """Delete all tags of a given kind; see getall."""
166 if key in self: del(self[key])
167 else:
168 key = key + ":"
169 for k in filter(lambda s: s.startswith(key), self.keys()):
170 del(self[k])
172 def setall(self, key, values):
173 """Delete frames of the given type and add frames in 'values'."""
174 self.delall(key)
175 for tag in values:
176 self[tag.HashKey] = tag
178 def pprint(self):
179 """Return tags in a human-readable format.
181 "Human-readable" is used loosely here. The format is intended
182 to mirror that used for Vorbis or APEv2 output, e.g.
183 TIT2=My Title
184 However, ID3 frames can have multiple keys:
185 POPM=user@example.org=3 128/255
187 return "\n".join(map(Frame.pprint, self.values()))
189 def loaded_frame(self, tag):
190 """Deprecated; use the add method."""
191 # turn 2.2 into 2.3/2.4 tags
192 if len(type(tag).__name__) == 3: tag = type(tag).__base__(tag)
193 self[tag.HashKey] = tag
195 # add = loaded_frame (and vice versa) break applications that
196 # expect to be able to override loaded_frame (e.g. Quod Libet),
197 # as does making loaded_frame call add.
198 def add(self, frame):
199 """Add a frame to the tag."""
200 return self.loaded_frame(frame)
202 def __load_header(self):
203 fn = self.filename
204 data = self.__fullread(10)
205 id3, vmaj, vrev, flags, size = unpack('>3sBBB4s', data)
206 self.__flags = flags
207 self.size = BitPaddedInt(size) + 10
208 self.version = (2, vmaj, vrev)
210 if id3 != 'ID3':
211 raise ID3NoHeaderError("'%s' doesn't start with an ID3 tag" % fn)
212 if vmaj not in [2, 3, 4]:
213 raise ID3UnsupportedVersionError("'%s' ID3v2.%d not supported"
214 % (fn, vmaj))
216 if self.PEDANTIC:
217 if (2,4,0) <= self.version and (flags & 0x0f):
218 raise ValueError("'%s' has invalid flags %#02x" % (fn, flags))
219 elif (2,3,0) <= self.version and (flags & 0x1f):
220 raise ValueError("'%s' has invalid flags %#02x" % (fn, flags))
222 if self.f_extended:
223 if self.version >= (2,4,0):
224 # "Where the 'Extended header size' is the size of the whole
225 # extended header, stored as a 32 bit synchsafe integer."
226 self.__extsize = BitPaddedInt(self.__fullread(4)) - 4
227 else:
228 # "Where the 'Extended header size', currently 6 or 10 bytes,
229 # excludes itself."
230 self.__extsize = unpack('>L', self.__fullread(4))[0]
231 self.__extdata = self.__fullread(self.__extsize)
233 def __determine_bpi(self, data, frames):
234 if self.version < (2,4,0): return int
235 # have to special case whether to use bitpaddedints here
236 # spec says to use them, but iTunes has it wrong
238 # count number of tags found as BitPaddedInt and how far past
239 o = 0
240 asbpi = 0
241 while o < len(data)-10:
242 name, size, flags = unpack('>4sLH', data[o:o+10])
243 size = BitPaddedInt(size)
244 o += 10+size
245 if name in frames: asbpi += 1
246 bpioff = o - len(data)
248 # count number of tags found as int and how far past
249 o = 0
250 asint = 0
251 while o < len(data)-10:
252 name, size, flags = unpack('>4sLH', data[o:o+10])
253 o += 10+size
254 if name in frames: asint += 1
255 intoff = o - len(data)
257 # if more tags as int, or equal and bpi is past and int is not
258 if asint > asbpi or (asint == asbpi and (bpioff >= 1 and intoff <= 1)):
259 return int
260 return BitPaddedInt
262 def __read_frames(self, data, frames):
263 if self.version < (2,4,0) and self.f_unsynch:
264 try: data = unsynch.decode(data)
265 except ValueError: pass
267 if (2,3,0) <= self.version:
268 bpi = self.__determine_bpi(data, frames)
269 while data:
270 header = data[:10]
271 try: name, size, flags = unpack('>4sLH', header)
272 except struct.error: return # not enough header
273 if name.strip('\x00') == '': return
274 size = bpi(size)
275 framedata = data[10:10+size]
276 data = data[10+size:]
277 if size == 0: continue # drop empty frames
278 try: tag = frames[name]
279 except KeyError:
280 if is_valid_frame_id(name): yield header + framedata
281 else:
282 try: yield self.__load_framedata(tag, flags, framedata)
283 except NotImplementedError: yield header + framedata
284 except ID3JunkFrameError: pass
286 elif (2,2,0) <= self.version:
287 while data:
288 header = data[0:6]
289 try: name, size = unpack('>3s3s', header)
290 except struct.error: return # not enough header
291 size, = struct.unpack('>L', '\x00'+size)
292 if name.strip('\x00') == '': return
293 framedata = data[6:6+size]
294 data = data[6+size:]
295 if size == 0: continue # drop empty frames
296 try: tag = frames[name]
297 except KeyError:
298 if is_valid_frame_id(name): yield header + framedata
299 else:
300 try: yield self.__load_framedata(tag, 0, framedata)
301 except NotImplementedError: yield header + framedata
302 except ID3JunkFrameError: pass
304 def __load_framedata(self, tag, flags, framedata):
305 return tag.fromData(self, flags, framedata)
307 f_unsynch = property(lambda s: bool(s.__flags & 0x80))
308 f_extended = property(lambda s: bool(s.__flags & 0x40))
309 f_experimental = property(lambda s: bool(s.__flags & 0x20))
310 f_footer = property(lambda s: bool(s.__flags & 0x10))
312 #f_crc = property(lambda s: bool(s.__extflags & 0x8000))
314 def save(self, filename=None, v1=1):
315 """Save changes to a file.
317 If no filename is given, the one most recently loaded is used.
319 Keyword arguments:
320 v1 -- if 0, ID3v1 tags will be removed
321 if 1, ID3v1 tags will be updated but not added
322 if 2, ID3v1 tags will be created and/or updated
324 The lack of a way to update only an ID3v1 tag is intentional.
327 # Sort frames by 'importance'
328 order = ["TIT2", "TPE1", "TRCK", "TALB", "TPOS", "TDRC", "TCON"]
329 order = dict(zip(order, range(len(order))))
330 last = len(order)
331 frames = self.items()
332 frames.sort(lambda a, b: cmp(order.get(a[0][:4], last),
333 order.get(b[0][:4], last)))
335 framedata = [self.__save_frame(frame) for (key, frame) in frames]
336 framedata.extend([data for data in self.unknown_frames
337 if len(data) > 10])
338 if not framedata:
339 try:
340 self.delete(filename)
341 except EnvironmentError, err:
342 from errno import ENOENT
343 if err.errno != ENOENT: raise
344 return
346 framedata = ''.join(framedata)
347 framesize = len(framedata)
349 if filename is None: filename = self.filename
350 try: f = open(filename, 'rb+')
351 except IOError, err:
352 from errno import ENOENT
353 if err.errno != ENOENT: raise
354 f = open(filename, 'ab') # create, then reopen
355 f = open(filename, 'rb+')
356 try:
357 idata = f.read(10)
358 try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata)
359 except struct.error: id3, insize = '', 0
360 insize = BitPaddedInt(insize)
361 if id3 != 'ID3': insize = -10
363 if insize >= framesize: outsize = insize
364 else: outsize = (framesize + 1023) & ~0x3FF
365 framedata += '\x00' * (outsize - framesize)
367 framesize = BitPaddedInt.to_str(outsize, width=4)
368 flags = 0
369 header = pack('>3sBBB4s', 'ID3', 4, 0, flags, framesize)
370 data = header + framedata
372 if (insize < outsize):
373 insert_bytes(f, outsize-insize, insize+10)
374 f.seek(0)
375 f.write(data)
377 try:
378 f.seek(-128, 2)
379 except IOError, err:
380 from errno import EINVAL
381 if err.errno != EINVAL: raise
382 f.seek(0, 2) # ensure read won't get "TAG"
384 if f.read(3) == "TAG":
385 f.seek(-128, 2)
386 if v1 > 0: f.write(MakeID3v1(self))
387 else: f.truncate()
388 elif v1 == 2:
389 f.seek(0, 2)
390 f.write(MakeID3v1(self))
392 finally:
393 f.close()
395 def delete(self, filename=None, delete_v1=True, delete_v2=True):
396 """Remove tags from a file.
398 If no filename is given, the one most recently loaded is used.
400 Keyword arguments:
401 delete_v1 -- delete any ID3v1 tag
402 delete_v2 -- delete any ID3v2 tag
404 if filename is None:
405 filename = self.filename
406 delete(filename, delete_v1, delete_v2)
407 self.clear()
409 def __save_frame(self, frame):
410 flags = 0
411 if self.PEDANTIC and isinstance(frame, TextFrame):
412 if len(str(frame)) == 0: return ''
413 framedata = frame._writeData()
414 usize = len(framedata)
415 if usize > 2048:
416 # Disabled as this causes iTunes and other programs
417 # to fail to find these frames, which usually includes
418 # e.g. APIC.
419 #framedata = BitPaddedInt.to_str(usize) + framedata.encode('zlib')
420 #flags |= Frame.FLAG24_COMPRESS | Frame.FLAG24_DATALEN
421 pass
422 datasize = BitPaddedInt.to_str(len(framedata), width=4)
423 header = pack('>4s4sH', type(frame).__name__, datasize, flags)
424 return header + framedata
426 def update_to_v24(self):
427 """Convert older tags into an ID3v2.4 tag.
429 This updates old ID3v2 frames to ID3v2.4 ones (e.g. TYER to
430 TDRC). If you intend to save tags, you must call this function
431 at some point; it is called by default when loading the tag.
434 if self.version < (2,3,0): del self.unknown_frames[:]
435 # unsafe to write
437 # TDAT, TYER, and TIME have been turned into TDRC.
438 try:
439 if str(self.get("TYER", "")).strip("\x00"):
440 date = str(self.pop("TYER"))
441 if str(self.get("TDAT", "")).strip("\x00"):
442 dat = str(self.pop("TDAT"))
443 date = "%s-%s-%s" % (date, dat[2:], dat[:2])
444 if str(self.get("TIME", "")).strip("\x00"):
445 time = str(self.pop("TIME"))
446 date += "T%s:%s:00" % (time[:2], time[2:])
447 if "TDRC" not in self:
448 self.add(TDRC(encoding=0, text=date))
449 except UnicodeDecodeError:
450 # Old ID3 tags have *lots* of Unicode problems, so if TYER
451 # is bad, just chuck the frames.
452 pass
454 # TORY can be the first part of a TDOR.
455 if "TORY" in self:
456 f = self.pop("TORY")
457 if "TDOR" not in self:
458 try:
459 self.add(TDOR(encoding=0, text=str(f)))
460 except UnicodeDecodeError:
461 pass
463 # IPLS is now TIPL.
464 if "IPLS" in self:
465 f = self.pop("IPLS")
466 if "TIPL" not in self:
467 self.add(TIPL(encoding=f.encoding, people=f.people))
469 if "TCON" in self:
470 # Get rid of "(xx)Foobr" format.
471 self["TCON"].genres = self["TCON"].genres
473 if self.version < (2, 3):
474 # ID3v2.2 PIC frames are slightly different.
475 pics = self.getall("APIC")
476 mimes = { "PNG": "image/png", "JPG": "image/jpeg" }
477 self.delall("APIC")
478 for pic in pics:
479 newpic = APIC(
480 encoding=pic.encoding, mime=mimes.get(pic.mime, pic.mime),
481 type=pic.type, desc=pic.desc, data=pic.data)
482 self.add(newpic)
484 # ID3v2.2 LNK frames are just way too different to upgrade.
485 self.delall("LINK")
487 # These can't be trivially translated to any ID3v2.4 tags, or
488 # should have been removed already.
489 for key in ["RVAD", "EQUA", "TRDA", "TSIZ", "TDAT", "TIME", "CRM"]:
490 if key in self: del(self[key])
492 def delete(filename, delete_v1=True, delete_v2=True):
493 """Remove tags from a file.
495 Keyword arguments:
496 delete_v1 -- delete any ID3v1 tag
497 delete_v2 -- delete any ID3v2 tag
500 f = open(filename, 'rb+')
502 if delete_v1:
503 try:
504 f.seek(-128, 2)
505 except IOError: pass
506 else:
507 if f.read(3) == "TAG":
508 f.seek(-128, 2)
509 f.truncate()
511 # technically an insize=0 tag is invalid, but we delete it anyway
512 # (primarily because we used to write it)
513 if delete_v2:
514 f.seek(0, 0)
515 idata = f.read(10)
516 try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata)
517 except struct.error: id3, insize = '', -1
518 insize = BitPaddedInt(insize)
519 if id3 == 'ID3' and insize >= 0:
520 delete_bytes(f, insize + 10, 0)
522 class BitPaddedInt(int):
523 def __new__(cls, value, bits=7, bigendian=True):
524 "Strips 8-bits bits out of every byte"
525 mask = (1<<(bits))-1
526 if isinstance(value, (int, long)):
527 bytes = []
528 while value:
529 bytes.append(value & ((1<<bits)-1))
530 value = value >> 8
531 if isinstance(value, str):
532 bytes = [ord(byte) & mask for byte in value]
533 if bigendian: bytes.reverse()
534 numeric_value = 0
535 for shift, byte in zip(range(0, len(bytes)*bits, bits), bytes):
536 numeric_value += byte << shift
537 if isinstance(numeric_value, long):
538 self = long.__new__(BitPaddedLong, numeric_value)
539 else:
540 self = int.__new__(BitPaddedInt, numeric_value)
541 self.bits = bits
542 self.bigendian = bigendian
543 return self
545 def as_str(value, bits=7, bigendian=True, width=4):
546 bits = getattr(value, 'bits', bits)
547 bigendian = getattr(value, 'bigendian', bigendian)
548 value = int(value)
549 mask = (1<<bits)-1
550 bytes = []
551 while value:
552 bytes.append(value & mask)
553 value = value >> bits
554 # PCNT and POPM use growing integers of at least 4 bytes as counters.
555 if width == -1: width = max(4, len(bytes))
556 if len(bytes) > width:
557 raise ValueError, 'Value too wide (%d bytes)' % len(bytes)
558 else: bytes.extend([0] * (width-len(bytes)))
559 if bigendian: bytes.reverse()
560 return ''.join(map(chr, bytes))
561 to_str = staticmethod(as_str)
563 class BitPaddedLong(long):
564 def as_str(value, bits=7, bigendian=True, width=4):
565 return BitPaddedInt.to_str(value, bits, bigendian, width)
566 to_str = staticmethod(as_str)
568 class unsynch(object):
569 def decode(value):
570 output = []
571 safe = True
572 append = output.append
573 for val in value:
574 if safe:
575 append(val)
576 safe = val != '\xFF'
577 else:
578 if val >= '\xE0': raise ValueError('invalid sync-safe string')
579 elif val != '\x00': append(val)
580 safe = True
581 if not safe: raise ValueError('string ended unsafe')
582 return ''.join(output)
583 decode = staticmethod(decode)
585 def encode(value):
586 output = []
587 safe = True
588 append = output.append
589 for val in value:
590 if safe:
591 append(val)
592 if val == '\xFF': safe = False
593 elif val == '\x00' or val >= '\xE0':
594 append('\x00')
595 append(val)
596 safe = val != '\xFF'
597 else:
598 append(val)
599 safe = True
600 if not safe: append('\x00')
601 return ''.join(output)
602 encode = staticmethod(encode)
604 class Spec(object):
605 def __init__(self, name): self.name = name
606 def __hash__(self): raise TypeError("Spec objects are unhashable")
608 class ByteSpec(Spec):
609 def read(self, frame, data): return ord(data[0]), data[1:]
610 def write(self, frame, value): return chr(value)
611 def validate(self, frame, value): return value
613 class IntegerSpec(Spec):
614 def read(self, frame, data):
615 return int(BitPaddedInt(data, bits=8)), ''
616 def write(self, frame, value):
617 return BitPaddedInt.to_str(value, bits=8, width=-1)
618 def validate(self, frame, value):
619 return value
621 class SizedIntegerSpec(Spec):
622 def __init__(self, name, size):
623 self.name, self.__sz = name, size
624 def read(self, frame, data):
625 return int(BitPaddedInt(data[:self.__sz], bits=8)), data[self.__sz:]
626 def write(self, frame, value):
627 return BitPaddedInt.to_str(value, bits=8, width=self.__sz)
628 def validate(self, frame, value):
629 return value
631 class EncodingSpec(ByteSpec):
632 def read(self, frame, data):
633 enc, data = super(EncodingSpec, self).read(frame, data)
634 if enc < 16: return enc, data
635 else: return 0, chr(enc)+data
637 def validate(self, frame, value):
638 if 0 <= value <= 3: return value
639 if value is None: return None
640 raise ValueError, 'Invalid Encoding: %r' % value
642 class StringSpec(Spec):
643 def __init__(self, name, length):
644 super(StringSpec, self).__init__(name)
645 self.len = length
646 def read(s, frame, data): return data[:s.len], data[s.len:]
647 def write(s, frame, value):
648 if value is None: return '\x00' * s.len
649 else: return (str(value) + '\x00' * s.len)[:s.len]
650 def validate(s, frame, value):
651 if value is None: return None
652 if isinstance(value, basestring) and len(value) == s.len: return value
653 raise ValueError, 'Invalid StringSpec[%d] data: %r' % (s.len, value)
655 class BinaryDataSpec(Spec):
656 def read(self, frame, data): return data, ''
657 def write(self, frame, value): return str(value)
658 def validate(self, frame, value): return str(value)
660 class EncodedTextSpec(Spec):
661 # Okay, seriously. This is private and defined explicitly and
662 # completely by the ID3 specification. You can't just add
663 # encodings here however you want.
664 _encodings = ( ('latin1', '\x00'), ('utf16', '\x00\x00'),
665 ('utf_16_be', '\x00\x00'), ('utf8', '\x00') )
667 def read(self, frame, data):
668 enc, term = self._encodings[frame.encoding]
669 ret = ''
670 if len(term) == 1:
671 if term in data:
672 data, ret = data.split(term, 1)
673 else:
674 offset = -1
675 try:
676 while True:
677 offset = data.index(term, offset+1)
678 if offset & 1: continue
679 data, ret = data[0:offset], data[offset+2:]; break
680 except ValueError: pass
682 if len(data) < len(term): return u'', ret
683 return data.decode(enc), ret
685 def write(self, frame, value):
686 enc, term = self._encodings[frame.encoding]
687 return value.encode(enc) + term
689 def validate(self, frame, value): return unicode(value)
691 class MultiSpec(Spec):
692 def __init__(self, name, *specs, **kw):
693 super(MultiSpec, self).__init__(name)
694 self.specs = specs
695 self.sep = kw.get('sep')
697 def read(self, frame, data):
698 values = []
699 while data:
700 record = []
701 for spec in self.specs:
702 value, data = spec.read(frame, data)
703 record.append(value)
704 if len(self.specs) != 1: values.append(record)
705 else: values.append(record[0])
706 return values, data
708 def write(self, frame, value):
709 data = []
710 if len(self.specs) == 1:
711 for v in value:
712 data.append(self.specs[0].write(frame, v))
713 else:
714 for record in value:
715 for v, s in zip(record, self.specs):
716 data.append(s.write(frame, v))
717 return ''.join(data)
719 def validate(self, frame, value):
720 if value is None: return []
721 if self.sep and isinstance(value, basestring):
722 value = value.split(self.sep)
723 if isinstance(value, list):
724 if len(self.specs) == 1:
725 return [self.specs[0].validate(frame, v) for v in value]
726 else:
727 return [
728 [s.validate(frame, v) for (v,s) in zip(val, self.specs)]
729 for val in value ]
730 raise ValueError, 'Invalid MultiSpec data: %r' % value
732 class EncodedNumericTextSpec(EncodedTextSpec): pass
733 class EncodedNumericPartTextSpec(EncodedTextSpec): pass
735 class Latin1TextSpec(EncodedTextSpec):
736 def read(self, frame, data):
737 if '\x00' in data: data, ret = data.split('\x00',1)
738 else: ret = ''
739 return data.decode('latin1'), ret
741 def write(self, data, value):
742 return value.encode('latin1') + '\x00'
744 def validate(self, frame, value): return unicode(value)
746 class ID3TimeStamp(object):
747 """A time stamp in ID3v2 format.
749 This is a restricted form of the ISO 8601 standard; time stamps
750 take the form of:
751 YYYY-MM-DD HH:MM:SS
752 Or some partial form (YYYY-MM-DD HH, YYYY, etc.).
754 The 'text' attribute contains the raw text data of the time stamp.
757 import re
758 def __init__(self, text):
759 if isinstance(text, ID3TimeStamp): text = text.text
760 self.text = text
762 __formats = ['%04d'] + ['%02d'] * 5
763 __seps = ['-', '-', ' ', ':', ':', 'x']
764 def get_text(self):
765 parts = [self.year, self.month, self.day,
766 self.hour, self.minute, self.second]
767 pieces = []
768 for i, part in enumerate(iter(iter(parts).next, None)):
769 pieces.append(self.__formats[i]%part + self.__seps[i])
770 return u''.join(pieces)[:-1]
772 def set_text(self, text, splitre=re.compile('[-T:/.]|\s+')):
773 year, month, day, hour, minute, second = \
774 splitre.split(text + ':::::')[:6]
775 for a in 'year month day hour minute second'.split():
776 try: v = int(locals()[a])
777 except ValueError: v = None
778 setattr(self, a, v)
780 text = property(get_text, set_text, doc="ID3v2.4 date and time.")
782 def __str__(self): return self.text
783 def __repr__(self): return repr(self.text)
784 def __cmp__(self, other): return cmp(self.text, other.text)
785 def encode(self, *args): return self.text.encode(*args)
787 class TimeStampSpec(EncodedTextSpec):
788 def read(self, frame, data):
789 value, data = super(TimeStampSpec, self).read(frame, data)
790 return self.validate(frame, value), data
792 def write(self, frame, data):
793 return super(TimeStampSpec, self).write(frame,
794 data.text.replace(' ', 'T'))
796 def validate(self, frame, value):
797 try: return ID3TimeStamp(value)
798 except TypeError: raise ValueError, "Invalid ID3TimeStamp: %r" % value
800 class ChannelSpec(ByteSpec):
801 (OTHER, MASTER, FRONTRIGHT, FRONTLEFT, BACKRIGHT, BACKLEFT, FRONTCENTRE,
802 BACKCENTRE, SUBWOOFER) = range(9)
804 class VolumeAdjustmentSpec(Spec):
805 def read(self, frame, data):
806 value, = unpack('>h', data[0:2])
807 return value/512.0, data[2:]
809 def write(self, frame, value):
810 return pack('>h', int(round(value * 512)))
812 def validate(self, frame, value): return value
814 class VolumePeakSpec(Spec):
815 def read(self, frame, data):
816 # http://bugs.xmms.org/attachment.cgi?id=113&action=view
817 peak = 0
818 bits = ord(data[0])
819 bytes = min(4, (bits + 7) >> 3)
820 # not enough frame data
821 if bytes + 1 > len(data): raise ID3JunkFrameError
822 shift = ((8 - (bits & 7)) & 7) + (4 - bytes) * 8
823 for i in range(1, bytes+1):
824 peak *= 256
825 peak += ord(data[i])
826 peak *= 2**shift
827 return (float(peak) / (2**31-1)), data[1+bytes:]
829 def write(self, frame, value):
830 # always write as 16 bits for sanity.
831 return "\x10" + pack('>H', int(round(value * 32768)))
833 def validate(self, frame, value): return value
835 class SynchronizedTextSpec(EncodedTextSpec):
836 def read(self, frame, data):
837 texts = []
838 encoding, term = self._encodings[frame.encoding]
839 while data:
840 l = len(term)
841 value_idx = data.index(term)
842 value = data[:value_idx].decode(encoding)
843 time, = struct.unpack(">I", data[value_idx+l:value_idx+l+4])
844 texts.append((value, time))
845 data = data[value_idx+l+4:]
846 return texts, ""
848 def write(self, frame, value):
849 data = []
850 encoding, term = self._encodings[frame.encoding]
851 for text, time in frame.text:
852 text = text.encode(encoding) + term
853 data.append(text + struct.pack(">I", time))
854 return "".join(data)
856 def validate(self, frame, value):
857 return value
859 class KeyEventSpec(Spec):
860 def read(self, frame, data):
861 events = []
862 while len(data) >= 5:
863 events.append(struct.unpack(">bI", data[:5]))
864 data = data[5:]
865 return events, data
867 def write(self, frame, value):
868 return "".join([struct.pack(">bI", *event) for event in value])
870 def validate(self, frame, value):
871 return value
873 class VolumeAdjustmentsSpec(Spec):
874 # Not to be confused with VolumeAdjustmentSpec.
875 def read(self, frame, data):
876 adjustments = {}
877 while len(data) >= 4:
878 freq, adj = struct.unpack(">Hh", data[:4])
879 data = data[4:]
880 freq /= 2.0
881 adj /= 512.0
882 adjustments[freq] = adj
883 adjustments = adjustments.items()
884 adjustments.sort()
885 return adjustments, data
887 def write(self, frame, value):
888 value.sort()
889 return "".join([struct.pack(">Hh", int(freq * 2), int(adj * 512))
890 for (freq, adj) in value])
892 def validate(self, frame, value):
893 return value
895 class ASPIIndexSpec(Spec):
896 def read(self, frame, data):
897 if frame.b == 16:
898 format = "H"
899 size = 2
900 elif frame.b == 8:
901 format = "B"
902 size = 1
903 else:
904 warn("invalid bit count in ASPI (%d)" % frame.b, ID3Warning)
905 return [], data
907 indexes = data[:frame.N * size]
908 data = data[frame.N * size:]
909 return list(struct.unpack(">" + format * frame.N, indexes)), data
911 def write(self, frame, values):
912 if frame.b == 16: format = "H"
913 elif frame.b == 8: format = "B"
914 else: raise ValueError("frame.b must be 8 or 16")
915 return struct.pack(">" + format * frame.N, *values)
917 def validate(self, frame, values):
918 return values
920 class Frame(object):
921 """Fundamental unit of ID3 data.
923 ID3 tags are split into frames. Each frame has a potentially
924 different structure, and so this base class is not very featureful.
927 FLAG23_ALTERTAG = 0x8000
928 FLAG23_ALTERFILE = 0x4000
929 FLAG23_READONLY = 0x2000
930 FLAG23_COMPRESS = 0x0080
931 FLAG23_ENCRYPT = 0x0040
932 FLAG23_GROUP = 0x0020
934 FLAG24_ALTERTAG = 0x4000
935 FLAG24_ALTERFILE = 0x2000
936 FLAG24_READONLY = 0x1000
937 FLAG24_GROUPID = 0x0040
938 FLAG24_COMPRESS = 0x0008
939 FLAG24_ENCRYPT = 0x0004
940 FLAG24_UNSYNCH = 0x0002
941 FLAG24_DATALEN = 0x0001
943 _framespec = []
944 def __init__(self, *args, **kwargs):
945 if len(args)==1 and len(kwargs)==0 and isinstance(args[0], type(self)):
946 other = args[0]
947 for checker in self._framespec:
948 val = checker.validate(self, getattr(other, checker.name))
949 setattr(self, checker.name, val)
950 else:
951 for checker, val in zip(self._framespec, args):
952 setattr(self, checker.name, checker.validate(self, val))
953 for checker in self._framespec[len(args):]:
954 validated = checker.validate(
955 self, kwargs.get(checker.name, None))
956 setattr(self, checker.name, validated)
958 HashKey = property(
959 lambda s: s.FrameID,
960 doc="an internal key used to ensure frame uniqueness in a tag")
961 FrameID = property(
962 lambda s: type(s).__name__,
963 doc="ID3v2 three or four character frame ID")
965 def __repr__(self):
966 """Python representation of a frame.
968 The string returned is a valid Python expression to construct
969 a copy of this frame.
971 kw = []
972 for attr in self._framespec:
973 kw.append('%s=%r' % (attr.name, getattr(self, attr.name)))
974 return '%s(%s)' % (type(self).__name__, ', '.join(kw))
976 def _readData(self, data):
977 odata = data
978 for reader in self._framespec:
979 if len(data):
980 try: value, data = reader.read(self, data)
981 except UnicodeDecodeError:
982 raise ID3JunkFrameError
983 else: raise ID3JunkFrameError
984 setattr(self, reader.name, value)
985 if data.strip('\x00'):
986 warn('Leftover data: %s: %r (from %r)' % (
987 type(self).__name__, data, odata),
988 ID3Warning)
990 def _writeData(self):
991 data = []
992 for writer in self._framespec:
993 data.append(writer.write(self, getattr(self, writer.name)))
994 return ''.join(data)
996 def pprint(self):
997 """Return a human-readable representation of the frame."""
998 return "%s=%s" % (type(self).__name__, self._pprint())
1000 def _pprint(self):
1001 return "[unrepresentable data]"
1003 def fromData(cls, id3, tflags, data):
1004 """Construct this ID3 frame from raw string data."""
1006 if (2,4,0) <= id3.version:
1007 if tflags & (Frame.FLAG24_COMPRESS | Frame.FLAG24_DATALEN):
1008 # The data length int is syncsafe in 2.4 (but not 2.3).
1009 # However, we don't actually need the data length int,
1010 # except to work around a QL 0.12 bug, and in that case
1011 # all we need are the raw bytes.
1012 datalen_bytes = data[:4]
1013 data = data[4:]
1014 if tflags & Frame.FLAG24_UNSYNCH or id3.f_unsynch:
1015 try: data = unsynch.decode(data)
1016 except ValueError, err:
1017 if id3.PEDANTIC:
1018 raise ID3BadUnsynchData, '%s: %r' % (err, data)
1019 if tflags & Frame.FLAG24_ENCRYPT:
1020 raise ID3EncryptionUnsupportedError
1021 if tflags & Frame.FLAG24_COMPRESS:
1022 try: data = data.decode('zlib')
1023 except zlibError, err:
1024 # the initial mutagen that went out with QL 0.12 did not
1025 # write the 4 bytes of uncompressed size. Compensate.
1026 data = datalen_bytes + data
1027 try: data = data.decode('zlib')
1028 except zlibError, err:
1029 if id3.PEDANTIC:
1030 raise ID3BadCompressedData, '%s: %r' % (err, data)
1032 elif (2,3,0) <= id3.version:
1033 if tflags & Frame.FLAG23_COMPRESS:
1034 usize, = unpack('>L', data[:4])
1035 data = data[4:]
1036 if tflags & Frame.FLAG23_ENCRYPT:
1037 raise ID3EncryptionUnsupportedError
1038 if tflags & Frame.FLAG23_COMPRESS:
1039 try: data = data.decode('zlib')
1040 except zlibError, err:
1041 if id3.PEDANTIC:
1042 raise ID3BadCompressedData, '%s: %r' % (err, data)
1044 frame = cls()
1045 frame._rawdata = data
1046 frame._flags = tflags
1047 frame._readData(data)
1048 return frame
1049 fromData = classmethod(fromData)
1051 def __hash__(self):
1052 raise TypeError("Frame objects are unhashable")
1054 class FrameOpt(Frame):
1055 """A frame with optional parts.
1057 Some ID3 frames have optional data; this class extends Frame to
1058 provide support for those parts.
1060 _optionalspec = []
1062 def __init__(self, *args, **kwargs):
1063 super(FrameOpt, self).__init__(*args, **kwargs)
1064 for spec in self._optionalspec:
1065 if spec.name in kwargs:
1066 validated = spec.validate(self, kwargs[spec.name])
1067 setattr(self, spec.name, validated)
1068 else: break
1070 def _readData(self, data):
1071 odata = data
1072 for reader in self._framespec:
1073 if len(data): value, data = reader.read(self, data)
1074 else: raise ID3JunkFrameError
1075 setattr(self, reader.name, value)
1076 if data:
1077 for reader in self._optionalspec:
1078 if len(data): value, data = reader.read(self, data)
1079 else: break
1080 setattr(self, reader.name, value)
1081 if data.strip('\x00'):
1082 warn('Leftover data: %s: %r (from %r)' % (
1083 type(self).__name__, data, odata),
1084 ID3Warning)
1086 def _writeData(self):
1087 data = []
1088 for writer in self._framespec:
1089 data.append(writer.write(self, getattr(self, writer.name)))
1090 for writer in self._optionalspec:
1091 try: data.append(writer.write(self, getattr(self, writer.name)))
1092 except AttributeError: break
1093 return ''.join(data)
1095 def __repr__(self):
1096 kw = []
1097 for attr in self._framespec:
1098 kw.append('%s=%r' % (attr.name, getattr(self, attr.name)))
1099 for attr in self._optionalspec:
1100 if hasattr(self, attr.name):
1101 kw.append('%s=%r' % (attr.name, getattr(self, attr.name)))
1102 return '%s(%s)' % (type(self).__name__, ', '.join(kw))
1105 class TextFrame(Frame):
1106 """Text strings.
1108 Text frames support casts to unicode or str objects, as well as
1109 list-like indexing, extend, and append.
1111 Iterating over a TextFrame iterates over its strings, not its
1112 characters.
1114 Text frames have a 'text' attribute which is the list of strings,
1115 and an 'encoding' attribute; 0 for ISO-8859 1, 1 UTF-16, 2 for
1116 UTF-16BE, and 3 for UTF-8. If you don't want to worry about
1117 encodings, just set it to 3.
1120 _framespec = [ EncodingSpec('encoding'),
1121 MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000') ]
1122 def __str__(self): return self.__unicode__().encode('utf-8')
1123 def __unicode__(self): return u'\u0000'.join(self.text)
1124 def __eq__(self, other):
1125 if isinstance(other, str): return str(self) == other
1126 elif isinstance(other, unicode):
1127 return u'\u0000'.join(self.text) == other
1128 return self.text == other
1129 def __getitem__(self, item): return self.text[item]
1130 def __iter__(self): return iter(self.text)
1131 def append(self, value): return self.text.append(value)
1132 def extend(self, value): return self.text.extend(value)
1133 def _pprint(self): return " / ".join(self.text)
1135 class NumericTextFrame(TextFrame):
1136 """Numerical text strings.
1138 The numeric value of these frames can be gotten with unary plus, e.g.
1139 frame = TLEN('12345')
1140 length = +frame
1143 _framespec = [ EncodingSpec('encoding'),
1144 MultiSpec('text', EncodedNumericTextSpec('text'), sep=u'\u0000') ]
1146 def __pos__(self):
1147 """Return the numerical value of the string."""
1148 return int(self.text[0])
1150 class NumericPartTextFrame(TextFrame):
1151 """Multivalue numerical text strings.
1153 These strings indicate 'part (e.g. track) X of Y', and unary plus
1154 returns the first value:
1155 frame = TRCK('4/15')
1156 track = +frame # track == 4
1159 _framespec = [ EncodingSpec('encoding'),
1160 MultiSpec('text', EncodedNumericPartTextSpec('text'), sep=u'\u0000') ]
1161 def __pos__(self):
1162 return int(self.text[0].split("/")[0])
1164 class TimeStampTextFrame(TextFrame):
1165 """A list of time stamps.
1167 The 'text' attribute in this frame is a list of ID3TimeStamp
1168 objects, not a list of strings.
1171 _framespec = [ EncodingSpec('encoding'),
1172 MultiSpec('text', TimeStampSpec('stamp'), sep=u',') ]
1173 def __str__(self): return self.__unicode__().encode('utf-8')
1174 def __unicode__(self): return ','.join([stamp.text for stamp in self.text])
1175 def _pprint(self):
1176 return " / ".join([stamp.text for stamp in self.text])
1178 class UrlFrame(Frame):
1179 """A frame containing a URL string.
1181 The ID3 specification is silent about IRIs and normalized URL
1182 forms. Mutagen assumes all URLs in files are encoded as Latin 1,
1183 but string conversion of this frame returns a UTF-8 representation
1184 for compatibility with other string conversions.
1186 The only sane way to handle URLs in MP3s is to restrict them to
1187 ASCII.
1190 _framespec = [ Latin1TextSpec('url') ]
1191 def __str__(self): return self.url.encode('utf-8')
1192 def __unicode__(self): return self.url
1193 def __eq__(self, other): return self.url == other
1194 def _pprint(self): return self.url
1196 class UrlFrameU(UrlFrame):
1197 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.url))
1199 class TALB(TextFrame): "Album"
1200 class TBPM(NumericTextFrame): "Beats per minute"
1201 class TCOM(TextFrame): "Composer"
1203 class TCON(TextFrame):
1204 """Content type (Genre)
1206 ID3 has several ways genres can be represented; for convenience,
1207 use the 'genres' property rather than the 'text' attribute.
1210 from mutagen._constants import GENRES
1212 def __get_genres(self):
1213 genres = []
1214 import re
1215 genre_re = re.compile(r"((?:\((?P<id>[0-9]+|RX|CR)\))*)(?P<str>.+)?")
1216 for value in self.text:
1217 if value.isdigit():
1218 try: genres.append(self.GENRES[int(value)])
1219 except IndexError: genres.append(u"Unknown")
1220 elif value == "CR": genres.append(u"Cover")
1221 elif value == "RX": genres.append(u"Remix")
1222 elif value:
1223 newgenres = []
1224 genreid, dummy, genrename = genre_re.match(value).groups()
1226 if genreid:
1227 for gid in genreid[1:-1].split(")("):
1228 if gid.isdigit() and int(gid) < len(self.GENRES):
1229 gid = unicode(self.GENRES[int(gid)])
1230 newgenres.append(gid)
1231 elif gid == "CR": newgenres.append(u"Cover")
1232 elif gid == "RX": newgenres.append(u"Remix")
1233 else: newgenres.append(u"Unknown")
1235 if genrename:
1236 # "Unescaping" the first parenthesis
1237 if genrename.startswith("(("): genrename = genrename[1:]
1238 if genrename not in newgenres: newgenres.append(genrename)
1240 genres.extend(newgenres)
1242 return genres
1244 def __set_genres(self, genres):
1245 if isinstance(genres, basestring): genres = [genres]
1246 self.text = map(self.__decode, genres)
1248 def __decode(self, value):
1249 if isinstance(value, str):
1250 enc = EncodedTextSpec._encodings[self.encoding][0]
1251 return value.decode(enc)
1252 else: return value
1254 genres = property(__get_genres, __set_genres, None,
1255 "A list of genres parsed from the raw text data.")
1257 def _pprint(self):
1258 return " / ".join(self.genres)
1260 class TCOP(TextFrame): "Copyright (c)"
1261 class TCMP(NumericTextFrame): "iTunes Compilation Flag"
1262 class TDAT(TextFrame): "Date of recording (DDMM)"
1263 class TDEN(TimeStampTextFrame): "Encoding Time"
1264 class TDOR(TimeStampTextFrame): "Original Release Time"
1265 class TDLY(NumericTextFrame): "Audio Delay (ms)"
1266 class TDRC(TimeStampTextFrame): "Recording Time"
1267 class TDRL(TimeStampTextFrame): "Release Time"
1268 class TDTG(TimeStampTextFrame): "Tagging Time"
1269 class TENC(TextFrame): "Encoder"
1270 class TEXT(TextFrame): "Lyricist"
1271 class TFLT(TextFrame): "File type"
1272 class TIME(TextFrame): "Time of recording (HHMM)"
1273 class TIT1(TextFrame): "Content group description"
1274 class TIT2(TextFrame): "Title"
1275 class TIT3(TextFrame): "Subtitle/Description refinement"
1276 class TKEY(TextFrame): "Starting Key"
1277 class TLAN(TextFrame): "Audio Languages"
1278 class TLEN(NumericTextFrame): "Audio Length (ms)"
1279 class TMED(TextFrame): "Source Media Type"
1280 class TMOO(TextFrame): "Mood"
1281 class TOAL(TextFrame): "Original Album"
1282 class TOFN(TextFrame): "Original Filename"
1283 class TOLY(TextFrame): "Original Lyricist"
1284 class TOPE(TextFrame): "Original Artist/Performer"
1285 class TORY(NumericTextFrame): "Original Release Year"
1286 class TOWN(TextFrame): "Owner/Licensee"
1287 class TPE1(TextFrame): "Lead Artist/Performer/Soloist/Group"
1288 class TPE2(TextFrame): "Band/Orchestra/Accompaniment"
1289 class TPE3(TextFrame): "Conductor"
1290 class TPE4(TextFrame): "Interpreter/Remixer/Modifier"
1291 class TPOS(NumericPartTextFrame): "Part of set"
1292 class TPRO(TextFrame): "Produced (P)"
1293 class TPUB(TextFrame): "Publisher"
1294 class TRCK(NumericPartTextFrame): "Track Number"
1295 class TRDA(TextFrame): "Recording Dates"
1296 class TRSN(TextFrame): "Internet Radio Station Name"
1297 class TRSO(TextFrame): "Internet Radio Station Owner"
1298 class TSIZ(NumericTextFrame): "Size of audio data (bytes)"
1299 class TSOA(TextFrame): "Album Sort Order key"
1300 class TSOP(TextFrame): "Perfomer Sort Order key"
1301 class TSOT(TextFrame): "Title Sort Order key"
1302 class TSRC(TextFrame): "International Standard Recording Code (ISRC)"
1303 class TSSE(TextFrame): "Encoder settings"
1304 class TSST(TextFrame): "Set Subtitle"
1305 class TYER(NumericTextFrame): "Year of recording"
1307 class TXXX(TextFrame):
1308 """User-defined text data.
1310 TXXX frames have a 'desc' attribute which is set to any Unicode
1311 value (though the encoding of the text and the description must be
1312 the same). Many taggers use this frame to store freeform keys.
1314 _framespec = [ EncodingSpec('encoding'), EncodedTextSpec('desc'),
1315 MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000') ]
1316 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1317 def _pprint(self): return "%s=%s" % (self.desc, " / ".join(self.text))
1319 class WCOM(UrlFrameU): "Commercial Information"
1320 class WCOP(UrlFrame): "Copyright Information"
1321 class WOAF(UrlFrame): "Official File Information"
1322 class WOAR(UrlFrameU): "Official Artist/Performer Information"
1323 class WOAS(UrlFrame): "Official Source Information"
1324 class WORS(UrlFrame): "Official Internet Radio Information"
1325 class WPAY(UrlFrame): "Payment Information"
1326 class WPUB(UrlFrame): "Official Publisher Information"
1328 class WXXX(UrlFrame):
1329 """User-defined URL data.
1331 Like TXXX, this has a freeform description associated with it.
1333 _framespec = [ EncodingSpec('encoding'), EncodedTextSpec('desc'),
1334 Latin1TextSpec('url') ]
1335 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1337 class PairedTextFrame(Frame):
1338 """Paired text strings.
1340 Some ID3 frames pair text strings, to associate names with a more
1341 specific involvement in the song. The 'people' attribute of these
1342 frames contains a list of pairs:
1343 [['trumpet', 'Miles Davis'], ['bass', 'Paul Chambers']]
1345 Like text frames, these frames also have an encoding attribute.
1348 _framespec = [ EncodingSpec('encoding'), MultiSpec('people',
1349 EncodedTextSpec('involvement'), EncodedTextSpec('person')) ]
1350 def __eq__(self, other):
1351 return self.people == other
1353 class TIPL(PairedTextFrame): "Involved People List"
1354 class TMCL(PairedTextFrame): "Musicians Credits List"
1355 class IPLS(TIPL): "Involved People List"
1357 class MCDI(Frame):
1358 """Binary dump of CD's TOC.
1360 The 'data' attribute contains the raw byte string.
1362 _framespec = [ BinaryDataSpec('data') ]
1363 def __eq__(self, other): return self.data == other
1365 class ETCO(Frame):
1366 """Event timing codes."""
1367 _framespec = [ ByteSpec("format"), KeyEventSpec("events") ]
1368 def __eq__(self, other): return self.events == other
1370 class MLLT(Frame):
1371 """MPEG location lookup table.
1373 This frame's attributes may be changed in the future based on
1374 feedback from real-world use.
1376 _framespec = [ SizedIntegerSpec('frames', 2),
1377 SizedIntegerSpec('bytes', 3),
1378 SizedIntegerSpec('milliseconds', 3),
1379 ByteSpec('bits_for_bytes'),
1380 ByteSpec('bits_for_milliseconds'),
1381 BinaryDataSpec('data') ]
1382 def __eq__(self, other): return self.data == other
1384 class SYTC(Frame):
1385 """Synchronised tempo codes.
1387 This frame's attributes may be changed in the future based on
1388 feedback from real-world use.
1390 _framespec = [ ByteSpec("format"), BinaryDataSpec("data") ]
1391 def __eq__(self, other): return self.data == other
1393 class USLT(Frame):
1394 """Unsynchronised lyrics/text transcription.
1396 Lyrics have a three letter ISO language code ('lang'), a
1397 description ('desc'), and a block of plain text ('text').
1400 _framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1401 EncodedTextSpec('desc'), EncodedTextSpec('text') ]
1402 HashKey = property(lambda s: '%s:%s:%r' % (s.FrameID, s.desc, s.lang))
1404 def __str__(self): return self.text.encode('utf-8')
1405 def __unicode__(self): return self.text
1406 def __eq__(self, other): return self.text == other
1408 class SYLT(Frame):
1409 """Synchronised lyrics/text."""
1411 _framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1412 ByteSpec('format'), ByteSpec('type'), EncodedTextSpec('desc'),
1413 SynchronizedTextSpec('text') ]
1414 HashKey = property(lambda s: '%s:%s:%r' % (s.FrameID, s.desc, s.lang))
1416 def __eq__(self, other):
1417 return str(self) == other
1419 def __str__(self):
1420 return "".join([text for (text, time) in self.text]).encode('utf-8')
1422 class COMM(TextFrame):
1423 """User comment.
1425 User comment frames have a descrption, like TXXX, and also a three
1426 letter ISO language code in the 'lang' attribute.
1428 _framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1429 EncodedTextSpec('desc'),
1430 MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000') ]
1431 HashKey = property(lambda s: '%s:%s:%r' % (s.FrameID, s.desc, s.lang))
1432 def _pprint(self): return "%s=%r=%s" % (
1433 self.desc, self.lang, " / ".join(self.text))
1435 class RVA2(Frame):
1436 """Relative volume adjustment (2).
1438 This frame is used to implemented volume scaling, and in
1439 particular, normalization using ReplayGain.
1441 Attributes:
1442 desc -- description or context of this adjustment
1443 channel -- audio channel to adjust (master is 1)
1444 gain -- a + or - dB gain relative to some reference level
1445 peak -- peak of the audio as a floating point number, [0, 1]
1447 When storing ReplayGain tags, use descriptions of 'album' and
1448 'track' on channel 1.
1451 _framespec = [ Latin1TextSpec('desc'), ChannelSpec('channel'),
1452 VolumeAdjustmentSpec('gain'), VolumePeakSpec('peak') ]
1453 _channels = ["Other", "Master volume", "Front right", "Front left",
1454 "Back right", "Back left", "Front centre", "Back centre",
1455 "Subwoofer"]
1456 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1458 def __eq__(self, other):
1459 return ((str(self) == other) or
1460 (self.desc == other.desc and
1461 self.channel == other.channel and
1462 self.gain == other.gain and
1463 self.peak == other.peak))
1465 def __str__(self):
1466 return "%s: %+0.4f dB/%0.4f" % (
1467 self._channels[self.channel], self.gain, self.peak)
1469 class EQU2(Frame):
1470 """Equalisation (2).
1472 Attributes:
1473 method -- interpolation method (0 = band, 1 = linear)
1474 desc -- identifying description
1475 adjustments -- list of (frequency, vol_adjustment) pairs
1477 _framespec = [ ByteSpec("method"), Latin1TextSpec("desc"),
1478 VolumeAdjustmentsSpec("adjustments") ]
1479 def __eq__(self, other): return self.adjustments == other
1480 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1482 # class RVAD: unsupported
1483 # class EQUA: unsupported
1485 class RVRB(Frame):
1486 """Reverb."""
1487 _framespec = [ SizedIntegerSpec('left', 2), SizedIntegerSpec('right', 2),
1488 ByteSpec('bounce_left'), ByteSpec('bounce_right'),
1489 ByteSpec('feedback_ltl'), ByteSpec('feedback_ltr'),
1490 ByteSpec('feedback_rtr'), ByteSpec('feedback_rtl'),
1491 ByteSpec('premix_ltr'), ByteSpec('premix_rtl') ]
1493 def __eq__(self, other): return (self.left, self.right) == other
1495 class APIC(Frame):
1496 """Attached (or linked) Picture.
1498 Attributes:
1499 encoding -- text encoding for the description
1500 mime -- a MIME type (e.g. image/jpeg) or '-->' if the data is a URI
1501 type -- the source of the image (3 is the album front cover)
1502 desc -- a text description of the image
1503 data -- raw image data, as a byte string
1505 Mutagen will automatically compress large images when saving tags.
1507 _framespec = [ EncodingSpec('encoding'), Latin1TextSpec('mime'),
1508 ByteSpec('type'), EncodedTextSpec('desc'), BinaryDataSpec('data') ]
1509 def __eq__(self, other): return self.data == other
1510 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1511 def _pprint(self):
1512 return "%s (%s, %d bytes)" % (
1513 self.desc, self.mime, len(self.data))
1515 class PCNT(Frame):
1516 """Play counter.
1518 The 'count' attribute contains the (recorded) number of times this
1519 file has been played.
1521 This frame is basically obsoleted by POPM.
1523 _framespec = [ IntegerSpec('count') ]
1525 def __eq__(self, other): return self.count == other
1526 def __pos__(self): return self.count
1527 def _pprint(self): return unicode(self.count)
1529 class POPM(Frame):
1530 """Popularimeter.
1532 This frame keys a rating (out of 255) and a play count to an email
1533 address.
1535 Attributes:
1536 email -- email this POPM frame is for
1537 rating -- rating from 0 to 255
1538 count -- number of times the files has been played
1540 _framespec = [ Latin1TextSpec('email'), ByteSpec('rating'),
1541 IntegerSpec('count') ]
1542 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.email))
1544 def __eq__(self, other): return self.rating == other
1545 def __pos__(self): return self.rating
1546 def _pprint(self): return "%s=%s %s/255" % (
1547 self.email, self.count, self.rating)
1549 class GEOB(Frame):
1550 """General Encapsulated Object.
1552 A blob of binary data, that is not a picture (those go in APIC).
1554 Attributes:
1555 encoding -- encoding of the description
1556 mime -- MIME type of the data or '-->' if the data is a URI
1557 filename -- suggested filename if extracted
1558 desc -- text description of the data
1559 data -- raw data, as a byte string
1561 _framespec = [ EncodingSpec('encoding'), Latin1TextSpec('mime'),
1562 EncodedTextSpec('filename'), EncodedTextSpec('desc'),
1563 BinaryDataSpec('data') ]
1564 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.desc))
1566 def __eq__(self, other): return self.data == other
1568 class RBUF(FrameOpt):
1569 """Recommended buffer size.
1571 Attributes:
1572 size -- recommended buffer size in bytes
1573 info -- if ID3 tags may be elsewhere in the file (optional)
1574 offset -- the location of the next ID3 tag, if any
1576 Mutagen will not find the next tag itself.
1578 _framespec = [ SizedIntegerSpec('size', 3) ]
1579 _optionalspec = [ ByteSpec('info'), SizedIntegerSpec('offset', 4) ]
1581 def __eq__(self, other): return self.size == other
1582 def __pos__(self): return self.size
1584 class AENC(FrameOpt):
1585 """Audio encryption.
1587 Attributes:
1588 owner -- key identifying this encryption type
1589 preview_start -- unencrypted data block offset
1590 preview_length -- number of unencrypted blocks
1591 data -- data required for decryption (optional)
1593 Mutagen cannot decrypt files.
1595 _framespec = [ Latin1TextSpec('owner'),
1596 SizedIntegerSpec('preview_start', 2),
1597 SizedIntegerSpec('preview_length', 2) ]
1598 _optionalspec = [ BinaryDataSpec('data') ]
1599 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.owner))
1601 def __str__(self): return self.owner.encode('utf-8')
1602 def __unicode__(self): return self.owner
1603 def __eq__(self, other): return self.owner == other
1605 class LINK(FrameOpt):
1606 """Linked information.
1608 Attributes:
1609 frameid -- the ID of the linked frame
1610 url -- the location of the linked frame
1611 data -- further ID information for the frame
1614 _framespec = [ StringSpec('frameid', 4), Latin1TextSpec('url') ]
1615 _optionalspec = [ BinaryDataSpec('data') ]
1616 def __HashKey(self):
1617 try:
1618 return "%s:%s:%s:%r" % (
1619 self.FrameID, self.frameid, self.url, self.data)
1620 except AttributeError:
1621 return "%s:%s:%s" % (self.FrameID, self.frameid, self.url)
1622 HashKey = property(__HashKey)
1623 def __eq__(self, other):
1624 try: return (self.frameid, self.url, self.data) == other
1625 except AttributeError: return (self.frameid, self.url) == other
1627 class POSS(Frame):
1628 """Position synchronisation frame
1630 Attribute:
1631 format -- format of the position attribute (frames or milliseconds)
1632 position -- current position of the file
1634 _framespec = [ ByteSpec('format'), IntegerSpec('position') ]
1636 def __pos__(self): return self.position
1637 def __eq__(self, other): return self.position == other
1639 class UFID(Frame):
1640 """Unique file identifier.
1642 Attributes:
1643 owner -- format/type of identifier
1644 data -- identifier
1647 _framespec = [ Latin1TextSpec('owner'), BinaryDataSpec('data') ]
1648 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.owner))
1649 def __eq__(s, o):
1650 if isinstance(o, UFI): return s.owner == o.owner and s.data == o.data
1651 else: return s.data == o
1652 def _pprint(self):
1653 isascii = ord(max(self.data)) < 128
1654 if isascii: return "%s=%s" % (self.owner, self.data)
1655 else: return "%s (%d bytes)" % (self.owner, len(self.data))
1657 class USER(Frame):
1658 """Terms of use.
1660 Attributes:
1661 encoding -- text encoding
1662 lang -- ISO three letter language code
1663 text -- licensing terms for the audio
1665 _framespec = [ EncodingSpec('encoding'), StringSpec('lang', 3),
1666 EncodedTextSpec('text') ]
1667 HashKey = property(lambda s: '%s:%r' % (s.FrameID, s.lang))
1669 def __str__(self): return self.text.encode('utf-8')
1670 def __unicode__(self): return self.text
1671 def __eq__(self, other): return self.text == other
1672 def _pprint(self): return "%r=%s" % (self.lang, self.text)
1674 class OWNE(Frame):
1675 """Ownership frame."""
1676 _framespec = [ EncodingSpec('encoding'), Latin1TextSpec('price'),
1677 StringSpec('date', 8), EncodedTextSpec('seller') ]
1679 def __str__(self): return self.seller.encode('utf-8')
1680 def __unicode__(self): return self.seller
1681 def __eq__(self, other): return self.seller == other
1683 class COMR(FrameOpt):
1684 """Commercial frame."""
1685 _framespec = [ EncodingSpec('encoding'), Latin1TextSpec('price'),
1686 StringSpec('valid_until', 8), Latin1TextSpec('contact'),
1687 ByteSpec('format'), EncodedTextSpec('seller'),
1688 EncodedTextSpec('desc')]
1689 _optionalspec = [ Latin1TextSpec('mime'), BinaryDataSpec('logo') ]
1690 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s._writeData()))
1691 def __eq__(self, other): return self._writeData() == other._writeData()
1693 class ENCR(Frame):
1694 """Encryption method registration.
1696 The standard does not allow multiple ENCR frames with the same owner
1697 or the same method. Mutagen only verifies that the owner is unique.
1699 _framespec = [ Latin1TextSpec('owner'), ByteSpec('method'),
1700 BinaryDataSpec('data') ]
1701 HashKey = property(lambda s: "%s:%s" % (s.FrameID, s.owner))
1702 def __str__(self): return self.data
1703 def __eq__(self, other): return self.data == other
1705 class GRID(FrameOpt):
1706 """Group identification registration."""
1707 _framespec = [ Latin1TextSpec('owner'), ByteSpec('group') ]
1708 _optionalspec = [ BinaryDataSpec('data') ]
1709 HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.group))
1710 def __pos__(self): return self.group
1711 def __str__(self): return self.owner.encode('utf-8')
1712 def __unicode__(self): return self.owner
1713 def __eq__(self, other): return self.owner == other or self.group == other
1716 class PRIV(Frame):
1717 """Private frame."""
1718 _framespec = [ Latin1TextSpec('owner'), BinaryDataSpec('data') ]
1719 HashKey = property(lambda s: '%s:%s:%s' % (
1720 s.FrameID, s.owner, s.data.decode('latin1')))
1721 def __str__(self): return self.data
1722 def __eq__(self, other): return self.data == other
1723 def _pprint(self):
1724 isascii = ord(max(self.data)) < 128
1725 if isascii: return "%s=%s" % (self.owner, self.data)
1726 else: return "%s (%d bytes)" % (self.owner, len(self.data))
1728 class SIGN(Frame):
1729 """Signature frame."""
1730 _framespec = [ ByteSpec('group'), BinaryDataSpec('sig') ]
1731 HashKey = property(lambda s: '%s:%c:%s' % (s.FrameID, s.group, s.sig))
1732 def __str__(self): return self.sig
1733 def __eq__(self, other): return self.sig == other
1735 class SEEK(Frame):
1736 """Seek frame.
1738 Mutagen does not find tags at seek offsets.
1740 _framespec = [ IntegerSpec('offset') ]
1741 def __pos__(self): return self.offset
1742 def __eq__(self, other): return self.offset == other
1744 class ASPI(Frame):
1745 """Audio seek point index.
1747 Attributes: S, L, N, b, and Fi. For the meaning of these, see
1748 the ID3v2.4 specification. Fi is a list of integers.
1750 _framespec = [ SizedIntegerSpec("S", 4), SizedIntegerSpec("L", 4),
1751 SizedIntegerSpec("N", 2), ByteSpec("b"),
1752 ASPIIndexSpec("Fi") ]
1753 def __eq__(self, other): return self.Fi == other
1755 Frames = dict([(k,v) for (k,v) in globals().items()
1756 if len(k)==4 and isinstance(v, type) and issubclass(v, Frame)])
1757 """All supported ID3v2 frames, keyed by frame name."""
1758 del(k); del(v)
1760 # ID3v2.2 frames
1761 class UFI(UFID): "Unique File Identifier"
1763 class TT1(TIT1): "Content group description"
1764 class TT2(TIT2): "Title"
1765 class TT3(TIT3): "Subtitle/Description refinement"
1766 class TP1(TPE1): "Lead Artist/Performer/Soloist/Group"
1767 class TP2(TPE2): "Band/Orchestra/Accompaniment"
1768 class TP3(TPE3): "Conductor"
1769 class TP4(TPE4): "Interpreter/Remixer/Modifier"
1770 class TCM(TCOM): "Composer"
1771 class TXT(TEXT): "Lyricist"
1772 class TLA(TLAN): "Audio Language(s)"
1773 class TCO(TCON): "Content Type (Genre)"
1774 class TAL(TALB): "Album"
1775 class TPA(TPOS): "Part of set"
1776 class TRK(TRCK): "Track Number"
1777 class TRC(TSRC): "International Standard Recording Code (ISRC)"
1778 class TYE(TYER): "Year of recording"
1779 class TDA(TDAT): "Date of recording (DDMM)"
1780 class TIM(TIME): "Time of recording (HHMM)"
1781 class TRD(TRDA): "Recording Dates"
1782 class TMT(TMED): "Source Media Type"
1783 class TFT(TFLT): "File Type"
1784 class TBP(TBPM): "Beats per minute"
1785 class TCP(TCMP): "iTunes Compilation Flag"
1786 class TCR(TCOP): "Copyright (C)"
1787 class TPB(TPUB): "Publisher"
1788 class TEN(TENC): "Encoder"
1789 class TSS(TSSE): "Encoder settings"
1790 class TOF(TOFN): "Original Filename"
1791 class TLE(TLEN): "Audio Length (ms)"
1792 class TSI(TSIZ): "Audio Data size (bytes)"
1793 class TDY(TDLY): "Audio Delay (ms)"
1794 class TKE(TKEY): "Starting Key"
1795 class TOT(TOAL): "Original Album"
1796 class TOA(TOPE): "Original Artist/Perfomer"
1797 class TOL(TOLY): "Original Lyricist"
1798 class TOR(TORY): "Original Release Year"
1800 class TXX(TXXX): "User-defined Text"
1802 class WAF(WOAF): "Official File Information"
1803 class WAR(WOAR): "Official Artist/Performer Information"
1804 class WAS(WOAS): "Official Source Information"
1805 class WCM(WCOM): "Commercial Information"
1806 class WCP(WCOP): "Copyright Information"
1807 class WPB(WPUB): "Official Publisher Information"
1809 class WXX(WXXX): "User-defined URL"
1811 class IPL(IPLS): "Involved people list"
1812 class MCI(MCDI): "Binary dump of CD's TOC"
1813 class ETC(ETCO): "Event timing codes"
1814 class MLL(MLLT): "MPEG location lookup table"
1815 class STC(SYTC): "Synced tempo codes"
1816 class ULT(USLT): "Unsychronised lyrics/text transcription"
1817 class SLT(SYLT): "Synchronised lyrics/text"
1818 class COM(COMM): "Comment"
1819 #class RVA(RVAD)
1820 #class EQU(EQUA)
1821 class REV(RVRB): "Reverb"
1822 class PIC(APIC):
1823 """Attached Picture.
1825 The 'mime' attribute of an ID3v2.2 attached picture must be either
1826 'PNG' or 'JPG'.
1828 _framespec = [ EncodingSpec('encoding'), StringSpec('mime', 3),
1829 ByteSpec('type'), EncodedTextSpec('desc'), BinaryDataSpec('data') ]
1830 class GEO(GEOB): "General Encapsulated Object"
1831 class CNT(PCNT): "Play counter"
1832 class POP(POPM): "Popularimeter"
1833 class BUF(RBUF): "Recommended buffer size"
1835 class CRM(Frame):
1836 """Encrypted meta frame"""
1837 _framespec = [ Latin1TextSpec('owner'), Latin1TextSpec('desc'),
1838 BinaryDataSpec('data') ]
1839 def __eq__(self, other): return self.data == other
1841 class CRA(AENC): "Audio encryption"
1843 class LNK(LINK):
1844 """Linked information"""
1845 _framespec = [ StringSpec('frameid', 3), Latin1TextSpec('url') ]
1846 _optionalspec = [ BinaryDataSpec('data') ]
1848 Frames_2_2 = dict([(k,v) for (k,v) in globals().items()
1849 if len(k)==3 and isinstance(v, type) and issubclass(v, Frame)])
1851 # support open(filename) as interface
1852 Open = ID3
1854 # ID3v1.1 support.
1855 def ParseID3v1(string):
1856 """Parse an ID3v1 tag, returning a list of ID3v2.4 frames."""
1857 from struct import error as StructError
1858 frames = {}
1859 try:
1860 tag, title, artist, album, year, comment, track, genre = unpack(
1861 "3s30s30s30s4s29sBB", string)
1862 except StructError: return None
1864 if tag != "TAG": return None
1865 def fix(string):
1866 return string.split("\x00")[0].strip().decode('latin1')
1867 title, artist, album, year, comment = map(
1868 fix, [title, artist, album, year, comment])
1870 if title: frames["TIT2"] = TIT2(encoding=0, text=title)
1871 if artist: frames["TPE1"] = TPE1(encoding=0, text=[artist])
1872 if album: frames["TALB"] = TALB(encoding=0, text=album)
1873 if year: frames["TDRC"] = TDRC(encoding=0, text=year)
1874 if comment: frames["COMM"] = COMM(
1875 encoding=0, lang="eng", desc="ID3v1 Comment", text=comment)
1876 # Don't read a track number if it looks like the comment was
1877 # padded with spaces instead of nulls (thanks, WinAmp).
1878 if track and (track != 32 or string[-3] == '\x00'):
1879 frames["TRCK"] = TRCK(encoding=0, text=str(track))
1880 if genre != 255: frames["TCON"] = TCON(encoding=0, text=str(genre))
1881 return frames
1883 def MakeID3v1(id3):
1884 """Return an ID3v1.1 tag string from a dict of ID3v2.4 frames."""
1886 v1 = {}
1888 for v2id, name in {"TIT2": "title", "TPE1": "artist",
1889 "TALB": "album"}.items():
1890 if v2id in id3:
1891 text = id3[v2id].text[0].encode('latin1', 'replace')[:30]
1892 else: text = ""
1893 v1[name] = text + ("\x00" * (30 - len(text)))
1895 if "COMM" in id3:
1896 cmnt = id3["COMM"].text[0].encode('latin1', 'replace')[:28]
1897 else: cmnt = ""
1898 v1["comment"] = cmnt + ("\x00" * (29 - len(cmnt)))
1900 if "TRCK" in id3:
1901 try: v1["track"] = chr(+id3["TRCK"])
1902 except ValueError: v1["track"] = "\x00"
1903 else: v1["track"] = "\x00"
1905 if "TCON" in id3:
1906 try: genre = id3["TCON"].genres[0]
1907 except IndexError: pass
1908 else:
1909 if genre in TCON.GENRES:
1910 v1["genre"] = chr(TCON.GENRES.index(genre))
1911 if "genre" not in v1: v1["genre"] = "\xff"
1913 if "TDRC" in id3: v1["year"] = str(id3["TDRC"])[:4]
1914 else: v1["year"] = "\x00\x00\x00\x00"
1916 return ("TAG%(title)s%(artist)s%(album)s%(year)s%(comment)s"
1917 "%(track)s%(genre)s") % v1
1919 class ID3FileType(mutagen.FileType):
1920 """An unknown type of file with ID3 tags."""
1922 class _Info(object):
1923 length = 0
1924 def __init__(self, fileobj, offset): pass
1925 pprint = staticmethod(lambda: "Unknown format with ID3 tag")
1927 def score(filename, fileobj, header):
1928 return header.startswith("ID3")
1929 score = staticmethod(score)
1931 def add_tags(self, ID3=ID3):
1932 """Add an empty ID3 tag to the file.
1934 A custom tag reader may be used in instead of the default
1935 mutagen.id3.ID3 object, e.g. an EasyID3 reader.
1937 if self.tags is None:
1938 self.tags = ID3()
1939 else:
1940 raise error("an ID3 tag already exists")
1942 def load(self, filename, ID3=ID3, **kwargs):
1943 """Load stream and tag information from a file.
1945 A custom tag reader may be used in instead of the default
1946 mutagen.id3.ID3 object, e.g. an EasyID3 reader.
1948 self.filename = filename
1949 try: self.tags = ID3(filename, **kwargs)
1950 except error: self.tags = None
1951 if self.tags is not None:
1952 try: offset = self.tags.size
1953 except AttributeError: offset = None
1954 else: offset = None
1955 try:
1956 fileobj = file(filename, "rb")
1957 self.info = self._Info(fileobj, offset)
1958 finally:
1959 fileobj.close()