|
@ -2,8 +2,11 @@ from __future__ import unicode_literals |
|
|
|
|
|
|
|
|
import os.path |
|
|
import os.path |
|
|
import subprocess |
|
|
import subprocess |
|
|
|
|
|
import sys |
|
|
|
|
|
import re |
|
|
|
|
|
|
|
|
from .common import FileDownloader |
|
|
from .common import FileDownloader |
|
|
|
|
|
from ..postprocessor.ffmpeg import FFmpegPostProcessor, EXT_TO_OUT_FORMATS |
|
|
from ..utils import ( |
|
|
from ..utils import ( |
|
|
cli_option, |
|
|
cli_option, |
|
|
cli_valueless_option, |
|
|
cli_valueless_option, |
|
@ -11,6 +14,8 @@ from ..utils import ( |
|
|
cli_configuration_args, |
|
|
cli_configuration_args, |
|
|
encodeFilename, |
|
|
encodeFilename, |
|
|
encodeArgument, |
|
|
encodeArgument, |
|
|
|
|
|
handle_youtubedl_headers, |
|
|
|
|
|
check_executable, |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -45,10 +50,18 @@ class ExternalFD(FileDownloader): |
|
|
def exe(self): |
|
|
def exe(self): |
|
|
return self.params.get('external_downloader') |
|
|
return self.params.get('external_downloader') |
|
|
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
|
def available(cls): |
|
|
|
|
|
return check_executable(cls.get_basename(), [cls.AVAILABLE_OPT]) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def supports(cls, info_dict): |
|
|
def supports(cls, info_dict): |
|
|
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps') |
|
|
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps') |
|
|
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
|
def can_download(cls, info_dict): |
|
|
|
|
|
return cls.available() and cls.supports(info_dict) |
|
|
|
|
|
|
|
|
def _option(self, command_option, param): |
|
|
def _option(self, command_option, param): |
|
|
return cli_option(self.params, command_option, param) |
|
|
return cli_option(self.params, command_option, param) |
|
|
|
|
|
|
|
@ -76,6 +89,8 @@ class ExternalFD(FileDownloader): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CurlFD(ExternalFD): |
|
|
class CurlFD(ExternalFD): |
|
|
|
|
|
AVAILABLE_OPT = '-V' |
|
|
|
|
|
|
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
cmd = [self.exe, '--location', '-o', tmpfilename] |
|
|
cmd = [self.exe, '--location', '-o', tmpfilename] |
|
|
for key, val in info_dict['http_headers'].items(): |
|
|
for key, val in info_dict['http_headers'].items(): |
|
@ -89,6 +104,8 @@ class CurlFD(ExternalFD): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AxelFD(ExternalFD): |
|
|
class AxelFD(ExternalFD): |
|
|
|
|
|
AVAILABLE_OPT = '-V' |
|
|
|
|
|
|
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
cmd = [self.exe, '-o', tmpfilename] |
|
|
cmd = [self.exe, '-o', tmpfilename] |
|
|
for key, val in info_dict['http_headers'].items(): |
|
|
for key, val in info_dict['http_headers'].items(): |
|
@ -99,6 +116,8 @@ class AxelFD(ExternalFD): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WgetFD(ExternalFD): |
|
|
class WgetFD(ExternalFD): |
|
|
|
|
|
AVAILABLE_OPT = '--version' |
|
|
|
|
|
|
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies'] |
|
|
cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies'] |
|
|
for key, val in info_dict['http_headers'].items(): |
|
|
for key, val in info_dict['http_headers'].items(): |
|
@ -112,6 +131,8 @@ class WgetFD(ExternalFD): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Aria2cFD(ExternalFD): |
|
|
class Aria2cFD(ExternalFD): |
|
|
|
|
|
AVAILABLE_OPT = '-v' |
|
|
|
|
|
|
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
cmd = [self.exe, '-c'] |
|
|
cmd = [self.exe, '-c'] |
|
|
cmd += self._configuration_args([ |
|
|
cmd += self._configuration_args([ |
|
@ -130,12 +151,85 @@ class Aria2cFD(ExternalFD): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HttpieFD(ExternalFD): |
|
|
class HttpieFD(ExternalFD): |
|
|
|
|
|
@classmethod |
|
|
|
|
|
def available(cls): |
|
|
|
|
|
return check_executable('http', ['--version']) |
|
|
|
|
|
|
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
def _make_cmd(self, tmpfilename, info_dict): |
|
|
cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']] |
|
|
cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']] |
|
|
for key, val in info_dict['http_headers'].items(): |
|
|
for key, val in info_dict['http_headers'].items(): |
|
|
cmd += ['%s:%s' % (key, val)] |
|
|
cmd += ['%s:%s' % (key, val)] |
|
|
return cmd |
|
|
return cmd |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FFmpegFD(ExternalFD): |
|
|
|
|
|
@classmethod |
|
|
|
|
|
def supports(cls, info_dict): |
|
|
|
|
|
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps', 'm3u8', 'rtsp', 'rtmp', 'mms') |
|
|
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
|
def available(cls): |
|
|
|
|
|
return FFmpegPostProcessor().available |
|
|
|
|
|
|
|
|
|
|
|
def _call_downloader(self, tmpfilename, info_dict): |
|
|
|
|
|
url = info_dict['url'] |
|
|
|
|
|
ffpp = FFmpegPostProcessor(downloader=self) |
|
|
|
|
|
if not ffpp.available: |
|
|
|
|
|
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.') |
|
|
|
|
|
return False |
|
|
|
|
|
ffpp.check_version() |
|
|
|
|
|
|
|
|
|
|
|
args = [ffpp.executable, '-y'] |
|
|
|
|
|
|
|
|
|
|
|
args += self._configuration_args() |
|
|
|
|
|
|
|
|
|
|
|
# start_time = info_dict.get('start_time') or 0 |
|
|
|
|
|
# if start_time: |
|
|
|
|
|
# args += ['-ss', compat_str(start_time)] |
|
|
|
|
|
# end_time = info_dict.get('end_time') |
|
|
|
|
|
# if end_time: |
|
|
|
|
|
# args += ['-t', compat_str(end_time - start_time)] |
|
|
|
|
|
|
|
|
|
|
|
if info_dict['http_headers'] and re.match(r'^https?://', url): |
|
|
|
|
|
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv: |
|
|
|
|
|
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header. |
|
|
|
|
|
headers = handle_youtubedl_headers(info_dict['http_headers']) |
|
|
|
|
|
args += [ |
|
|
|
|
|
'-headers', |
|
|
|
|
|
''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())] |
|
|
|
|
|
|
|
|
|
|
|
args += ['-i', url, '-c', 'copy'] |
|
|
|
|
|
if info_dict.get('protocol') == 'm3u8': |
|
|
|
|
|
if self.params.get('hls_use_mpegts', False): |
|
|
|
|
|
args += ['-f', 'mpegts'] |
|
|
|
|
|
else: |
|
|
|
|
|
args += ['-f', 'mp4', '-bsf:a', 'aac_adtstoasc'] |
|
|
|
|
|
else: |
|
|
|
|
|
args += ['-f', EXT_TO_OUT_FORMATS.get(info_dict['ext'], info_dict['ext'])] |
|
|
|
|
|
|
|
|
|
|
|
args = [encodeArgument(opt) for opt in args] |
|
|
|
|
|
args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True)) |
|
|
|
|
|
|
|
|
|
|
|
self._debug_cmd(args) |
|
|
|
|
|
|
|
|
|
|
|
proc = subprocess.Popen(args, stdin=subprocess.PIPE) |
|
|
|
|
|
try: |
|
|
|
|
|
retval = proc.wait() |
|
|
|
|
|
except KeyboardInterrupt: |
|
|
|
|
|
# subprocces.run would send the SIGKILL signal to ffmpeg and the |
|
|
|
|
|
# mp4 file couldn't be played, but if we ask ffmpeg to quit it |
|
|
|
|
|
# produces a file that is playable (this is mostly useful for live |
|
|
|
|
|
# streams). Note that Windows is not affected and produces playable |
|
|
|
|
|
# files (see https://github.com/rg3/youtube-dl/issues/8300). |
|
|
|
|
|
if sys.platform != 'win32': |
|
|
|
|
|
proc.communicate(b'q') |
|
|
|
|
|
raise |
|
|
|
|
|
return retval |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AVconvFD(FFmpegFD): |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
_BY_NAME = dict( |
|
|
_BY_NAME = dict( |
|
|
(klass.get_basename(), klass) |
|
|
(klass.get_basename(), klass) |
|
|
for name, klass in globals().items() |
|
|
for name, klass in globals().items() |
|
|