def _parse(self): self._trak, self._moov_pos = self._file.trak(self._label) stbl = get_sample_table(self._trak) co_box = next(find_boxes(stbl.boxes, [b"stco", b"co64"])) self._file.seek(self._moov_pos + co_box.header.start_pos + co_box.header.header_size + 4) # entry_count self._co_buffer = self._file.read(co_box.header.box_size - co_box.header.header_size - 4) # entry_count self._co = np.frombuffer( self._co_buffer, np.dtype(">u4") if co_box.header.type == b"stco" else np.dtype(">u8")) sz_box = next(find_boxes(stbl.boxes, [b"stsz"])) if sz_box.sample_size > 0: self._sz = np.full(co_box.entry_count, sz_box.sample_size, np.uint32) else: self._file.seek(self._moov_pos + sz_box.header.start_pos + sz_box.header.header_size + 4 + # sample_size 4) # sample_count self._sz_buffer = self._file.read(sz_box.header.box_size - sz_box.header.header_size - 4 - # sample_size 4) # sample_count self._sz = np.frombuffer(self._sz_buffer, np.dtype(">u4")) self._len = co_box.entry_count
def video_configuration_location(self): stsd = next(find_boxes(get_sample_table(self._trak).boxes, [b"stsd"])) c1 = next(find_boxes(stsd.boxes, [b"avc1", b"hec1", b"hvc1"]), None) if not c1: return None cC = next(find_boxes(c1.boxes, [b"avcC", b"hvcC"])) return (self._moov_pos + cC.header.start_pos + cC.header.header_size, cC.header.box_size - cC.header.header_size)
def _append_index_bzna_target(filename, moov_filename, mdat_targets_offset): moov = _load_moov(moov_filename) mvhd = next(find_boxes(moov.boxes, [b"mvhd"])) samples_trak = next(find_traks(moov.boxes, [b"bzna_input\0"])) # TRAK.MDIA.MINF.STBL stbl = samples_trak.boxes[-1].boxes[-1].boxes[-1] samples_offsets = next(find_boxes(stbl.boxes, [b"stco", b"co64"])) samples_sizes = next(find_boxes(stbl.boxes, [b"stsz"])) # bzna_target trak if next(find_traks(moov.boxes, [b"bzna_target\0"]), None) is not None: trak = next(find_traks(moov.boxes, [b"bzna_target\0"])) moov.boxes = [box for box in moov.boxes if box is not trak] targets_size = 0 targets = [] sizes = [] with open(filename, "rb") as container_file: for offset, size in zip( (o.chunk_offset for o in samples_offsets.entries), (s.entry_size for s in samples_sizes.samples)): container_file.seek(offset) sample_bstr = ConstBitStream(container_file.read(size)) # Create a new tmp object to hold the content sample_moov = next(find_boxes(Parser.parse(sample_bstr), [b"moov"])) sample_moov.load(sample_bstr) target = get_trak_sample_bytes(sample_bstr, sample_moov.boxes, b"bzna_target\0", 0) # Test subset is reached meaning that remaining entries will # not contain a target if target is None: break targets.append(target) sizes.append(len(target)) targets_size += len(target) # MOOV.TRAK trak = _make_bzna_target_trak(sizes, mdat_targets_offset, mvhd.next_track_id) moov.append(trak) mvhd.next_track_id += 1 moov.refresh_box_size() with open(filename, "rb+") as container_file, \ open(moov_filename, "wb") as moov_file: moov_file.write(bytes(moov)) container_file.seek(mdat_targets_offset) container_file.write(b''.join(targets)) return mdat_targets_offset + targets_size
def test_find_headers_at(): creation_time = utils.to_mp4_time(datetime(2019, 9, 15, 0, 0, 0)) modification_time = utils.to_mp4_time(datetime(2019, 9, 16, 0, 0, 0)) samples_sizes = [198297, 127477, 192476] samples_offset = 10 trak = utils.make_trak(creation_time, modification_time, samples_sizes, samples_offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] tkhd.track_id = 1 tkhd.width = [512, 0] tkhd.height = [512, 0] trak.refresh_box_size() buffer = io.BytesIO(bytes(trak)) for (pos, box_size, box_type, header_size), \ box in zip(mp4.find_headers_at(buffer, {b'tkhd', b'mdia'}, offset=trak.header.header_size), utils.find_boxes(trak.boxes, {b'tkhd', b'mdia'})): assert pos < buffer.tell() assert box_size == box.header.box_size assert box_type == box.header.type if isinstance(box.header, headers.FullBoxHeader): assert header_size + 4 == box.header.header_size else: assert header_size == box.header.header_size
def test_find_traks(): boxes = [ utils.make_meta_trak(0, 0, b"trak1\0", [], 0), utils.make_meta_trak(0, 0, b"trak2\0", [], 0), utils.make_meta_trak(0, 0, b"trak3\0", [], 0), bx_def.UnknownBox(hd_def.BoxHeader()), utils.make_meta_trak(0, 0, b"trak2\0", [], 0) ] boxes[3].header.type = b"0001" assert utils.get_name(next(utils.find_traks(boxes, b"trak1\0"))) == b"trak1\0" assert utils.get_name(next(utils.find_traks(boxes, b"trak2\0"))) == b"trak2\0" assert utils.get_name(next(utils.find_traks(boxes, b"trak3\0"))) == b"trak3\0" assert [ utils.get_name(trak) for trak in utils.find_traks(boxes, b"trak2\0") ] == [b"trak2\0", b"trak2\0"] assert [ utils.get_name(trak) for trak in utils.find_traks(boxes, [b"trak1\0", b"trak3\0"]) ] == [b"trak1\0", b"trak3\0"] assert next(utils.find_boxes(boxes, b"0004"), None) is None
def _update_mdat_size(filename, mdat_data_end): bstr = ConstBitStream(filename=filename) mdat = next(find_boxes(Parser.parse(bstr, recursive=False), [b"mdat"])) del bstr mdat.header.box_ext_size = mdat_data_end - mdat.header.start_pos # Update mdat header with open(filename, "rb+") as container_file: container_file.seek(mdat.header.start_pos) container_file.write(bytes(mdat.header)) return mdat_data_end
def _index_bzna_thumb(filename, moov_filename, mdat_data_end): moov = _load_moov(moov_filename) mvhd = next(find_boxes(moov.boxes, [b"mvhd"])) samples_trak = next(find_traks(moov.boxes, [b"bzna_input\0"])) # TRAK.MDIA.MINF.STBL stbl = samples_trak.boxes[-1].boxes[-1].boxes[-1] samples_offsets = next(find_boxes(stbl.boxes, [b"stco", b"co64"])) samples_sizes = next(find_boxes(stbl.boxes, [b"stsz"])) # bzna_target trak if next(find_traks(moov.boxes, [b"bzna_thumb\0"]), None) is not None: trak = next(find_traks(moov.boxes, [b"bzna_thumb\0"])) moov.boxes = [box for box in moov.boxes if box is not trak] hvc1 = None offsets = [] sizes = [] with open(filename, "rb") as container_file: for offset, size in zip( (o.chunk_offset for o in samples_offsets.entries), (s.entry_size for s in samples_sizes.samples)): container_file.seek(offset) sample_bstr = ConstBitStream(container_file.read(size)) # Create a new tmp object to hold the content sample_moov = next(find_boxes(Parser.parse(sample_bstr), [b"moov"])) sample_moov.load(sample_bstr) # MOOV.TRAK trak = next(find_traks(sample_moov.boxes, [b"bzna_thumb\0"])) # MOOV.TRAK.MDIA.MINF.STBL stbl = trak.boxes[-1].boxes[-1].boxes[-1] co = next(find_boxes(stbl.boxes, [b"stco", b"co64"])) stsz = next(find_boxes(stbl.boxes, [b"stsz"])) if hvc1 is None: # MOOV.TRAK.MDIA.MINF.STBL.STSD._VC1 hvc1 = stbl.boxes[0].boxes[-1] offsets.append(offset + co.entries[0].chunk_offset) sizes.append(stsz.sample_size) # MOOV.TRAK trak = _make_bzna_thumb_trak(hvc1, sizes, offsets, mvhd.next_track_id) moov.append(trak) mvhd.next_track_id += 1 moov.refresh_box_size() with open(moov_filename, "wb") as moov_file: moov_file.write(bytes(moov)) return mdat_data_end
def _index_bzna_input(filename, moov_filename, mdat_input_offset): moov = _load_moov(moov_filename) mvhd = next(find_boxes(moov.boxes, [b"mvhd"])) samples_headers = _get_samples_headers(filename, mdat_input_offset) samples_headers = jug.value(samples_headers) # bzna_input trak if next(find_traks(moov.boxes, [b"bzna_input\0"]), None) is not None: trak = next(find_traks(moov.boxes, [b"bzna_input\0"])) moov.boxes = [box for box in moov.boxes if box is not trak] samples_size = 0 sample_size = -1 sizes = [] for sample_header in samples_headers: # Every sample starts with a ftyp box if sample_header.type == b"ftyp": if sample_size >= 0: sizes.append(sample_size) samples_size += sample_size sample_size = 0 sample_size += sample_header.box_size sizes.append(sample_size) samples_size += sample_size # MOOV.TRAK trak = _make_bzna_input_trak(sizes, mdat_input_offset, mvhd.next_track_id) moov.append(trak) mvhd.next_track_id += 1 moov.refresh_box_size() with open(moov_filename, "wb") as moov_file: moov_file.write(bytes(moov)) return mdat_input_offset + samples_size
def test_get_sample_size_at(): creation_time = utils.to_mp4_time(datetime(2019, 9, 15, 0, 0, 0)) modification_time = utils.to_mp4_time(datetime(2019, 9, 16, 0, 0, 0)) samples_sizes = [198297, 127477, 192476] samples_offset = 10 trak = utils.make_trak(creation_time, modification_time, samples_sizes, samples_offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] tkhd.track_id = 1 tkhd.width = [512, 0] tkhd.height = [512, 0] trak.refresh_box_size() buffer = io.BytesIO(bytes(trak)) stbl_pos, _, _, _ = mp4.find_sample_table_at(buffer, 0) sz, _ = mp4.get_sample_size_at(buffer, stbl_pos) stbl = utils.get_sample_table(trak) stsz = next(utils.find_boxes(stbl.boxes, {b"stsz"})) assert (sz == [e.entry_size for e in stsz.samples]).all()
def test_find_boxes(): boxes = [ bx_def.UnknownBox(hd_def.BoxHeader()), bx_def.UnknownBox(hd_def.BoxHeader()), bx_def.UnknownBox(hd_def.BoxHeader()), bx_def.UnknownBox(hd_def.BoxHeader()) ] boxes[0].header.type = b"0001" boxes[1].header.type = b"0002" boxes[2].header.type = b"0003" boxes[3].header.type = b"0002" assert next(utils.find_boxes(boxes, b"0001")).header.type == b"0001" assert next(utils.find_boxes(boxes, b"0002")).header.type == b"0002" assert next(utils.find_boxes(boxes, b"0003")).header.type == b"0003" assert [box.header.type for box in utils.find_boxes(boxes, b"0002")] == [b"0002", b"0002"] assert [ box.header.type for box in utils.find_boxes(boxes, [b"0001", b"0003"]) ] == [b"0001", b"0003"] assert next(utils.find_boxes(boxes, b"0004"), None) is None
def test_index_metadata(): try: container_filename, transcoded_files = _create_container() _run_tasks([index_metadata(container_filename)]) assert not os.path.exists(container_filename + ".moov") bstr = ConstBitStream(filename=container_filename) boxes = list(Parser.parse(bstr)) # mdat should be using the box extended size field to prevent having to # shift data if it is bigger than the limit of the regular box size field mdat = next(find_boxes(boxes, b"mdat")) assert mdat.header.box_ext_size is not None moov = next(find_boxes(boxes, b"moov")) moov.load(bstr) explicit_targets = [ b'\x00\x00\x00\x00\x00\x00\x00\x00', b'\x00\x00\x00\x00\x00\x00\x00\x00', b'\x00\x00\x00\x00\x00\x00\x00\x00', b'\x00\x00\x00\x00\x00\x00\x00\x00', b'\x00\x00\x00\x00\x00\x00\x00\x00', b'\x01\x00\x00\x00\x00\x00\x00\x00', b'\x01\x00\x00\x00\x00\x00\x00\x00', b'\x01\x00\x00\x00\x00\x00\x00\x00', b'\x01\x00\x00\x00\x00\x00\x00\x00', None ] explicit_filenames = [ b'n01440764_11155.JPEG\x00', b'n01440764_7719.JPEG\x00', b'n01440764_7304.JPEG\x00', b'n01440764_8469.JPEG\x00', b'n01440764_6432.JPEG\x00', b'n01443537_2772.JPEG\x00', b'n01443537_1029.JPEG\x00', b'n01443537_1955.JPEG\x00', b'n01443537_962.JPEG\x00', b'n01443537_2563.JPEG\x00' ] samples = [ get_trak_sample_bytes(bstr, moov.boxes, b"bzna_input\0", i) for i in range(10) ] targets = [ get_trak_sample_bytes(bstr, moov.boxes, b"bzna_target\0", i) for i in range(10) ] filenames = [ get_trak_sample_bytes(bstr, moov.boxes, b"bzna_fname\0", i) for i in range(10) ] thumbs = [ get_trak_sample_bytes(bstr, moov.boxes, b"bzna_thumb\0", i) for i in range(10) ] for i, (sample, target, filename, thumb, transcoded_file) in \ enumerate(zip(samples, targets, filenames, thumbs, transcoded_files)): sample_bstr = ConstBitStream(bytes=sample) sample_moov = next(find_boxes(Parser.parse(sample_bstr), b"moov")) sample_moov.load(sample_bstr) sample_transcoded_filename = filename.decode("utf-8")[:-1] + \ ".transcoded" assert transcoded_file.endswith(sample_transcoded_filename) assert hashlib.md5(sample).hexdigest() == \ _md5(transcoded_file) assert target == explicit_targets[i] assert target == get_trak_sample_bytes(sample_bstr, sample_moov.boxes, b"bzna_target\0", 0) assert filename == explicit_filenames[i] assert filename == get_trak_sample_bytes(sample_bstr, sample_moov.boxes, b"bzna_fname\0", 0) assert thumb == get_trak_sample_bytes(sample_bstr, sample_moov.boxes, b"bzna_thumb\0", 0) finally: shutil.rmtree(".", ignore_errors=True)
def __init__(self, src, ar_format="mp4"): super().__init__(src, ar_format) bstr = ConstBitStream(filename=self._src) moov = next(find_boxes(Parser.parse(bstr, recursive=False), [b"moov"])) moov.parse_boxes(bstr, recursive=False) for trak in find_boxes(moov.boxes, [b"trak"]): trak.parse_boxes(bstr, recursive=False) mdia = next(find_boxes(trak.boxes, [b"mdia"])) mdia.parse_boxes(bstr, recursive=False) trak = next(find_traks(moov.boxes, [b"bzna_input\0"])) mdia = next(find_boxes(trak.boxes, [b"mdia"])) minf = next(find_boxes(mdia.boxes, [b"minf"])) minf.parse_boxes(bstr, recursive=False) stbl = next(find_boxes(minf.boxes, [b"stbl"])) stbl.parse_boxes(bstr, recursive=False) co = next(find_boxes(stbl.boxes, {b"stco", b"co64"})) sz = next(find_boxes(stbl.boxes, [b"stsz"])) self._input_co = co self._input_sz = sz trak = next(find_traks(moov.boxes, [b"bzna_fname\0"])) mdia = next(find_boxes(trak.boxes, [b"mdia"])) minf = next(find_boxes(mdia.boxes, [b"minf"])) minf.parse_boxes(bstr, recursive=False) stbl = next(find_boxes(minf.boxes, [b"stbl"])) stbl.parse_boxes(bstr, recursive=False) co = next(find_boxes(stbl.boxes, {b"stco", b"co64"})) sz = next(find_boxes(stbl.boxes, [b"stsz"])) self._fname_co = co self._fname_sz = sz self._len = max(self._input_co.entry_count, self._input_sz.sample_count)