def __read_offsets(self, filename): fileobj = open(filename, 'rb') atoms = Atoms(fileobj) moov = atoms[b'moov'] samples = [] for atom in moov.findall(b'stco', True): fileobj.seek(atom.offset + 12) data = fileobj.read(atom.length - 12) fmt = ">{}I".format(cdata.uint_be(data[:4])) offsets = struct.unpack(fmt, data[4:]) for offset in offsets: fileobj.seek(offset) samples.append(fileobj.read(8)) for atom in moov.findall(b'co64', True): fileobj.seek(atom.offset + 12) data = fileobj.read(atom.length - 12) fmt = ">{}Q".format(cdata.uint_be(data[:4])) offsets = struct.unpack(fmt, data[4:]) for offset in offsets: fileobj.seek(offset) samples.append(fileobj.read(8)) try: for atom in atoms[b"moof"].findall(b'tfhd', True): data = fileobj.read(atom.length - 9) flags = cdata.uint_be(b"\x00" + data[:3]) if flags & 1: offset = cdata.ulonglong_be(data[7:15]) fileobj.seek(offset) samples.append(fileobj.read(8)) except KeyError: pass fileobj.close() return samples
def __init__(self, atoms, fileobj): for trak in list(atoms[b"moov"].findall(b"trak")): hdlr = trak[b"mdia", b"hdlr"] fileobj.seek(hdlr.offset) data = fileobj.read(hdlr.length) if data[16:20] == b"soun": break else: raise MP4StreamInfoError("track has no audio data") mdhd = trak[b"mdia", b"mdhd"] fileobj.seek(mdhd.offset) data = fileobj.read(mdhd.length) if data[8] == 0: offset = 20 fmt = ">2I" else: offset = 28 fmt = ">IQ" end = offset + struct.calcsize(fmt) unit, length = struct.unpack(fmt, data[offset:end]) self.length = length / unit try: atom = trak[b"mdia", b"minf", b"stbl", b"stsd"] fileobj.seek(atom.offset) data = fileobj.read(atom.length) if data[20:24] == b"mp4a": length = cdata.uint_be(data[16:20]) (self.channels, self.bits_per_sample, _, self.sample_rate) = struct.unpack(">3HI", data[40:50]) # ES descriptor type if data[56:60] == b"esds" and data[64] == b'\x03'[0]: pos = 65 # skip extended descriptor type tag, length, ES ID # and stream priority if data[pos:pos + 3] == b"\x80\x80\x80": pos += 3 pos += 4 # decoder config descriptor type if data[pos] == b'\x04'[0]: pos += 1 # skip extended descriptor type tag, length, # object type ID, stream type, buffer size # and maximum bitrate if data[pos:pos + 3] == b"\x80\x80\x80": pos += 3 pos += 10 # average bitrate self.bitrate = cdata.uint_be(data[pos:pos + 4]) except (ValueError, KeyError): # stsd atoms are optional pass
def __init__(self, atoms, fileobj): for trak in list(atoms[b"moov"].findall(b"trak")): hdlr = trak[b"mdia", b"hdlr"] fileobj.seek(hdlr.offset) data = fileobj.read(hdlr.length) if data[16:20] == b"soun": break else: raise MP4StreamInfoError("track has no audio data") mdhd = trak[b"mdia", b"mdhd"] fileobj.seek(mdhd.offset) data = fileobj.read(mdhd.length) if data[8] == 0: offset = 20 fmt = ">2I" else: offset = 28 fmt = ">IQ" end = offset + struct.calcsize(fmt) unit, length = struct.unpack(fmt, data[offset:end]) self.length = length / unit try: atom = trak[b"mdia", b"minf", b"stbl", b"stsd"] fileobj.seek(atom.offset) data = fileobj.read(atom.length) if data[20:24] == b"mp4a": length = cdata.uint_be(data[16:20]) (self.channels, self.bits_per_sample, _, self.sample_rate) = struct.unpack(">3HI", data[40:50]) # ES descriptor type if data[56:60] == b"esds" and data[64] == b'\x03'[0]: pos = 65 # skip extended descriptor type tag, length, ES ID # and stream priority if data[pos:pos+3] == b"\x80\x80\x80": pos += 3 pos += 4 # decoder config descriptor type if data[pos] == b'\x04'[0]: pos += 1 # skip extended descriptor type tag, length, # object type ID, stream type, buffer size # and maximum bitrate if data[pos:pos+3] == b"\x80\x80\x80": pos += 3 pos += 10 # average bitrate self.bitrate = cdata.uint_be(data[pos:pos+4]) except (ValueError, KeyError): # stsd atoms are optional pass
def __update_offset_table(self, fileobj, fmt, atom, delta, offset): """Update offset table in the specified atom.""" if atom.offset > offset: atom.offset += delta fileobj.seek(atom.offset + 12) data = fileobj.read(atom.length - 12) fmt = fmt % cdata.uint_be(data[:4]) offsets = struct.unpack(fmt, data[4:]) offsets = [o + (0, delta)[offset < o] for o in offsets] fileobj.seek(atom.offset + 16) fileobj.write(struct.pack(fmt, *offsets))
def __update_tfhd(self, fileobj, atom, delta, offset): if atom.offset > offset: atom.offset += delta fileobj.seek(atom.offset + 9) data = fileobj.read(atom.length - 9) flags = cdata.uint_be(b"\x00" + data[:3]) if flags & 1: o = cdata.ulonglong_be(data[7:15]) if o > offset: o += delta fileobj.seek(atom.offset + 16) fileobj.write(cdata.to_ulonglong_be(o))
def __update_parents(self, fileobj, path, delta): """Update all parent atoms with the new size.""" for atom in path: fileobj.seek(atom.offset) size = cdata.uint_be(fileobj.read(4)) if size == 1: # 64bit # skip name (4B) and read size (8B) size = cdata.ulonglong_be(fileobj.read(12)[4:]) fileobj.seek(atom.offset + 8) fileobj.write(cdata.to_ulonglong_be(size + delta)) else: # 32bit fileobj.seek(atom.offset) fileobj.write(cdata.to_uint_be(size + delta))
def __parse_freeform(self, atom, data): length = cdata.uint_be(data[:4]) mean = data[12:length] pos = length length = cdata.uint_be(data[pos:pos + 4]) name = data[pos + 12:pos + length] pos += length value = [] while pos < atom.length - 8: length, atom_name = struct.unpack(">I4s", data[pos:pos + 8]) if atom_name != b"data": raise MP4MetadataError("unexpected atom %r inside %r" % (atom_name, atom.name)) version = data[pos + 8] if version != 0: raise MP4MetadataError("Unsupported version: %r" % version) flags = struct.unpack(">I", b"\x00" + data[pos + 9:pos + 12])[0] value.append( MP4FreeForm(data[pos + 16:pos + length], dataformat=flags)) pos += length if value: self[atom.name + b":" + mean + b":" + name] = value
def __parse_freeform(self, atom, data): length = cdata.uint_be(data[:4]) mean = data[12:length] pos = length length = cdata.uint_be(data[pos:pos+4]) name = data[pos+12:pos+length] pos += length value = [] while pos < atom.length - 8: length, atom_name = struct.unpack(">I4s", data[pos:pos+8]) if atom_name != b"data": raise MP4MetadataError( "unexpected atom %r inside %r" % (atom_name, atom.name)) version = data[pos+8] if version != 0: raise MP4MetadataError("Unsupported version: %r" % version) flags = struct.unpack(">I", b"\x00" + data[pos+9:pos+12])[0] value.append(MP4FreeForm(data[pos+16:pos+length], dataformat=flags)) pos += length if value: self[atom.name + b":" + mean + b":" + name] = value
def __init__(self, fileobj): page = OggPage(fileobj) while not page.packets[0].startswith(b"\x80theora"): page = OggPage(fileobj) if not page.first: raise OggTheoraHeaderError( "page has ID header, but doesn't start a stream") data = page.packets[0] vmaj, vmin = struct.unpack("2B", data[7:9]) if (vmaj, vmin) != (3, 2): raise OggTheoraHeaderError( "found Theora version %d.%d != 3.2" % (vmaj, vmin)) fps_num, fps_den = struct.unpack(">2I", data[22:30]) self.fps = fps_num / fps_den self.bitrate = cdata.uint_be(b"\x00" + data[37:40]) self.granule_shift = (cdata.ushort_be(data[40:42]) >> 5) & 0x1F self.serial = page.serial
def __init__(self, fileobj): page = OggPage(fileobj) while not page.packets[0].startswith(b"\x80theora"): page = OggPage(fileobj) if not page.first: raise OggTheoraHeaderError( "page has ID header, but doesn't start a stream") data = page.packets[0] vmaj, vmin = struct.unpack("2B", data[7:9]) if (vmaj, vmin) != (3, 2): raise OggTheoraHeaderError( "found Theora version %d.%d != 3.2" % (vmaj, vmin)) fps_num, fps_den = struct.unpack(">2I", data[22:30]) self.fps = fps_num / float(fps_den) self.bitrate = cdata.uint_be(b"\x00" + data[37:40]) self.granule_shift = (cdata.ushort_be(data[40:42]) >> 5) & 0x1F self.serial = page.serial