def test_mdhd_build(self): mdhd_data = Box.build( dict(type=b"mdhd", creation_time=0, modification_time=0, timescale=1000000, duration=0, language=u"und")) self.assertEqual(len(mdhd_data), 32) self.assertEqual( mdhd_data, b'\x00\x00\x00\x20mdhd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0fB@\x00\x00\x00\x00U\xc4\x00\x00' ) mdhd_data64 = Box.build( dict(type=b"mdhd", version=1, creation_time=0, modification_time=0, timescale=1000000, duration=0, language=u"und")) self.assertEqual(len(mdhd_data64), 44) self.assertEqual( mdhd_data64, b'\x00\x00\x00,mdhd\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0fB@\x00\x00\x00\x00\x00\x00\x00\x00U\xc4\x00\x00' )
def test_ftyp_build(self): self.assertEqual( Box.build( dict(type=b"ftyp", major_brand=b"iso5", minor_version=1, compatible_brands=[b"iso5", b"avc1"])), b'\x00\x00\x00\x18ftypiso5\x00\x00\x00\x01iso5avc1')
def test_tenc_build(self): self.assertEqual( Box.build(dict( type=b"tenc", key_ID=UUID('337b9643-21b6-4355-9e59-3eccb46c7ef7'), iv_size=8, is_encrypted=1)), b'\x00\x00\x00 tenc\x00\x00\x00\x00\x00\x00\x01\x083{\x96C!\xb6CU\x9eY>\xcc\xb4l~\xf7')
def decrypt(key, inp, out): """ decrypt() @param key: AES-128 CENC key in bytes @param inp: Open input file @param out: Open output file """ with BufferedReader(inp) as reader: senc_boxes = deque() trun_boxes = deque() while reader.peek(1): box = Box.parse_stream(reader) fix_headers(box) for stsd_box in BoxUtil.find(box, b'stsz'): sample_size = stsd_box.sample_size if box.type == b'moof': senc_boxes.extend(BoxUtil.find(box, b'senc')) trun_boxes.extend(BoxUtil.find(box, b'trun')) elif box.type == b'mdat': senc_box = senc_boxes.popleft() trun_box = trun_boxes.popleft() clear_box = b'' with BytesIO(box.data) as box_bytes: for sample, sample_info in zip( senc_box.sample_encryption_info, trun_box.sample_info): counter = Counter.new(64, prefix=sample.iv, initial_value=0) cipher = AES.new(key, AES.MODE_CTR, counter=counter) if sample_size: cipher_bytes = box_bytes.read(sample_size) clear_box += cipher.decrypt(cipher_bytes) elif not sample.subsample_encryption_info: cipher_bytes = box_bytes.read( sample_info.sample_size) clear_box += cipher.decrypt(cipher_bytes) else: for subsample in sample.subsample_encryption_info: clear_box += box_bytes.read( subsample.clear_bytes) cipher_bytes = box_bytes.read( subsample.cipher_bytes) clear_box += cipher.decrypt(cipher_bytes) box.data = clear_box out.write(Box.build(box)) return
def test_moov_build(self): moov = \ Container(type=b"moov")(children=[ # 96 bytes Container(type=b"mvex")(children=[ # 88 bytes Container(type=b"mehd")(version=0)(flags=0)(fragment_duration=0), # 16 bytes Container(type=b"trex")(track_ID=1), # 32 bytes Container(type=b"trex")(track_ID=2), # 32 bytes ]) ]) moov_data = Box.build(moov) self.assertEqual(len(moov_data), 96) self.assertEqual( moov_data, b'\x00\x00\x00\x60moov' b'\x00\x00\x00\x58mvex' b'\x00\x00\x00\x10mehd\x00\x00\x00\x00\x00\x00\x00\x00' b'\x00\x00\x00\x20trex\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b'\x00\x00\x00\x20trex\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' )
def test_build_emib(self): emib_b = Box.build( dict(type=b"emib", version=0, reserved=1, presentation_time_delta=-1000, value=b'', id=1, scheme_id_uri=b"my_test_scheme", duration=2000, message_data=b"asdfdasgfaghhgsdgh")) emib_b_p = Box.parse(emib_b) self.assertEqual(emib_b_p["type"], b'emib') self.assertEqual(emib_b_p["presentation_time_delta"], -1000) self.assertEqual(emib_b_p["value"], b'') self.assertEqual(emib_b_p["id"], 1) self.assertEqual(emib_b_p["scheme_id_uri"], b"my_test_scheme") self.assertEqual(emib_b_p["duration"], 2000) self.assertEqual(emib_b_p["reserved"], 1) self.assertEqual(emib_b_p["message_data"], b"asdfdasgfaghhgsdgh")
def test_build_emsg(self): emsg_b = Box.build( dict(type=b"emsg", version=1, presentation_time=1000, value=b'', id=1, scheme_id_uri=b"my_test_scheme", event_duration=20, timescale=1, message_data=b"asdfdasgfaghhgsdgh")) emsg_b_p = Box.parse(emsg_b) self.assertEqual(emsg_b_p["type"], b'emsg') self.assertEqual(emsg_b_p["version"], 1) self.assertEqual(emsg_b_p["presentation_time"], 1000) self.assertEqual(emsg_b_p["value"], b'') self.assertEqual(emsg_b_p["id"], 1) self.assertEqual(emsg_b_p["scheme_id_uri"], b"my_test_scheme") self.assertEqual(emsg_b_p["event_duration"], 20) self.assertEqual(emsg_b_p["timescale"], 1) self.assertEqual(emsg_b_p["message_data"], b"asdfdasgfaghhgsdgh")
def test_parse_edit_list(self): elst_b = Box.parse( b'\x00\x00\x00\x1celst\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xa0\x00\x00\x04\x00\x00\x01\x00\x00' ) [Container(edit_duration=4000)(media_time=1024)] self.assertEqual(elst_b["type"], b"elst") self.assertEqual(len(elst_b["entries"]), 1) self.assertEqual(elst_b["entries"][0]["edit_duration"], 4000) self.assertEqual(elst_b["entries"][0]["media_time"], 1024) self.assertEqual(elst_b["entries"][0]["media_rate_integer"], 1) self.assertEqual(elst_b["entries"][0]["media_rate_fraction"], 0) t = dict( type=b"elst", version=1, flags=0, entries=[dict(edit_duration=1,media_time=1, media_rate_integer=1, media_rate_fraction=1)\ ,dict(edit_duration=2,media_time=2, media_rate_integer=1, media_rate_fraction=1) ], ) elst_b = Box.build(t) t2 = Box.parse(elst_b) self.assertEqual(len(t["entries"]), len(t2["entries"]))
def test_build_evte(self): evte = Box.build(dict(type=b"evte", children=[]))
def test_smhd_build(self): smhd_data = Box.build(dict(type=b"smhd", balance=0)) self.assertEqual(len(smhd_data), 16), self.assertEqual( smhd_data, b'\x00\x00\x00\x10smhd\x00\x00\x00\x00\x00\x00\x00\x00')
def _output_mp4_footer(self, framerate, resolution): # Extact all the variables used after in the construction of the boxes sample_count = len(self._sample_sizes) timescale = framerate.numerator sample_delta = framerate.denominator duration = sample_count * sample_delta chunk_offset = self.mdat_payload_offset width = resolution[0] height = resolution[1] profile, compatibility, level = DEFAULT_SPS_INDICATIONS if self.indications is None else self.indications sample_sizes = self._sample_sizes sps = list(self.seq_parm_sets) pps = list(self.pic_parm_sets) # Build all the boxes we need HDLR = Container(type=b'hdlr') HDLR(version=0) HDLR(flags=0) HDLR(handler_type=b'vide') HDLR(name='VideoHandler') MDHD = Container(type=b'mdhd') MDHD(version=0) MDHD(flags=0) MDHD(creation_time=0) MDHD(modification_time=0) MDHD(timescale=timescale) MDHD(duration=duration) MDHD(language='und') URL_ = Container(type=b'url ') URL_(version=0) URL_(flags=Container(self_contained=True)) URL_(location=None) DREF = Container(type=b'dref') DREF(version=0) DREF(flags=0) DREF(data_entries=[URL_]) DINF = Container(type=b'dinf') DINF(children=[DREF]) STTS = Container(type=b'stts') STTS(version=0) STTS(flags=0) STTS(entries=[ Container(sample_count=sample_count)(sample_delta=sample_delta) ]) AVCC = Container(type=b'avcC') AVCC(version=1) AVCC(profile=profile) AVCC(compatibility=compatibility) AVCC(level=level) AVCC(nal_unit_length_field=3) AVCC(sps=sps) AVCC(pps=pps) AVC1 = Container(format=b'avc1') AVC1(data_reference_index=1) AVC1(version=0) AVC1(revision=0) AVC1(vendor=b'') AVC1(temporal_quality=0) AVC1(spatial_quality=0) AVC1(width=width) AVC1(height=height) AVC1(horizontal_resolution=72) AVC1(vertical_resolution=72) AVC1(data_size=0) AVC1(frame_count=1) AVC1(compressor_name=b'') AVC1(depth=24) AVC1(color_table_id=-1) AVC1(avc_data=AVCC) STSD = Container(type=b'stsd') STSD(version=0) STSD(flags=0) STSD(entries=[AVC1]) STSC = Container(type=b'stsc') STSC(version=0) STSC(flags=0) STSC(entries=[ Container(first_chunk=1)(samples_per_chunk=sample_count)( sample_description_index=1) ]) STCO = Container(type=b'stco') STCO(version=0) STCO(flags=0) STCO(entries=[Container(chunk_offset=chunk_offset)]) STSZ = Container(type=b'stsz') STSZ(version=0) STSZ(flags=0) STSZ(sample_size=0) STSZ(sample_count=sample_count) STSZ(entry_sizes=sample_sizes) STBL = Container(type=b'stbl') STBL(children=[STSD, STTS, STSC, STSZ, STCO]) VMHD = Container(type=b'vmhd') VMHD(version=0) VMHD(flags=1) VMHD(graphics_mode=0) VMHD(opcolor=Container(red=0)(green=0)(blue=0)) MINF = Container(type=b'minf') MINF(children=[VMHD, DINF, STBL]) MDIA = Container(type=b'mdia') MDIA(children=[MDHD, HDLR, MINF]) # Width and height in TKHD are 16.16 integers TKHD = Container(type=b'tkhd') TKHD(version=0) TKHD(flags=3) TKHD(creation_time=0) TKHD(modification_time=0) TKHD(track_ID=1) TKHD(duration=duration) TKHD(layer=0) TKHD(alternate_group=0) TKHD(volume=0) TKHD(matrix=UNITY_MATRIX) TKHD(width=width << 16) TKHD(height=height << 16) TRAK = Container(type=b'trak') TRAK(children=[TKHD, MDIA]) MVHD = Container(type=b'mvhd') MVHD(version=0) MVHD(flags=0) MVHD(creation_time=0) MVHD(modification_time=0) MVHD(timescale=timescale) MVHD(duration=duration) MVHD(rate=0x10000) MVHD(volume=0x100) MVHD(matrix=UNITY_MATRIX) MVHD(pre_defined=[0, 0, 0, 0, 0, 0]) MVHD(next_track_ID=2) MOOV = Container(type=b'moov') MOOV(children=[MVHD, TRAK]) # Finally write self._write(Box.build(MOOV))
# 2 bits for nal ref idc # 5 bits for nal type return nal_data[0] & ((1 << 5) - 1) def sps_get_indications(nal_data): assert (nal_get_unit_type(nal_data) == NAL_TYPE_SPS) # After the nal type, follows the profile_idc, the "constraint set", aka # the compatibility byte, followed by level_idc return SPSIndications(profile=nal_data[1], compatibility=nal_data[2], level=nal_data[3]) STATIC_FTYP = Box.build( Container(type=b'ftyp')(major_brand=b'isom')(minor_version=0x200)( compatible_brands=[b'isom', b'iso2', b'avc1', b'mp41'])) STATIC_EMPTY_MDAT = Box.build(Container(type=b'mdat')(data=b'')) class MP4Muxer(object): def __init__(self): super(MP4Muxer, self).__init__() self.indications = None self.pic_parm_sets = set() self.seq_parm_sets = set() self.current_mdat_size = 0 self._sample_sizes = [] self._nal_size_patches = []