mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-30 22:25:19 +00:00 
			
		
		
		
	[downloader] Lay groundwork for external downloaders.
This comes with a very simply implementation for wget; the real work is in setting up the infrastructure.
This commit is contained in:
		| @@ -219,6 +219,7 @@ class YoutubeDL(object): | |||||||
|     call_home:         Boolean, true iff we are allowed to contact the |     call_home:         Boolean, true iff we are allowed to contact the | ||||||
|                        youtube-dl servers for debugging. |                        youtube-dl servers for debugging. | ||||||
|     sleep_interval:    Number of seconds to sleep before each download. |     sleep_interval:    Number of seconds to sleep before each download. | ||||||
|  |     external_downloader:  Executable of the external downloader to call. | ||||||
|  |  | ||||||
|  |  | ||||||
|     The following parameters are not used by YoutubeDL itself, they are used by |     The following parameters are not used by YoutubeDL itself, they are used by | ||||||
|   | |||||||
| @@ -330,6 +330,7 @@ def _real_main(argv=None): | |||||||
|         'source_address': opts.source_address, |         'source_address': opts.source_address, | ||||||
|         'call_home': opts.call_home, |         'call_home': opts.call_home, | ||||||
|         'sleep_interval': opts.sleep_interval, |         'sleep_interval': opts.sleep_interval, | ||||||
|  |         'external_downloader': opts.external_downloader, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     with YoutubeDL(ydl_opts) as ydl: |     with YoutubeDL(ydl_opts) as ydl: | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
| from .common import FileDownloader | from .common import FileDownloader | ||||||
|  | from .external import get_external_downloader | ||||||
|  | from .f4m import F4mFD | ||||||
| from .hls import HlsFD | from .hls import HlsFD | ||||||
| from .hls import NativeHlsFD | from .hls import NativeHlsFD | ||||||
| from .http import HttpFD | from .http import HttpFD | ||||||
| from .mplayer import MplayerFD | from .mplayer import MplayerFD | ||||||
| from .rtmp import RtmpFD | from .rtmp import RtmpFD | ||||||
| from .f4m import F4mFD |  | ||||||
|  |  | ||||||
| from ..utils import ( | from ..utils import ( | ||||||
|     determine_protocol, |     determine_protocol, | ||||||
| @@ -27,6 +28,12 @@ def get_suitable_downloader(info_dict, params={}): | |||||||
|     protocol = determine_protocol(info_dict) |     protocol = determine_protocol(info_dict) | ||||||
|     info_dict['protocol'] = protocol |     info_dict['protocol'] = protocol | ||||||
|  |  | ||||||
|  |     external_downloader = params.get('external_downloader') | ||||||
|  |     if external_downloader is not None: | ||||||
|  |         ed = get_external_downloader(external_downloader) | ||||||
|  |         if ed.supports(info_dict): | ||||||
|  |             return ed | ||||||
|  |  | ||||||
|     return PROTOCOL_MAP.get(protocol, HttpFD) |     return PROTOCOL_MAP.get(protocol, HttpFD) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -325,3 +325,24 @@ class FileDownloader(object): | |||||||
|         # See YoutubeDl.py (search for progress_hooks) for a description of |         # See YoutubeDl.py (search for progress_hooks) for a description of | ||||||
|         # this interface |         # this interface | ||||||
|         self._progress_hooks.append(ph) |         self._progress_hooks.append(ph) | ||||||
|  |  | ||||||
|  |     def _debug_cmd(self, args, subprocess_encoding, exe=None): | ||||||
|  |         if not self.params.get('verbose', False): | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if exe is None: | ||||||
|  |             exe = os.path.basename(args[0]) | ||||||
|  |  | ||||||
|  |         if subprocess_encoding: | ||||||
|  |             str_args = [ | ||||||
|  |                 a.decode(subprocess_encoding) if isinstance(a, bytes) else a | ||||||
|  |                 for a in args] | ||||||
|  |         else: | ||||||
|  |             str_args = args | ||||||
|  |         try: | ||||||
|  |             import pipes | ||||||
|  |             shell_quote = lambda args: ' '.join(map(pipes.quote, str_args)) | ||||||
|  |         except ImportError: | ||||||
|  |             shell_quote = repr | ||||||
|  |         self.to_screen('[debug] %s command line: %s' % ( | ||||||
|  |             exe, shell_quote(str_args))) | ||||||
|   | |||||||
							
								
								
									
										131
									
								
								youtube_dl/downloader/external.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								youtube_dl/downloader/external.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | |||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import os.path | ||||||
|  | import subprocess | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | from .common import FileDownloader | ||||||
|  | from ..utils import ( | ||||||
|  |     encodeFilename, | ||||||
|  |     std_headers, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ExternalFD(FileDownloader): | ||||||
|  |     def real_download(self, filename, info_dict): | ||||||
|  |         self.report_destination(filename) | ||||||
|  |         tmpfilename = self.temp_name(filename) | ||||||
|  |  | ||||||
|  |         retval = self._call_downloader(tmpfilename, info_dict) | ||||||
|  |         if retval == 0: | ||||||
|  |             fsize = os.path.getsize(encodeFilename(tmpfilename)) | ||||||
|  |             self.to_screen('\r[%s] Downloaded %s bytes' % (self.get_basename(), fsize)) | ||||||
|  |             self.try_rename(tmpfilename, filename) | ||||||
|  |             self._hook_progress({ | ||||||
|  |                 'downloaded_bytes': fsize, | ||||||
|  |                 'total_bytes': fsize, | ||||||
|  |                 'filename': filename, | ||||||
|  |                 'status': 'finished', | ||||||
|  |             }) | ||||||
|  |             return True | ||||||
|  |         else: | ||||||
|  |             self.to_stderr('\n') | ||||||
|  |             self.report_error('%s exited with code %d' % ( | ||||||
|  |                 self.get_basename(), retval)) | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def get_basename(cls): | ||||||
|  |         return cls.__name__[:-2].lower() | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def exe(self): | ||||||
|  |         return self.params.get('external_downloader') | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def supports(cls, info_dict): | ||||||
|  |         return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps') | ||||||
|  |  | ||||||
|  |     def _calc_headers(self, info_dict): | ||||||
|  |         res = std_headers.copy() | ||||||
|  |  | ||||||
|  |         ua = info_dict.get('user_agent') | ||||||
|  |         if ua is not None: | ||||||
|  |             res['User-Agent'] = ua | ||||||
|  |  | ||||||
|  |         cookies = self._calc_cookies(info_dict) | ||||||
|  |         if cookies: | ||||||
|  |             res['Cookie'] = cookies | ||||||
|  |  | ||||||
|  |         return res | ||||||
|  |  | ||||||
|  |     def _calc_cookies(self, info_dict): | ||||||
|  |         class _PseudoRequest(object): | ||||||
|  |             def __init__(self, url): | ||||||
|  |                 self.url = url | ||||||
|  |                 self.headers = {} | ||||||
|  |                 self.unverifiable = False | ||||||
|  |  | ||||||
|  |             def add_unredirected_header(self, k, v): | ||||||
|  |                 self.headers[k] = v | ||||||
|  |  | ||||||
|  |             def get_full_url(self): | ||||||
|  |                 return self.url | ||||||
|  |  | ||||||
|  |             def is_unverifiable(self): | ||||||
|  |                 return self.unverifiable | ||||||
|  |  | ||||||
|  |             def has_header(self, h): | ||||||
|  |                 return h in self.headers | ||||||
|  |  | ||||||
|  |         pr = _PseudoRequest(info_dict['url']) | ||||||
|  |         self.ydl.cookiejar.add_cookie_header(pr) | ||||||
|  |         return pr.headers.get('Cookie') | ||||||
|  |  | ||||||
|  |     def _call_downloader(self, tmpfilename, info_dict): | ||||||
|  |         """ Either overwrite this or implement _make_cmd """ | ||||||
|  |         cmd = self._make_cmd(tmpfilename, info_dict) | ||||||
|  |  | ||||||
|  |         if sys.platform == 'win32' and sys.version_info < (3, 0): | ||||||
|  |             # Windows subprocess module does not actually support Unicode | ||||||
|  |             # on Python 2.x | ||||||
|  |             # See http://stackoverflow.com/a/9951851/35070 | ||||||
|  |             subprocess_encoding = sys.getfilesystemencoding() | ||||||
|  |             cmd = [a.encode(subprocess_encoding, 'ignore') for a in cmd] | ||||||
|  |         else: | ||||||
|  |             subprocess_encoding = None | ||||||
|  |         self._debug_cmd(cmd, subprocess_encoding) | ||||||
|  |  | ||||||
|  |         p = subprocess.Popen( | ||||||
|  |             cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||||||
|  |         stdout, stderr = p.communicate() | ||||||
|  |         if p.returncode != 0: | ||||||
|  |             self.to_stderr(stderr) | ||||||
|  |         return p.returncode | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WgetFD(ExternalFD): | ||||||
|  |     def _make_cmd(self, tmpfilename, info_dict): | ||||||
|  |         cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies'] | ||||||
|  |         for key, val in self._calc_headers(info_dict).items(): | ||||||
|  |             cmd += ['--header', '%s: %s' % (key, val)] | ||||||
|  |         cmd += ['--', info_dict['url']] | ||||||
|  |         return cmd | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _BY_NAME = dict( | ||||||
|  |     (klass.get_basename(), klass) | ||||||
|  |     for name, klass in globals().items() | ||||||
|  |     if name.endswith('FD') and name != 'ExternalFD' | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def list_external_downloaders(): | ||||||
|  |     return sorted(_BY_NAME.keys()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_external_downloader(external_downloader): | ||||||
|  |     """ Given the name of the executable, see whether we support the given | ||||||
|  |         downloader . """ | ||||||
|  |     bn = os.path.basename(external_downloader) | ||||||
|  |     return _BY_NAME[bn] | ||||||
| @@ -152,19 +152,7 @@ class RtmpFD(FileDownloader): | |||||||
|         else: |         else: | ||||||
|             subprocess_encoding = None |             subprocess_encoding = None | ||||||
|  |  | ||||||
|         if self.params.get('verbose', False): |         self._debug_cmd(args, subprocess_encoding, exe='rtmpdump') | ||||||
|             if subprocess_encoding: |  | ||||||
|                 str_args = [ |  | ||||||
|                     a.decode(subprocess_encoding) if isinstance(a, bytes) else a |  | ||||||
|                     for a in args] |  | ||||||
|             else: |  | ||||||
|                 str_args = args |  | ||||||
|             try: |  | ||||||
|                 import pipes |  | ||||||
|                 shell_quote = lambda args: ' '.join(map(pipes.quote, str_args)) |  | ||||||
|             except ImportError: |  | ||||||
|                 shell_quote = repr |  | ||||||
|             self.to_screen('[debug] rtmpdump command line: ' + shell_quote(str_args)) |  | ||||||
|  |  | ||||||
|         RD_SUCCESS = 0 |         RD_SUCCESS = 0 | ||||||
|         RD_FAILED = 1 |         RD_FAILED = 1 | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import optparse | |||||||
| import shlex | import shlex | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
|  | from .downloader.external import list_external_downloaders | ||||||
| from .compat import ( | from .compat import ( | ||||||
|     compat_expanduser, |     compat_expanduser, | ||||||
|     compat_getenv, |     compat_getenv, | ||||||
| @@ -389,6 +390,11 @@ def parseOpts(overrideArguments=None): | |||||||
|         '--playlist-reverse', |         '--playlist-reverse', | ||||||
|         action='store_true', |         action='store_true', | ||||||
|         help='Download playlist videos in reverse order') |         help='Download playlist videos in reverse order') | ||||||
|  |     downloader.add_option( | ||||||
|  |         '--external-downloader', | ||||||
|  |         dest='external_downloader', metavar='COMMAND', | ||||||
|  |         help='(experimental) Use the specified external downloader. ' | ||||||
|  |              'Currently supports %s' % ','.join(list_external_downloaders())) | ||||||
|  |  | ||||||
|     workarounds = optparse.OptionGroup(parser, 'Workarounds') |     workarounds = optparse.OptionGroup(parser, 'Workarounds') | ||||||
|     workarounds.add_option( |     workarounds.add_option( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Philipp Hagemeister
					Philipp Hagemeister