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.

184 lines
7.9 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. app = info_dict.get('app', None)
  87. play_path = info_dict.get('play_path', None)
  88. tc_url = info_dict.get('tc_url', None)
  89. flash_version = info_dict.get('flash_version', None)
  90. live = info_dict.get('rtmp_live', False)
  91. conn = info_dict.get('rtmp_conn', None)
  92. self.report_destination(filename)
  93. tmpfilename = self.temp_name(filename)
  94. test = self.params.get('test', False)
  95. # Check for rtmpdump first
  96. try:
  97. subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
  98. except (OSError, IOError):
  99. self.report_error(u'RTMP download detected but "rtmpdump" could not be run')
  100. return False
  101. # Download using rtmpdump. rtmpdump returns exit code 2 when
  102. # the connection was interrumpted and resuming appears to be
  103. # possible. This is part of rtmpdump's normal usage, AFAIK.
  104. basic_args = ['rtmpdump', '--verbose', '-r', url, '-o', tmpfilename]
  105. if player_url is not None:
  106. basic_args += ['--swfVfy', player_url]
  107. if page_url is not None:
  108. basic_args += ['--pageUrl', page_url]
  109. if app is not None:
  110. basic_args += ['--app', app]
  111. if play_path is not None:
  112. basic_args += ['--playpath', play_path]
  113. if tc_url is not None:
  114. basic_args += ['--tcUrl', url]
  115. if test:
  116. basic_args += ['--stop', '1']
  117. if flash_version is not None:
  118. basic_args += ['--flashVer', flash_version]
  119. if live:
  120. basic_args += ['--live']
  121. if conn:
  122. basic_args += ['--conn', conn]
  123. args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)]
  124. if sys.platform == 'win32' and sys.version_info < (3, 0):
  125. # Windows subprocess module does not actually support Unicode
  126. # on Python 2.x
  127. # See http://stackoverflow.com/a/9951851/35070
  128. subprocess_encoding = sys.getfilesystemencoding()
  129. args = [a.encode(subprocess_encoding, 'ignore') for a in args]
  130. else:
  131. subprocess_encoding = None
  132. if self.params.get('verbose', False):
  133. if subprocess_encoding:
  134. str_args = [
  135. a.decode(subprocess_encoding) if isinstance(a, bytes) else a
  136. for a in args]
  137. else:
  138. str_args = args
  139. try:
  140. import pipes
  141. shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
  142. except ImportError:
  143. shell_quote = repr
  144. self.to_screen(u'[debug] rtmpdump command line: ' + shell_quote(str_args))
  145. retval = run_rtmpdump(args)
  146. while (retval == 2 or retval == 1) and not test:
  147. prevsize = os.path.getsize(encodeFilename(tmpfilename))
  148. self.to_screen(u'[rtmpdump] %s bytes' % prevsize)
  149. time.sleep(5.0) # This seems to be needed
  150. retval = run_rtmpdump(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1])
  151. cursize = os.path.getsize(encodeFilename(tmpfilename))
  152. if prevsize == cursize and retval == 1:
  153. break
  154. # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
  155. if prevsize == cursize and retval == 2 and cursize > 1024:
  156. self.to_screen(u'[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
  157. retval = 0
  158. break
  159. if retval == 0 or (test and retval == 2):
  160. fsize = os.path.getsize(encodeFilename(tmpfilename))
  161. self.to_screen(u'[rtmpdump] %s bytes' % fsize)
  162. self.try_rename(tmpfilename, filename)
  163. self._hook_progress({
  164. 'downloaded_bytes': fsize,
  165. 'total_bytes': fsize,
  166. 'filename': filename,
  167. 'status': 'finished',
  168. })
  169. return True
  170. else:
  171. self.to_stderr(u"\n")
  172. self.report_error(u'rtmpdump exited with code %d' % retval)
  173. return False