mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-11-04 08:35:12 +00:00 
			
		
		
		
	[f4m] Refresh fragment list periodically on live streams
This commit is contained in:
		@@ -121,7 +121,8 @@ class FlvReader(io.BytesIO):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.read_unsigned_int()  # BootstrapinfoVersion
 | 
					        self.read_unsigned_int()  # BootstrapinfoVersion
 | 
				
			||||||
        # Profile,Live,Update,Reserved
 | 
					        # Profile,Live,Update,Reserved
 | 
				
			||||||
        self.read(1)
 | 
					        flags = self.read_unsigned_char()
 | 
				
			||||||
 | 
					        live = flags & 0x20 != 0
 | 
				
			||||||
        # time scale
 | 
					        # time scale
 | 
				
			||||||
        self.read_unsigned_int()
 | 
					        self.read_unsigned_int()
 | 
				
			||||||
        # CurrentMediaTime
 | 
					        # CurrentMediaTime
 | 
				
			||||||
@@ -160,6 +161,7 @@ class FlvReader(io.BytesIO):
 | 
				
			|||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'segments': segments,
 | 
					            'segments': segments,
 | 
				
			||||||
            'fragments': fragments,
 | 
					            'fragments': fragments,
 | 
				
			||||||
 | 
					            'live': live,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def read_bootstrap_info(self):
 | 
					    def read_bootstrap_info(self):
 | 
				
			||||||
@@ -182,6 +184,10 @@ def build_fragments_list(boot_info):
 | 
				
			|||||||
    for segment, fragments_count in segment_run_table['segment_run']:
 | 
					    for segment, fragments_count in segment_run_table['segment_run']:
 | 
				
			||||||
        for _ in range(fragments_count):
 | 
					        for _ in range(fragments_count):
 | 
				
			||||||
            res.append((segment, next(fragments_counter)))
 | 
					            res.append((segment, next(fragments_counter)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if boot_info['live']:
 | 
				
			||||||
 | 
					        res = res[-2:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return res
 | 
					    return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -246,6 +252,38 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
            self.report_error('Unsupported DRM')
 | 
					            self.report_error('Unsupported DRM')
 | 
				
			||||||
        return media
 | 
					        return media
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_bootstrap_from_url(self, bootstrap_url):
 | 
				
			||||||
 | 
					        bootstrap = self.ydl.urlopen(bootstrap_url).read()
 | 
				
			||||||
 | 
					        return read_bootstrap_info(bootstrap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _update_live_fragments(self, bootstrap_url, latest_fragment):
 | 
				
			||||||
 | 
					        fragments_list = []
 | 
				
			||||||
 | 
					        retries = 30
 | 
				
			||||||
 | 
					        while (not fragments_list) and (retries > 0):
 | 
				
			||||||
 | 
					            boot_info = self._get_bootstrap_from_url(bootstrap_url)
 | 
				
			||||||
 | 
					            fragments_list = build_fragments_list(boot_info)
 | 
				
			||||||
 | 
					            fragments_list = [f for f in fragments_list if f[1] > latest_fragment]
 | 
				
			||||||
 | 
					            if not fragments_list:
 | 
				
			||||||
 | 
					                # Retry after a while
 | 
				
			||||||
 | 
					                time.sleep(5.0)
 | 
				
			||||||
 | 
					                retries -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not fragments_list:
 | 
				
			||||||
 | 
					            self.report_error('Failed to update fragments')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return fragments_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _parse_bootstrap_node(self, node, base_url):
 | 
				
			||||||
 | 
					        if node.text is None:
 | 
				
			||||||
 | 
					            bootstrap_url = compat_urlparse.urljoin(
 | 
				
			||||||
 | 
					                base_url, node.attrib['url'])
 | 
				
			||||||
 | 
					            boot_info = self._get_bootstrap_from_url(bootstrap_url)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            bootstrap_url = None
 | 
				
			||||||
 | 
					            bootstrap = base64.b64decode(node.text)
 | 
				
			||||||
 | 
					            boot_info = read_bootstrap_info(bootstrap)
 | 
				
			||||||
 | 
					        return (boot_info, bootstrap_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def real_download(self, filename, info_dict):
 | 
					    def real_download(self, filename, info_dict):
 | 
				
			||||||
        man_url = info_dict['url']
 | 
					        man_url = info_dict['url']
 | 
				
			||||||
        requested_bitrate = info_dict.get('tbr')
 | 
					        requested_bitrate = info_dict.get('tbr')
 | 
				
			||||||
@@ -265,18 +303,13 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
 | 
					        base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
 | 
				
			||||||
        bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
 | 
					        bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
 | 
				
			||||||
        if bootstrap_node.text is None:
 | 
					        boot_info, bootstrap_url = self._parse_bootstrap_node(bootstrap_node, base_url)
 | 
				
			||||||
            bootstrap_url = compat_urlparse.urljoin(
 | 
					        live = boot_info['live']
 | 
				
			||||||
                base_url, bootstrap_node.attrib['url'])
 | 
					 | 
				
			||||||
            bootstrap = self.ydl.urlopen(bootstrap_url).read()
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            bootstrap = base64.b64decode(bootstrap_node.text)
 | 
					 | 
				
			||||||
        metadata_node = media.find(_add_ns('metadata'))
 | 
					        metadata_node = media.find(_add_ns('metadata'))
 | 
				
			||||||
        if metadata_node is not None:
 | 
					        if metadata_node is not None:
 | 
				
			||||||
            metadata = base64.b64decode(metadata_node.text)
 | 
					            metadata = base64.b64decode(metadata_node.text)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            metadata = None
 | 
					            metadata = None
 | 
				
			||||||
        boot_info = read_bootstrap_info(bootstrap)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fragments_list = build_fragments_list(boot_info)
 | 
					        fragments_list = build_fragments_list(boot_info)
 | 
				
			||||||
        if self.params.get('test', False):
 | 
					        if self.params.get('test', False):
 | 
				
			||||||
@@ -301,7 +334,8 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
        (dest_stream, tmpfilename) = sanitize_open(tmpfilename, 'wb')
 | 
					        (dest_stream, tmpfilename) = sanitize_open(tmpfilename, 'wb')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        write_flv_header(dest_stream)
 | 
					        write_flv_header(dest_stream)
 | 
				
			||||||
        write_metadata_tag(dest_stream, metadata)
 | 
					        if not live:
 | 
				
			||||||
 | 
					            write_metadata_tag(dest_stream, metadata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # This dict stores the download progress, it's updated by the progress
 | 
					        # This dict stores the download progress, it's updated by the progress
 | 
				
			||||||
        # hook
 | 
					        # hook
 | 
				
			||||||
@@ -348,7 +382,8 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
        http_dl.add_progress_hook(frag_progress_hook)
 | 
					        http_dl.add_progress_hook(frag_progress_hook)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        frags_filenames = []
 | 
					        frags_filenames = []
 | 
				
			||||||
        for (seg_i, frag_i) in fragments_list:
 | 
					        while fragments_list:
 | 
				
			||||||
 | 
					            seg_i, frag_i = fragments_list.pop(0)
 | 
				
			||||||
            name = 'Seg%d-Frag%d' % (seg_i, frag_i)
 | 
					            name = 'Seg%d-Frag%d' % (seg_i, frag_i)
 | 
				
			||||||
            url = base_url + name
 | 
					            url = base_url + name
 | 
				
			||||||
            if akamai_pv:
 | 
					            if akamai_pv:
 | 
				
			||||||
@@ -367,6 +402,10 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
                        break
 | 
					                        break
 | 
				
			||||||
            frags_filenames.append(frag_filename)
 | 
					            frags_filenames.append(frag_filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not fragments_list and live and bootstrap_url:
 | 
				
			||||||
 | 
					                fragments_list = self._update_live_fragments(bootstrap_url, frag_i)
 | 
				
			||||||
 | 
					                self.to_screen('Updated available fragments: %d' % len(fragments_list))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dest_stream.close()
 | 
					        dest_stream.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        elapsed = time.time() - start
 | 
					        elapsed = time.time() - start
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user