def __init__(self, atoms, fileobj): for trak in list(atoms["moov"].findall("trak")): hdlr = trak["mdia", "hdlr"] fileobj.seek(hdlr.offset) data = fileobj.read(hdlr.length) if data[16:20] == "soun": break else: raise MP4StreamInfoError("track has no audio data") mdhd = trak["mdia", "mdhd"] fileobj.seek(mdhd.offset) data = fileobj.read(mdhd.length) if ord(data[8]) == 0: offset = 20 format = ">2I" else: offset = 28 format = ">IQ" end = offset + struct.calcsize(format) unit, length = struct.unpack(format, data[offset:end]) self.length = float(length) / unit try: atom = trak["mdia", "minf", "stbl", "stsd"] fileobj.seek(atom.offset) data = fileobj.read(atom.length) if data[20:24] == "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] == "esds" and ord(data[64:65]) == 0x03: pos = 65 # skip extended descriptor type tag, length, ES ID # and stream priority if data[pos:pos+3] == "\x80\x80\x80": pos += 3 pos += 4 # decoder config descriptor type if ord(data[pos]) == 0x04: pos += 1 # skip extended descriptor type tag, length, # object type ID, stream type, buffer size # and maximum bitrate if data[pos:pos+3] == "\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_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)) + delta fileobj.seek(atom.offset) fileobj.write(cdata.to_uint_be(size))
def __init__(self, atoms, fileobj): hdlr = atoms["moov.trak.mdia.hdlr"] fileobj.seek(hdlr.offset) if "soun" not in fileobj.read(hdlr.length): raise M4AStreamInfoError("track has no audio data") mdhd = atoms["moov.trak.mdia.mdhd"] fileobj.seek(mdhd.offset) data = fileobj.read(mdhd.length) if ord(data[8]) == 0: offset = 20 format = ">2I" else: offset = 28 format = ">IQ" end = offset + struct.calcsize(format) unit, length = struct.unpack(format, data[offset:end]) self.length = float(length) / unit try: atom = atoms["moov.trak.mdia.minf.stbl.stsd"] fileobj.seek(atom.offset) data = fileobj.read(atom.length) self.bitrate = cdata.uint_be(data[-17:-13]) except (ValueError, KeyError): # Bitrate values are optional. pass
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 != "data": raise MP4MetadataError( "unexpected atom %r inside %r" % (atom_name, atom.name)) value.append(data[pos+16:pos+length]) pos += length if value: self["%s:%s:%s" % (atom.name, mean, name)] = value
def __parse_freeform(self, atom, data): try: fileobj = StringIO(data) mean_length = cdata.uint_be(fileobj.read(4)) # skip over 8 bytes of atom name, flags mean = fileobj.read(mean_length - 4)[8:] name_length = cdata.uint_be(fileobj.read(4)) name = fileobj.read(name_length - 4)[8:] value_length = cdata.uint_be(fileobj.read(4)) # Name, flags, and reserved bytes value = fileobj.read(value_length - 4)[12:] except struct.error: # Some ---- atoms have no data atom, I have no clue why # they actually end up in the file. pass else: self["%s:%s:%s" % (atom.name, mean, name)] = value
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("\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 __parse_text(self, atom, data): flags = cdata.uint_be(data[8:12]) if flags == 1: self[atom.name] = data[16:].decode('utf-8', 'replace')