def test_long(self): if PY2: data = BitPaddedInt.to_str(sys.maxint + 1, width=16) val = BitPaddedInt(data) self.assertEqual(val, sys.maxint + 1) self.assertTrue(isinstance(val, BitPaddedLong)) else: self.assertTrue(BitPaddedInt is BitPaddedLong)
def _digest_from_file(self): with io.open(self.name, 'rb') as f: start = 0 end = self.size try: # technically an insize=0 tag is invalid, but we skip it anyway f.seek(start) idata = f.read(10) try: id3, insize = struct.unpack('>3sxxx4s', idata) insize = BitPaddedInt(insize) if id3 == 'ID3' and insize >= 0 and insize + 10 <= end: start = insize + 10 except struct.error: pass f.seek(-128, 2) idata = f.read(3) if idata == "TAG": end -= 128 except IOError: pass f.seek(start) buf = f.read(end - start) return hashlib.sha256(buf).hexdigest()
def __init__(self, fileobj, offset=None): """Parse MPEG stream information from a file-like object. If an offset argument is given, it is used to start looking for stream information and Xing headers; otherwise, ID3v2 tags will be skipped automatically. A correct offset can make loading files significantly faster. """ try: size = os.path.getsize(fileobj.name) except (IOError, OSError, AttributeError): fileobj.seek(0, 2) size = fileobj.tell() # If we don't get an offset, try to skip an ID3v2 tag. if offset is None: fileobj.seek(0, 0) idata = fileobj.read(10) try: id3, insize = struct.unpack('>3sxxx4s', idata) except struct.error: id3, insize = '', 0 insize = BitPaddedInt(insize) if id3 == 'ID3' and insize > 0: offset = insize + 10 else: offset = 0 # Try to find two valid headers (meaning, very likely MPEG data) # at the given offset, 30% through the file, 60% through the file, # and 90% through the file. for i in [offset, 0.3 * size, 0.6 * size, 0.9 * size]: try: self.__try(fileobj, int(i), size - offset) except error, e: pass else: break
def save_frame(frame): #Copied from mutagen.id3.ID3 flags = 0 framedata = frame._writeData() datasize = BitPaddedInt.to_str(len(framedata), width=4) header = pack('>4s4sH', type(frame).__name__, datasize, flags) return header + framedata
def save_frame(frame): #Copied from mutagen.id3.ID3 flags = 0 framedata = frame._writeData() datasize = BitPaddedInt.to_str(len(framedata), width=4) header = pack('>4s4sH', type(frame).__name__, datasize, flags) return header + framedata
def test_long(self): if PY2: data = BitPaddedInt.to_str(sys.maxint + 1, width=16) val = BitPaddedInt(data) self.assertEqual(val, sys.maxint + 1) self.assertTrue(isinstance(val, BitPaddedLong)) else: self.assertTrue(BitPaddedInt is BitPaddedLong)
def __save_frame(self, frame, v2): flags = 0 if self.PEDANTIC and isinstance(frame, TextFrame): if len(str(frame)) == 0: return '' framedata = frame._writeData() if v2 == 3: bits=8 else: bits=7 datasize = BitPaddedInt.to_str(len(framedata), width=4, bits=bits) header = pack('>4s4sH', type(frame).__name__, datasize, flags) return header + framedata
def get_id3(client, path, **defaults): logger = client.logger logger.info('getting ID3 for %r', path) resp = client.get_file(path) cl = long(resp.getheader('Content-Length')) logger.info('got resp status=%s; len=%d', resp.status, cl) # Possibly ID3v2 header data = resp.read(10) id3, vmaj, vrev, flags, size = unpack('>3sBBB4s', data) if id3 == 'ID3': extra = 100 # HACK to avoid ocasional EOF size = BitPaddedInt(size) logger.info( 'trying ID3v2.x; fetching additional' ' %s bytes + %s extra', size, extra) body = resp.read(size + extra) logger.info('got %s bytes' % len(body)) data += body resp.close() else: logger.info('trying ID3v1.x') resp.close() data = client.get_byte_range(path, cl - 128, cl) if not data.startswith('TAG'): logger.warning('no ID3 tags found') return None with NamedTemporaryFile('wb') as f: f.write(data) f.flush() try: tags = EasyID3(f.name) except ID3Error: logger.exception('exception from mutagen') return None logger.debug('extracted ID3 %r', tags) return id3_to_dict(tags, defaults)
def _id3_header_length(f): try: # technically an insize=0 tag is invalid, but we skip it anyway end = f.seek(0, io.SEEK_END) f.seek(0) idata = f.read(10) id3, insize = struct.unpack('>3sxxx4s', idata) insize = BitPaddedInt(insize) if id3 == 'ID3' and 0 <= insize <= end - 10: return insize + 10 except (IOError, struct.error): pass return 0
def __check_header(self, fileobj): size = 4 header = fileobj.read(4) if header != "fLaC": size = None if header[:3] == "ID3": size = 14 + BitPaddedInt(fileobj.read(6)[2:]) fileobj.seek(size - 4) if fileobj.read(4) != "fLaC": size = None if size is None: raise FLACNoHeaderError("%r is not a valid FLAC file" % fileobj.name) return size
def __init__(self, fileobj): header = bytearray(fileobj.read(32)) if len(header) != 32: raise MusepackHeaderError("not a Musepack file") # Skip ID3v2 tags if header[:3] == b"ID3": size = 10 + BitPaddedInt(header[6:10]) fileobj.seek(size) header = bytearray(fileobj.read(32)) if len(header) != 32: raise MusepackHeaderError("not a Musepack file") # SV7 if header.startswith(b"MP+"): self.version = header[3] & 0xF if self.version < 7: raise MusepackHeaderError("not a Musepack file") frames = cdata.uint_le(header[4:8]) flags = cdata.uint_le(header[8:12]) self.title_peak, self.title_gain = struct_unpack( "<Hh", header[12:16]) self.album_peak, self.album_gain = struct_unpack( "<Hh", header[16:20]) self.title_gain /= 100.0 self.album_gain /= 100.0 self.title_peak /= 65535.0 self.album_peak /= 65535.0 self.sample_rate = RATES[(flags >> 16) & 0x0003] self.bitrate = 0 # SV4-SV6 else: header_dword = cdata.uint_le(header[0:4]) self.version = (header_dword >> 11) & 0x03FF if self.version < 4 or self.version > 6: raise MusepackHeaderError("not a Musepack file") self.bitrate = (header_dword >> 23) & 0x01FF self.sample_rate = 44100 if self.version >= 5: frames = cdata.uint_le(header[4:8]) else: frames = cdata.ushort_le(header[6:8]) if self.version < 6: frames -= 1 self.channels = 2 self.length = float(frames * 1152 - 576) / self.sample_rate if not self.bitrate and self.length != 0: fileobj.seek(0, 2) self.bitrate = int(fileobj.tell() * 8 / (self.length * 1000) + 0.5)
def __init__(self, fileobj): # skip id3v2 header start_offset = 0 header = fileobj.read(10) from mutagen.id3 import BitPaddedInt if header.startswith(b"ID3"): size = BitPaddedInt(header[6:]) start_offset = size + 10 fileobj.seek(start_offset) adif = fileobj.read(4) if adif == b"ADIF": self._parse_adif(fileobj) self._type = "ADIF" else: self._parse_adts(fileobj, start_offset) self._type = "ADTS"
def __check_header(self, fileobj): """Returns the offset of the flac block start (skipping id3 tags if found). The passed fileobj will be advanced to that offset as well. """ size = 4 header = fileobj.read(4) if header != b"fLaC": size = None if header[:3] == b"ID3": size = 14 + BitPaddedInt(fileobj.read(6)[2:]) fileobj.seek(size - 4) if fileobj.read(4) != b"fLaC": size = None if size is None: raise FLACNoHeaderError("%r is not a valid FLAC file" % fileobj.name) return size
def __init__(self, fileobj): header = fileobj.read(4) if len(header) != 4: raise MusepackHeaderError("not a Musepack file") # Skip ID3v2 tags if header[:3] == b"ID3": header = fileobj.read(6) if len(header) != 6: raise MusepackHeaderError("not a Musepack file") size = 10 + BitPaddedInt(header[2:6]) fileobj.seek(size) header = fileobj.read(4) if len(header) != 4: raise MusepackHeaderError("not a Musepack file") if header.startswith(b"MPCK"): self.__parse_sv8(fileobj) else: self.__parse_sv467(fileobj) if not self.bitrate and self.length != 0: fileobj.seek(0, 2) self.bitrate = int(round(fileobj.tell() * 8 / self.length))
def test_s32b(self): self.assertEquals(BitPaddedInt(b'\xFF\xFF\xFF\xFF', bits=8).as_str(), b'\xFF\xFF\xFF\xFF')
def test_has_valid_padding(self): self.failUnless(BitPaddedInt.has_valid_padding(b"\xff\xff", bits=8)) self.failIf(BitPaddedInt.has_valid_padding(b"\xff")) self.failIf(BitPaddedInt.has_valid_padding(b"\x00\xff")) self.failUnless(BitPaddedInt.has_valid_padding(b"\x7f\x7f")) self.failIf(BitPaddedInt.has_valid_padding(b"\x7f", bits=6)) self.failIf(BitPaddedInt.has_valid_padding(b"\x9f", bits=6)) self.failUnless(BitPaddedInt.has_valid_padding(b"\x3f", bits=6)) self.failUnless(BitPaddedInt.has_valid_padding(0xff, bits=8)) self.failIf(BitPaddedInt.has_valid_padding(0xff)) self.failIf(BitPaddedInt.has_valid_padding(0xff << 8)) self.failUnless(BitPaddedInt.has_valid_padding(0x7f << 8)) self.failIf(BitPaddedInt.has_valid_padding(0x9f << 32, bits=6)) self.failUnless(BitPaddedInt.has_valid_padding(0x3f << 16, bits=6))
def test_minwidth(self): self.assertEquals( len(BitPaddedInt.to_str(100, width=-1, minwidth=6)), 6)
def test_str_int_init(self): from struct import pack self.assertEquals(BitPaddedInt(238).as_str(), BitPaddedInt(pack('>L', 238)).as_str())
def test_w129(self): self.assertEquals(BitPaddedInt.to_str(129, width=2), b'\x01\x01')
def test_s129(self): self.assertEquals(BitPaddedInt.to_str(129), b'\x00\x00\x01\x01')
def save(self, filename=None, v1=1, v2=4): """Save changes to a file. If no filename is given, the one most recently loaded is used. Keyword arguments: v1 -- if 0, ID3v1 tags will be removed if 1, ID3v1 tags will be updated but not added if 2, ID3v1 tags will be created and/or updated v2 -- version of ID3v2 tags (3 or 4). By default Mutagen saves ID3v2.4 tags. If you want to save ID3v2.3 tags, you must call method update_to_v23 before saving the file. The lack of a way to update only an ID3v1 tag is intentional. """ # Sort frames by 'importance' order = ["TIT2", "TPE1", "TRCK", "TALB", "TPOS", "TDRC", "TCON"] order = dict(zip(order, range(len(order)))) last = len(order) frames = self.items() frames.sort(lambda a, b: cmp(order.get(a[0][:4], last), order.get(b[0][:4], last))) framedata = [self.__save_frame(frame, v2) for (key, frame) in frames] framedata.extend([data for data in self.unknown_frames if len(data) > 10]) if not framedata: try: self.delete(filename) except EnvironmentError as err: from errno import ENOENT if err.errno != ENOENT: raise return framedata = ''.join(framedata) framesize = len(framedata) if filename is None: filename = self.filename try: f = open(filename, 'rb+') except IOError as err: from errno import ENOENT if err.errno != ENOENT: raise f = open(filename, 'ab') # create, then reopen f = open(filename, 'rb+') try: idata = f.read(10) try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata) except struct.error: id3, insize = '', 0 insize = BitPaddedInt(insize) if id3 != 'ID3': insize = -10 if insize >= framesize: outsize = insize else: outsize = (framesize + 1023) & ~0x3FF framedata += '\x00' * (outsize - framesize) framesize = BitPaddedInt.to_str(outsize, width=4) flags = 0 header = pack('>3sBBB4s', 'ID3', v2, 0, flags, framesize) data = header + framedata if (insize < outsize): insert_bytes(f, outsize - insize, insize + 10) f.seek(0) f.write(data) try: f.seek(-128, 2) except IOError as err: from errno import EINVAL if err.errno != EINVAL: raise f.seek(0, 2) # ensure read won't get "TAG" if f.read(3) == "TAG": f.seek(-128, 2) if v1 > 0: f.write(MakeID3v1(self)) else: f.truncate() elif v1 == 2: f.seek(0, 2) f.write(MakeID3v1(self)) finally: f.close()
idata = f.read(10) try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata) except struct.error: id3, insize = '', 0 insize = BitPaddedInt(insize) if id3 != 'ID3': insize = -10 if insize >= framesize: outsize = insize else: outsize = (framesize + 1023) & ~0x3FF framedata += '\x00' * (outsize - framesize) framesize = BitPaddedInt.to_str(outsize, width=4) flags = 0 header = pack('>3sBBB4s', 'ID3', v2, 0, flags, framesize) data = header + framedata if (insize < outsize): insert_bytes(f, outsize-insize, insize+10) f.seek(0) f.write(data) try: f.seek(-128, 2) except IOError, err: from errno import EINVAL if err.errno != EINVAL: raise
def test_s1(self): self.assertEquals(BitPaddedInt.to_str(1), "\x00\x00\x00\x01")
def save(self, filename=None, v1=0): """Save changes to a file. If no filename is given, the one most recently loaded is used. Keyword arguments: v1 -- if 0, ID3v1 tags will be removed if 1, ID3v1 tags will be updated but not added if 2, ID3v1 tags will be created and/or updated The lack of a way to update only an ID3v1 tag is intentional. """ # Sort frames by 'importance' order = ["TIT2", "TPE1", "TRCK", "TALB", "TPOS", "TDRC", "TCON"] order = dict(zip(order, range(len(order)))) last = len(order) frames = self.items() frames.sort(lambda a, b: cmp(order.get(a[0][:4], last), order.get(b[0][:4], last))) framedata = [self.__save_frame(frame) for (key, frame) in frames] framedata.extend([data for data in self.unknown_frames if len(data) > 10]) framedata = "".join(framedata) framesize = len(framedata) if filename is None: filename = self.filename f = open(filename, "rb+") try: idata = f.read(10) try: id3, vmaj, vrev, flags, insize = struct.unpack(">3sBBB4s", idata) except struct.error: id3, insize = "", 0 insize = BitPaddedInt(insize) if id3 != "ID3": insize = -10 if insize >= framesize: outsize = insize else: outsize = (framesize + 1023) & ~0x3FF framedata += "\x00" * (outsize - framesize) framesize = BitPaddedInt.to_str(outsize, width=4) flags = 0 header = struct.pack(">3sBBB4s", "ID3", 4, 0, flags, framesize) data = header + framedata if insize < outsize: insert_bytes(f, outsize - insize, insize + 10) f.seek(0) try: f.seek(-128, 2) except IOError, err: if err.errno != EINVAL: raise f.seek(0, 2) # ensure read won't get "TAG" if f.read(3) == "TAG": f.seek(-128, 2) if v1 > 0: f.write(MakeID3v1(self)) else: f.truncate() elif v1 == 2: f.seek(0, 2) f.write(MakeID3v1(self))
def test_s0(self): self.assertEquals(BitPaddedInt.to_str(0), b'\x00\x00\x00\x00')
def test_s1(self): self.assertEquals(BitPaddedInt.to_bytes(1), b'\x00\x00\x00\x01')
def test_s1l(self): self.assertEquals( BitPaddedInt.to_str(1, bigendian=False), b'\x01\x00\x00\x00')
def test_1l(self): self.assertEquals( BitPaddedInt(b'\x01\x00\x00\x00', bigendian=False), 1)
def test_s65(self): self.assertEquals(BitPaddedInt.to_str(0x41, 6), b'\x00\x00\x01\x01')
def test_129b(self): self.assertEquals(BitPaddedInt(b'\x00\x00\x01\x81'), 0x81)
def test_w129l(self): self.assertEquals( BitPaddedInt.to_str(129, width=2, bigendian=False), b'\x01\x01')
def test_65(self): self.assertEquals(BitPaddedInt(b'\x00\x00\x01\x81', 6), 0x41)
def test_varwidth(self): self.assertEquals(len(BitPaddedInt.to_str(100)), 4) self.assertEquals(len(BitPaddedInt.to_str(100, width=-1)), 4) self.assertEquals(len(BitPaddedInt.to_str(2 ** 32, width=-1)), 5)
def test_32b(self): self.assertEquals(BitPaddedInt(b'\xFF\xFF\xFF\xFF', bits=8), 0xFFFFFFFF)
def test_promote_long(self): l = BitPaddedInt(sys.maxint ** 2) self.assertTrue(isinstance(l, long)) self.assertEqual(BitPaddedInt(l.as_str(width=-1)), l)
def test_32bi(self): self.assertEquals(BitPaddedInt(0xFFFFFFFF, bits=8), 0xFFFFFFFF)
def test_1(self): self.assertEquals(BitPaddedInt(b'\x00\x00\x00\x01'), 1)
def test_has_valid_padding(self): self.failUnless(BitPaddedInt.has_valid_padding("\xff\xff", bits=8)) self.failIf(BitPaddedInt.has_valid_padding("\xff")) self.failIf(BitPaddedInt.has_valid_padding("\x00\xff")) self.failUnless(BitPaddedInt.has_valid_padding("\x7f\x7f")) self.failIf(BitPaddedInt.has_valid_padding("\x7f", bits=6)) self.failIf(BitPaddedInt.has_valid_padding("\x9f", bits=6)) self.failUnless(BitPaddedInt.has_valid_padding("\x3f", bits=6)) self.failUnless(BitPaddedInt.has_valid_padding(0xFF, bits=8)) self.failIf(BitPaddedInt.has_valid_padding(0xFF)) self.failIf(BitPaddedInt.has_valid_padding(0xFF << 8)) self.failUnless(BitPaddedInt.has_valid_padding(0x7F << 8)) self.failIf(BitPaddedInt.has_valid_padding(0x9F << 32, bits=6)) self.failUnless(BitPaddedInt.has_valid_padding(0x3F << 16, bits=6))