]> jfr.im git - yt-dlp.git/commitdiff
[downloader] Allow streaming unmerged formats to stdout using ffmpeg
authorpukkandan <redacted>
Sat, 31 Jul 2021 10:53:54 +0000 (16:23 +0530)
committerpukkandan <redacted>
Sun, 1 Aug 2021 07:08:06 +0000 (12:38 +0530)
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

yt_dlp/YoutubeDL.py
yt_dlp/downloader/__init__.py
yt_dlp/downloader/dash.py
yt_dlp/downloader/external.py
yt_dlp/downloader/hls.py

index aa8a54a5522fd1e4707b509ea8e1149500776668..11add73ade0af662e3d42f3c7a51e5e0e828cbf8 100644 (file)
@@ -2405,7 +2405,7 @@ def dl(self, name, info, subtitle=False, test=False):
             }
         else:
             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:
             for ph in self._progress_hooks:
                 fd.add_progress_hook(ph)
@@ -2677,6 +2677,8 @@ def compatible_formats(formats):
                             'Requested formats are incompatible for merge and will be merged into mkv.')
 
                     def correct_ext(filename):
+                        if filename == '-':
+                            return filename
                         filename_real_ext = os.path.splitext(filename)[1][1:]
                         filename_wo_ext = (
                             os.path.splitext(filename)[0]
@@ -2696,7 +2698,8 @@ def correct_ext(filename):
                     directly_mergable = FFmpegFD.can_merge_formats(info_dict)
                     if dl_filename is not None:
                         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)
                         success, real_download = self.dl(temp_filename, info_dict)
                         info_dict['__real_download'] = real_download
@@ -2713,14 +2716,23 @@ def correct_ext(filename):
                                 'You have requested merging of multiple formats but ffmpeg is not installed. '
                                 '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:
                             new_info = dict(info_dict)
                             del new_info['requested_formats']
                             new_info.update(f)
-                            fname = prepend_extension(temp_filename, 'f%s' % f['format_id'], new_info['ext'])
-                            if not self._ensure_dir_exists(fname):
-                                return
-                            downloaded.append(fname)
+                            if temp_filename != '-':
+                                fname = prepend_extension(temp_filename, 'f%s' % f['format_id'], new_info['ext'])
+                                if not self._ensure_dir_exists(fname):
+                                    return
+                                downloaded.append(fname)
                             partial_success, real_download = self.dl(fname, new_info)
                             info_dict['__real_download'] = info_dict['__real_download'] or real_download
                             success = success and partial_success
index 53393e89f5253747f35068e5500402dc03f35640..a0144227e16214089b8096c810ffb0b0e9fb3fae 100644 (file)
@@ -7,11 +7,12 @@
 )
 
 
-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_copy = info_dict.copy()
     if protocol:
         info_copy['protocol'] = protocol
+    info_copy['to_stdout'] = to_stdout
     return _get_suitable_downloader(info_copy, params, default)
 
 
@@ -84,10 +85,11 @@ def _get_suitable_downloader(info_dict, params, default):
     external_downloader = (
         downloaders if isinstance(downloaders, compat_str) or downloaders is None
         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)
         if ed.can_download(info_dict, external_downloader):
             return ed
@@ -95,9 +97,10 @@ def _get_suitable_downloader(info_dict, params, default):
     if protocol in ('m3u8', 'm3u8_native'):
         if info_dict.get('is_live'):
             return FFmpegFD
-        elif external_downloader == 'native':
+        elif (external_downloader or '').lower() == 'native':
             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
         elif params.get('hls_prefer_native') is True:
             return HlsFD
index ccc41e1588c4edc5c180424a6d37be74289a88cc..881ef3a1d3d6380c398689c1885d3f8c285dd8a9 100644 (file)
@@ -22,7 +22,8 @@ def real_download(self, filename, info_dict):
         fragments = info_dict['fragments'][:1] if self.params.get(
             '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 = {
             'filename': filename,
index b2a605458bb7a48bbe6fcc9a6fb71e4f5c588bfb..73c2f9edf6e0a00616bda0b518832a0d824792dc 100644 (file)
@@ -36,6 +36,7 @@
 
 class ExternalFD(FileDownloader):
     SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps')
+    can_download_to_stdout = False
 
     def real_download(self, filename, info_dict):
         self.report_destination(filename)
@@ -93,7 +94,9 @@ def available(cls, path=None):
 
     @classmethod
     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
     def can_download(cls, info_dict, path=None):
@@ -341,6 +344,7 @@ def _make_cmd(self, tmpfilename, info_dict):
 
 class FFmpegFD(ExternalFD):
     SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'rtmp_ffmpeg', 'mms')
+    can_download_to_stdout = True
 
     @classmethod
     def available(cls, path=None):
index 79d4ad5e9d8ff06030f4c79d8af5ed1f2ccbe4b7..6f9dbeedbfa3a9457c796f0876912edc6807139c 100644 (file)
@@ -86,7 +86,8 @@ def real_download(self, filename, info_dict):
         if is_webvtt:
             real_downloader = None  # Packing the fragments is not currently supported for external downloader
         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):
             real_downloader = None
         if real_downloader: