You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
7.6 KiB

  1. import os
  2. import re
  3. import subprocess
  4. import sys
  5. import time
  6. from .common import FileDownloader
  7. from ..utils import (
  8. encodeFilename,
  9. format_bytes,
  10. )
  11. class RtmpFD(FileDownloader):
  12. def real_download(self, filename, info_dict):
  13. def run_rtmpdump(args):
  14. start = time.time()
  15. resume_percent = None
  16. resume_downloaded_data_len = None
  17. proc = subprocess.Popen(args, stderr=subprocess.PIPE)
  18. cursor_in_new_line = True
  19. proc_stderr_closed = False
  20. while not proc_stderr_closed:
  21. # read line from stderr
  22. line = u''
  23. while True:
  24. char = proc.stderr.read(1)
  25. if not char:
  26. proc_stderr_closed = True
  27. break
  28. if char in [b'\r', b'\n']:
  29. break
  30. line += char.decode('ascii', 'replace')
  31. if not line:
  32. # proc_stderr_closed is True
  33. continue
  34. mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec \(([0-9]{1,2}\.[0-9])%\)', line)
  35. if mobj:
  36. downloaded_data_len = int(float(mobj.group(1))*1024)
  37. percent = float(mobj.group(2))
  38. if not resume_percent:
  39. resume_percent = percent
  40. resume_downloaded_data_len = downloaded_data_len
  41. eta = self.calc_eta(start, time.time(), 100-resume_percent, percent-resume_percent)
  42. speed = self.calc_speed(start, time.time(), downloaded_data_len-resume_downloaded_data_len)
  43. data_len = None
  44. if percent > 0:
  45. data_len = int(downloaded_data_len * 100 / percent)
  46. data_len_str = u'~' + format_bytes(data_len)
  47. self.report_progress(percent, data_len_str, speed, eta)
  48. cursor_in_new_line = False
  49. self._hook_progress({
  50. 'downloaded_bytes': downloaded_data_len,
  51. 'total_bytes': data_len,
  52. 'tmpfilename': tmpfilename,
  53. 'filename': filename,
  54. 'status': 'downloading',
  55. 'eta': eta,
  56. 'speed': speed,
  57. })
  58. else:
  59. # no percent for live streams
  60. mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec', line)
  61. if mobj:
  62. downloaded_data_len = int(float(mobj.group(1))*1024)
  63. time_now = time.time()
  64. speed = self.calc_speed(start, time_now, downloaded_data_len)
  65. self.report_progress_live_stream(downloaded_data_len, speed, time_now - start)
  66. cursor_in_new_line = False
  67. self._hook_progress({
  68. 'downloaded_bytes': downloaded_data_len,
  69. 'tmpfilename': tmpfilename,
  70. 'filename': filename,
  71. 'status': 'downloading',
  72. 'speed': speed,
  73. })
  74. elif self.params.get('verbose', False):
  75. if not cursor_in_new_line:
  76. self.to_screen(u'')
  77. cursor_in_new_line = True
  78. self.to_screen(u'[rtmpdump] '+line)
  79. proc.wait()
  80. if not cursor_in_new_line:
  81. self.to_screen(u'')
  82. return proc.returncode
  83. url = info_dict['url']
  84. player_url = info_dict.get('player_url', None)
  85. page_url = info_dict.get('page_url', None)
  86. play_path = info_dict.get('play_path', None)
  87. tc_url = info_dict.get('tc_url', None)
  88. live = info_dict.get('rtmp_live', False)
  89. conn = info_dict.get('rtmp_conn', None)
  90. self.report_destination(filename)
  91. tmpfilename = self.temp_name(filename)
  92. test = self.params.get('test', False)
  93. # Check for rtmpdump first
  94. try:
  95. subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
  96. except (OSError, IOError):
  97. self.report_error(u'RTMP download detected but "rtmpdump" could not be run')
  98. return False
  99. # Download using rtmpdump. rtmpdump returns exit code 2 when
  100. # the connection was interrumpted and resuming appears to be
  101. # possible. This is part of rtmpdump's normal usage, AFAIK.
  102. basic_args = ['rtmpdump', '--verbose', '-r', url, '-o', tmpfilename]
  103. if player_url is not None:
  104. basic_args += ['--swfVfy', player_url]
  105. if page_url is not None:
  106. basic_args += ['--pageUrl', page_url]
  107. if play_path is not None:
  108. basic_args += ['--playpath', play_path]
  109. if tc_url is not None:
  110. basic_args += ['--tcUrl', url]
  111. if test:
  112. basic_args += ['--stop', '1']
  113. if live:
  114. basic_args += ['--live']
  115. if conn:
  116. basic_args += ['--conn', conn]
  117. args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)]
  118. if sys.platform == 'win32' and sys.version_info < (3, 0):
  119. # Windows subprocess module does not actually support Unicode
  120. # on Python 2.x
  121. # See http://stackoverflow.com/a/9951851/35070
  122. subprocess_encoding = sys.getfilesystemencoding()
  123. args = [a.encode(subprocess_encoding, 'ignore') for a in args]
  124. else:
  125. subprocess_encoding = None
  126. if self.params.get('verbose', False):
  127. if subprocess_encoding:
  128. str_args = [
  129. a.decode(subprocess_encoding) if isinstance(a, bytes) else a
  130. for a in args]
  131. else:
  132. str_args = args
  133. try:
  134. import pipes
  135. shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
  136. except ImportError:
  137. shell_quote = repr
  138. self.to_screen(u'[debug] rtmpdump command line: ' + shell_quote(str_args))
  139. retval = run_rtmpdump(args)
  140. while (retval == 2 or retval == 1) and not test:
  141. prevsize = os.path.getsize(encodeFilename(tmpfilename))
  142. self.to_screen(u'[rtmpdump] %s bytes' % prevsize)
  143. time.sleep(5.0) # This seems to be needed
  144. retval = run_rtmpdump(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1])
  145. cursize = os.path.getsize(encodeFilename(tmpfilename))
  146. if prevsize == cursize and retval == 1:
  147. break
  148. # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
  149. if prevsize == cursize and retval == 2 and cursize > 1024:
  150. self.to_screen(u'[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
  151. retval = 0
  152. break
  153. if retval == 0 or (test and retval == 2):
  154. fsize = os.path.getsize(encodeFilename(tmpfilename))
  155. self.to_screen(u'[rtmpdump] %s bytes' % fsize)
  156. self.try_rename(tmpfilename, filename)
  157. self._hook_progress({
  158. 'downloaded_bytes': fsize,
  159. 'total_bytes': fsize,
  160. 'filename': filename,
  161. 'status': 'finished',
  162. })
  163. return True
  164. else:
  165. self.to_stderr(u"\n")
  166. self.report_error(u'rtmpdump exited with code %d' % retval)
  167. return False