def mux_on_sample_level(self): "Mux media samples into one mdata. This is done by simple concatenation." def get_traf_with_mod_offset(mstruct, delta_offset): "Get a traf box but with modified offset." if mstruct.trun_data_offset is None: return mstruct.traf new_data_offset = mstruct.trun_data_offset + delta_offset traf = mstruct.traf offset = mstruct.trun_data_offset_in_traf return traf[:offset] + uint32_to_str(new_data_offset) + traf[offset+4:] delta_offset1 = len(self.mstruct2.traf) delta_offset2 = len(self.mstruct1.traf) + len(self.mstruct1.mdat) - 8 traf1 = get_traf_with_mod_offset(self.mstruct1, delta_offset1) traf2 = get_traf_with_mod_offset(self.mstruct2, delta_offset2) moof_size = 8 + len(self.mstruct1.mfhd) + len(self.mstruct1.traf) + len(self.mstruct2.traf) mdat_size = len(self.mstruct1.mdat) + len(self.mstruct2.mdat) - 8 data = [] data.append(self.mstruct1.styp) data.append(uint32_to_str(moof_size)) data.append(b'moof') data.append(self.mstruct1.mfhd) data.append(traf1) data.append(traf2) data.append(uint32_to_str(mdat_size)) data.append(b'mdat') data.append(self.mstruct1.mdat[8:]) data.append(self.mstruct2.mdat[8:]) return b"".join(data)
def process_tfdt(self, data): """Generate new timestamps for tfdt and change size of boxes above if needed. Try to keep in 32 bits if possible.""" version = data[8] if self.track_timescale is not None: tfdt_offset = self.offset * self.track_timescale else: tfdt_offset = 0 if version == 0: # 32-bit baseMediaDecodeTime base_media_decode_time = str_to_uint32(data[12:16]) new_base_media_decode_time = base_media_decode_time + tfdt_offset if new_base_media_decode_time < 4294967296: output = data[:12] output += uint32_to_str(new_base_media_decode_time) else: # print "Forced to change to 64-bit tfdt." self.size_change = 4 output = uint32_to_str( str_to_uint32(data[:4]) + self.size_change) output += data[4:8] output += b'\x01' output += data[9:12] output += uint64_to_str(new_base_media_decode_time) else: # 64-bit # print "Staying at 64-bit tfdt." output = data[:12] base_media_decode_time = str_to_uint64(data[12:20]) new_base_media_decode_time = base_media_decode_time + tfdt_offset output += uint64_to_str(new_base_media_decode_time) self.tfdt_value = new_base_media_decode_time return output
def process_tfhd(self, data): "Process a tfhd box and set trackID, defaultSampleDuration and defaultSampleSize" tf_flags = str_to_uint32(data[8:12]) & 0xffffff assert tf_flags == 0x020018, "Can only handle certain tf_flags combinations" output = data[:12] output += uint32_to_str(self.track_id) output += uint32_to_str(self.default_sample_duration) output += uint32_to_str(len(self.ttml_data)) return output
def update_ttml_mdat(self, data): "Update the ttml payload of mdat and its size." ttml_xml = data[8:] ttml_out = adjust_ttml_content(ttml_xml, self.offset, self.seg_nr) self.ttml_size = len(ttml_out) out_size = self.ttml_size + 8 return uint32_to_str(out_size) + b'mdat' + ttml_out
def get_box(self): "Return emsg box as string." size = 12 + 4 * 4 + len(self.scheme_id_uri) + 1 + len( self.value) + 1 + len(self.messagedata) parts = [] parts.append(uint32_to_str(size)) parts.append(b"emsg") parts.append(b"\x00\x00\x00\x00") parts.append(self.scheme_id_uri.encode("utf-8") + b"\x00") parts.append(self.value.encode('utf-8') + b"\x00") parts.append(uint32_to_str(self.timescale)) parts.append(uint32_to_str(self.presentation_time_delta)) parts.append(uint32_to_str(self.event_duration)) parts.append(uint32_to_str(self.emsg_id)) parts.append(self.messagedata.encode("utf-8")) return b"".join(parts)
def create_sidx(self, seg_size): """Return a sidx box which can be inserted right before the moof. This is optional, but some clients require it to present.""" output = uint32_to_str(52) # Size of box output += b'sidx\x01\x00\x00\x00' # type, version and flags output += b'\x00\x00\x00\x01' # refID output += uint32_to_str(self.track_timescale) output += uint64_to_str(self.tfdt_value) # decode_time for now output += uint64_to_str(0) # first_offset = 0 output += b'\x00\x00\x00\x01' # reserved and reference_count # Next 1 bit reference type + 31 bit size of segment output += uint32_to_str(seg_size) output += uint32_to_str(self.duration) output += b'\x90\x00\x00\x00' return output
def process_saio(self, data): "Process saio and possibly change offset by size_change if needed." version_flags = str_to_uint32(data[8:12]) version = version_flags >> 24 flags = version_flags & 0xffffff pos = 12 if flags & 0x1: pos += 8 entry_count = str_to_uint32(data[pos:pos + 4]) pos += 4 output = data[:pos] delta_offset = self.size_change if version == 0: for i in range(entry_count): offset = str_to_uint32(data[pos:pos + 4]) pos += 4 output += uint32_to_str(offset + delta_offset) if i == 0: self.new_saio_value = offset + delta_offset else: for i in range(entry_count): offset = str_to_uint64(data[pos:pos + 8]) pos += 8 output += uint64_to_str(offset + delta_offset) if i == 0: self.new_saio_value = offset + delta_offset return output
def process_mvhd(self, data): "Set nextTrackId and time and movie timescale." output = self._insert_timing_data(data) pos = len(output) output += data[pos:-4] output += uint32_to_str(self.track_id + 1) # next_track_ID return output
def process_tfhd(self, data): "Process tfhd (assuming that we know the ttml size size)." tf_flags = str_to_uint32(data[8:12]) & 0xffffff pos = 16 if tf_flags & 0x01: raise MediaSegmentFilterError( "base-data-offset-present not supported in ttml segments") if tf_flags & 0x02: pos += 4 if tf_flags & 0x08: self.default_sample_duration = str_to_uint32(data[pos:pos + 4]) pos += 4 elif self.ttml_size: raise MediaSegmentFilterError( "Cannot handle ttml segments with default_sample_duration absent" ) output = data[:pos] if self.ttml_size: if tf_flags & 0x10: # old_ttml__size = str_to_uint32(data[pos:pos+4]) output += uint32_to_str(self.ttml_size) # print("Changed ttml sample size from %d to %d" % (old_ttml__size, self.ttml_size)) pos += 4 else: raise MediaSegmentFilterError( "Cannot handle ttml segments if default_sample_size_offset is absent" ) if self.ttml_size: output += data[pos:] else: output = data return output
def filter_box(self, boxtype, data, file_pos, path=b""): "Filter box or tree of boxes recursively." if boxtype == b"moof": self.moof_start = file_pos elif boxtype == b"mdat": self.mdat_start = file_pos if path == b"": path = boxtype else: path = b"%s.%s" % (path, boxtype) if boxtype in self.composite_boxes_to_parse: # print("Parsing %s" % path) output = data[:8] pos = 8 while pos < len(data): child_size, child_box_type = self.check_box(data[pos:pos + 8]) output_child_box = self.filter_box(child_box_type, data[pos:pos + child_size], file_pos + pos, path) output += output_child_box pos += child_size if len(output) != len(data): output = uint32_to_str(len(output)) + output[4:] else: method_name = "process_%s" % boxtype.decode('utf-8') method = getattr(self, method_name, None) if method is not None: output = method(data) else: output = data return output
def process_sidx(self, data): "Process sidx data and add to output." if not KEEP_SIDX: return b"" output = b"" version = data[8] timescale = str_to_uint32(data[16:20]) if version == 0: # print("Changing sidx version to 1") size = str_to_uint32(data[0:4]) sidx_size_expansion = 8 output += uint32_to_str(size + sidx_size_expansion) output += data[4:8] output += b'\x01' output += data[9:20] earliest_presentation_time = str_to_uint32(data[20:24]) first_offset = str_to_uint32(data[24:28]) else: output += data[0:20] earliest_presentation_time = str_to_uint64(data[20:28]) first_offset = str_to_uint64(data[28:36]) new_presentation_time = earliest_presentation_time + timescale * self.offset output += uint64_to_str(new_presentation_time) output += uint64_to_str(first_offset) if version == 0: output += data[28:] else: output += data[36:] return output
def get_traf_with_mod_offset(mstruct, delta_offset): "Get a traf box but with modified offset." if mstruct.trun_data_offset is None: return mstruct.traf new_data_offset = mstruct.trun_data_offset + delta_offset traf = mstruct.traf offset = mstruct.trun_data_offset_in_traf return traf[:offset] + uint32_to_str(new_data_offset) + traf[offset+4:]
def process_tkhd(self, data): "Set trackID and time." version = data[8] output = data[:12] if version == 1: if self.creation_modfication_time: output += uint64_to_str(self.creation_modfication_time) output += uint64_to_str(self.creation_modfication_time) else: output += data[12:28] output += uint32_to_str(self.track_id) output += uint32_to_str(0) output += uint64_to_str(0) # duration pos = 44 else: if self.creation_modfication_time: output += uint32_to_str(self.creation_modfication_time) output += uint32_to_str(self.creation_modfication_time) else: output += data[12:20] output += uint32_to_str(self.track_id) output += uint32_to_str(0) output += uint32_to_str(0) # duration pos = 32 output += data[pos:] return output
def construct_muxed(self): "Construct a multiplexed init segment." data = [] data.append(self.istruct1.ftyp) mvex_size = 8 + len(self.istruct1.trex) + len(self.istruct2.trex) moov_size = 8 + len(self.istruct1.mvhd) + mvex_size + len(self.istruct1.trak) + len(self.istruct2.trak) data.append(uint32_to_str(moov_size)) data.append(b'moov') data.append(self.istruct1.mvhd) data.append(uint32_to_str(mvex_size)) data.append(b'mvex') data.append(self.istruct1.trex) data.append(self.istruct2.trex) data.append(self.istruct1.trak) data.append(self.istruct2.trak) return b"".join(data)
def process_hdlr(self, data): "Set handler name, if desired." hdlr = data[16:20] hdlr_name = data[32:-1] # Actually UTF-8 encoded print("Found hdlr %s: %s" % (hdlr, hdlr_name)) if self.handler_name: output = uint32_to_str(len(self.handler_name) + 33) + data[4:32] + self.handler_name + '\x00' print("Wrote hdlr %s" % self.handler_name) else: output = data return output
def _insert_timing_data(self, data): "Help function to insert timestamps and timescale in similar boxes." version = data[8] output = data[:12] if version == 1: if self.creation_modfication_time: output += uint64_to_str(self.creation_modfication_time) output += uint64_to_str(self.creation_modfication_time) else: output += data[12:28] output += uint32_to_str(self.timescale) output += uint64_to_str(0) # duration else: if self.creation_modfication_time: output += uint32_to_str(self.creation_modfication_time) output += uint32_to_str(self.creation_modfication_time) else: output += data[12:20] output += uint32_to_str(self.timescale) output += uint32_to_str(0) return output
def process_mdhd(self, data): "Set the timescale for the trak, language and time." def get_char_bits(char): "Each character in the language is smaller case and offset at 0x60." return ord(char) - 96 output = self._insert_timing_data(data) assert len(self.lang) == 3 lang = self.lang lang_bits = (get_char_bits(lang[0]) << 10) + (get_char_bits(lang[1]) << 5) + get_char_bits(lang[2]) output += uint32_to_str(lang_bits << 16) return output
def process_tfdt_to_64bit(self, data, output): """Generate new timestamps for tfdt and change size of boxes above if needed. Note that the input output will be returned and can have another size.""" version = data[8] tfdt_offset = self.offset * self.track_timescale if version == 0: # 32-bit baseMediaDecodeTime self.size_change = 4 output = uint32_to_str(str_to_uint32(data[:4]) + self.size_change) output += data[4:8] output += b'\x01' output += data[9:12] base_media_decode_time = str_to_uint32(data[12:16]) else: # 64-bit output = data[:12] base_media_decode_time = str_to_uint64(data[12:20]) new_base_media_decode_time = base_media_decode_time + tfdt_offset output += uint64_to_str(new_base_media_decode_time) self.tfdt_value = new_base_media_decode_time return output
def process_styp(self, data): "Process styp and make sure lmsg presence follows the lmsg flag parameter. Add scte35 box if appropriate" lmsg = self.lmsg output = b"" size = str_to_uint32(data[:4]) pos = 8 brands = [] while pos < size: brand = data[pos:pos + 4] if brand != b"lmsg": brands.append(brand) pos += 4 if lmsg: brands.append(b"lmsg") new_size = 8 + 4 * len(brands) output += uint32_to_str(new_size) output += b"styp" for brand in brands: output += brand scte35box = self.create_scte35box() output += scte35box return output
def process_mfhd(self, data): "Set sequence number." return data[:12] + uint32_to_str(self.sequence_nr)
def process_trun(self, data): """Process trun box.""" # pylint: disable=too-many-locals,too-many-branches,too-many-statements output = data[:16] flags = str_to_uint32(data[8:12]) & 0xffffff sample_count = str_to_uint32(data[12:16]) pos = 16 # data_offset_present = False if flags & 0x1: # Data offset present # data_offset_present = True self.trun_offset = str_to_uint32(data[16:20]) output += uint32_to_str(self.trun_offset) pos += 4 if flags & 0x4: pos += 4 # First sample flags present sample_duration_present = flags & 0x100 sample_size_present = flags & 0x200 sample_flags_present = flags & 0x400 sample_comp_time_present = flags & 0x800 sample_time_tfdt = self.tfdt orig_sample_pos = 0 for i in range(sample_count): duration = 0 size = 0 flags = 0 comp_time = 0 if sample_duration_present: duration = str_to_uint32(data[pos:pos + 4]) pos += 4 if sample_size_present: size = str_to_uint32(data[pos:pos + 4]) pos += 4 if sample_flags_present: flags = str_to_uint32(data[pos:pos + 4]) pos += 4 if sample_comp_time_present: comp_time = str_to_uint32(data[pos:pos + 4]) pos += 4 start_time = 0 if i == 0: start_time = (sample_time_tfdt) / float(self.time_scale) else: start_time = (sample_time_tfdt + comp_time) / float( self.time_scale) end_time = (sample_time_tfdt + comp_time + duration) / float( self.time_scale) # start_time = (sample_time_tfdt) / float(self.time_scale) # end_time = (sample_time_tfdt + duration) / float(self.time_scale) # print("startTime:", start_time, "(", comp_time, ")", ", endTime:", end_time) scc_samples = self.get_scc_data(start_time, end_time) orig_sample_pos += size if len(scc_samples): print( " ", i, "SampleTime: " + str( (sample_time_tfdt) / float(self.time_scale)), "num samples to add: ", len(scc_samples)) scc_generated_data = generate_data(scc_samples) self.scc_map.append({ 'pos': orig_sample_pos, 'scc': scc_generated_data, 'len': len(scc_generated_data) }) size += len(scc_generated_data) if sample_duration_present: output += uint32_to_str(duration) if sample_size_present: output += uint32_to_str(size) if sample_flags_present: output += uint32_to_str(flags) if sample_comp_time_present: output += uint32_to_str(comp_time) sample_time_tfdt += duration return output
def process_mdat(self, data): "Make an mdat box with the right size to contain the one-and-only ttml sample." size = len(self.ttml_data) + 8 return uint32_to_str(size) + b'mdat' + self.ttml_data
def process_mfhd(self, data): "Process mfhd box and set segmentNumber if requested." if self.seg_nr is None: return data else: return data[0:12] + uint32_to_str(self.seg_nr)