|
@@ -2,8 +2,11 @@ from __future__ import unicode_literals
|
|
|
|
|
|
import os.path
|
|
|
import subprocess
|
|
|
+import sys
|
|
|
+import re
|
|
|
|
|
|
from .common import FileDownloader
|
|
|
+from ..postprocessor.ffmpeg import FFmpegPostProcessor, EXT_TO_OUT_FORMATS
|
|
|
from ..utils import (
|
|
|
cli_option,
|
|
|
cli_valueless_option,
|
|
@@ -11,6 +14,8 @@ from ..utils import (
|
|
|
cli_configuration_args,
|
|
|
encodeFilename,
|
|
|
encodeArgument,
|
|
|
+ handle_youtubedl_headers,
|
|
|
+ check_executable,
|
|
|
)
|
|
|
|
|
|
|
|
@@ -45,10 +50,18 @@ class ExternalFD(FileDownloader):
|
|
|
def exe(self):
|
|
|
return self.params.get('external_downloader')
|
|
|
|
|
|
+ @classmethod
|
|
|
+ def available(cls):
|
|
|
+ return check_executable(cls.get_basename(), [cls.AVAILABLE_OPT])
|
|
|
+
|
|
|
@classmethod
|
|
|
def supports(cls, info_dict):
|
|
|
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):
|
|
|
return cli_option(self.params, command_option, param)
|
|
|
|
|
@@ -76,6 +89,8 @@ class ExternalFD(FileDownloader):
|
|
|
|
|
|
|
|
|
class CurlFD(ExternalFD):
|
|
|
+ AVAILABLE_OPT = '-V'
|
|
|
+
|
|
|
def _make_cmd(self, tmpfilename, info_dict):
|
|
|
cmd = [self.exe, '--location', '-o', tmpfilename]
|
|
|
for key, val in info_dict['http_headers'].items():
|
|
@@ -89,6 +104,8 @@ class CurlFD(ExternalFD):
|
|
|
|
|
|
|
|
|
class AxelFD(ExternalFD):
|
|
|
+ AVAILABLE_OPT = '-V'
|
|
|
+
|
|
|
def _make_cmd(self, tmpfilename, info_dict):
|
|
|
cmd = [self.exe, '-o', tmpfilename]
|
|
|
for key, val in info_dict['http_headers'].items():
|
|
@@ -99,6 +116,8 @@ class AxelFD(ExternalFD):
|
|
|
|
|
|
|
|
|
class WgetFD(ExternalFD):
|
|
|
+ AVAILABLE_OPT = '--version'
|
|
|
+
|
|
|
def _make_cmd(self, tmpfilename, info_dict):
|
|
|
cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies']
|
|
|
for key, val in info_dict['http_headers'].items():
|
|
@@ -112,6 +131,8 @@ class WgetFD(ExternalFD):
|
|
|
|
|
|
|
|
|
class Aria2cFD(ExternalFD):
|
|
|
+ AVAILABLE_OPT = '-v'
|
|
|
+
|
|
|
def _make_cmd(self, tmpfilename, info_dict):
|
|
|
cmd = [self.exe, '-c']
|
|
|
cmd += self._configuration_args([
|
|
@@ -130,12 +151,85 @@ class Aria2cFD(ExternalFD):
|
|
|
|
|
|
|
|
|
class HttpieFD(ExternalFD):
|
|
|
+ @classmethod
|
|
|
+ def available(cls):
|
|
|
+ return check_executable('http', ['--version'])
|
|
|
+
|
|
|
def _make_cmd(self, tmpfilename, info_dict):
|
|
|
cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']]
|
|
|
for key, val in info_dict['http_headers'].items():
|
|
|
cmd += ['%s:%s' % (key, val)]
|
|
|
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(
|
|
|
(klass.get_basename(), klass)
|
|
|
for name, klass in globals().items()
|