diff options
Diffstat (limited to 'youtube_dl/downloader/rtmp.py')
| -rw-r--r-- | youtube_dl/downloader/rtmp.py | 178 | 
1 files changed, 178 insertions, 0 deletions
| diff --git a/youtube_dl/downloader/rtmp.py b/youtube_dl/downloader/rtmp.py new file mode 100644 index 000000000..b165e396f --- /dev/null +++ b/youtube_dl/downloader/rtmp.py @@ -0,0 +1,178 @@ +import os +import re +import subprocess +import sys +import time + +from .common import FileDownloader +from ..utils import ( +    encodeFilename, +    format_bytes, +) + + +class RtmpFD(FileDownloader): +    def real_download(self, filename, info_dict): +        def run_rtmpdump(args): +            start = time.time() +            resume_percent = None +            resume_downloaded_data_len = None +            proc = subprocess.Popen(args, stderr=subprocess.PIPE) +            cursor_in_new_line = True +            proc_stderr_closed = False +            while not proc_stderr_closed: +                # read line from stderr +                line = u'' +                while True: +                    char = proc.stderr.read(1) +                    if not char: +                        proc_stderr_closed = True +                        break +                    if char in [b'\r', b'\n']: +                        break +                    line += char.decode('ascii', 'replace') +                if not line: +                    # proc_stderr_closed is True +                    continue +                mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec \(([0-9]{1,2}\.[0-9])%\)', line) +                if mobj: +                    downloaded_data_len = int(float(mobj.group(1))*1024) +                    percent = float(mobj.group(2)) +                    if not resume_percent: +                        resume_percent = percent +                        resume_downloaded_data_len = downloaded_data_len +                    eta = self.calc_eta(start, time.time(), 100-resume_percent, percent-resume_percent) +                    speed = self.calc_speed(start, time.time(), downloaded_data_len-resume_downloaded_data_len) +                    data_len = None +                    if percent > 0: +                        data_len = int(downloaded_data_len * 100 / percent) +                    data_len_str = u'~' + format_bytes(data_len) +                    self.report_progress(percent, data_len_str, speed, eta) +                    cursor_in_new_line = False +                    self._hook_progress({ +                        'downloaded_bytes': downloaded_data_len, +                        'total_bytes': data_len, +                        'tmpfilename': tmpfilename, +                        'filename': filename, +                        'status': 'downloading', +                        'eta': eta, +                        'speed': speed, +                    }) +                else: +                    # no percent for live streams +                    mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec', line) +                    if mobj: +                        downloaded_data_len = int(float(mobj.group(1))*1024) +                        time_now = time.time() +                        speed = self.calc_speed(start, time_now, downloaded_data_len) +                        self.report_progress_live_stream(downloaded_data_len, speed, time_now - start) +                        cursor_in_new_line = False +                        self._hook_progress({ +                            'downloaded_bytes': downloaded_data_len, +                            'tmpfilename': tmpfilename, +                            'filename': filename, +                            'status': 'downloading', +                            'speed': speed, +                        }) +                    elif self.params.get('verbose', False): +                        if not cursor_in_new_line: +                            self.to_screen(u'') +                        cursor_in_new_line = True +                        self.to_screen(u'[rtmpdump] '+line) +            proc.wait() +            if not cursor_in_new_line: +                self.to_screen(u'') +            return proc.returncode + +        url = info_dict['url'] +        player_url = info_dict.get('player_url', None) +        page_url = info_dict.get('page_url', None) +        play_path = info_dict.get('play_path', None) +        tc_url = info_dict.get('tc_url', None) +        live = info_dict.get('rtmp_live', False) +        conn = info_dict.get('rtmp_conn', None) + +        self.report_destination(filename) +        tmpfilename = self.temp_name(filename) +        test = self.params.get('test', False) + +        # Check for rtmpdump first +        try: +            subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT) +        except (OSError, IOError): +            self.report_error(u'RTMP download detected but "rtmpdump" could not be run') +            return False + +        # Download using rtmpdump. rtmpdump returns exit code 2 when +        # the connection was interrumpted and resuming appears to be +        # possible. This is part of rtmpdump's normal usage, AFAIK. +        basic_args = ['rtmpdump', '--verbose', '-r', url, '-o', tmpfilename] +        if player_url is not None: +            basic_args += ['--swfVfy', player_url] +        if page_url is not None: +            basic_args += ['--pageUrl', page_url] +        if play_path is not None: +            basic_args += ['--playpath', play_path] +        if tc_url is not None: +            basic_args += ['--tcUrl', url] +        if test: +            basic_args += ['--stop', '1'] +        if live: +            basic_args += ['--live'] +        if conn: +            basic_args += ['--conn', conn] +        args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)] + +        if sys.platform == 'win32' and sys.version_info < (3, 0): +            # Windows subprocess module does not actually support Unicode +            # on Python 2.x +            # See http://stackoverflow.com/a/9951851/35070 +            subprocess_encoding = sys.getfilesystemencoding() +            args = [a.encode(subprocess_encoding, 'ignore') for a in args] +        else: +            subprocess_encoding = None + +        if self.params.get('verbose', False): +            if subprocess_encoding: +                str_args = [ +                    a.decode(subprocess_encoding) if isinstance(a, bytes) else a +                    for a in args] +            else: +                str_args = args +            try: +                import pipes +                shell_quote = lambda args: ' '.join(map(pipes.quote, str_args)) +            except ImportError: +                shell_quote = repr +            self.to_screen(u'[debug] rtmpdump command line: ' + shell_quote(str_args)) + +        retval = run_rtmpdump(args) + +        while (retval == 2 or retval == 1) and not test: +            prevsize = os.path.getsize(encodeFilename(tmpfilename)) +            self.to_screen(u'[rtmpdump] %s bytes' % prevsize) +            time.sleep(5.0) # This seems to be needed +            retval = run_rtmpdump(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1]) +            cursize = os.path.getsize(encodeFilename(tmpfilename)) +            if prevsize == cursize and retval == 1: +                break +             # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those +            if prevsize == cursize and retval == 2 and cursize > 1024: +                self.to_screen(u'[rtmpdump] Could not download the whole video. This can happen for some advertisements.') +                retval = 0 +                break +        if retval == 0 or (test and retval == 2): +            fsize = os.path.getsize(encodeFilename(tmpfilename)) +            self.to_screen(u'[rtmpdump] %s bytes' % 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(u"\n") +            self.report_error(u'rtmpdump exited with code %d' % retval) +            return False | 
