[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:
parent
a055469faf
commit
222516d97d
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user