def test_add_gap(): obj = m3u8.Segment(uri='fileSequence271.ts', duration=4, gap_tag=True) result = str(obj) expected = '#EXTINF:4,\n#EXT-X-GAP\nfileSequence271.ts' assert result == expected
def insert_video(input_m3u, video, index): """ insert IN.M3U INDEX VIDEO: update m3u by inserting video at specified index (0 for start) """ m3u8_obj = m3u8.load(input_m3u) new_segment = m3u8.Segment(uri=video, title=video, duration=0) segment = m3u8_obj.segments[int(index)] print("Inserting {} before {}".format(video, segment.uri)) m3u8_obj.segments.insert(int(index), new_segment) with open(input_m3u, "w") as f: f.write(m3u8_obj.dumps())
def append_video(input_m3u, video): """ append IN.M3U VIDEO: update m3u by appending video to end """ try: m3u8_obj = m3u8.load(input_m3u) new_segment = m3u8.Segment(uri=video, title=video, duration=0) print("Append {} to end of playlist".format(video)) m3u8_obj.segments.append(new_segment) with open(input_m3u, "w") as f: f.write(m3u8_obj.dumps()) except: with open(input_m3u, "w") as f: f.write(stub_m3u_tmpl.format(video=video))
async def download(self, link): m3u8_obj = m3u8.loads(await self.fetch_with_retry(link, text=True), uri=link) if not m3u8_obj.media_sequence: if m3u8_obj.is_variant: for i, playlist in enumerate(m3u8_obj.playlists): click.echo( f'{i}: bandwidth={playlist.stream_info.bandwidth} ' f'resolution={playlist.stream_info.resolution} ' f'codecs={playlist.stream_info.codecs} ') index = click.prompt('Which playlist to download?', type=click.Choice( list(range(len(m3u8_obj.playlists)))), value_proc=int, default=0) return await self.download( m3u8_obj.playlists[index].absolute_uri) else: tmp_list = m3u8.M3U8() tmp_list.version = '3' tmp_list.media_sequence = '0' tmp_list.target_duration = m3u8_obj.target_duration tmp_list.is_endlist = True tasks = [] os.makedirs(self.cache_dir, exist_ok=True) bar = ShadyBar(self.name, max=len(m3u8_obj.segments), suffix='%(percent).1f%% - %(eta_td)s') for i, segment in enumerate(m3u8_obj.segments): tmp_list.add_segment( m3u8.Segment( f'{os.path.realpath(self.cache_dir)}/{i}.ts', duration=segment.duration, base_uri='file://')) tasks.append( asyncio.ensure_future( self.download_segment(i, segment, bar))) tmp_list.dump(f'{self.cache_dir}/filelist.m3u8') await asyncio.gather(*tasks) else: click.echo('Live streaming media is not suppported!')
def create_iframe_segments(segment): """ Takes a transport stream segment and returns I-frame segments for it """ iframes, ts_data, packets_pos = get_segment_data(segment.absolute_uri) segment_bytes = 0 segment_duration = 0 iframe_segments = [] for i, frame in enumerate(iframes): for j, pos in enumerate(packets_pos): if j < len(packets_pos) - 1 and frame[1] == pos: # We compared the output of our library to Apple's # example streams, and we were off by 188 bytes # for each I-frame byte-range. pkt_size = int(packets_pos[j + 1]) - int(pos) + 188 break else: pkt_size = frame[2] byterange = str(pkt_size) + '@' + frame[1] if i < len(iframes) - 1: extinf = float(iframes[i + 1][0]) - float(frame[0]) else: last_frame_time = ts_data[-1]['best_effort_timestamp_time'] extinf = float(last_frame_time) - float(frame[0]) segment_bytes += int(frame[2]) segment_duration += extinf iframe_segments.append( m3u8.Segment(segment.uri, segment.base_uri, duration=extinf, byterange=byterange)) return iframe_segments, segment_bytes, segment_duration
def rewind_playlist(m3u8_obj, start_at, segment_base_uri, key_headers={}): if m3u8_obj.is_variant: return None if m3u8_obj.media_sequence is None and m3u8_obj.is_endlist: # FIXME: This will attempt to play the file via the proxy, which will # fail due to the relative URLs. return m3u8_obj # Parse out the important pieces of the segment URI. base_segment = m3u8_obj.segments[0].uri segment_name_prefix = base_segment[:base_segment.rfind('_') + 1] segment_name_suffix = base_segment[base_segment.rfind('.'):] segment_time = base_segment[len(segment_name_prefix):len(base_segment) - len(segment_name_suffix)] segment_time = parser.parse(segment_time).replace(tzinfo=tz.tzutc()) # Rewinding is easy. Going forward in time is, sadly, not possible. if start_at >= segment_time: return m3u8_obj start_at -= timedelta(seconds=start_at.second % m3u8_obj.target_duration) # Rewind the playlist to the specified time. segment_duration = timedelta(seconds=m3u8_obj.target_duration) playlist_beginning = segment_time - (segment_duration * m3u8_obj.media_sequence) if start_at < playlist_beginning: start_at = playlist_beginning time_difference = segment_time - start_at time_difference = time_difference.seconds + time_difference.days * 24 * 3600 segment_time = start_at # Start building our new playlist. extra_headers = '' if len(key_headers) > 0: extra_headers = '&headers=' + urllib.quote( urllib.urlencode(key_headers)) playlist = m3u8.M3U8() playlist.target_duration = m3u8_obj.target_duration playlist.media_sequence = int(m3u8_obj.media_sequence - (time_difference / m3u8_obj.target_duration)) playlist.is_endlist = True # Stream encryption key parameters. key = None if m3u8_obj.key is not None: key_base_uri = 'http://%s:%d/key?url=' % (__socket_host__, __socket_port__) key_uri_fmt = '%s%%s%s' % ( m3u8_obj.key.uri[:m3u8_obj.key.uri.rfind('/') + 1], m3u8_obj.key.uri[m3u8_obj.key.uri.rfind('/') + 15:], ) key_time_fmt = '%Y%m%d%H0000' key = { 'method': m3u8_obj.key.method, 'uri': key_base_uri + \ urllib.quote_plus(key_uri_fmt % segment_time.strftime(key_time_fmt)) + \ extra_headers, } key_hour = segment_time.hour playlist.key = m3u8_obj.key playlist.key.uri = key['uri'] # Add the video segments. segment_uri_fmt = '%s%s%%s%s' % (segment_base_uri, segment_name_prefix, segment_name_suffix) segment_time_fmt = '%Y%m%d%H%M%S' # Give the playlist a six hour duration. # The longest game that I've seen so far is just over 5 hours. Hopefully # 6 hours will be long enough. for i in xrange(0, int(21600 / m3u8_obj.target_duration)): if key is not None and segment_time.hour != key_hour: key_hour = segment_time.hour key['uri'] = key_base_uri + \ urllib.quote_plus(key_uri_fmt % segment_time.strftime(key_time_fmt)) + \ extra_headers segment_uri = segment_uri_fmt % segment_time.strftime(segment_time_fmt) segment = m3u8.Segment(segment_uri, '', duration=m3u8_obj.target_duration, key=key) playlist.add_segment(segment) segment_time += segment_duration return playlist