def walk_s_v2(self, segmenttimeline: SegmentTimeline, adaptationset: AdaptationSet, period: Period, sindex: int, uri_item: BaseUri): stream = DASHStream(sindex, uri_item, self.args.save_dir) stream.set_skey(adaptationset.id, None) sindex += 1 return [stream]
def walk_contentprotection(self, representation: Representation, stream: DASHStream): ''' 流的加密方案 ''' contentprotections = representation.find( 'ContentProtection') # type: List[ContentProtection] for contentprotection in contentprotections: # DASH流的解密通常是合并完整后一次解密 # 不适宜每个分段单独解密 # 那么这里就不用给每个分段设置解密key了 # 而且往往key不好拿到 所以这里仅仅做一个存储 stream.append_key(DASHKey(contentprotection))
def generate_v1(self, period: Period, rid: str, st: SegmentTemplate, stream: DASHStream): init_url = st.get_url() if '$RepresentationID$' in init_url: init_url = init_url.replace('$RepresentationID$', rid) stream.set_init_url(init_url) if st.timescale == 0: interval = st.duration else: interval = float(int(st.duration) / int(st.timescale)) repeat = math.ceil(period.duration / interval) for number in range(int(st.startNumber), repeat + int(st.startNumber)): media_url = st.get_media_url() if '$Number$' in media_url: media_url = media_url.replace('$Number$', str(number)) if re.match('.*?(\$Number%(.+?)\$)', media_url): old, fmt = re.match('.*?(\$Number(%.+?)\$)', media_url).groups() new = fmt % number media_url = media_url.replace(old, new) if '$RepresentationID$' in media_url: media_url = media_url.replace('$RepresentationID$', rid) stream.set_media_url(media_url, name_from_url=self.args.name_from_url) stream.set_segments_duration(interval)
def walk_segmentlist(self, segmentlist: SegmentList, representation: Representation, period: Period, stream: DASHStream): initializations = segmentlist.find( 'Initialization') # type: List[Initialization] has_initialization = False if len(initializations) == 1: has_initialization = True stream.set_init_url(initializations[0].sourceURL) segmenturls = segmentlist.find('SegmentURL') # type: List[SegmentURL] for segmenturl in segmenturls: stream.set_media_url(segmenturl.media, name_from_url=self.args.name_from_url) if has_initialization: interval = float(segmentlist.duration / segmentlist.timescale) stream.set_segments_duration(interval)
def walk_segmenttemplate(self, representation: Representation, period: Period, stream: DASHStream): baseurls = representation.find('BaseURL') # type: List[BaseURL] segmentbases = representation.find( 'SegmentBase') # type: List[SegmentBaee] if len(segmentbases) == 1 and len(baseurls) == 1: if stream.base_url.startswith( 'http') or stream.base_url.startswith('/'): stream.set_init_url(stream.base_url) else: # set baseurls[0].innertext.strip() ? stream.set_init_url('../' + stream.base_url) # stream.set_segment_duration(-1) return segmenttemplates = representation.find( 'SegmentTemplate') # type: List[SegmentTemplate] # segmenttimelines = representation.find('SegmentTimeline') # type: List[SegmentTimeline] if len(segmenttemplates) != 1: # 正常情况下 这里应该只有一个SegmentTemplate # 没有就无法计算分段 则跳过 # 不止一个可能是没见过的类型 提醒上报 if len(segmenttemplates) > 1: logger.error('please report this DASH content.') else: logger.warning( 'stream has no SegmentTemplate between Representation tag.' ) if stream.base_url.startswith('http'): stream.set_init_url(stream.base_url) return if len(segmenttemplates[0].find('SegmentTimeline')) == 0: self.generate_v1(period, representation.id, segmenttemplates[0], stream) return self.walk_segmenttimeline(segmenttemplates[0], representation, period, stream)
def walk_s(self, segmenttimeline: SegmentTimeline, st: SegmentTemplate, representation: Representation, period: Period, stream: DASHStream): init_url = st.get_url() if init_url is not None: if '$RepresentationID$' in init_url: init_url = init_url.replace('$RepresentationID$', representation.id) if '$Bandwidth$' in init_url: init_url = init_url.replace('$Bandwidth$', str(representation.bandwidth)) if re.match('.*?as=audio_(.*?)\)', init_url): _lang = re.match('.*?as=audio_(.*?)\)', init_url).groups()[0] stream.set_lang(_lang) stream.set_init_url(init_url) else: # 这种情况可能是因为流是字幕 pass target_r = 0 # type: int ss = segmenttimeline.find('S') # type: List[S] if len(ss) > 0 and self.is_live and ss[0].t > 0: # timeShiftBufferDepth => cdn max cache time for segments # newest available segment $Time$ should meet below condition # SegmentTimeline.S.t / timescale + (mpd.availabilityStartTime + Period.start) <= time.time() base_time = None # type: int assert isinstance(self.root.availabilityStartTime, float), 'report mpd to me' current_utctime = self.root.publishTime.timestamp( ) - self.args.live_utc_offset presentation_start = period.start - st.presentationTimeOffset / st.timescale + 30 start_utctime = self.root.availabilityStartTime + presentation_start logger.debug( f'mpd.presentationTimeOffset {st.presentationTimeOffset} timescale {st.timescale}' ) logger.debug( f'mpd.availabilityStartTime {self.root.availabilityStartTime} Period.start {period.start}' ) logger.debug( f'start_utctime {start_utctime} current_utctime {current_utctime}' ) tmp_t = ss[0].t for s in ss: for number in range(s.r): if (tmp_t + s.d ) / st.timescale + start_utctime > current_utctime: base_time = tmp_t logger.debug( f'set base_time {base_time} target_r {target_r}') break if target_r > 0: tmp_t += s.d target_r += 1 if base_time: break if base_time is None: logger.debug( f'{representation.id} report mpd to me, maybe need wait {current_utctime - start_utctime - tmp_t / st.timescale}s' ) assert base_time is not None, f'{representation.id} report mpd to me, maybe need wait {current_utctime - start_utctime - tmp_t / st.timescale}s' # if base_time is None: # base_time = ss[0].t elif ss[0].t > 0: base_time = ss[0].t logger.debug(f'ss[0].t > 0, set base_time {base_time}') else: base_time = 0 # 如果 base_time 不为 0 即第一个 s.t 不为 # 那么 time_offset 就不需要 即设置为 0 time_offset = st.presentationTimeOffset if base_time == 0 else 0 start_number = st.startNumber tmp_offset_r = 0 for index, s in enumerate(ss): if self.args.multi_s and index > 0 and s.t > 0: base_time = s.t if st.timescale == 0: interval = 0 else: interval = s.d / st.timescale for number in range(s.r): tmp_offset_r += 1 if self.is_live and tmp_offset_r < target_r: continue media_url = st.get_media_url() if '$Bandwidth$' in media_url: media_url = media_url.replace( '$Bandwidth$', str(representation.bandwidth)) if '$Number$' in media_url: media_url = media_url.replace('$Number$', str(start_number)) start_number += 1 if re.match('.*?(\$Number%(.+?)\$)', media_url): old, fmt = re.match('.*?(\$Number(%.+?)\$)', media_url).groups() new = fmt % start_number media_url = media_url.replace(old, new) start_number += 1 if '$RepresentationID$' in media_url: media_url = media_url.replace('$RepresentationID$', representation.id) if '$Time$' in media_url: fmt_time = time_offset + base_time stream.set_segment_fmt_time(fmt_time) media_url = media_url.replace('$Time$', str(fmt_time)) time_offset += s.d stream.set_segment_duration(interval) stream.set_media_url(media_url, name_from_url=self.args.name_from_url)
def walk_representation(self, adaptationset: AdaptationSet, period: Period, sindex: int, uri_item: BaseUri): ''' 每一个<Representation></Representation>都对应轨道的一/整段 ''' representations = adaptationset.find( 'Representation') # type: List[Representation] segmenttemplates = adaptationset.find( 'SegmentTemplate') # type: List[SegmentTemplate] streams = [] for representation in representations: # 修正 Representation 节点的 BaseURL base_url = self.fix_dash_base_url(uri_item.base_url, representation) current_uri_item = uri_item.new_base_url(base_url) logger.debug(f'current_base_url {current_uri_item.base_url}') stream = DASHStream(sindex, current_uri_item, self.args.save_dir) sindex += 1 self.walk_contentprotection(adaptationset, stream) self.walk_contentprotection(representation, stream) # 给流设置属性 stream.set_skey(adaptationset.id, representation.id) stream.set_lang(adaptationset.lang) stream.set_bandwidth(representation.bandwidth) if representation.codecs is None: stream.set_codecs(adaptationset.codecs) else: stream.set_codecs(representation.codecs) if representation.mimeType is None: stream.set_stream_type(adaptationset.mimeType) else: stream.set_stream_type(representation.mimeType) if representation.width is None or representation.height is None: stream.set_resolution(adaptationset.width, adaptationset.height) else: stream.set_resolution(representation.width, representation.height) # 针对字幕直链类型 Roles = adaptationset.find('Role') # type: List[Role] if stream.stream_type == '' and len(Roles) > 0: stream.set_stream_type(Roles[0].value) BaseURLs = representation.find('BaseURL') # type: List[BaseURL] if len(BaseURLs) == 1: if len(Roles) == 1 and Roles[0].value in [ 'subtitle', 'caption' ]: base_url = BaseURLs[0].innertext.strip() if base_url.startswith('http') or base_url.startswith('/'): stream.set_subtitle_url(base_url) else: stream.set_subtitle_url('../' + base_url) streams.append(stream) continue # if len(segmenttemplates) == 0 and len(representation.find('SegmentTimeline')) == 0: # stream.base2url(period.duration) # streams.append(stream) # continue segmentlists = representation.find( 'SegmentList') # type: List[SegmentList] r_segmenttemplates = representation.find( 'SegmentTemplate') # type: List[SegmentTemplate] # 针对视频音频流处理 分情况生成链接 if len(segmentlists) == 1: self.walk_segmentlist(segmentlists[0], representation, period, stream) elif len(segmenttemplates) == 0: self.walk_segmenttemplate(representation, period, stream) elif len(segmenttemplates) == 1 and len( segmenttemplates[0].find('SegmentTimeline')) == 1: self.walk_segmenttimeline(segmenttemplates[0], representation, period, stream) elif len(r_segmenttemplates) == 1 and len( r_segmenttemplates[0].find('SegmentTimeline')) == 1: self.walk_segmenttimeline(r_segmenttemplates[0], representation, period, stream) elif len(segmenttemplates ) == 1 and segmenttemplates[0].initialization is None: # tv-player.ap1.admint.biz live _segmenttemplates = representation.find('SegmentTemplate') if len(_segmenttemplates) != 1: # AdaptationSet 的 SegmentTemplate 没有 initialization # Representation 没有 SegmentTemplate 则跳过 continue # assert len(_segmenttemplates) == 1, '请报告出现此异常提示的mpd/report plz' segmenttemplate = segmenttemplates[0] _segmenttemplate = _segmenttemplates[0] if segmenttemplate.timescale is not None: _segmenttemplate.timescale = segmenttemplate.timescale if segmenttemplate.duration is not None: _segmenttemplate.duration = segmenttemplate.timescale self.generate_v1(period, representation.id, _segmenttemplate, stream) else: # SegmentTemplate 和多个 Representation 在同一级 # 那么 SegmentTemplate 的时长参数等就是多个 Representation 的参数 # 同一级的时候 只有一个 SegmentTemplate self.generate_v1(period, representation.id, segmenttemplates[0], stream) streams.append(stream) return streams