diff options
| -rw-r--r-- | youtube_dl/downloader/__init__.py | 20 | ||||
| -rw-r--r-- | youtube_dl/downloader/external.py | 65 | ||||
| -rw-r--r-- | youtube_dl/downloader/hls.py | 74 | ||||
| -rw-r--r-- | youtube_dl/downloader/rtsp.py | 45 | 
4 files changed, 80 insertions, 124 deletions
| diff --git a/youtube_dl/downloader/__init__.py b/youtube_dl/downloader/__init__.py index dccc59212..bb6afb1f8 100644 --- a/youtube_dl/downloader/__init__.py +++ b/youtube_dl/downloader/__init__.py @@ -1,14 +1,15 @@  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 .external import ( +    get_external_downloader, +    FFmpegFD, +)  from ..utils import (      determine_protocol, @@ -16,10 +17,10 @@ from ..utils import (  PROTOCOL_MAP = {      'rtmp': RtmpFD, -    'm3u8_native': NativeHlsFD, -    'm3u8': HlsFD, -    'mms': RtspFD, -    'rtsp': RtspFD, +    'm3u8_native': HlsFD, +    'm3u8': FFmpegFD, +    'mms': FFmpegFD, +    'rtsp': FFmpegFD,      'f4m': F4mFD,      'http_dash_segments': DashSegmentsFD,  } @@ -30,6 +31,9 @@ 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 FFmpegFD.supports(info_dict): +        return FFmpegFD +      external_downloader = params.get('external_downloader')      if external_downloader is not None:          ed = get_external_downloader(external_downloader) @@ -37,7 +41,7 @@ def get_suitable_downloader(info_dict, params={}):              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..bb43677b7 100644 --- a/youtube_dl/downloader/external.py +++ b/youtube_dl/downloader/external.py @@ -2,8 +2,12 @@ from __future__ import unicode_literals  import os.path  import subprocess +import sys +import re  from .common import FileDownloader +from ..postprocessor.ffmpeg import FFmpegPostProcessor +from ..compat import compat_str  from ..utils import (      cli_option,      cli_valueless_option, @@ -11,6 +15,7 @@ from ..utils import (      cli_configuration_args,      encodeFilename,      encodeArgument, +    handle_youtubedl_headers,  ) @@ -136,6 +141,66 @@ class HttpieFD(ExternalFD):              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') + +    def _call_downloader(self, tmpfilename, info_dict): +        url = info_dict['url'] +        ffpp = FFmpegPostProcessor(downloader=self) +        ffpp.check_version() + +        args = [ffpp.executable, '-y'] + +        start_time = info_dict.get('start_time', 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', 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/downloader/rtsp.py b/youtube_dl/downloader/rtsp.py deleted file mode 100644 index 3eb29526c..000000000 --- a/youtube_dl/downloader/rtsp.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import unicode_literals - -import os -import subprocess - -from .common import FileDownloader -from ..utils import ( -    check_executable, -    encodeFilename, -) - - -class RtspFD(FileDownloader): -    def real_download(self, filename, info_dict): -        url = info_dict['url'] -        self.report_destination(filename) -        tmpfilename = self.temp_name(filename) - -        if check_executable('mplayer', ['-h']): -            args = [ -                'mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy', -                '-dumpstream', '-dumpfile', tmpfilename, url] -        elif check_executable('mpv', ['-h']): -            args = [ -                'mpv', '-really-quiet', '--vo=null', '--stream-dump=' + tmpfilename, url] -        else: -            self.report_error('MMS or RTSP download detected but neither "mplayer" nor "mpv" could be run. Please install any.') -            return False - -        retval = subprocess.call(args) -        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' % (args[0], retval)) -            return False | 
