[downloader/external] Add FFmpegFD(fixes #622)
- replace HlsFD and RtspFD - add basic support for downloading part of the video or audio
This commit is contained in:
		@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user