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_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 process_trex(self, data): "Get potential default values." self.track_id = str_to_uint32(data[12:16]) self.default_sample_description_index = str_to_uint32(data[16:20]) self.default_sample_duration = str_to_uint32(data[20:24]) self.default_sample_size = str_to_uint32(data[24:28]) self.default_sample_flags = str_to_uint32(data[28:32]) return data
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 filterbox(self, box_type, data, file_pos, path=""): "Filter box or tree of boxes recursively." #pylint: disable=too-many-branches if path == "": path = box_type else: path = "%s.%s" % (path, box_type) output = "" if path in ("moov", "moov.trak", "moov.trak.mdia"): output += data[:8] pos = 8 while pos < len(data): size, box_type = self.check_box(data[pos:pos + 8]) output += self.filterbox(box_type, data[pos:pos + size], file_pos + len(output), path) pos += size elif path == "moov.mvhd": # Set movie duration version = ord(data[8]) if version == 1: self.movie_timescale = str_to_uint32(data[28:32]) output += data[:32] output += '\xff' * 8 # duration output += data[40:] else: # version = 0 self.movie_timescale = str_to_uint32(data[20:24]) output += data[:24] output += '\xff' * 4 # duration output += data[28:] elif path == "moov.trak.tkhd": # Set trak duration version = ord(data[8]) if version == 1: output += data[:36] output += '\xff' * 8 # duration output += data[44:] else: # version = 0 output += data[:28] output += '\xff' * 4 # duration output += data[32:] elif path == "moov.trak.mdia.mdhd": # Set media duration version = ord(data[8]) if version == 1: output += data[:32] output += '\xff' * 8 # duration output += data[40:] else: # version = 0 output += data[:24] output += '\xff' * 4 # duration output += data[28:] else: output = data return output
def filterbox(self, box_type, data, file_pos, path=""): "Filter box or tree of boxes recursively." #pylint: disable=too-many-branches if path == "": path = box_type else: path = "%s.%s" % (path, box_type) output = "" if path in ("moov", "moov.trak", "moov.trak.mdia"): output += data[:8] pos = 8 while pos < len(data): size, box_type = self.check_box(data[pos:pos + 8]) output += self.filterbox(box_type, data[pos:pos + size], file_pos + len(output), path) pos += size elif path == "moov.mvhd": # Set movie duration version = ord(data[8]) if version == 1: self.movie_timescale = str_to_uint32(data[28:32]) output += data[:32] output += '\xff'*8 # duration output += data[40:] else: # version = 0 self.movie_timescale = str_to_uint32(data[20:24]) output += data[:24] output += '\xff'*4 # duration output += data[28:] elif path == "moov.trak.tkhd": # Set trak duration version = ord(data[8]) if version == 1: output += data[:36] output += '\xff'*8 # duration output += data[44:] else: # version = 0 output += data[:28] output += '\xff'*4 # duration output += data[32:] elif path == "moov.trak.mdia.mdhd": # Set media duration version = ord(data[8]) if version == 1: output += data[:32] output += '\xff'*8 # duration output += data[40:] else: # version = 0 output += data[:24] output += '\xff'*4 # duration output += data[28:] else: output = data return output
def process_mvhd(self, data): "Process the mvhd box and set timescale." output = "" version = ord(data[8]) if version == 1: self.movie_timescale = str_to_uint32(data[28:32]) output += data[:32] output += '\x00' * 8 # duration output += data[40:] else: # version = 0 self.movie_timescale = str_to_uint32(data[20:24]) output += data[:24] output += '\x00' * 4 # duration output += data[28:] return output
def process_mvhd(self, data): "Process the mvhd box and set timescale." output = "" version = ord(data[8]) if version == 1: self.movie_timescale = str_to_uint32(data[28:32]) output += data[:32] output += '\x00'*8 # duration output += data[40:] else: # version = 0 self.movie_timescale = str_to_uint32(data[20:24]) output += data[:24] output += '\x00'*4 # duration output += data[28:] return output
def filterbox(self, box_type, data, file_pos, path=""): "Filter box or tree of boxes recursively." if path == "": path = box_type else: path = "%s.%s" % (path, box_type) output = "" if path in ("moov", "moov.trak", "moov.trak.mdia"): output += data[:8] pos = 8 while pos < len(data): size, box_type = self.check_box(data[pos:pos + 8]) output += self.filterbox(box_type, data[pos:pos + size], file_pos + len(output), path) pos += size elif path == "moov.trak.mdia.mdhd": # Find timescale self.track_timescale = str_to_uint32(data[20:24]) #print "Found track_timescale=%d" % self.track_timescale output = data elif path == "moov.trak.mdia.hdlr": # Find track type self.handler_type = data[16:20] output = data else: output = data return output
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_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 composition_time_offset to start at 0 if present.""" version_and_flags = str_to_uint32(data[8:12]) version = version_and_flags >> 24 flags = version_and_flags & 0x00ffffff cto_present = flags & 0x000800 # composition_time_offset_present if not cto_present: return data # Nothing to do output = data[:8] + '\x01' + data[9:12] # Full header version 1 sample_count = str_to_uint32(data[12:16]) offset = 16 if flags & 0x000001: # data-offset-present offset += 4 if flags & 0x000004: # first-sample-flags-present offset += 4 output += data[12:offset] cto_shift = None optional_bytes_before_cto = 0 if flags & 0x000100: # sample-duration-present optional_bytes_before_cto += 4 if flags & 0x000200: # sample-size-present optional_bytes_before_cto += 4 if flags & 0x000400: # sample-flags-present optional_bytes_before_cto += 4 for i in range(sample_count): output += data[offset:offset + optional_bytes_before_cto] offset += optional_bytes_before_cto cto = str_to_sint32(data[offset:offset + 4]) if i == 0: cto_shift = -cto cto += cto_shift output += sint32_to_str(cto) offset += 4 return output
def process_mfhd(self, data): "Extract sequence number." sequence_number = str_to_uint32(data[12:16]) segment = { 'sequence_number': sequence_number, 'moof_start_offset': self.last_moof_start } self.input_segments.append(segment) return data
def process_mdhd(self, data): "Extract track timescale." version = ord(data[8]) if version == 1: offset = 28 else: offset = 20 self.track_timescale = str_to_uint32(data[offset:offset + 4]) return data
def process_trun(self, data): """Extract trun information into self.segments[-1] and self.samples""" 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]) first_sample_flags = None pos = 16 start = self.base_media_decode_time data_offset = self.last_moof_start if flags & 0x1: # data_offset_present data_offset += str_to_uint32(data[pos:pos + 4]) pos += 4 else: raise ValueError("Cannot handle case without data_offset") if flags & 0x4: # first_sample_flags first_sample_flags = str_to_uint32(data[pos:pos + 4]) pos += 4 if flags & 0x400: # sample_flags present raise ValueError("Sample flags are not allowed with first") self.trun_base_size = pos # How many bytes this far if self.trun_sample_flags is None: self.trun_sample_flags = flags for i in range(sample_count): sample_duration = self.default_sample_duration sample_size = self.default_sample_size sample_flags = self.default_sample_flags if i == 0 and first_sample_flags is not None: sample_flags = first_sample_flags cto = self.default_sample_cto if flags & 0x100: # sample_duration present sample_duration = str_to_uint32(data[pos:pos + 4]) pos += 4 if flags & 0x200: # sample_size present sample_size = str_to_uint32(data[pos:pos + 4]) pos += 4 if flags & 0x400: # sample_flags present sample_flags = str_to_uint32(data[pos:pos + 4]) pos += 4 if flags & 0x800: # composition_time_offset present cto = str_to_uint32(data[pos:pos + 4]) pos += 4 if cto is None: cto = 0 sample = SampleData(start, sample_duration, sample_size, data_offset, sample_flags, cto) self.samples.append(sample) start += sample_duration data_offset += sample_size seg = self.input_segments[-1] seg['duration'] = start - self.base_media_decode_time return 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 process_tfdt(self, data): "Extract baseMediaDecodeTime." version = ord(data[8]) if version == 0: self.base_media_decode_time = str_to_uint32(data[12:16]) else: self.base_media_decode_time = str_to_uint64(data[12:20]) seg = self.input_segments[-1] seg['base_media_decode_time'] = self.base_media_decode_time self.tfdt_size = len(data) return data
def process_sidx(self, data, file_pos): "Extract sidx parts." version = ord(data[8]) timescale = str_to_uint32(data[16:20]) if version == 0: earliest_time = str_to_uint32(data[20:24]) first_offset = str_to_uint32(data[24:28]) pos = 28 else: earliest_time = str_to_uint32(data[20:28]) first_offset = str_to_uint64(data[28:36]) pos = 36 if first_offset != 0: raise ValueError("Only supports first_offset == 0") pos += 2 reference_count = str_to_uint16(data[pos:pos + 2]) pos += 2 sidx_data = {'timescale': timescale, 'segments': []} offset = file_pos + len(data) + first_offset start = earliest_time for i in range(reference_count): field = str_to_uint32(data[pos:pos + 4]) pos += 4 reference_type = field >> 31 if reference_type != 0: raise ValueError("Only sidx reference type == 0 supported") size = field & 0x7fffffff duration = str_to_uint32(data[pos:pos + 4]) if self.verbose: print("Input sidx %d: dur=%d" % (i + 1, duration)) pos += 4 field = str_to_uint32(data[pos:pos + 4]) pos += 4 starts_with_sap = field >> 31 if starts_with_sap != 1: raise ValueError("Only sidx with starts_with_sap supported") sap_type = (field >> 28) & 0x7 if sap_type != 1: raise ValueError("Only sap type 1 supported, not %d" % sap_type) sap_delta_time = field & 0x0fffffff if sap_delta_time != 0: raise ValueError("Only sap_delta_time == 0 supported") seg_data = { 'offset': offset, 'size': size, 'start': start, 'duration': duration } sidx_data['segments'].append(seg_data) offset += size start += duration self.sidx_data = sidx_data return 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 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_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 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 filterbox(self, box_type, data, file_pos, path=""): "Filter box or tree of boxes recursively." if path == "": path = box_type else: path = "%s.%s" % (path, box_type) output = "" if path in ("moov", "moov.trak", "moov.trak.mdia"): output += data[:8] pos = 8 while pos < len(data): size, box_type = self.check_box(data[pos:pos + 8]) output += self.filterbox(box_type, data[pos:pos+size], file_pos + len(output), path) pos += size elif path == "moov.trak.mdia.mdhd": # Find timescale self.track_timescale = str_to_uint32(data[20:24]) #print "Found track_timescale=%d" % self.track_timescale output = data elif path == "moov.trak.mdia.hdlr": # Find track type self.handler_type = data[16:20] output = data else: output = data return output
def process_tfhd(self, data): """Check flags and set default values. We are only interested in some values.""" tf_flags = str_to_uint32(data[8:12]) & 0xffffff self.track_id = str_to_uint32(data[12:16]) pos = 16 if tf_flags & 0x000001: # base_data_offset_present base_data_offset = str_to_uint64(data[pos:pos + 8]) pos += 8 if tf_flags & 0x000002: # sample-description-index-present sample_description_index = str_to_uint32(data[pos:pos + 4]) pos += 4 if tf_flags & 0x000008: # default_sample_duration_present self.default_sample_duration = str_to_uint32(data[pos:pos + 4]) pos += 4 if tf_flags & 0x000010: # default_sample_size_present self.default_sample_size = str_to_uint32(data[pos:pos + 4]) pos += 4 if tf_flags & 0x000020: # default_sample_flags_present self.default_sample_flags = str_to_uint32(data[pos:pos + 4]) pos += 4 return data
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 check_box(self, data): "Check the type of box starting at position pos." #pylint: disable=no-self-use size = str_to_uint32(data[:4]) box_type = data[4:8] return (size, box_type)