def __save_existing(self, fileobj, atoms, path, ilst_data, padding_func): # Replace the old ilst atom. ilst = path[-1] offset = ilst.offset length = ilst.length # Use adjacent free atom if there is one free = _find_padding(path) if free is not None: offset = min(offset, free.offset) length += free.length # Always add a padding atom to make things easier padding_overhead = len(Atom.render(b"free", b"")) content_size = get_size(fileobj) - (offset + length) padding_size = length - (len(ilst_data) + padding_overhead) info = PaddingInfo(padding_size, content_size) new_padding = info._get_padding(padding_func) # Limit padding size so we can be sure the free atom overhead is as we # calculated above (see Atom.render) new_padding = min(0xFFFFFFFF, new_padding) ilst_data += Atom.render(b"free", b"\x00" * new_padding) resize_bytes(fileobj, length, len(ilst_data), offset) delta = len(ilst_data) - length fileobj.seek(offset) fileobj.write(ilst_data) self.__update_parents(fileobj, path[:-1], delta) self.__update_offsets(fileobj, atoms, delta, offset)
def __save_new(self, fileobj, atoms, ilst_data, padding_func): hdlr = Atom.render(b"hdlr", b"\x00" * 8 + b"mdirappl" + b"\x00" * 9) meta_data = b"\x00\x00\x00\x00" + hdlr + ilst_data try: path = atoms.path(b"moov", b"udta") except KeyError: path = atoms.path(b"moov") offset = path[-1]._dataoffset # ignoring some atom overhead... but we don't have padding left anyway # and padding_size is guaranteed to be less than zero content_size = get_size(fileobj) - offset padding_size = -len(meta_data) assert padding_size < 0 info = PaddingInfo(padding_size, content_size) new_padding = info._get_padding(padding_func) new_padding = min(0xFFFFFFFF, new_padding) free = Atom.render(b"free", b"\x00" * new_padding) meta = Atom.render(b"meta", meta_data + free) if path[-1].name != b"udta": # moov.udta not found -- create one data = Atom.render(b"udta", meta) else: data = meta insert_bytes(fileobj, len(data), offset) fileobj.seek(offset) fileobj.write(data) self.__update_parents(fileobj, path, len(data)) self.__update_offsets(fileobj, atoms, len(data), offset)
def save(self, filething=None, padding=None): values = [] items = sorted(self.items(), key=lambda kv: _item_sort_key(*kv)) for key, value in items: try: values.append(self._render(key, value)) except (TypeError, ValueError) as s: reraise(MP4MetadataValueError, s, sys.exc_info()[2]) for key, failed in iteritems(self._failed_atoms): # don't write atoms back if we have added a new one with # the same name, this excludes freeform which can have # multiple atoms with the same key (most parsers seem to be able # to handle that) if key in self: assert _key2name(key) != b"----" continue for data in failed: values.append(Atom.render(_key2name(key), data)) data = Atom.render(b"ilst", b"".join(values)) # Find the old atoms. try: atoms = Atoms(filething.fileobj) except AtomError as err: reraise(error, err, sys.exc_info()[2]) self.__save(filething.fileobj, atoms, data, padding)
def __fixed_render_cover(self, key, value): atom_data = [] for cover in value: try: imageformat = cover.imageformat except AttributeError: imageformat = MP4Cover.FORMAT_JPEG atom_data.append(Atom.render(b"data", struct.pack(">2I", imageformat, 0) + bytes(cover))) return Atom.render(_key2name(key), b"".join(atom_data))
def __fixed_render_cover(self, key, value): atom_data = [] for cover in value: try: imageformat = cover.imageformat except AttributeError: imageformat = MP4Cover.FORMAT_JPEG atom_data.append(Atom.render( b"data", struct.pack(">2I", imageformat, 0) + bytes(cover))) return Atom.render(_key2name(key), b"".join(atom_data))
def __render_data(self, key, version, flags, value): if isinstance(value, bytes): return Atom.render( _key2name(key), Atom.render( b"data", struct.pack(">2I", version << 24 | flags, 0) + value)) return Atom.render( _key2name(key), b"".join([ Atom.render( b"data", struct.pack(">2I", version << 24 | flags, 0) + data) for data in value ]))
def _parse_stsd(self, atom, fileobj): """Sets channels, bits_per_sample, sample_rate and optionally bitrate. Can raise MP4StreamInfoError. """ assert atom.name == b"stsd" ok, data = atom.read(fileobj) if not ok: raise MP4StreamInfoError("Invalid stsd") try: version, flags, data = parse_full_atom(data) except ValueError as e: raise MP4StreamInfoError(e) if version != 0: raise MP4StreamInfoError("Unsupported stsd version") try: num_entries, offset = cdata.uint32_be_from(data, 0) except cdata.error as e: raise MP4StreamInfoError(e) if num_entries == 0: return # look at the first entry if there is one entry_fileobj = cBytesIO(data[offset:]) try: entry_atom = Atom(entry_fileobj) except AtomError as e: raise MP4StreamInfoError(e) try: entry = AudioSampleEntry(entry_atom, entry_fileobj) except ASEntryError as e: raise MP4StreamInfoError(e) else: self.channels = entry.channels self.bits_per_sample = entry.sample_size self.sample_rate = entry.sample_rate self.bitrate = entry.bitrate self.codec = entry.codec self.codec_description = entry.codec_description
def __render_freeform(self, key, value): if isinstance(value, bytes): value = [value] dummy, mean, name = _key2name(key).split(b":", 2) mean = struct.pack(">I4sI", len(mean) + 12, b"mean", 0) + mean name = struct.pack(">I4sI", len(name) + 12, b"name", 0) + name data = b"" ret = b"" for v in value: flags = AtomDataType.UTF8 version = 0 if isinstance(v, MP4FreeForm): flags = v.dataformat version = v.version # for every element we give render once data = struct.pack(">I4s2I", len(v) + 16, b"data", version << 24 | flags, 0) data += v ret += Atom.render(b"----", mean + name + data) return ret