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

Generated by cgit