summaryrefslogtreecommitdiff
path: root/youtube_dl/downloader/rtmp.py
blob: fbb7f51b018fabde5d140a033b953a35f9ca711e (plain)
    1 from __future__ import unicode_literals
    2 
    3 import os
    4 import re
    5 import subprocess
    6 import time
    7 
    8 from .common import FileDownloader
    9 from ..compat import compat_str
   10 from ..utils import (
   11     check_executable,
   12     encodeFilename,
   13     encodeArgument,
   14     get_exe_version,
   15 )
   16 
   17 
   18 def rtmpdump_version():
   19     return get_exe_version(
   20         'rtmpdump', ['--help'], r'(?i)RTMPDump\s*v?([0-9a-zA-Z._-]+)')
   21 
   22 
   23 class RtmpFD(FileDownloader):
   24     def real_download(self, filename, info_dict):
   25         def run_rtmpdump(args):
   26             start = time.time()
   27             resume_percent = None
   28             resume_downloaded_data_len = None
   29             proc = subprocess.Popen(args, stderr=subprocess.PIPE)
   30             cursor_in_new_line = True
   31             proc_stderr_closed = False
   32             try:
   33                 while not proc_stderr_closed:
   34                     # read line from stderr
   35                     line = ''
   36                     while True:
   37                         char = proc.stderr.read(1)
   38                         if not char:
   39                             proc_stderr_closed = True
   40                             break
   41                         if char in [b'\r', b'\n']:
   42                             break
   43                         line += char.decode('ascii', 'replace')
   44                     if not line:
   45                         # proc_stderr_closed is True
   46                         continue
   47                     mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec \(([0-9]{1,2}\.[0-9])%\)', line)
   48                     if mobj:
   49                         downloaded_data_len = int(float(mobj.group(1)) * 1024)
   50                         percent = float(mobj.group(2))
   51                         if not resume_percent:
   52                             resume_percent = percent
   53                             resume_downloaded_data_len = downloaded_data_len
   54                         time_now = time.time()
   55                         eta = self.calc_eta(start, time_now, 100 - resume_percent, percent - resume_percent)
   56                         speed = self.calc_speed(start, time_now, downloaded_data_len - resume_downloaded_data_len)
   57                         data_len = None
   58                         if percent > 0:
   59                             data_len = int(downloaded_data_len * 100 / percent)
   60                         self._hook_progress({
   61                             'status': 'downloading',
   62                             'downloaded_bytes': downloaded_data_len,
   63                             'total_bytes_estimate': data_len,
   64                             'tmpfilename': tmpfilename,
   65                             'filename': filename,
   66                             'eta': eta,
   67                             'elapsed': time_now - start,
   68                             'speed': speed,
   69                         })
   70                         cursor_in_new_line = False
   71                     else:
   72                         # no percent for live streams
   73                         mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec', line)
   74                         if mobj:
   75                             downloaded_data_len = int(float(mobj.group(1)) * 1024)
   76                             time_now = time.time()
   77                             speed = self.calc_speed(start, time_now, downloaded_data_len)
   78                             self._hook_progress({
   79                                 'downloaded_bytes': downloaded_data_len,
   80                                 'tmpfilename': tmpfilename,
   81                                 'filename': filename,
   82                                 'status': 'downloading',
   83                                 'elapsed': time_now - start,
   84                                 'speed': speed,
   85                             })
   86                             cursor_in_new_line = False
   87                         elif self.params.get('verbose', False):
   88                             if not cursor_in_new_line:
   89                                 self.to_screen('')
   90                             cursor_in_new_line = True
   91                             self.to_screen('[rtmpdump] ' + line)
   92             finally:
   93                 proc.wait()
   94             if not cursor_in_new_line:
   95                 self.to_screen('')
   96             return proc.returncode
   97 
   98         url = info_dict['url']
   99         player_url = info_dict.get('player_url')
  100         page_url = info_dict.get('page_url')
  101         app = info_dict.get('app')
  102         play_path = info_dict.get('play_path')
  103         tc_url = info_dict.get('tc_url')
  104         flash_version = info_dict.get('flash_version')
  105         live = info_dict.get('rtmp_live', False)
  106         conn = info_dict.get('rtmp_conn')
  107         protocol = info_dict.get('rtmp_protocol')
  108         real_time = info_dict.get('rtmp_real_time', False)
  109         no_resume = info_dict.get('no_resume', False)
  110         continue_dl = self.params.get('continuedl', True)
  111 
  112         self.report_destination(filename)
  113         tmpfilename = self.temp_name(filename)
  114         test = self.params.get('test', False)
  115 
  116         # Check for rtmpdump first
  117         if not check_executable('rtmpdump', ['-h']):
  118             self.report_error('RTMP download detected but "rtmpdump" could not be run. Please install it.')
  119             return False
  120 
  121         # Download using rtmpdump. rtmpdump returns exit code 2 when
  122         # the connection was interrupted and resuming appears to be
  123         # possible. This is part of rtmpdump's normal usage, AFAIK.
  124         basic_args = [
  125             'rtmpdump', '--verbose', '-r', url,
  126             '-o', tmpfilename]
  127         if player_url is not None:
  128             basic_args += ['--swfVfy', player_url]
  129         if page_url is not None:
  130             basic_args += ['--pageUrl', page_url]
  131         if app is not None:
  132             basic_args += ['--app', app]
  133         if play_path is not None:
  134             basic_args += ['--playpath', play_path]
  135         if tc_url is not None:
  136             basic_args += ['--tcUrl', tc_url]
  137         if test:
  138             basic_args += ['--stop', '1']
  139         if flash_version is not None:
  140             basic_args += ['--flashVer', flash_version]
  141         if live:
  142             basic_args += ['--live']
  143         if isinstance(conn, list):
  144             for entry in conn:
  145                 basic_args += ['--conn', entry]
  146         elif isinstance(conn, compat_str):
  147             basic_args += ['--conn', conn]
  148         if protocol is not None:
  149             basic_args += ['--protocol', protocol]
  150         if real_time:
  151             basic_args += ['--realtime']
  152 
  153         args = basic_args
  154         if not no_resume and continue_dl and not live:
  155             args += ['--resume']
  156         if not live and continue_dl:
  157             args += ['--skip', '1']
  158 
  159         args = [encodeArgument(a) for a in args]
  160 
  161         self._debug_cmd(args, exe='rtmpdump')
  162 
  163         RD_SUCCESS = 0
  164         RD_FAILED = 1
  165         RD_INCOMPLETE = 2
  166         RD_NO_CONNECT = 3
  167 
  168         started = time.time()
  169 
  170         try:
  171             retval = run_rtmpdump(args)
  172         except KeyboardInterrupt:
  173             if not info_dict.get('is_live'):
  174                 raise
  175             retval = RD_SUCCESS
  176             self.to_screen('\n[rtmpdump] Interrupted by user')
  177 
  178         if retval == RD_NO_CONNECT:
  179             self.report_error('[rtmpdump] Could not connect to RTMP server.')
  180             return False
  181 
  182         while retval in (RD_INCOMPLETE, RD_FAILED) and not test and not live:
  183             prevsize = os.path.getsize(encodeFilename(tmpfilename))
  184             self.to_screen('[rtmpdump] Downloaded %s bytes' % prevsize)
  185             time.sleep(5.0)  # This seems to be needed
  186             args = basic_args + ['--resume']
  187             if retval == RD_FAILED:
  188                 args += ['--skip', '1']
  189             args = [encodeArgument(a) for a in args]
  190             retval = run_rtmpdump(args)
  191             cursize = os.path.getsize(encodeFilename(tmpfilename))
  192             if prevsize == cursize and retval == RD_FAILED:
  193                 break
  194             # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
  195             if prevsize == cursize and retval == RD_INCOMPLETE and cursize > 1024:
  196                 self.to_screen('[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
  197                 retval = RD_SUCCESS
  198                 break
  199         if retval == RD_SUCCESS or (test and retval == RD_INCOMPLETE):
  200             fsize = os.path.getsize(encodeFilename(tmpfilename))
  201             self.to_screen('[rtmpdump] Downloaded %s bytes' % fsize)
  202             self.try_rename(tmpfilename, filename)
  203             self._hook_progress({
  204                 'downloaded_bytes': fsize,
  205                 'total_bytes': fsize,
  206                 'filename': filename,
  207                 'status': 'finished',
  208                 'elapsed': time.time() - started,
  209             })
  210             return True
  211         else:
  212             self.to_stderr('\n')
  213             self.report_error('rtmpdump exited with code %d' % retval)
  214             return False

Generated by cgit