Merge pull request #8611 from remitamine/ffmpegfd
[downloader/external] Add FFmpegFD
This commit is contained in:
		| @@ -1,14 +1,16 @@ | |||||||
| 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 .f4m import F4mFD | ||||||
| from .hls import HlsFD | from .hls import HlsFD | ||||||
| from .hls import NativeHlsFD |  | ||||||
| from .http import HttpFD | from .http import HttpFD | ||||||
| from .rtsp import RtspFD |  | ||||||
| from .rtmp import RtmpFD | from .rtmp import RtmpFD | ||||||
| from .dash import DashSegmentsFD | from .dash import DashSegmentsFD | ||||||
|  | from .rtsp import RtspFD | ||||||
|  | from .external import ( | ||||||
|  |     get_external_downloader, | ||||||
|  |     FFmpegFD, | ||||||
|  | ) | ||||||
|  |  | ||||||
| from ..utils import ( | from ..utils import ( | ||||||
|     determine_protocol, |     determine_protocol, | ||||||
| @@ -16,8 +18,8 @@ from ..utils import ( | |||||||
|  |  | ||||||
| PROTOCOL_MAP = { | PROTOCOL_MAP = { | ||||||
|     'rtmp': RtmpFD, |     'rtmp': RtmpFD, | ||||||
|     'm3u8_native': NativeHlsFD, |     'm3u8_native': HlsFD, | ||||||
|     'm3u8': HlsFD, |     'm3u8': FFmpegFD, | ||||||
|     'mms': RtspFD, |     'mms': RtspFD, | ||||||
|     'rtsp': RtspFD, |     'rtsp': RtspFD, | ||||||
|     'f4m': F4mFD, |     'f4m': F4mFD, | ||||||
| @@ -30,14 +32,17 @@ 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 | ||||||
|  |  | ||||||
|  |     # if (info_dict.get('start_time') or info_dict.get('end_time')) and not info_dict.get('requested_formats') and FFmpegFD.can_download(info_dict): | ||||||
|  |     #     return FFmpegFD | ||||||
|  |  | ||||||
|     external_downloader = params.get('external_downloader') |     external_downloader = params.get('external_downloader') | ||||||
|     if external_downloader is not None: |     if external_downloader is not None: | ||||||
|         ed = get_external_downloader(external_downloader) |         ed = get_external_downloader(external_downloader) | ||||||
|         if ed.supports(info_dict): |         if ed.can_download(info_dict): | ||||||
|             return ed |             return ed | ||||||
|  |  | ||||||
|     if protocol == 'm3u8' and params.get('hls_prefer_native'): |     if protocol == 'm3u8' and params.get('hls_prefer_native'): | ||||||
|         return NativeHlsFD |         return HlsFD | ||||||
|  |  | ||||||
|     return PROTOCOL_MAP.get(protocol, HttpFD) |     return PROTOCOL_MAP.get(protocol, HttpFD) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -1,87 +1,19 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
| import os | import os.path | ||||||
| import re | import re | ||||||
| import subprocess |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| from .common import FileDownloader |  | ||||||
| from .fragment import FragmentFD | from .fragment import FragmentFD | ||||||
|  |  | ||||||
| from ..compat import compat_urlparse | from ..compat import compat_urlparse | ||||||
| from ..postprocessor.ffmpeg import FFmpegPostProcessor |  | ||||||
| from ..utils import ( | from ..utils import ( | ||||||
|     encodeArgument, |  | ||||||
|     encodeFilename, |     encodeFilename, | ||||||
|     sanitize_open, |     sanitize_open, | ||||||
|     handle_youtubedl_headers, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class HlsFD(FileDownloader): | class HlsFD(FragmentFD): | ||||||
|     def real_download(self, filename, info_dict): |     """ A limited implementation that does not require ffmpeg """ | ||||||
|         url = info_dict['url'] |  | ||||||
|         self.report_destination(filename) |  | ||||||
|         tmpfilename = self.temp_name(filename) |  | ||||||
|  |  | ||||||
|         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'] |  | ||||||
|  |  | ||||||
|         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 self.params.get('hls_use_mpegts', False): |  | ||||||
|             args += ['-f', 'mpegts'] |  | ||||||
|         else: |  | ||||||
|             args += ['-f', 'mp4', '-bsf:a', 'aac_adtstoasc'] |  | ||||||
|  |  | ||||||
|         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 |  | ||||||
|         if retval == 0: |  | ||||||
|             fsize = os.path.getsize(encodeFilename(tmpfilename)) |  | ||||||
|             self.to_screen('\r[%s] %s bytes' % (args[0], 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' % (ffpp.basename, retval)) |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NativeHlsFD(FragmentFD): |  | ||||||
|     """ A more limited implementation that does not require ffmpeg """ |  | ||||||
|  |  | ||||||
|     FD_NAME = 'hlsnative' |     FD_NAME = 'hlsnative' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,19 @@ from ..utils import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | EXT_TO_OUT_FORMATS = { | ||||||
|  |     "aac": "adts", | ||||||
|  |     "m4a": "ipod", | ||||||
|  |     "mka": "matroska", | ||||||
|  |     "mkv": "matroska", | ||||||
|  |     "mpg": "mpeg", | ||||||
|  |     "ogv": "ogg", | ||||||
|  |     "ts": "mpegts", | ||||||
|  |     "wma": "asf", | ||||||
|  |     "wmv": "asf", | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class FFmpegPostProcessorError(PostProcessingError): | class FFmpegPostProcessorError(PostProcessingError): | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 remitamine
					remitamine