def insert_segmentbase(period, presentation_time_offset): "Insert SegmentBase element." segmentbase_elem = ElementTree.Element(add_ns('SegmentBase')) if presentation_time_offset != 0: segmentbase_elem.set('presentationTimeOffset', str(presentation_time_offset)) period.insert(0, segmentbase_elem)
def process_mpd(self, mpd, mpd_data): """Process the root element (MPD)""" assert mpd.tag == add_ns('MPD') mpd.set('type', mpd_data.get('type', 'dynamic')) if self.scte35_present: old_profiles = mpd.get('profiles') if not old_profiles.find(scte35.PROFILE) >= 0: new_profiles = old_profiles + "," + scte35.PROFILE mpd.set('profiles', new_profiles) key_list = [ 'availabilityStartTime', 'availabilityEndTime', 'timeShiftBufferDepth', 'minimumUpdatePeriod', 'maxSegmentDuration', 'mediaPresentationDuration', 'suggestedPresentationDelay' ] if mpd_data.get('type', 'dynamic') == 'static': key_list.remove('minimumUpdatePeriod') if (mpd_data.get('type', 'dynamic') == 'static' or mpd_data.get('mediaPresentationDuration')): key_list.remove('timeShiftBufferDepth') set_values_from_dict(mpd, key_list, mpd_data) if mpd.attrib.has_key( 'mediaPresentationDuration' ) and not mpd_data.has_key('mediaPresentationDuration'): del mpd.attrib['mediaPresentationDuration'] mpd.set('publishTime', make_timestamp( self.mpd_proc_cfg['now'])) #TODO Correlate time with change in MPD mpd.set('id', 'Config part of url maybe?') if self.segtimeline or self.segtimeline_nr: if mpd.attrib.has_key('maxSegmentDuration'): del mpd.attrib['maxSegmentDuration'] if mpd_data.get('type', 'dynamic') != 'static': mpd.set('minimumUpdatePeriod', "PT0S")
def generate_s_elem(self, start_time, duration, repeat): "Generate the S elements for the SegmentTimeline." s_elem = ElementTree.Element(add_ns('S')) if start_time is not None: s_elem.set("t", str(start_time)) s_elem.set("d", str(duration)) if repeat > 0: s_elem.set('r', str(repeat)) s_elem.tail = "\n" return s_elem
def insert_baseurl(self, mpd, pos, new_baseurl, new_ato): "Create and insert a new <BaseURL> element." baseurl_elem = ElementTree.Element(add_ns('BaseURL')) baseurl_elem.text = new_baseurl baseurl_elem.tail = "\n" if float(new_ato) == -1: self.insert_ato(baseurl_elem, 'INF') elif float(new_ato) > 0: # don't add this attribute when the value is 0 self.insert_ato(baseurl_elem, new_ato) mpd.insert(pos, baseurl_elem)
def create_descriptor_elem(self, name, scheme_id_uri, value=None, elem_id=None, messageData=None): "Create an element of DescriptorType." elem = ElementTree.Element(add_ns(name)) elem.set("schemeIdUri", scheme_id_uri) if value: elem.set("value", value) if elem_id: elem.set("id", elem_id) if messageData: elem.set("messageData", messageData) elem.tail = "\n" return elem
def create_segtimeline(self, start_time, end_time, use_closest=False): "Create and insert a new <SegmentTimeline> element and S entries." seg_timeline = ElementTree.Element(add_ns('SegmentTimeline')) seg_timeline.text = "\n" seg_timeline.tail = "\n" start = start_time * self.timescale end = end_time * self.timescale # The start segment is the latest one that starts before or at start # The end segment is the latest one that ends before or at end. (end_index, end_repeats, end_wraps) = self.find_latest_starting_before(end) if end_index is None: raise SegmentTimeLineGeneratorError( "No end_index for %d %d. Before AST" % (start_time, end_time)) end_tics = self.get_seg_endtime(end_wraps, end_index, end_repeats) #print "end_time %d %d" % (end, end_tics) while end_tics > end: if end_repeats > 0: end_repeats -= 1 # Just move one segment back in the repeat elif end_index > 0: end_index -= 1 end_repeats = self.segtimedata[end_index].repeats else: end_wraps -= 1 end_index = len(self.segtimedata) - 1 end_repeats = self.segtimedata[end_index].repeats if (end_wraps < 0): return (None, None, None) end_tics = self.get_seg_endtime(end_wraps, end_index, end_repeats) #print "end_time2 %d %d %d" % (end, end_tics, (end-end_tics)/(self.timescale*1.0)) #print "end time %d %d %d" % (end_index, end_repeats, end_wraps) if use_closest: result = self.find_closest_start(start) else: result = self.find_latest_starting_before(start) (start_index, start_repeats, start_wraps) = result #print "start %d %d %d" % (start_index, start_repeats, start_wraps) start_tics = self.get_seg_starttime(start_wraps, start_index, start_repeats) start_tics_end = self.get_seg_starttime(end_wraps, end_index, end_repeats) if (start_tics_end < start_tics): return seg_timeline # Empty timeline in this case #print "start time %d %d %d" % (start_tics, start, start - start_tics) repeat_index = end_index nr_wraps = end_wraps # Create the S elements in backwards order while repeat_index != start_index or nr_wraps != start_wraps: seg_data = self.segtimedata[repeat_index] #print repeat_index, start_index, nr_wraps, start_wraps if repeat_index == end_index: s_elem = self.generate_s_elem(None, seg_data.duration, end_repeats) else: s_elem = self.generate_s_elem(None, seg_data.duration, seg_data.repeats) seg_timeline.insert(0, s_elem) repeat_index -= 1 if repeat_index < 0: nr_wraps -= 1 repeat_index = len(self.segtimedata) - 1 # Now at first entry corresponding to start_index and start_wraps seg_data = self.segtimedata[start_index] seg_start_time = self.get_seg_starttime(nr_wraps, start_index, start_repeats) if start_index != end_index: nr_repeats = seg_data.repeats - start_repeats else: # There was only one entry which was repeated nr_repeats = end_repeats - start_repeats s_elem = self.generate_s_elem(seg_start_time, seg_data.duration, nr_repeats) seg_timeline.insert(0, s_elem) self.start_number = self.get_seg_number(nr_wraps, start_index, start_repeats) return seg_timeline
def update_periods(self, mpd, period_data, offset_at_period_level=False): "Update periods to provide appropriate values." def set_attribs(elem, keys, data): "Set element attributes from data." for key in keys: if data.has_key(key): if key == "presentationTimeOffset" and str( data[key]) == "0": # Remove default value if key in elem: del elem[key] continue elem.set(key, str(data[key])) def remove_attribs(elem, keys): "Remove attributes from elem." for key in keys: if key in elem.attrib: del elem.attrib[key] def insert_segmentbase(period, presentation_time_offset): "Insert SegmentBase element." segmentbase_elem = ElementTree.Element(add_ns('SegmentBase')) if presentation_time_offset != 0: segmentbase_elem.set('presentationTimeOffset', str(presentation_time_offset)) period.insert(0, segmentbase_elem) def create_inband_scte35stream_elem(): "Create an InbandEventStream element for SCTE-35." return self.create_descriptor_elem("InbandEventStream", scte35.SCHEME_ID_URI, value=str(scte35.PID)) def create_inline_mpdcallback_elem(BaseURLSegmented): "Create an EventStream element for MPD Callback." return self.create_descriptor_elem( "EventStream", "urn:mpeg:dash:event:callback:2015", value=str(1), elem_id=None, messageData=BaseURLSegmented) if self.segtimeline or self.segtimeline_nr: segtimeline_generators = {} for content_type in ('video', 'audio'): segtimeline_generators[ content_type] = SegmentTimeLineGenerator( self.cfg.media_data[content_type], self.cfg) periods = mpd.findall(add_ns('Period')) BaseURL = mpd.findall(add_ns('BaseURL')) if len(BaseURL) > 0: BaseURLParts = BaseURL[0].text.split('/') if len(BaseURLParts) > 3: BaseURLSegmented = BaseURLParts[0] + '//' + BaseURLParts[ 2] + '/' + BaseURLParts[3] + '/mpdcallback/' # From the Base URL last_period_id = '-1' for (period, pdata) in zip(periods, period_data): set_attribs(period, ('id', 'start'), pdata) if pdata.has_key('etpDuration'): period.set('duration', "PT%dS" % pdata['etpDuration']) if pdata.has_key('periodDuration'): period.set('duration', pdata['periodDuration']) segmenttemplate_attribs = ['startNumber'] pto = pdata['presentationTimeOffset'] if pto: if offset_at_period_level: insert_segmentbase(period, pto) else: segmenttemplate_attribs.append('presentationTimeOffset') if pdata.has_key('mpdCallback'): # Add the mpdCallback element only if the flag is raised. mpdcallback_elem = create_inline_mpdcallback_elem( BaseURLSegmented) period.insert(0, mpdcallback_elem) adaptation_sets = period.findall(add_ns('AdaptationSet')) for ad_set in adaptation_sets: ad_pos = 0 content_type = ad_set.get('contentType') if content_type == 'video' and self.scte35_present: scte35_elem = create_inband_scte35stream_elem() ad_set.insert(0, scte35_elem) ad_pos += 1 if self.continuous and last_period_id != '-1': supplementalprop_elem = self.create_descriptor_elem("SupplementalProperty", \ "urn:mpeg:dash:period_continuity:2014", last_period_id) ad_set.insert(ad_pos, supplementalprop_elem) seg_templates = ad_set.findall(add_ns('SegmentTemplate')) for seg_template in seg_templates: set_attribs(seg_template, segmenttemplate_attribs, pdata) if pdata.get('startNumber') == '-1': # Default to 1 remove_attribs(seg_template, ['startNumber']) if self.segtimeline or self.segtimeline_nr: # add SegmentTimeline block in SegmentTemplate with timescale and window. segtime_gen = segtimeline_generators[content_type] now = self.mpd_proc_cfg['now'] tsbd = self.cfg.timeshift_buffer_depth_in_s ast = self.cfg.availability_start_time_in_s start_time = max(ast + pdata['start_s'], now - tsbd) if pdata.has_key('period_duration_s'): end_time = min( ast + pdata['start_s'] + pdata['period_duration_s'], now) else: end_time = now start_time -= self.cfg.availability_start_time_in_s end_time -= self.cfg.availability_start_time_in_s use_closest = False if self.cfg.stop_time and self.cfg.timeoffset == 0: start_time = self.cfg.start_time end_time = min(now, self.cfg.stop_time) use_closest = True seg_timeline = segtime_gen.create_segtimeline( start_time, end_time, use_closest) remove_attribs(seg_template, ['duration']) seg_template.set( 'timescale', str(self.cfg.media_data[content_type] ['timescale'])) if pto != "0" and not offset_at_period_level: # rescale presentationTimeOffset based on the local timescale seg_template.set( 'presentationTimeOffset', str( int(pto) * int(self.cfg.media_data[content_type] ['timescale']))) media_template = seg_template.attrib['media'] if self.segtimeline: media_template = media_template.replace( '$Number$', 't$Time$') remove_attribs(seg_template, ['startNumber']) elif self.segtimeline_nr: # Set number to the first number listed set_attribs( seg_template, ('startNumber', ), {'startNumber': segtime_gen.start_number}) seg_template.set('media', media_template) seg_template.text = "\n" seg_template.insert(0, seg_timeline) last_period_id = pdata.get('id')
def insert_location(self, mpd, pos, location_url): location_elem = ElementTree.Element(add_ns('Location')) location_elem.text = location_url location_elem.tail = "\n" mpd.insert(pos, location_elem)
def process_mpd_children(self, mpd, data, period_data): """Process the children of the MPD element. They should be in order ProgramInformation, BaseURL, Location, Period, UTCTiming, Metrics.""" ato = 0 if data.has_key('availabilityTimeOffset'): ato = data['availabilityTimeOffset'] children = mpd.getchildren() pos = 0 for child in children: if child.tag != add_ns('ProgramInformation'): break pos += 1 next_child = mpd.getchildren()[pos] set_baseurl = SET_BASEURL if self.cfg and self.cfg.add_location: set_baseurl = False # Cannot have both BASEURL and Location if next_child.tag == add_ns('BaseURL'): if not data.has_key('BaseURL') or not set_baseurl: self.root.remove(next_child) else: self.modify_baseurl(next_child, data['BaseURL']) pos += 1 elif data.has_key('BaseURL') and set_baseurl: if data.has_key('urls') and data[ 'urls']: # check if we have to set multiple URLs url_header, url_body = data['BaseURL'].split('//') url_parts = url_body.split('/') i = -1 for part in url_parts: i += 1 if part.find("_") < 0: #Not a configuration continue cfg_parts = part.split("_", 1) key, _ = cfg_parts if key == "baseurl": url_parts[i] = "" #Remove all the baseurl elements url_parts = filter(None, url_parts) for url in data['urls']: url_parts.insert(-1, "baseurl_" + url) self.insert_baseurl( mpd, pos, url_header + "//" + "/".join(url_parts) + "/", ato) del url_parts[-2] pos += 1 else: self.insert_baseurl(mpd, pos, data['BaseURL'], ato) pos += 1 if self.cfg and self.cfg.add_location and self.full_url is not None: loc_url = re.sub(r"/startrel_[-\d]+", "/start_%d" % self.cfg.start_time, self.full_url) loc_url = re.sub(r"/stoprel_[-\d]+", "/stop_%d" % self.cfg.stop_time, loc_url) self.insert_location(mpd, pos, loc_url) pos += 1 children = mpd.getchildren() for ch_nr in range(pos, len(children)): if children[ch_nr].tag == add_ns("Period"): period = mpd.getchildren()[pos] pos = ch_nr break else: raise MpdModifierError("No period found.") for i in range(1, len(period_data)): new_period = copy.deepcopy(period) mpd.insert(pos + i, new_period) self.insert_utc_timings(mpd, pos + len(period_data)) self.update_periods(mpd, period_data, data['periodOffset'] >= 0)
def create_segtimeline(self, start_time, end_time, use_closest=False): "Create and insert a new <SegmentTimeline> element and S entries." seg_timeline = ElementTree.Element(add_ns('SegmentTimeline')) seg_timeline.text = "\n" seg_timeline.tail = "\n" start = start_time * self.timescale end = end_time * self.timescale # The start segment is the latest one that starts before or at start # The end segment is the latest one that ends before or at end. (end_index, end_repeats, end_wraps) = self.find_latest_starting_before(end) if end_index is None: raise SegmentTimeLineGeneratorError("No end_index for %d %d. Before AST" % (start_time, end_time)) end_tics = self.get_seg_endtime(end_wraps, end_index, end_repeats) #print "end_time %d %d" % (end, end_tics) while end_tics > end: if end_repeats > 0: end_repeats -= 1 # Just move one segment back in the repeat elif end_index > 0: end_index -= 1 end_repeats = self.segtimedata[end_index].repeats else: end_wraps -= 1 end_index = len(self.segtimedata) - 1 end_repeats = self.segtimedata[end_index].repeats if (end_wraps < 0): return (None, None, None) end_tics = self.get_seg_endtime(end_wraps, end_index, end_repeats) #print "end_time2 %d %d %d" % (end, end_tics, (end-end_tics)/(self.timescale*1.0)) #print "end time %d %d %d" % (end_index, end_repeats, end_wraps) if use_closest: result = self.find_closest_start(start) else: result = self.find_latest_starting_before(start) (start_index, start_repeats, start_wraps) = result #print "start %d %d %d" % (start_index, start_repeats, start_wraps) start_tics = self.get_seg_starttime(start_wraps, start_index, start_repeats) start_tics_end = self.get_seg_starttime(end_wraps, end_index, end_repeats) if (start_tics_end < start_tics): return seg_timeline # Empty timeline in this case #print "start time %d %d %d" % (start_tics, start, start - start_tics) repeat_index = end_index nr_wraps = end_wraps # Create the S elements in backwards order while repeat_index != start_index or nr_wraps != start_wraps: seg_data = self.segtimedata[repeat_index] #print repeat_index, start_index, nr_wraps, start_wraps if repeat_index == end_index: s_elem = self.generate_s_elem(None, seg_data.duration, end_repeats) else: s_elem = self.generate_s_elem(None, seg_data.duration, seg_data.repeats) seg_timeline.insert(0, s_elem) repeat_index -= 1 if repeat_index < 0: nr_wraps -= 1 repeat_index = len(self.segtimedata) - 1 # Now at first entry corresponding to start_index and start_wraps seg_data = self.segtimedata[start_index] seg_start_time = self.get_seg_starttime(nr_wraps, start_index, start_repeats) if start_index != end_index: nr_repeats = seg_data.repeats - start_repeats else: # There was only one entry which was repeated nr_repeats = end_repeats - start_repeats s_elem = self.generate_s_elem(seg_start_time, seg_data.duration, nr_repeats) seg_timeline.insert(0, s_elem) self.start_number = self.get_seg_number(nr_wraps, start_index, start_repeats) return seg_timeline