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