def test_box_header_force_box_extended_size(): bs = pack("uintbe:32, bytes:4, uintbe:64", 1, b"abcd", 32) box_header = BoxHeader() box_header.type = b"abcd" box_header.box_ext_size = 32 assert box_header.type == b"abcd" assert box_header.box_size == 32 assert box_header.box_ext_size == 32 assert box_header.header_size == 16 assert box_header.content_size == 16 assert bytes(box_header) == bs.bytes
def test_mett_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, " "uintbe:16, " "bytes:1, bytes:11", 28, b"mett", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 1, b'\0', b'image/heif\0') box_header = BoxHeader() mett = bx_def.METT(box_header) mett.header.type = b"mett" mett.data_reference_index = 1 mett.content_encoding = b'\0' mett.mime_format = b'image/heif\0' mett.refresh_box_size() box = mett assert box.header.type == b"mett" assert box.header.box_size == 28 assert box.data_reference_index == 1 assert box.content_encoding == b'\0' assert box.mime_format == b'image/heif\0' assert len(box.boxes) == 0 assert bytes(next(Parser.parse(bs))) == bs.bytes assert bytes(box) == bs.bytes
def test_sbtt_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, " "uintbe:16, " "bytes:1, bytes:11", 28, b"sbtt", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 1, b'\0', b'text/plain\0') box_header = BoxHeader() stxt = bx_def.STXT(box_header) stxt.header.type = b"sbtt" stxt.data_reference_index = 1 stxt.content_encoding = b'\0' stxt.mime_format = b'text/plain\0' stxt.refresh_box_size() box = stxt assert box.header.type == b"sbtt" assert box.header.box_size == 28 assert box.data_reference_index == 1 assert box.content_encoding == b'\0' assert box.mime_format == b'text/plain\0' assert len(box.boxes) == 0 assert bytes(next(Parser.parse(bs))) == bs.bytes assert bytes(box) == bs.bytes
def test_ftyp_box_w_no_type(): bs = pack("uintbe:32, bytes:4, " "bytes:4, uintbe:32, bytes:8", 24, b"ftyp", b"bzna", 10, b"mp42mp41") box_header = BoxHeader() ftyp = bx_def.FTYP(box_header) ftyp.header.type = b"ftyp" ftyp.major_brand = (1652190817,) # b"bzna" ftyp.minor_version = (10,) ftyp.compatible_brands = ([1836069938, # b"mp42" 1836069937],) # b"mp41" ftyp.refresh_box_size() box = ftyp assert box.header.type == b"ftyp" assert box.header.box_size == 24 assert box.major_brand == 1652190817 # b"bzna" assert box.minor_version == 10 assert box.compatible_brands == [1836069938, # b"mp42" 1836069937] # b"mp41" assert bytes(next(Parser.parse(bs))) == bs.bytes assert bytes(box) == bs.bytes
def test_sample_entry_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, " "uintbe:16", 16, b"____", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 1) box_header = BoxHeader() sample_entry_box = bx_def.SampleEntryBox(box_header) sample_entry_box.header.type = b"____" sample_entry_box.data_reference_index = 1 sample_entry_box.refresh_box_size() box = sample_entry_box assert box.header.type == b"____" assert box.header.box_size == 16 assert box.data_reference_index == 1 assert len(box.boxes) == 0 assert bytes(box) == bs.bytes
def tune_video(filename, im_width, im_height): bstr = ConstBitStream(filename=filename) boxes = [box for box in Parser.parse(bstr)] # skip 'free' box boxes = boxes[:1] + boxes[2:] ftyp = boxes[0] mdat = boxes[1] moov = boxes[2] # pop 'udta' box moov.boxes.pop() trak = moov.boxes[-1] # remove edts mdia = trak.pop() trak.pop() trak.append(mdia) for box in boxes: box.load(bstr) box.refresh_box_size() ftyp.major_brand = 1769172845 # b"isom" ftyp.minor_version = 0 ftyp.compatible_brands = [1769172845] # b"isom" ftyp.refresh_box_size() # moov.trak.mdia.minf.stbl stbl = moov.boxes[-1].boxes[-1].boxes[-1].boxes[-1] # moov.trak.mdia.minf.stbl.stsd.hvc1 hvc1 = stbl.boxes[0].boxes[0] clap = bx_def.CLAP(BoxHeader()) clap.header.type = b"clap" clap.clean_aperture_width_n = im_width clap.clean_aperture_width_d = 1 clap.clean_aperture_height_n = im_height clap.clean_aperture_height_d = 1 clap.horiz_off_n = im_width - 512 clap.horiz_off_d = 2 clap.vert_off_n = im_height - 512 clap.vert_off_d = 2 # insert clap before pasp pasp = hvc1.pop() hvc1.append(clap) hvc1.append(pasp) stco = stbl.boxes[-1] stco.entries[ 0].chunk_offset = ftyp.header.box_size + mdat.header.header_size moov.refresh_box_size() return b''.join(bytes(box) for box in boxes)
def test_avc1_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, " "uintbe:16, " "uintbe:16, uintbe:16, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16, uintbe:16, uintbe:16, " "uintbe:32, " "uintbe:16, bytes:32, uintbe:16, " "intbe:16", 86, b"avc1", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 1, 0x0, 0x0, 0x0, 0x0, 0x0, 512, 512, 72, 0, 72, 0, 0x0, 1, b'\0' * 32, 24, -1) box_header = BoxHeader() avc1 = bx_def.AVC1(box_header) avc1.header.type = b"avc1" avc1.data_reference_index = 1 avc1.width = 512 avc1.height = 512 avc1.horizresolution = [72, 0] avc1.vertresolution = [72, 0] avc1.frame_count = 1 avc1.compressorname = b'\0' * 32 avc1.depth = 24 avc1.refresh_box_size() box = avc1 assert box.header.type == b"avc1" assert box.header.box_size == 86 assert box.data_reference_index == 1 assert box.width == 512 assert box.height == 512 assert box.horizresolution == [72, 0] assert box.vertresolution == [72, 0] assert box.frame_count == 1 assert box.compressorname == b'\0' * 32 assert box.depth == 24 assert len(box.boxes) == 0 assert bytes(next(Parser.parse(bs))) == bs.bytes assert bytes(box) == bs.bytes
def test_visual_sample_entry_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, uintbe:8, " "uintbe:16, " "uintbe:16, uintbe:16, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16, uintbe:16, uintbe:16, " "uintbe:32, " "uintbe:16, bytes:32, uintbe:16, " "intbe:16", 86, b"____", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 1, 0x0, 0x0, 0x0, 0x0, 0x0, 512, 512, 72, 0, 72, 0, 0x0, 1, b'\0' * 32, 24, -1) box_header = BoxHeader() visual_sample_entry_box = bx_def.VisualSampleEntryBox(box_header) visual_sample_entry_box.header.type = b"____" visual_sample_entry_box.data_reference_index = 1 visual_sample_entry_box.width = 512 visual_sample_entry_box.height = 512 visual_sample_entry_box.horizresolution = [72, 0] visual_sample_entry_box.vertresolution = [72, 0] visual_sample_entry_box.frame_count = 1 visual_sample_entry_box.compressorname = b'\0' * 32 visual_sample_entry_box.depth = 24 visual_sample_entry_box.refresh_box_size() box = visual_sample_entry_box assert box.header.type == b"____" assert box.header.box_size == 86 assert box.data_reference_index == 1 assert box.width == 512 assert box.height == 512 assert box.horizresolution == [72, 0] assert box.vertresolution == [72, 0] assert box.frame_count == 1 assert box.compressorname == b'\0' * 32 assert box.depth == 24 assert len(box.boxes) == 0 assert bytes(box) == bs.bytes
def test_box_header_extended_user_type_w_drop(): bs = pack("uintbe:32, bytes:4, uintbe:64, bytes:16", 1, b"uuid", MAX_UINT_32 + 1, b":benzina\x00\x00\x00\x00\x00\x00\x00\x00") box_header = BoxHeader() box_header.type = b"uuid:benzina\x00\x00\x00\x00\x00\x00\x00\x00" box_header.box_size = MAX_UINT_32 + 1 box_header.type = b"abcd" box_header.box_size = 100 box_header.type = b"uuid:benzina\x00\x00\x00\x00\x00\x00\x00\x00" box_header.box_size = MAX_UINT_32 + 1 assert box_header.type == b"uuid:benzina\x00\x00\x00\x00\x00\x00\x00\x00" assert box_header.box_size == MAX_UINT_32 + 1 assert box_header.header_size == 32 assert box_header.content_size == MAX_UINT_32 + 1 - 32 assert bytes(box_header) == bs.bytes
def test_box_header(): bs = pack("uintbe:32, bytes:4", 100, b"abcd") box_header = BoxHeader() box_header.type = b"abcd" box_header.box_size = 100 assert box_header.type == b"abcd" assert box_header.box_size == 100 assert box_header.header_size == 8 assert box_header.content_size == 92 assert bytes(box_header) == bs.bytes box_header = BoxHeader() box_header.box_size = 100 box_header.type = b"abcd" assert box_header.type == b"abcd" assert box_header.box_size == 100 assert box_header.header_size == 8 assert box_header.content_size == 92 assert bytes(box_header) == bs.bytes
def test_box_header_w_drop(): bs = pack("uintbe:32, bytes:4", 100, b"abcd") box_header = BoxHeader() box_header.type = b"uuid:benzina\x00\x00\x00\x00\x00\x00\x00\x00" box_header.box_size = MAX_UINT_32 + 1 box_header.type = b"abcd" box_header.box_size = 100 assert box_header.type == b"abcd" assert box_header.box_size == 100 assert box_header.header_size == 8 assert box_header.content_size == 92 assert bytes(box_header) == bs.bytes
def make_text_trak(creation_time, modification_time, name, samples_sizes, samples_offsets): trak = make_trak(creation_time, modification_time, samples_sizes, samples_offsets) # MOOV.TRAK.MDIA mdia = trak.boxes[-1] # MOOV.TRAK.MDIA.HDLR hdlr = mdia.boxes[1] hdlr.handler_type = (b"text",) hdlr.name = (name,) # MOOV.TRAK.MDIA.MINF minf = mdia.boxes[-1] # MOOV.TRAK.MDIA.MINF.NMHD nmhd = bx_def.NMHD(FullBoxHeader()) minf.boxes[0] = nmhd nmhd.header.type = b"nmhd" nmhd.header.version = (0,) nmhd.header.flags = (b"\x00\x00\x00",) # MOOV.TRAK.MDIA.MINF.STBL stbl = minf.boxes[-1] # MOOV.TRAK.MDIA.MINF.STBL.STSD stsd = stbl.boxes[0] # MOOV.TRAK.MDIA.MINF.STBL.STSD.STXT stxt = bx_def.STXT(BoxHeader()) stxt.header.type = b"stxt" stxt.data_reference_index = (1,) stxt.content_encoding = (b'\0',) stxt.mime_format = (b'text/plain\0',) stsd.append(stxt) return trak
def test_pasp_box(): bs = pack("uintbe:32, bytes:4, uintbe:32, uintbe:32", 16, b"pasp", 150, 157) box_header = BoxHeader() pasp = bx_def.PASP(box_header) pasp.header.type = b"pasp" pasp.h_spacing = 150 pasp.v_spacing = 157 pasp.refresh_box_size() box = pasp assert box.header.type == b"pasp" assert box.header.box_size == 16 assert box.h_spacing == 150 assert box.v_spacing == 157 assert bytes(next(Parser.parse(bs))) == bs.bytes assert bytes(box) == bs.bytes
def test_clap_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:32, uintbe:32, uintbe:32, uintbe:32, " "intbe:32, uintbe:32, intbe:32, uintbe:32", 40, b"clap", 500, 1, 333, 1, -12, 2, -179, 2) box_header = BoxHeader() clap = bx_def.CLAP(box_header) clap.header.type = b"clap" clap.clean_aperture_width_n = 500 clap.clean_aperture_width_d = 1 clap.clean_aperture_height_n = 333 clap.clean_aperture_height_d = 1 clap.horiz_off_n = -12 clap.horiz_off_d = 2 clap.vert_off_n = -179 clap.vert_off_d = 2 clap.refresh_box_size() box = clap assert box.header.type == b"clap" assert box.header.box_size == 40 assert box.clean_aperture_width_n == 500 assert box.clean_aperture_width_d == 1 assert box.clean_aperture_height_n == 333 assert box.clean_aperture_height_d == 1 assert box.horiz_off_n == -12 assert box.horiz_off_d == 2 assert box.vert_off_n == -179 assert box.vert_off_d == 2 assert bytes(next(Parser.parse(bs))) == bs.bytes assert bytes(box) == bs.bytes
def make_vide_trak(creation_time, modification_time, name, samples_sizes, samples_offsets): trak = make_trak(creation_time, modification_time, samples_sizes, samples_offsets) # MOOV.TRAK.MDIA mdia = trak.boxes[-1] # MOOV.TRAK.MDIA.HDLR hdlr = mdia.boxes[1] hdlr.handler_type = (b"vide",) hdlr.name = (name,) # MOOV.TRAK.MDIA.MINF minf = mdia.boxes[-1] # MOOV.TRAK.MDIA.MINF.VMHD vmhd = bx_def.VMHD(FullBoxHeader()) minf.boxes[0] = vmhd vmhd.header.type = b"vmhd" vmhd.header.version = (0,) # flag is 1 vmhd.header.flags = (b"\x00\x00\x01",) vmhd.graphicsmode = (0,) vmhd.opcolor = ([0, 0, 0],) # MOOV.TRAK.MDIA.MINF stbl = minf.boxes[-1] # MOOV.TRAK.MDIA.MINF.STBL.STSD stsd = stbl.boxes[0] # MOOV.TRAK.MDIA.MINF.STBL.STSD.AVC1 avc1 = bx_def.AVC1(BoxHeader()) avc1.header.type = b"avc1" avc1.data_reference_index = (1,) avc1.width = (-1,) avc1.height = (-1,) avc1.horizresolution = ([72, 0],) avc1.vertresolution = ([72, 0],) avc1.frame_count = (1,) avc1.compressorname = (b'\0' * 32,) avc1.depth = (24,) # TODO: implement MOOV.TRAK.MDIA.MINF.STBL.STSD.AVC1.AVCC avcC = bx_def.UnknownBox(BoxHeader()) avcC.header.type = b"avcC" avcC.payload = b'\x01d\x10\x16\xff\xe1\x00\x1bgd\x10\x16\xac\xb8\x10\x02' \ b'\r\xff\x80K\x00N\xb6\xa5\x00\x00\x03\x00\x01\x00\x00\x03' \ b'\x00\x02\x04\x01\x00\x07h\xee\x01\x9cL\x84\xc0' avc1.append(avcC) # MOOV.TRAK.MDIA.MINF.STBL.STSD.AVC1.PASP pasp = bx_def.PASP(BoxHeader()) pasp.header.type = b"pasp" pasp.h_spacing = (1,) pasp.v_spacing = (1,) avc1.append(pasp) stsd.append(avc1) return trak
def test_hvcc_box(): bs = pack("uintbe:32, bytes:4, " "uintbe:8, " "int:2, int:1, int:5, uintbe:32, uintbe:48, uintbe:8, " "bits:4, int:12, bits:6, int:2, bits:6, int:2, bits:5, int:3, bits:5, int:3, " "uintbe:16, uint:2, uint:3, uint:1, uint:2, uint:8, " "uint:1, bits:1, uint:6, uintbe:16, " "uint:16, bytes:3", 39, b"hvcc", 1, 0, 0, 3, 1879048192, 193514046488576, 90, '0b1111', 0, '0b111111', 0, '0b111111', 1, '0b11111', 0, '0b11111', 0, 0, 0, 1, 0, 3, 1, 1, '0b0', 32, 1, 3, b"321") box_header = BoxHeader() hvcc = bx_def.HVCC(box_header) hvcc.header.type = b"hvcc" hvcc.header.box_size = 39 hvcc.configuration_version = 1 hvcc.general_profile_space = 0 hvcc.general_tier_flag = 0 hvcc.general_profile_idc = 3 hvcc.general_profile_compatibility_flags = 1879048192 hvcc.general_constraint_indicator_flags = 193514046488576 hvcc.general_level_idc = 90 hvcc.min_spatial_segmentation_idc = 0 hvcc.parallelism_type = 0 hvcc.chroma_format = 1 hvcc.bit_depth_luma_minus_8 = 0 hvcc.bit_depth_chroma_minus_8 = 0 hvcc.avg_frame_rate = 0 hvcc.constant_frame_rate = 0 hvcc.num_temporal_layers = 1 hvcc.temporal_id_nested = 0 hvcc.length_size_minus_one = 3 array = hvcc.append_and_return() array.array_completeness = 1 array.nal_unit_type = 32 nalu = array.append_and_return() nalu.nal_unit_length = 3 nalu.nal_unit = (b"321", "bytes:3") box = hvcc assert box.header.type == b"hvcc" assert box.header.box_size == 39 assert box.configuration_version == 1 assert box.general_profile_space == 0 assert box.general_tier_flag == 0 assert box.general_profile_idc == 3 assert box.general_profile_compatibility_flags == 1879048192 assert box.general_constraint_indicator_flags == 193514046488576 assert box.general_level_idc == 90 assert box.min_spatial_segmentation_idc == 0 assert box.parallelism_type == 0 assert box.chroma_format == 1 assert box.bit_depth_luma_minus_8 == 0 assert box.bit_depth_chroma_minus_8 == 0 assert box.avg_frame_rate == 0 assert box.constant_frame_rate == 0 assert box.num_temporal_layers == 1 assert box.temporal_id_nested == 0 assert box.length_size_minus_one == 3 assert box.num_of_arrays == 1 assert len(box.arrays) == 1 array = box.arrays[0] assert array.array_completeness == 1 assert array.nal_unit_type == 32 assert array.num_nalus == 1 assert len(array.nalus) == 1 nalu = array.nalus[0] assert nalu.nal_unit_length == 3 assert nalu.nal_unit == b"321" parsed_box = next(Parser.parse(bs)) parsed_box.load(bs) assert bytes(parsed_box) == bs.bytes assert bytes(box) == bs.bytes
def make_trak(creation_time, modification_time, samples_sizes, samples_offsets): # MOOV.TRAK trak = bx_def.TRAK(BoxHeader()) trak.header.type = b"trak" # MOOV.TRAK.TKHD tkhd = bx_def.TKHD(FullBoxHeader()) tkhd.header.type = b"tkhd" tkhd.header.version = (1,) tkhd.header.flags = (b"\x00\x00\x00",) tkhd.creation_time = (creation_time,) tkhd.modification_time = (modification_time,) tkhd.track_id = (-1,) tkhd.duration = (20 * len(samples_sizes),) tkhd.layer = (0,) tkhd.alternate_group = (0,) tkhd.volume = ([0, 0],) # TODO: validate matrix (and check if those are 16.16 floats) tkhd.matrix = ([65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824],) # TODO: make sure that this is the canvas size tkhd.width = ([-1, 0],) tkhd.height = ([-1, 0],) trak.append(tkhd) # # MOOV.TRAK.EDTS # edts = bx_def.EDTS(BoxHeader()) # edts.header.type = b"edts" # # # MOOV.TRAK.EDTS.ELST # elst = bx_def.ELST(FullBoxHeader()) # # elst.header.type = b"elst" # elst.header.version = (1,) # elst.header.flags = (b"\x00\x00\x00",) # # entry = elst.append_and_return() # entry.segment_duration = (60,) # entry.media_time = (0,) # entry.media_rate_integer = (1,) # entry.media_rate_fraction = (0,) # # edts.append(elst) # # trak.append(edts) # MOOV.TRAK.MDIA mdia = bx_def.MDIA(BoxHeader()) mdia.header.type = b"mdia" # MOOV.TRAK.MDIA.MDHD mdhd = bx_def.MDHD(FullBoxHeader()) mdhd.header.type = b"mdhd" mdhd.header.version = (1,) mdhd.header.flags = (b"\x00\x00\x00",) mdhd.creation_time = (creation_time,) mdhd.modification_time = (modification_time,) mdhd.timescale = (20,) mdhd.duration = (20 * len(samples_sizes),) # TODO: check the language code mdhd.language = ([21, 14, 4],) mdhd.pre_defined = (0,) mdia.append(mdhd) # MOOV.TRAK.MDIA.HDLR hdlr = bx_def.HDLR(FullBoxHeader()) hdlr.header.type = b"hdlr" hdlr.header.version = (0,) hdlr.header.flags = (b"\x00\x00\x00",) hdlr.pre_defined = (0,) hdlr.handler_type = (b"____",) hdlr.name = (b"\0",) mdia.append(hdlr) # MOOV.TRAK.MDIA.MINF minf = bx_def.MINF(BoxHeader()) minf.header.type = b"minf" # MOOV.TRAK.MDIA.MINF._MHD (placeholder) _mhd = bx_def.UnknownBox(BoxHeader()) _mhd.header.type = b"_mhd" minf.append(_mhd) # MOOV.TRAK.MDIA.MINF.DINF dinf = bx_def.DINF(BoxHeader()) dinf.header.type = b"dinf" # MOOV.TRAK.MDIA.MINF.DINF.DREF dref = bx_def.DREF(FullBoxHeader()) dref.header.type = b"dref" dref.header.version = (0,) dref.header.flags = (b"\x00\x00\x00",) # MOOV.TRAK.MDIA.MINF.DINF.DREF.URL_ url_ = bx_def.URL_(FullBoxHeader()) url_.header.type = b"url " url_.header.version = (0,) # TODO: validate that this flags means that the data is in the same file url_.header.flags = (b"\x00\x00\x01",) dref.append(url_) dinf.append(dref) minf.append(dinf) # MOOV.TRAK.MDIA.MINF.STBL stbl = bx_def.STBL(BoxHeader()) stbl.header.type = b"stbl" # MOOV.TRAK.MDIA.MINF.STBL.STSD stsd = bx_def.STSD(FullBoxHeader()) stsd.header.type = b"stsd" stsd.header.version = (0,) stsd.header.flags = (b"\x00\x00\x00",) stbl.append(stsd) # MOOV.TRAK.MDIA.MINF.STBL.STTS stts = bx_def.STTS(FullBoxHeader()) stts.header.type = b"stts" stts.header.version = (0,) stts.header.flags = (b"\x00\x00\x00",) entry = stts.append_and_return() # imges count entry.sample_count = (len(samples_sizes),) # 1 img / sec entry.sample_delta = (20,) stbl.append(stts) # MOOV.TRAK.MDIA.MINF.STBL.STSZ stsz = bx_def.STSZ(FullBoxHeader()) stsz.header.type = b"stsz" stsz.header.version = (0,) stsz.header.flags = (b"\x00\x00\x00",) stsz.sample_size = (0,) for samples_size in samples_sizes: sample = stsz.append_and_return() sample.entry_size = (samples_size,) stbl.append(stsz) # MOOV.TRAK.MDIA.MINF.STBL.STSC stsc = bx_def.STSC(FullBoxHeader()) stsc.header.type = b"stsc" stsc.header.version = (0,) stsc.header.flags = (b"\x00\x00\x00",) entry = stsc.append_and_return() entry.first_chunk = (1,) entry.samples_per_chunk = (1,) entry.sample_description_index = (1,) stbl.append(stsc) # MOOV.TRAK.MDIA.MINF.STBL.STCO co = bx_def.STCO(FullBoxHeader()) co.header.type = b"stco" co.header.version = (0,) co.header.flags = (b"\x00\x00\x00",) for offset in gen_sample_offsets(samples_sizes, samples_offsets): # If the offset gets bigger than 2^32-1, use the 64 bits implementation if offset > MAX_UINT_32: # MOOV.TRAK.MDIA.MINF.STBL.CO64 co = bx_def.CO64(FullBoxHeader()) co.header.type = b"co64" co.header.version = (0,) co.header.flags = (b"\x00\x00\x00",) break entry = co.append_and_return() entry.chunk_offset = (offset,) # If the offset gets bigger than 2^32-1, add again using co64 if isinstance(co, bx_def.CO64): for offset in gen_sample_offsets(samples_sizes, samples_offsets): entry = co.append_and_return() entry.chunk_offset = (offset,) stbl.append(co) minf.append(stbl) mdia.append(minf) trak.append(mdia) return trak
def test_mp4_dataset(): creation_time = to_mp4_time(datetime(2019, 9, 15, 0, 0, 0)) modification_time = to_mp4_time(datetime(2019, 9, 16, 0, 0, 0)) # FTYP ftyp = bx_def.FTYP(BoxHeader()) ftyp.header.type = b"ftyp" ftyp.major_brand = 1769172845 # b"isom" ftyp.minor_version = 0 ftyp.compatible_brands = [1652190817, # b"bzna" 1769172845] # b"isom" ftyp.refresh_box_size() assert ftyp.header.type == b"ftyp" assert ftyp.header.box_size == 24 assert ftyp.major_brand == 1769172845 # b"isom" assert ftyp.minor_version == 0 assert ftyp.compatible_brands == [1652190817, # b"bzna" 1769172845] # b"isom" assert bytes(ftyp) == pack("uintbe:32, bytes:4, bytes:4, uintbe:32, " "bytes:8", 24, b"ftyp", b"isom", 0, b"bznaisom") # MDAT mdat = bx_def.MDAT(BoxHeader()) mdat.header.type = b"mdat" data = [] with open("tests/data/small_vid_mdat_im0", "rb") as f: data.append(f.read()) with open("tests/data/small_vid_mdat_im1", "rb") as f: data.append(f.read()) with open("tests/data/small_vid_mdat_im2", "rb") as f: data.append(f.read()) data.append(b"/path/image_1_name.JPEG") data.append(b"/path/image_2_name.JPEG") data.append(b"/path/image_3_name.JPEG") data.append((0).to_bytes(8, byteorder="little")) data.append((1).to_bytes(8, byteorder="little")) data.append((0).to_bytes(8, byteorder="little")) mdat.data = b''.join(data) mdat.refresh_box_size() assert mdat.header.type == b"mdat" assert mdat.header.box_size == 8 + sum(len(entry) for entry in data) # MOOV moov = bx_def.MOOV(BoxHeader()) moov.header.type = b"moov" # MOOV.MVHD mvhd = make_mvhd(creation_time, modification_time, 3) # == total number of tracks mvhd.next_track_id = 5 assert mvhd.next_track_id == 5 moov.append(mvhd) # MOOV.TRAK offset = ftyp.header.box_size + mdat.header.header_size sizes = [198297, 127477, 192476] trak = make_vide_trak(creation_time, modification_time, b"VideoHandler\0", sizes, offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] # "\x00\x00\x01" trak is enabled # "\x00\x00\x02" trak is used in the presentation # "\x00\x00\x04" trak is used in the preview # "\x00\x00\x08" trak size in not in pixel but in aspect ratio tkhd.header.flags = b"\x00\x00\x03" tkhd.track_id = 1 # TODO: make sure that this is the canvas size tkhd.width = [512, 0] tkhd.height = [512, 0] tkhd.refresh_box_size() assert tkhd.header.type == b"tkhd" assert tkhd.header.box_size == 104 assert tkhd.header.flags == b"\x00\x00\x03" assert tkhd.track_id == 1 assert tkhd.width == 512 assert tkhd.height == 512 assert bytes(tkhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, " "bits:32, " "uintbe:64, " "bits:32, bits:32, " "uintbe:16, uintbe:16, uintbe:8, uintbe:8, " "bits:16, " "uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16", 104, b"tkhd", 1, b"\x00\x00\x03", creation_time, modification_time, 1, b"\x00" * 4, 60, b"\x00" * 4, b"\x00" * 4, 0, 0, 0, 0, b"\x00" * 2, 65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824, 512, 0, 512, 0) # MOOV.TRAK.MDIA.MINF.STBL.STSD.AVC1 avc1 = trak.boxes[-1].boxes[-1].boxes[-1].boxes[0].boxes[0] avc1.width = 512 avc1.height = 512 assert avc1.header.type == b"avc1" assert avc1.width == 512 assert avc1.height == 512 moov.append(trak) # MOOV.TRAK trak = make_meta_trak(creation_time, modification_time, b"bzna_inputs\0", sizes, offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] # "\x00\x00\x01" trak is enabled # "\x00\x00\x02" trak is used in the presentation # "\x00\x00\x04" trak is used in the preview # "\x00\x00\x08" trak size in not in pixel but in aspect ratio tkhd.header.flags = b"\x00\x00\x00" tkhd.track_id = 2 # TODO: make sure that this is the canvas size tkhd.width = [0, 0] tkhd.height = [0, 0] tkhd.refresh_box_size() assert tkhd.header.type == b"tkhd" assert tkhd.header.box_size == 104 assert tkhd.header.flags == b"\x00\x00\x00" assert tkhd.track_id == 2 assert tkhd.width == 0 assert tkhd.height == 0 assert bytes(tkhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, " "bits:32, " "uintbe:64, " "bits:32, bits:32, " "uintbe:16, uintbe:16, uintbe:8, uintbe:8, " "bits:16, " "uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16", 104, b"tkhd", 1, b"\x00\x00\x00", creation_time, modification_time, 2, b"\x00" * 4, 60, b"\x00" * 4, b"\x00" * 4, 0, 0, 0, 0, b"\x00" * 2, 65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824, 0, 0, 0, 0) # MOOV.TRAK.MDIA.MDHD mdhd = trak.boxes[-1].boxes[0] mdhd.timescale = 20 mdhd.duration = 60 mdhd.refresh_box_size() assert mdhd.header.type == b"mdhd" assert mdhd.header.box_size == 44 assert mdhd.timescale == 20 assert mdhd.duration == 60 assert bytes(mdhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, uintbe:64, " "bits:1, uint:5, uint:5, uint:5, " "bits:16", 44, b"mdhd", 1, b"\x00\x00\x00", creation_time, modification_time, 20, 60, 0x1, 21, 14, 4, b"\x00" * 2) # MOOV.TRAK.MDIA.MINF.STBL.STSD.METT mett = trak.boxes[-1].boxes[-1].boxes[-1].boxes[0].boxes[0] mett.mime_format = b'video/h264\0' mett.refresh_box_size() assert mett.header.type == b"mett" assert mett.header.box_size == 28 assert mett.mime_format == b'video/h264\0' moov.append(trak) # MOOV.TRAK offset += sum(sizes) sizes = [23, 23, 23] trak = make_text_trak(creation_time, modification_time, b"bzna_fnames\0", sizes, offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] # "\x00\x00\x01" trak is enabled # "\x00\x00\x02" trak is used in the presentation # "\x00\x00\x04" trak is used in the preview # "\x00\x00\x08" trak size in not in pixel but in aspect ratio tkhd.header.flags = b"\x00\x00\x00" tkhd.track_id = 3 # TODO: make sure that this is the canvas size tkhd.width = [0, 0] tkhd.height = [0, 0] tkhd.refresh_box_size() assert tkhd.header.type == b"tkhd" assert tkhd.header.box_size == 104 assert tkhd.header.flags == b"\x00\x00\x00" assert tkhd.track_id == 3 assert tkhd.width == 0 assert tkhd.height == 0 assert bytes(tkhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, " "bits:32, " "uintbe:64, " "bits:32, bits:32, " "uintbe:16, uintbe:16, uintbe:8, uintbe:8, " "bits:16, " "uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16", 104, b"tkhd", 1, b"\x00\x00\x00", creation_time, modification_time, 3, b"\x00" * 4, 60, b"\x00" * 4, b"\x00" * 4, 0, 0, 0, 0, b"\x00" * 2, 65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824, 0, 0, 0, 0) # MOOV.TRAK.MDIA.MDHD mdhd = trak.boxes[-1].boxes[0] mdhd.timescale = 20 mdhd.duration = 60 mdhd.refresh_box_size() assert mdhd.header.type == b"mdhd" assert mdhd.header.box_size == 44 assert mdhd.timescale == 20 assert mdhd.duration == 60 assert bytes(mdhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, uintbe:64, " "bits:1, uint:5, uint:5, uint:5, " "bits:16", 44, b"mdhd", 1, b"\x00\x00\x00", creation_time, modification_time, 20, 60, 0x1, 21, 14, 4, b"\x00" * 2) moov.append(trak) # MOOV.TRAK offset += sum(sizes) sizes = [8, 8, 8] trak = make_text_trak(creation_time, modification_time, b"bzna_targets\0", sizes, offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] # "\x00\x00\x01" trak is enabled # "\x00\x00\x02" trak is used in the presentation # "\x00\x00\x04" trak is used in the preview # "\x00\x00\x08" trak size in not in pixel but in aspect ratio tkhd.header.flags = b"\x00\x00\x00" tkhd.track_id = 4 # TODO: make sure that this is the canvas size tkhd.width = [0, 0] tkhd.height = [0, 0] tkhd.refresh_box_size() assert tkhd.header.type == b"tkhd" assert tkhd.header.box_size == 104 assert tkhd.header.flags == b"\x00\x00\x00" assert tkhd.track_id == 4 assert tkhd.width == 0 assert tkhd.height == 0 assert bytes(tkhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, " "bits:32, " "uintbe:64, " "bits:32, bits:32, " "uintbe:16, uintbe:16, uintbe:8, uintbe:8, " "bits:16, " "uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16", 104, b"tkhd", 1, b"\x00\x00\x00", creation_time, modification_time, 4, b"\x00" * 4, 60, b"\x00" * 4, b"\x00" * 4, 0, 0, 0, 0, b"\x00" * 2, 65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824, 0, 0, 0, 0) # MOOV.TRAK.MDIA.MDHD mdhd = trak.boxes[-1].boxes[0] mdhd.timescale = 20 mdhd.duration = 60 mdhd.refresh_box_size() assert mdhd.header.type == b"mdhd" assert mdhd.header.box_size == 44 assert mdhd.timescale == 20 assert mdhd.duration == 60 assert bytes(mdhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, uintbe:64, " "bits:1, uint:5, uint:5, uint:5, " "bits:16", 44, b"mdhd", 1, b"\x00\x00\x00", creation_time, modification_time, 20, 60, 0x1, 21, 14, 4, b"\x00" * 2) moov.append(trak) moov.refresh_box_size() assert mvhd.next_track_id == len(moov.boxes) assert moov.header.type == b"moov" assert len(moov.boxes) == 5 mp4_bytes = b''.join([bytes(ftyp), bytes(mdat), bytes(moov)]) for box in moov.boxes: if isinstance(box, bx_def.TRAK): mdia = box.boxes[1] else: continue # trak.mdia.tkhd.name == b"bzna_inputs\0" if mdia.boxes[1].name == b"bzna_inputs\0": pass # trak.mdia.tkhd.name == b"bzna_names\0" elif mdia.boxes[1].name == b"bzna_fnames\0": # trak.mdia.minf.stbl.stsz stsz = mdia.boxes[2].boxes[2].boxes[2] # trak.mdia.minf.stbl.stco stco = mdia.boxes[2].boxes[2].boxes[4] for i, (sample, entry) in enumerate(zip(stsz.samples, stco.entries)): sample_end = entry.chunk_offset + sample.entry_size if i == 0: assert mp4_bytes[entry.chunk_offset:sample_end] == \ b"/path/image_1_name.JPEG" elif i == 1: assert mp4_bytes[entry.chunk_offset:sample_end] == \ b"/path/image_2_name.JPEG" elif i == 2: assert mp4_bytes[entry.chunk_offset:sample_end] == \ b"/path/image_3_name.JPEG" # trak.mdia.tkhd.name == b"bzna_targets\0" elif mdia.boxes[1].name == b"bzna_targets\0": # trak.mdia.minf.stbl.stsz stsz = mdia.boxes[2].boxes[2].boxes[2] # trak.mdia.minf.stbl.stco stco = mdia.boxes[2].boxes[2].boxes[4] for i, (sample, entry) in enumerate(zip(stsz.samples, stco.entries)): sample_end = entry.chunk_offset + sample.entry_size if i == 0: assert mp4_bytes[entry.chunk_offset:sample_end] == \ (0).to_bytes(8, byteorder="little") elif i == 1: assert mp4_bytes[entry.chunk_offset:sample_end] == \ (1).to_bytes(8, byteorder="little") elif i == 2: assert mp4_bytes[entry.chunk_offset:sample_end] == \ (0).to_bytes(8, byteorder="little") with open("tests/data/small_dataset.out.mp4", "rb") as f: assert mp4_bytes == f.read()
def test_mp4_small_vid(): creation_time = to_mp4_time(datetime(2019, 9, 15, 0, 0, 0)) modification_time = to_mp4_time(datetime(2019, 9, 16, 0, 0, 0)) # FTYP ftyp = bx_def.FTYP(BoxHeader()) ftyp.header.type = b"ftyp" ftyp.major_brand = 1836069937 # b"mp41" ftyp.minor_version = 0 ftyp.compatible_brands = [1836069937] # b'mp41' ftyp.refresh_box_size() assert ftyp.header.type == b"ftyp" assert ftyp.header.box_size == 20 assert ftyp.major_brand == 1836069937 # b"mp41" assert ftyp.minor_version == 0 assert ftyp.compatible_brands == [1836069937] # b'mp41' assert bytes(ftyp) == pack("uintbe:32, bytes:4, " "bytes:4, uintbe:32, bytes:4", 20, b"ftyp", b"mp41", 0, b'mp41') # MDAT mdat = bx_def.MDAT(BoxHeader()) mdat.header.type = b"mdat" data = [] with open("tests/data/small_vid_mdat_im0", "rb") as f: data.append(f.read()) with open("tests/data/small_vid_mdat_im1", "rb") as f: data.append(f.read()) with open("tests/data/small_vid_mdat_im2", "rb") as f: data.append(f.read()) mdat.data = b''.join(data) mdat.refresh_box_size() assert mdat.header.type == b"mdat" assert mdat.header.box_size == 518258 # MOOV moov = bx_def.MOOV(BoxHeader()) moov.header.type = b"moov" # MOOV.MVHD mvhd = make_mvhd(creation_time, modification_time, 3) # == total number of tracks mvhd.next_track_id = 2 assert mvhd.next_track_id == 2 moov.append(mvhd) # MOOV.TRAK offset = ftyp.header.box_size + mdat.header.header_size sizes = [198297, 127477, 192476] trak = make_vide_trak(creation_time, modification_time, b"VideoHandler\0", sizes, offset) # MOOV.TRAK.TKHD tkhd = trak.boxes[0] # "\x00\x00\x01" trak is enabled # "\x00\x00\x02" trak is used in the presentation # "\x00\x00\x04" trak is used in the preview # "\x00\x00\x08" trak size in not in pixel but in aspect ratio tkhd.header.flags = b"\x00\x00\x03" tkhd.track_id = 1 # TODO: make sure that this is the canvas size tkhd.width = [512, 0] tkhd.height = [512, 0] tkhd.refresh_box_size() assert tkhd.header.type == b"tkhd" assert tkhd.header.box_size == 104 assert tkhd.header.flags == b"\x00\x00\x03" assert tkhd.track_id == 1 assert tkhd.width == 512 assert tkhd.height == 512 assert bytes(tkhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, " "bits:32, " "uintbe:64, " "bits:32, bits:32, " "uintbe:16, uintbe:16, uintbe:8, uintbe:8, " "bits:16, " "uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, uintbe:32, " "uintbe:16, uintbe:16, uintbe:16, uintbe:16", 104, b"tkhd", 1, b"\x00\x00\x03", creation_time, modification_time, 1, b"\x00" * 4, 60, b"\x00" * 4, b"\x00" * 4, 0, 0, 0, 0, b"\x00" * 2, 65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824, 512, 0, 512, 0) # MOOV.TRAK.MDIA.MDHD mdhd = trak.boxes[-1].boxes[0] mdhd.timescale = 20 mdhd.duration = 60 mdhd.refresh_box_size() assert mdhd.header.type == b"mdhd" assert mdhd.header.box_size == 44 assert mdhd.timescale == 20 assert mdhd.duration == 60 assert bytes(mdhd) == \ pack("uintbe:32, bytes:4, uintbe:8, bits:24, " "uintbe:64, uintbe:64, uintbe:32, uintbe:64, " "bits:1, uint:5, uint:5, uint:5, " "bits:16", 44, b"mdhd", 1, b"\x00\x00\x00", creation_time, modification_time, 20, 60, 0x1, 21, 14, 4, b"\x00" * 2) # MOOV.TRAK.MDIA.MINF.STBL.STSD stsd = trak.boxes[-1].boxes[-1].boxes[-1].boxes[0] assert stsd.header.type == b"stsd" assert len(stsd.boxes) == 1 # MOOV.TRAK.MDIA.MINF.STBL.STSD.AVC1 avc1 = stsd.boxes[0] avc1.width = 512 avc1.height = 512 assert avc1.header.type == b"avc1" assert avc1.width == 512 assert avc1.height == 512 moov.append(trak) moov.refresh_box_size() assert mvhd.next_track_id == len(moov.boxes) assert moov.header.type == b"moov" assert len(moov.boxes) == 2 mp4_bytes = b''.join([bytes(ftyp), bytes(mdat), bytes(moov)]) with open("tests/data/small_vid.out.mp4", "rb") as f: assert mp4_bytes == f.read()