mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-11-04 00:25:15 +00:00 
			
		
		
		
	[downloader] Allow streaming unmerged formats to stdout using ffmpeg
For this to work: 1. The downloader must be ffmpeg 2. The selected formats must have the same protocol 3. The formats must be downloadable by ffmpeg to stdout Partial solution for: https://github.com/ytdl-org/youtube-dl/issues/28146, https://github.com/ytdl-org/youtube-dl/issues/27265
This commit is contained in:
		@@ -2405,7 +2405,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            params = self.params
 | 
					            params = self.params
 | 
				
			||||||
        fd = get_suitable_downloader(info, params)(self, params)
 | 
					        fd = get_suitable_downloader(info, params, to_stdout=(name == '-'))(self, params)
 | 
				
			||||||
        if not test:
 | 
					        if not test:
 | 
				
			||||||
            for ph in self._progress_hooks:
 | 
					            for ph in self._progress_hooks:
 | 
				
			||||||
                fd.add_progress_hook(ph)
 | 
					                fd.add_progress_hook(ph)
 | 
				
			||||||
@@ -2677,6 +2677,8 @@ class YoutubeDL(object):
 | 
				
			|||||||
                            'Requested formats are incompatible for merge and will be merged into mkv.')
 | 
					                            'Requested formats are incompatible for merge and will be merged into mkv.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    def correct_ext(filename):
 | 
					                    def correct_ext(filename):
 | 
				
			||||||
 | 
					                        if filename == '-':
 | 
				
			||||||
 | 
					                            return filename
 | 
				
			||||||
                        filename_real_ext = os.path.splitext(filename)[1][1:]
 | 
					                        filename_real_ext = os.path.splitext(filename)[1][1:]
 | 
				
			||||||
                        filename_wo_ext = (
 | 
					                        filename_wo_ext = (
 | 
				
			||||||
                            os.path.splitext(filename)[0]
 | 
					                            os.path.splitext(filename)[0]
 | 
				
			||||||
@@ -2696,7 +2698,8 @@ class YoutubeDL(object):
 | 
				
			|||||||
                    directly_mergable = FFmpegFD.can_merge_formats(info_dict)
 | 
					                    directly_mergable = FFmpegFD.can_merge_formats(info_dict)
 | 
				
			||||||
                    if dl_filename is not None:
 | 
					                    if dl_filename is not None:
 | 
				
			||||||
                        pass
 | 
					                        pass
 | 
				
			||||||
                    elif (directly_mergable and get_suitable_downloader(info_dict, self.params) == FFmpegFD):
 | 
					                    elif (directly_mergable and get_suitable_downloader(
 | 
				
			||||||
 | 
					                            info_dict, self.params, to_stdout=(temp_filename== '-')) == FFmpegFD):
 | 
				
			||||||
                        info_dict['url'] = '\n'.join(f['url'] for f in requested_formats)
 | 
					                        info_dict['url'] = '\n'.join(f['url'] for f in requested_formats)
 | 
				
			||||||
                        success, real_download = self.dl(temp_filename, info_dict)
 | 
					                        success, real_download = self.dl(temp_filename, info_dict)
 | 
				
			||||||
                        info_dict['__real_download'] = real_download
 | 
					                        info_dict['__real_download'] = real_download
 | 
				
			||||||
@@ -2713,10 +2716,19 @@ class YoutubeDL(object):
 | 
				
			|||||||
                                'You have requested merging of multiple formats but ffmpeg is not installed. '
 | 
					                                'You have requested merging of multiple formats but ffmpeg is not installed. '
 | 
				
			||||||
                                'The formats won\'t be merged.')
 | 
					                                'The formats won\'t be merged.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if temp_filename == '-':
 | 
				
			||||||
 | 
					                            reason = ('using a downloader other than ffmpeg' if directly_mergable
 | 
				
			||||||
 | 
					                                      else 'but the formats are incompatible for simultaneous download' if merger.available
 | 
				
			||||||
 | 
					                                      else 'but ffmpeg is not installed')
 | 
				
			||||||
 | 
					                            self.report_warning(
 | 
				
			||||||
 | 
					                                f'You have requested downloading multiple formats to stdout {reason}. '
 | 
				
			||||||
 | 
					                                'The formats will be streamed one after the other')
 | 
				
			||||||
 | 
					                            fname = temp_filename
 | 
				
			||||||
                        for f in requested_formats:
 | 
					                        for f in requested_formats:
 | 
				
			||||||
                            new_info = dict(info_dict)
 | 
					                            new_info = dict(info_dict)
 | 
				
			||||||
                            del new_info['requested_formats']
 | 
					                            del new_info['requested_formats']
 | 
				
			||||||
                            new_info.update(f)
 | 
					                            new_info.update(f)
 | 
				
			||||||
 | 
					                            if temp_filename != '-':
 | 
				
			||||||
                                fname = prepend_extension(temp_filename, 'f%s' % f['format_id'], new_info['ext'])
 | 
					                                fname = prepend_extension(temp_filename, 'f%s' % f['format_id'], new_info['ext'])
 | 
				
			||||||
                                if not self._ensure_dir_exists(fname):
 | 
					                                if not self._ensure_dir_exists(fname):
 | 
				
			||||||
                                    return
 | 
					                                    return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,12 @@ from ..utils import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_suitable_downloader(info_dict, params={}, default=NO_DEFAULT, protocol=None):
 | 
					def get_suitable_downloader(info_dict, params={}, default=NO_DEFAULT, protocol=None, to_stdout=False):
 | 
				
			||||||
    info_dict['protocol'] = determine_protocol(info_dict)
 | 
					    info_dict['protocol'] = determine_protocol(info_dict)
 | 
				
			||||||
    info_copy = info_dict.copy()
 | 
					    info_copy = info_dict.copy()
 | 
				
			||||||
    if protocol:
 | 
					    if protocol:
 | 
				
			||||||
        info_copy['protocol'] = protocol
 | 
					        info_copy['protocol'] = protocol
 | 
				
			||||||
 | 
					    info_copy['to_stdout'] = to_stdout
 | 
				
			||||||
    return _get_suitable_downloader(info_copy, params, default)
 | 
					    return _get_suitable_downloader(info_copy, params, default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,10 +85,11 @@ def _get_suitable_downloader(info_dict, params, default):
 | 
				
			|||||||
    external_downloader = (
 | 
					    external_downloader = (
 | 
				
			||||||
        downloaders if isinstance(downloaders, compat_str) or downloaders is None
 | 
					        downloaders if isinstance(downloaders, compat_str) or downloaders is None
 | 
				
			||||||
        else downloaders.get(shorten_protocol_name(protocol, True), downloaders.get('default')))
 | 
					        else downloaders.get(shorten_protocol_name(protocol, True), downloaders.get('default')))
 | 
				
			||||||
    if external_downloader and external_downloader.lower() == 'native':
 | 
					 | 
				
			||||||
        external_downloader = 'native'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if external_downloader not in (None, 'native'):
 | 
					    if external_downloader is None:
 | 
				
			||||||
 | 
					        if info_dict['to_stdout'] and FFmpegFD.can_merge_formats(info_dict, params):
 | 
				
			||||||
 | 
					            return FFmpegFD
 | 
				
			||||||
 | 
					    elif external_downloader.lower() != 'native':
 | 
				
			||||||
        ed = get_external_downloader(external_downloader)
 | 
					        ed = get_external_downloader(external_downloader)
 | 
				
			||||||
        if ed.can_download(info_dict, external_downloader):
 | 
					        if ed.can_download(info_dict, external_downloader):
 | 
				
			||||||
            return ed
 | 
					            return ed
 | 
				
			||||||
@@ -95,9 +97,10 @@ def _get_suitable_downloader(info_dict, params, default):
 | 
				
			|||||||
    if protocol in ('m3u8', 'm3u8_native'):
 | 
					    if protocol in ('m3u8', 'm3u8_native'):
 | 
				
			||||||
        if info_dict.get('is_live'):
 | 
					        if info_dict.get('is_live'):
 | 
				
			||||||
            return FFmpegFD
 | 
					            return FFmpegFD
 | 
				
			||||||
        elif external_downloader == 'native':
 | 
					        elif (external_downloader or '').lower() == 'native':
 | 
				
			||||||
            return HlsFD
 | 
					            return HlsFD
 | 
				
			||||||
        elif get_suitable_downloader(info_dict, params, None, protocol='m3u8_frag_urls'):
 | 
					        elif get_suitable_downloader(
 | 
				
			||||||
 | 
					                info_dict, params, None, protocol='m3u8_frag_urls', to_stdout=info_dict['to_stdout']):
 | 
				
			||||||
            return HlsFD
 | 
					            return HlsFD
 | 
				
			||||||
        elif params.get('hls_prefer_native') is True:
 | 
					        elif params.get('hls_prefer_native') is True:
 | 
				
			||||||
            return HlsFD
 | 
					            return HlsFD
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,8 @@ class DashSegmentsFD(FragmentFD):
 | 
				
			|||||||
        fragments = info_dict['fragments'][:1] if self.params.get(
 | 
					        fragments = info_dict['fragments'][:1] if self.params.get(
 | 
				
			||||||
            'test', False) else info_dict['fragments']
 | 
					            'test', False) else info_dict['fragments']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        real_downloader = get_suitable_downloader(info_dict, self.params, None, protocol='dash_frag_urls')
 | 
					        real_downloader = get_suitable_downloader(
 | 
				
			||||||
 | 
					            info_dict, self.params, None, protocol='dash_frag_urls', to_stdout=(filename== '-'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ctx = {
 | 
					        ctx = {
 | 
				
			||||||
            'filename': filename,
 | 
					            'filename': filename,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,7 @@ from ..utils import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ExternalFD(FileDownloader):
 | 
					class ExternalFD(FileDownloader):
 | 
				
			||||||
    SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps')
 | 
					    SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps')
 | 
				
			||||||
 | 
					    can_download_to_stdout = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def real_download(self, filename, info_dict):
 | 
					    def real_download(self, filename, info_dict):
 | 
				
			||||||
        self.report_destination(filename)
 | 
					        self.report_destination(filename)
 | 
				
			||||||
@@ -93,7 +94,9 @@ class ExternalFD(FileDownloader):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def supports(cls, info_dict):
 | 
					    def supports(cls, info_dict):
 | 
				
			||||||
        return info_dict['protocol'] in cls.SUPPORTED_PROTOCOLS
 | 
					        return (
 | 
				
			||||||
 | 
					            (cls.can_download_to_stdout or not info_dict.get('to_stdout'))
 | 
				
			||||||
 | 
					            and info_dict['protocol'] in cls.SUPPORTED_PROTOCOLS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def can_download(cls, info_dict, path=None):
 | 
					    def can_download(cls, info_dict, path=None):
 | 
				
			||||||
@@ -341,6 +344,7 @@ class HttpieFD(ExternalFD):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class FFmpegFD(ExternalFD):
 | 
					class FFmpegFD(ExternalFD):
 | 
				
			||||||
    SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'rtmp_ffmpeg', 'mms')
 | 
					    SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'rtmp_ffmpeg', 'mms')
 | 
				
			||||||
 | 
					    can_download_to_stdout = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def available(cls, path=None):
 | 
					    def available(cls, path=None):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,7 +86,8 @@ class HlsFD(FragmentFD):
 | 
				
			|||||||
        if is_webvtt:
 | 
					        if is_webvtt:
 | 
				
			||||||
            real_downloader = None  # Packing the fragments is not currently supported for external downloader
 | 
					            real_downloader = None  # Packing the fragments is not currently supported for external downloader
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            real_downloader = get_suitable_downloader(info_dict, self.params, None, protocol='m3u8_frag_urls')
 | 
					            real_downloader = get_suitable_downloader(
 | 
				
			||||||
 | 
					                info_dict, self.params, None, protocol='m3u8_frag_urls', to_stdout=(filename== '-'))
 | 
				
			||||||
        if real_downloader and not real_downloader.supports_manifest(s):
 | 
					        if real_downloader and not real_downloader.supports_manifest(s):
 | 
				
			||||||
            real_downloader = None
 | 
					            real_downloader = None
 | 
				
			||||||
        if real_downloader:
 | 
					        if real_downloader:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user