def process_tfhd(self, data): "Process the mvhd box and set timescale." tf_flags = str_to_uint32(data[8:12]) & 0xffffff #print "tfhd flags = %06x" % tf_flags if self.new_default_sample_duration is not None: output = uint32_to_str(str_to_uint32(data[0:4]) + 4) output += data[4:8] output += uint32_to_str(tf_flags | 0x8) else: output = data[0:8] output += uint32_to_str(tf_flags) if self.new_track_id is not None: old_track_id = str_to_uint32(data[12:16]) output += uint32_to_str(self.new_track_id) print "Changed trackId from %d to %d" % (old_track_id, self.new_track_id) else: output += data[12:16] if self.new_default_sample_duration is not None: output += uint32_to_str(self.new_default_sample_duration) print "Added new default sample duration: %d"%(self.new_default_sample_duration) output += data[16:] return output
def _generate_trun(self, seg_info, offset): "Generate trun box with correct sample data for segment." def nr_sample_bytes(sample_flags): nr_bytes = 0 for pattern in (0x100, 0x200, 0x400, 0x800): if sample_flags & pattern != 0: nr_bytes += 4 return nr_bytes version = 0 ip = self.input_parser sample_data_size = nr_sample_bytes(self.sample_flags) sample_count = seg_info.end_nr - seg_info.start_nr trun_size = 20 + sample_count * sample_data_size output = uint32_to_str(trun_size) + 'trun' flags = self.sample_flags | 0x01 # offset present version_and_flags = (version << 24) | flags output += uint32_to_str(version_and_flags) output += uint32_to_str(sample_count) output += uint32_to_str(offset + trun_size + 8) # 8 bytes into mdat for sample in ip.samples[seg_info.start_nr:seg_info.end_nr]: if self.sample_flags & 0x100: output += uint32_to_str(sample.dur) if self.sample_flags & 0x200: output += uint32_to_str(sample.size) if self.sample_flags & 0x400: output += uint32_to_str(sample.flags) if self.sample_flags & 0x800: output += uint32_to_str(sample.cto) return output
def _generate_tfdt(self, seg_info): if seg_info.start_time > 2**30: version = 1 size = 20 else: version = 0 size = 16 output = uint32_to_str(size) + 'tfdt' if version == 0: output += uint32_to_str(0x00000000) + uint32_to_str( seg_info.start_time) else: output += uint32_to_str(0x01000000) + uint64_to_str( seg_info.start_time) return output
def _generate_traf(self, seg_info, offset): tfhd = self._generate_tfhd(seg_info, self.track_id) tfdt = self._generate_tfdt(seg_info) offset += 8 + len(tfhd) + len(tfdt) trun = self._generate_trun(seg_info, offset) size = 8 + len(tfhd) + len(tfdt) + len(trun) return uint32_to_str(size) + 'traf' + tfhd + tfdt + trun
def process_tkhd(self, data): "Process tkhd and set flags, trackId and duration." version = ord(data[8]) output = data[0:9] output += '\x00' + '\x00' + '\x07' # Set flags to 0x000007 pos = 12 if version == 1: drange = 16 else: drange = 8 output += data[pos:pos+drange] pos += drange if self.new_track_id is not None: old_track_id = str_to_uint32(data[pos:pos+4]) output += uint32_to_str(self.new_track_id) print "Changed trackId from %d to %d" % (old_track_id, self.new_track_id) else: output += data[pos:pos+4] pos += 4 output += data[pos:pos+4] pos += 4 if version == 1: output += '\x00'*8 # duration pos += 8 else: # version = 0 output += '\x00'*4 # duration pos += 4 output += data[pos:] return output
def process_trun(self, data, output): """Adjust time of tfdt if offset set.""" version_and_flags = str_to_uint32(data[8:12]) # version = version_and_flags >> 24 flags = version_and_flags & 0xffffff sample_count = str_to_uint32(data[12:16]) offset = 16 if flags & 0x1: # data_offset_present offset += 4 if flags & 0x4: # first_sample_flags offset += 4 output += data[:offset] for i in range(sample_count): if flags & 0x100: # sample_duration present output += data[offset:offset + 4] offset += 4 if flags & 0x200: # sample_size present output += data[offset:offset + 4] offset += 4 if flags & 0x400: # sample_flags present sample_flags = str_to_uint32(data[offset:offset + 4]) if sample_flags != 0x2000000: # Depends on other samples sample_flags |= 0x10000 output += uint32_to_str(sample_flags) offset += 4 if flags & 0x800: # composition_time_offset present output += data[offset:offset + 4] offset += 4 return output
def process_mfhd(self, data, output): "Set the sequence number in mfhd." if self.seq_nr is not None: output += data[:12] + uint32_to_str(self.seq_nr) else: output += data return output
def _generate_moof(self, sequence_nr, seg_info): "Generate a moof box with the correct sample entries" mfhd = self._generate_mfhd(sequence_nr) offset = 8 + len(mfhd) traf = self._generate_traf(seg_info, offset) size = 8 + len(mfhd) + len(traf) return uint32_to_str(size) + 'moof' + mfhd + traf
def process_tkhd(self, data): "Process tkhd and set flags, trackId and duration." version = ord(data[8]) output = data[0:9] output += '\x00' + '\x00' + '\x07' # Set flags to 0x000007 pos = 12 if version == 1: drange = 16 else: drange = 8 output += data[pos:pos + drange] pos += drange if self.new_track_id is not None: old_track_id = str_to_uint32(data[pos:pos + 4]) output += uint32_to_str(self.new_track_id) print "Changed trackId from %d to %d" % (old_track_id, self.new_track_id) else: output += data[pos:pos + 4] pos += 4 output += data[pos:pos + 4] pos += 4 if version == 1: output += '\x00' * 8 # duration pos += 8 else: # version = 0 output += '\x00' * 4 # duration pos += 4 output += data[pos:] return output
def construct_new_mdat(self, media_info): "Return an mdat box with data for samples in media_info." start_nr = media_info.start_nr end_nr = media_info.end_nr sample_data = [] for i in range(media_info.start_nr, media_info.end_nr): sample = self.samples[i] sample_data.append(self.data[sample.offset:sample.offset + sample.size]) combined_data = "".join(sample_data) return uint32_to_str(8 + len(combined_data)) + 'mdat' + combined_data
def process_trun(self, data): tf_flags = str_to_uint32(data[8:12]) & 0xffffff output = data[0:8] output += uint32_to_str(tf_flags) print "Changing trun sample count" output += uint32_to_str(1) if tf_flags & 1: data_offset = str_to_uint32(data[16:20]) if self.new_default_sample_duration is not None: print "Changing trun data offset: %d to %d"%(data_offset, data_offset + 4) data_offset += 4 output += uint32_to_str(data_offset) output += data[20:] return output
def finalize(self): "Rewrite the size fields which have changed" op = self.output op_parts = [] size_reduction = self.trun_data['size'] - self.trun_data['new_size'] op_parts.append(op[:self.moof_data['pos']]) # Write new moof size new_moof_size = self.moof_data['size'] - size_reduction op_parts.append(uint32_to_str(new_moof_size)) pos = self.moof_data['pos'] + 4 op_parts.append(op[pos:self.traf_data['pos']]) # Write new traf size new_traf_size = self.traf_data['size'] - size_reduction op_parts.append(uint32_to_str(new_traf_size)) pos = self.traf_data['pos'] + 4 op_parts.append(op[pos:]) self.output = "".join(op_parts)
def process_tfhd(self, data): "Process the mvhd box and set timescale." tf_flags = str_to_uint32(data[8:12]) & 0xffffff print "tfhd flags = %06x" % tf_flags output = data[0:12] if self.new_track_id is not None: old_track_id = str_to_uint32(data[12:16]) output += uint32_to_str(self.new_track_id) print "Changed trackId from %d to %d" % (old_track_id, self.new_track_id) else: output += data[12:16] output += data[16:] return output
def process_composite_box(self, path, data): "Process a composite box." #pylint: disable=unused-variable container_size, container_box_type = self.check_box(data[0:8]) inner_data = "" pos = 8 while pos < len(data): size, box_type = self.check_box(data[pos:pos + 8]) inner_data += self.filterbox(box_type, data[pos:pos+size], None, path) pos += size new_container_size = 8 + len(inner_data) #Note. If this has changed, make sure that offsets are changes appropriately return uint32_to_str(new_container_size) + container_box_type + inner_data
def change_brands(indata, major_brand, compatibility_brands, minor_version=0): "Change brands in input string indata and return new string." size = str_to_uint32(indata) ftyp = indata[4:8] assert ftyp == "ftyp" in_major_brand = indata[8:12] print "Changing major brand %s - > %s" % (in_major_brand, major_brand) in_minor_version = str_to_uint32(indata[12:16]) print "minorVersion: %s -> %s" % (in_minor_version, minor_version) pos = 16 in_compatibility_brands = [] while pos < size: in_compatibility_brands.append(indata[pos:pos+4]) pos += 4 print "Compatibility brands: %s -> %s" % (in_compatibility_brands, compatibility_brands) out_size = 16 + 4*len(compatibility_brands) outdata = uint32_to_str(out_size) + "ftyp" + major_brand + uint32_to_str(minor_version) for brand in compatibility_brands: outdata += brand outdata += indata[size:] return outdata
def process_composite_box(self, path, data): "Process composite boxes." container_size, container_box_type = self.check_box(data[0:8]) inner_data = "" pos = 8 while pos < len(data): size, box_type = self.check_box(data[pos:pos + 8]) inner_data += self.filterbox(box_type, data[pos:pos+size], None, path) #print "Box:",box_type,", size:",size,", newsize:",len(inner_data) pos += size new_container_size = 8 + len(inner_data) #TODO. If this has changed, make sure that offsets are changes appropriately return uint32_to_str(new_container_size) + container_box_type + inner_data
def change_brands(indata, major_brand, compatibility_brands, minor_version=0): "Change brands in input string indata and return new string." size = str_to_uint32(indata) ftyp = indata[4:8] assert ftyp == "ftyp" in_major_brand = indata[8:12] print "Changing major brand %s - > %s" % (in_major_brand, major_brand) in_minor_version = str_to_uint32(indata[12:16]) print "minorVersion: %s -> %s" % (in_minor_version, minor_version) pos = 16 in_compatibility_brands = [] while pos < size: in_compatibility_brands.append(indata[pos : pos + 4]) pos += 4 print "Compatibility brands: %s -> %s" % (in_compatibility_brands, compatibility_brands) out_size = 16 + 4 * len(compatibility_brands) outdata = uint32_to_str(out_size) + "ftyp" + major_brand + uint32_to_str(minor_version) for brand in compatibility_brands: outdata += brand outdata += indata[size:] return outdata
def _generate_sidx(self, segment_info, segment_sizes, timescale): "Generate sidx box." earliest_presentation_time = segment_info[0].start_time parts = ['sidx', uint32_to_str(0), # version + flags uint32_to_str(1), # reference_ID uint32_to_str(timescale), uint32_to_str(earliest_presentation_time), uint32_to_str(0), # first_offset uint16_to_str(0), # reserved uint16_to_str(len(segment_sizes))] # reference_count for info, size in zip(segment_info, segment_sizes): parts.append(uint32_to_str(size)) # Setting reference type to 0 parts.append(uint32_to_str(info.dur)) parts.append(uint32_to_str(0x90000000)) # SAP info output = ''.join(parts) size = 4 + len(output) output = uint32_to_str(size) + output return output
def process_file(self): "Process the file and add or remove lmsg." ifh = open(self.file_path, "rb") data = ifh.read() styplen = str_to_uint32(data[:4]) box_type = data[4:8] assert box_type == "styp" pos = 16 lmsg_found = False while pos < styplen: compatible_brand = data[pos:pos+4] if compatible_brand == "lmsg": lmsg_found = True if self.remove_lmsg_flag: print "Found lmsg to remove" break pos += 4 ofh = cStringIO.StringIO() if self.add_lmsg_flag and not lmsg_found: ofh.write(uint32_to_str(styplen+4)) ofh.write(data[4:styplen]) ofh.write("lmsg") ofh.write(data[styplen:]) print "Adding lmsg" elif self.remove_lmsg_flag and lmsg_found: ofh.write(uint32_to_str(styplen-4)) ofh.write(data[4:pos]) ofh.write(data[pos+4:]) else: print "No change done to %s" % self.file_path return # Nothing to do ifh.close() replace = open(self.file_path, "wb") replace.write(ofh.getvalue()) replace.close() print "Wrote %s" % self.file_path
def process_file(self): "Process the file and add or remove lmsg." ifh = open(self.file_path, "rb") data = ifh.read() styplen = str_to_uint32(data[:4]) box_type = data[4:8] assert box_type == "styp" pos = 16 lmsg_found = False while pos < styplen: compatible_brand = data[pos:pos + 4] if compatible_brand == "lmsg": lmsg_found = True if self.remove_lmsg_flag: print "Found lmsg to remove" break pos += 4 ofh = cStringIO.StringIO() if self.add_lmsg_flag and not lmsg_found: ofh.write(uint32_to_str(styplen + 4)) ofh.write(data[4:styplen]) ofh.write("lmsg") ofh.write(data[styplen:]) print "Adding lmsg" elif self.remove_lmsg_flag and lmsg_found: ofh.write(uint32_to_str(styplen - 4)) ofh.write(data[4:pos]) ofh.write(data[pos + 4:]) else: print "No change done to %s" % self.file_path return # Nothing to do ifh.close() replace = open(self.file_path, "wb") replace.write(ofh.getvalue()) replace.close() print "Wrote %s" % self.file_path
def finalize(self): print "Changing default sample size" if self.new_default_sample_duration: newoutput = self.output[:76] else: newoutput = self.output[:72] newoutput += uint32_to_str(self.ttml_length) if self.new_default_sample_duration: newoutput += self.output[80:] else: newoutput += self.output[76:] self.output = newoutput
def process_mdat(self, data): print "Merging all ttml samples into one" ttml = data[8:] #print "------------------------------" #print ttml # Process TTML # TODO Fix this to use something better then string replace ttml = ttml.replace(REPLACE_STRING, "") self.ttml_length = len(ttml) #print "------------------------------" #print ttml output = uint32_to_str(self.ttml_length + 8) output += data[4:8] output += ttml return output
def process_tfdt(self, data, output): """Adjust time of tfdt if offset set.""" version = ord(data[8]) if version == 0: # 32-bit baseMediaDecodeTime tfdt = str_to_uint32(data[12:16]) if self.offset != None: tfdt += self.offset output += data[:12] + uint32_to_str(tfdt) + data[16:] else: output += data else: output = data[:12] tfdt = str_to_uint64(data[12:20]) if self.offset != None: tfdt += self.offset output += uint64_to_str(tfdt) else: output += data self.tfdt = tfdt return output
def _generate_tfhd(self, seg_info, track_id): ip = self.input_parser first_sample = ip.samples[seg_info.start_nr] common_size = first_sample.size common_dur = first_sample.dur common_flags = first_sample.flags common_cto = first_sample.cto for sample in ip.samples[seg_info.start_nr + 1:seg_info.end_nr]: if sample.dur != common_dur: common_dur = None if sample.size != common_size: common_size = None if sample.flags != common_flags: common_flags = None if sample.cto != common_cto: common_cto = None flags = 0x020000 data = "" sample_flags = 0 # Which individual sample data is needed if common_dur is not None: flags |= 0x08 data += uint32_to_str(common_dur) else: sample_flags |= 0x100 if common_size is not None: flags |= 0x10 data += uint32_to_str(common_size) else: sample_flags |= 0x200 if common_flags is not None: flags |= 0x20 data += uint32_to_str(common_flags) else: sample_flags |= 0x400 if common_cto is None or common_cto != 0: sample_flags |= 0x800 size = 16 + len(data) self.sample_flags = sample_flags version_and_flags = flags return (uint32_to_str(size) + 'tfhd' + uint32_to_str(version_and_flags) + uint32_to_str(track_id) + data)
def _generate_mfhd(self, sequence_nr): return (uint32_to_str(16) + # size 'mfhd' + uint32_to_str(0) + # version_and_flags uint32_to_str(sequence_nr))
def process_trun(self, data): """Remove all samples but one, and change offset and report size.""" size, b_type = self.check_box(data) assert b_type == "trun" self.trun_data = {'size': size} # version = ord(data[8]) flags = str_to_uint32(data[8:12]) & 0xffffff has_data_offset = flags & 0x0001 if not has_data_offset: raise ValueError("Cannot shorten segment without data_offset") has_first_sample_flags = flags & 0x0004 has_sample_duration = flags & 0x0100 if not has_sample_duration: raise ValueError("Cannot shorten segment without sample duration") has_sample_size = flags & 0x0200 if not has_sample_size: raise ValueError("Cannot shorten segment without sample_size") has_sample_flags = flags & 0x0400 has_sample_composition_time_offset = flags & 0x0800 sample_count = str_to_uint32(data[12:16]) entry_offset = 16 data_offset = str_to_uint32(data[entry_offset:entry_offset + 4]) entry_offset += 4 if has_first_sample_flags: entry_offset += 4 sample_row_size = ((has_sample_duration and 4) + (has_sample_size and 4) + (has_sample_flags and 4) + (has_sample_composition_time_offset and 4)) # Iterate over all samples and add up duration. # Set the duration of the I-frame to the total duration total_duration = 0 pos = entry_offset for i in range(sample_count): total_duration += str_to_uint32(data[pos:pos + 4]) pos += 4 if i == 0: sample_size = str_to_uint32(data[pos:pos + 4]) self.trun_data['first_sample_size'] = sample_size pos += 4 if has_sample_flags: pos += 4 if has_sample_composition_time_offset: pos += 4 new_size = size - (sample_count - 1) * sample_row_size self.trun_data['new_size'] = new_size new_data_offset = data_offset - (size - new_size) # Here starts the trun output output = uint32_to_str(new_size) + data[4:12] output += uint32_to_str(1) # 1 sample output += uint32_to_str(new_data_offset) pos = entry_offset if has_first_sample_flags: output += data[pos:pos + 4] pos += 4 output += uint32_to_str(total_duration) pos += 4 output += data[pos:pos + sample_row_size - 4] return output
def process_mdat(self, data): """Remove all samples but one, and change offset and report size.""" size, b_type = self.check_box(data) assert b_type == "mdat" data_size = self.trun_data['first_sample_size'] return uint32_to_str(data_size + 8) + "mdat" + data[8:8 + data_size]