def create_container(container): 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() mdat = bx_def.MDAT(BoxHeader()) mdat.header.type = b"mdat" mdat.data = b'' # Force the usage of box_ext_size in the computation of the box size to # prevent having to shift data if it ends up being bigger than the limit of # box_size mdat.header.box_ext_size = 0 mdat.refresh_box_size() if isinstance(container, str): container = open(container, "xb") container.write(bytes(ftyp) + bytes(mdat)) container.close()
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_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 test_make_targets_trak(): # MDAT mdat = bx_def.MDAT(BoxHeader()) mdat.header.type = b"mdat" mdat.data = (b"0123456789", ) mdat.refresh_box_size() target_trak = make_targets_trak(mdat, 20, "application/octet-stream", [int.to_bytes(100, 8, byteorder="little")]) # MOOV.TRAK.TKHD assert target_trak.boxes[0].header.flags == b"\x00\x00\x00" assert target_trak.boxes[0].width == 0 assert target_trak.boxes[0].height == 0 # MOOV.TRAK.MDIA.HDLR assert target_trak.boxes[1].boxes[1].name == b"bzna_target\0" # MOOV.TRAK.MDIA.MINF.STBL.STSD.METT mett = target_trak.boxes[-1].boxes[-1].boxes[-1].boxes[0].boxes[0] assert mett.mime_format == b"application/octet-stream\0" # MOOV.TRAK.MDIA.MINF.STBL.STSZ stsz = target_trak.boxes[-1].boxes[-1].boxes[-1].boxes[2] assert stsz.sample_count == 1 assert stsz.samples[0].entry_size == 8 # MOOV.TRAK.MDIA.MINF.STBL.STCO stsz = target_trak.boxes[-1].boxes[-1].boxes[-1].boxes[4] assert stsz.entry_count == 1 assert stsz.entries[0].chunk_offset == 38
def test_make_filenames_trak(): # MDAT mdat = bx_def.MDAT(BoxHeader()) mdat.header.type = b"mdat" mdat.data = (b"0123456789", ) mdat.refresh_box_size() filename_trak = make_filenames_trak(mdat, 20, [b"0001/n02100735_8211.JPEG"]) # MOOV.TRAK.TKHD assert filename_trak.boxes[0].header.flags == b"\x00\x00\x03" assert filename_trak.boxes[0].width == 0 assert filename_trak.boxes[0].height == 0 # MOOV.TRAK.MDIA.HDLR assert filename_trak.boxes[1].boxes[1].name == b"bzna_fname\0" # MOOV.TRAK.MDIA.MINF.STBL.STSD.METT mett = filename_trak.boxes[-1].boxes[-1].boxes[-1].boxes[0].boxes[0] assert mett.mime_format == b"text/plain\0" # MOOV.TRAK.MDIA.MINF.STBL.STSZ stsz = filename_trak.boxes[-1].boxes[-1].boxes[-1].boxes[2] assert stsz.sample_count == 1 assert stsz.samples[0].entry_size == 24 # MOOV.TRAK.MDIA.MINF.STBL.STCO stsz = filename_trak.boxes[-1].boxes[-1].boxes[-1].boxes[4] assert stsz.entry_count == 1 assert stsz.entries[0].chunk_offset == 38
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_reset_traks_id(): moov = bx_def.MOOV(BoxHeader()) mvhd = bx_def.MVHD(BoxHeader()) moov.append(mvhd) for i in range(4): trak = bx_def.TRAK(BoxHeader()) trak.header.type = b"trak" tkhd = bx_def.TKHD(BoxHeader()) tkhd.header.type = b"tkhd" trak.append(tkhd) moov.append(trak) reset_traks_id(moov) assert moov.boxes[0].next_track_id == 5 for i, trak in enumerate( [box for box in moov.boxes if box.header.type == b"trak"]): assert trak.boxes[0].track_id == i + 1
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 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_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 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 _create_moov_file(filename, samples_headers): moov_filename = "{}.moov".format(filename) creation_time = 0 modification_time = 0 moov = bx_def.MOOV(BoxHeader()) moov.header.type = b"moov" # MOOV.MVHD mvhd = make_mvhd( creation_time, modification_time, len([header for header in samples_headers if header.type == b"ftyp"])) mvhd.next_track_id = 1 moov.append(mvhd) moov.refresh_box_size() with open(moov_filename, "wb") as moov_file: moov_file.write(bytes(moov)) return moov_filename
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_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_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 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()
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