diff options
| -rw-r--r-- | youtube_dl/downloader/__init__.py | 19 | ||||
| -rw-r--r-- | youtube_dl/downloader/external.py | 94 | ||||
| -rw-r--r-- | youtube_dl/downloader/hls.py | 74 | ||||
| -rw-r--r-- | youtube_dl/postprocessor/ffmpeg.py | 13 | 
4 files changed, 122 insertions, 78 deletions
| diff --git a/youtube_dl/downloader/__init__.py b/youtube_dl/downloader/__init__.py index dccc59212..73b34fdae 100644 --- a/youtube_dl/downloader/__init__.py +++ b/youtube_dl/downloader/__init__.py @@ -1,14 +1,16 @@  from __future__ import unicode_literals  from .common import FileDownloader -from .external import get_external_downloader  from .f4m import F4mFD  from .hls import HlsFD -from .hls import NativeHlsFD  from .http import HttpFD -from .rtsp import RtspFD  from .rtmp import RtmpFD  from .dash import DashSegmentsFD +from .rtsp import RtspFD +from .external import ( +    get_external_downloader, +    FFmpegFD, +)  from ..utils import (      determine_protocol, @@ -16,8 +18,8 @@ from ..utils import (  PROTOCOL_MAP = {      'rtmp': RtmpFD, -    'm3u8_native': NativeHlsFD, -    'm3u8': HlsFD, +    'm3u8_native': HlsFD, +    'm3u8': FFmpegFD,      'mms': RtspFD,      'rtsp': RtspFD,      'f4m': F4mFD, @@ -30,14 +32,17 @@ def get_suitable_downloader(info_dict, params={}):      protocol = determine_protocol(info_dict)      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')      if external_downloader is not None:          ed = get_external_downloader(external_downloader) -        if ed.supports(info_dict): +        if ed.can_download(info_dict):              return ed      if protocol == 'm3u8' and params.get('hls_prefer_native'): -        return NativeHlsFD +        return HlsFD      return PROTOCOL_MAP.get(protocol, HttpFD) diff --git a/youtube_dl/downloader/external.py b/youtube_dl/downloader/external.py index 2bc011266..697f81e3f 100644 --- a/youtube_dl/downloader/external.py +++ b/youtube_dl/downloader/external.py @@ -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,  ) @@ -46,9 +51,17 @@ class ExternalFD(FileDownloader):          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() diff --git a/youtube_dl/downloader/hls.py b/youtube_dl/downloader/hls.py index 2a775bf00..a01dac031 100644 --- a/youtube_dl/downloader/hls.py +++ b/youtube_dl/downloader/hls.py @@ -1,87 +1,19 @@  from __future__ import unicode_literals -import os +import os.path  import re -import subprocess -import sys -from .common import FileDownloader  from .fragment import FragmentFD  from ..compat import compat_urlparse -from ..postprocessor.ffmpeg import FFmpegPostProcessor  from ..utils import ( -    encodeArgument,      encodeFilename,      sanitize_open, -    handle_youtubedl_headers,  ) -class HlsFD(FileDownloader): -    def real_download(self, filename, info_dict): -        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 """ +class HlsFD(FragmentFD): +    """ A limited implementation that does not require ffmpeg """      FD_NAME = 'hlsnative' diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py index 81102f9bb..a8819f258 100644 --- a/youtube_dl/postprocessor/ffmpeg.py +++ b/youtube_dl/postprocessor/ffmpeg.py @@ -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):      pass | 
