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 insert_service_description(self, mpd, pos):
     sd_elem = ElementTree.Element(add_ns('ServiceDescription'))
     sd_elem.set("id", "0")
     sd_elem.text = "\n"
     lat_elem = ElementTree.Element(add_ns('Latency'))
     lat_elem.set("min", "2000")
     lat_elem.set("max", "6000")
     lat_elem.set("target", "4000")
     lat_elem.set("referenceId", "0")
     lat_elem.tail = "\n"
     sd_elem.insert(0, lat_elem)
     pr_elem = ElementTree.Element(add_ns('PlaybackRate'))
     pr_elem.set("min", "0.96")
     pr_elem.set("max", "1.04")
     pr_elem.tail = "\n"
     sd_elem.insert(1, pr_elem)
     sd_elem.tail = "\n"
     mpd.insert(pos, sd_elem)
Example #3
0
 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_producer_reference(self, ad_set, pos):
     prt_elem = ElementTree.Element(add_ns('ProducerReferenceTime'))
     prt_elem.set("id", "0")
     prt_elem.set("type", "encoder")
     prt_elem.set("wallClockTime", "1970-01-01T00:00:00")
     prt_elem.set("presentationTime", "0")
     utc_elem = self.create_descriptor_elem(
         'UTCTiming', 'urn:mpeg:dash:utc:http-iso:2014',
         UTC_TIMING_HTTP_SERVER)
     prt_elem.insert(0, utc_elem)
     prt_elem.text = "\n"
     prt_elem.tail = "\n"
     ad_set.insert(pos, prt_elem)
 def insert_baseurl(self, mpd, pos, new_baseurl, new_ato, new_atc):
     "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)
     if new_atc in ('False', 'false', '0'):
         baseurl_elem.set('availabilityTimeComplete', new_atc)
     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 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)
        if self.segtimelineloss:
            old_profiles = mpd.get('profiles')
            if old_profiles.find("dash-if-simple") >= 0:
                new_profiles = old_profiles.replace("dash-if-simple",
                                                    "dash-if-main")
                mpd.set('profiles', new_profiles)
        if 'add_profiles' in mpd_data:
            profiles = mpd.get('profiles').split(",")
            for prof in mpd_data['add_profiles']:
                if prof not in profiles:
                    profiles.append(prof)
            mpd.set('profiles', ",".join(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 'mediaPresentationDuration' in mpd.attrib and 'mediaPresentationDuration' not in mpd_data:
            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 'maxSegmentDuration' in mpd.attrib:
                del mpd.attrib['maxSegmentDuration']
            if mpd_data.get('type', 'dynamic') != 'static':
                mpd.set('minimumUpdatePeriod', "PT0S")
    def update_periods(self, mpd, period_data, offset_at_period_level,
                       ll_data):
        "Update periods to provide appropriate values."

        # pylint: disable = too-many-statements

        def set_attribs(elem, keys, data):
            "Set element attributes from data."
            for key in keys:
                if key in data:
                    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_inband_stream_elem():
            """Create an InbandEventStream element for signalling emsg in Rep when encoder fails to generate new segments

            IOP 4.11.4.3 scenario."""
            return self.create_descriptor_elem("InbandEventStream",
                                               "urn:mpeg:dash:event:2012",
                                               value=str(1))

        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 'etpDuration' in pdata:
                period.set('duration', "PT%dS" % pdata['etpDuration'])
            if 'periodDuration' in pdata:
                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 'mpdCallback' in pdata:
                # 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 self.emsg_last_seg:
                    inband_event_elem = create_inband_stream_elem()
                    ad_set.insert(0, inband_event_elem)
                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)
                if ll_data:
                    self.insert_producer_reference(ad_set, ad_pos)
                seg_templates = ad_set.findall(add_ns('SegmentTemplate'))
                for seg_template in seg_templates:
                    set_attribs(seg_template, segmenttemplate_attribs, pdata)
                    if ll_data:
                        set_attribs(seg_template, ('availabilityTimeOffset',
                                                   'availabilityTimeComplete'),
                                    ll_data)
                    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 'period_duration_s' in pdata:
                            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, ll_data):
        """Process the children of the MPD element.
        They should be in order ProgramInformation, BaseURL, Location, ServiceDescription,
        Period, UTCTiming, Metrics."""
        ato = 0
        atc = 'true'
        if 'availabilityTimeOffset' in data:
            ato = data['availabilityTimeOffset']
        if 'availabilityTimeComplete' in data:
            atc = data['availabilityTimeComplete']
        children = list(mpd)
        pos = 0
        for child in children:
            if child.tag != add_ns('ProgramInformation'):
                break
            pos += 1
        next_child = list(mpd)[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 'BaseURL' not in data or not set_baseurl:
                self.root.remove(next_child)
            else:
                self.modify_baseurl(next_child, data['BaseURL'])
                pos += 1
        elif ('BaseURL' in data) and set_baseurl:
            if 'urls' in data 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 = [p for p in url_parts if p is not None]
                for url in data['urls']:
                    url_parts.insert(-1, "baseurl_" + url)
                    self.insert_baseurl(
                        mpd, pos,
                        url_header + "//" + "/".join(url_parts) + "/", ato,
                        atc)
                    del url_parts[-2]
                    pos += 1
            else:
                self.insert_baseurl(mpd, pos, data['BaseURL'], ato, atc)
                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

        if ll_data:
            self.insert_service_description(mpd, pos)
            pos += 1

        children = list(mpd)
        for ch_nr in range(pos, len(children)):
            if children[ch_nr].tag == add_ns("Period"):
                period = list(mpd)[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,
                            ll_data)
Example #11
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
        elif len(self.segtimedata) == 1 and end_repeats < start_repeats:
            nr_repeats = (self.segtimedata[0].repeats + end_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