fragment.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. from __future__ import division, unicode_literals
  2. import os
  3. import time
  4. from .common import FileDownloader
  5. from .http import HttpFD
  6. from ..utils import (
  7. encodeFilename,
  8. sanitize_open,
  9. )
  10. class HttpQuietDownloader(HttpFD):
  11. def to_screen(self, *args, **kargs):
  12. pass
  13. class FragmentFD(FileDownloader):
  14. """
  15. A base file downloader class for fragmented media (e.g. f4m/m3u8 manifests).
  16. Available options:
  17. fragment_retries: Number of times to retry a fragment for HTTP error (DASH
  18. and hlsnative only)
  19. skip_unavailable_fragments:
  20. Skip unavailable fragments (DASH and hlsnative only)
  21. """
  22. def report_retry_fragment(self, fragment_name, count, retries):
  23. self.to_screen(
  24. '[download] Got server HTTP error: %s. Retrying fragment %s (attempt %d of %s)...'
  25. % (fragment_name, count, self.format_retries(retries)))
  26. def report_skip_fragment(self, fragment_name):
  27. self.to_screen('[download] Skipping fragment %s...' % fragment_name)
  28. def _prepare_and_start_frag_download(self, ctx):
  29. self._prepare_frag_download(ctx)
  30. self._start_frag_download(ctx)
  31. def _prepare_frag_download(self, ctx):
  32. if 'live' not in ctx:
  33. ctx['live'] = False
  34. self.to_screen(
  35. '[%s] Total fragments: %s'
  36. % (self.FD_NAME, ctx['total_frags'] if not ctx['live'] else 'unknown (live)'))
  37. self.report_destination(ctx['filename'])
  38. dl = HttpQuietDownloader(
  39. self.ydl,
  40. {
  41. 'continuedl': True,
  42. 'quiet': True,
  43. 'noprogress': True,
  44. 'ratelimit': self.params.get('ratelimit'),
  45. 'retries': self.params.get('retries', 0),
  46. 'test': self.params.get('test', False),
  47. }
  48. )
  49. tmpfilename = self.temp_name(ctx['filename'])
  50. dest_stream, tmpfilename = sanitize_open(tmpfilename, 'wb')
  51. ctx.update({
  52. 'dl': dl,
  53. 'dest_stream': dest_stream,
  54. 'tmpfilename': tmpfilename,
  55. })
  56. def _start_frag_download(self, ctx):
  57. total_frags = ctx['total_frags']
  58. # This dict stores the download progress, it's updated by the progress
  59. # hook
  60. state = {
  61. 'status': 'downloading',
  62. 'downloaded_bytes': 0,
  63. 'frag_index': 0,
  64. 'frag_count': total_frags,
  65. 'filename': ctx['filename'],
  66. 'tmpfilename': ctx['tmpfilename'],
  67. }
  68. start = time.time()
  69. ctx.update({
  70. 'started': start,
  71. # Total complete fragments downloaded so far in bytes
  72. 'complete_frags_downloaded_bytes': 0,
  73. # Amount of fragment's bytes downloaded by the time of the previous
  74. # frag progress hook invocation
  75. 'prev_frag_downloaded_bytes': 0,
  76. })
  77. def frag_progress_hook(s):
  78. if s['status'] not in ('downloading', 'finished'):
  79. return
  80. time_now = time.time()
  81. state['elapsed'] = time_now - start
  82. frag_total_bytes = s.get('total_bytes') or 0
  83. if not ctx['live']:
  84. estimated_size = (
  85. (ctx['complete_frags_downloaded_bytes'] + frag_total_bytes) /
  86. (state['frag_index'] + 1) * total_frags)
  87. state['total_bytes_estimate'] = estimated_size
  88. if s['status'] == 'finished':
  89. state['frag_index'] += 1
  90. state['downloaded_bytes'] += frag_total_bytes - ctx['prev_frag_downloaded_bytes']
  91. ctx['complete_frags_downloaded_bytes'] = state['downloaded_bytes']
  92. ctx['prev_frag_downloaded_bytes'] = 0
  93. else:
  94. frag_downloaded_bytes = s['downloaded_bytes']
  95. state['downloaded_bytes'] += frag_downloaded_bytes - ctx['prev_frag_downloaded_bytes']
  96. if not ctx['live']:
  97. state['eta'] = self.calc_eta(
  98. start, time_now, estimated_size,
  99. state['downloaded_bytes'])
  100. state['speed'] = s.get('speed') or ctx.get('speed')
  101. ctx['speed'] = state['speed']
  102. ctx['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
  103. self._hook_progress(state)
  104. ctx['dl'].add_progress_hook(frag_progress_hook)
  105. return start
  106. def _finish_frag_download(self, ctx):
  107. ctx['dest_stream'].close()
  108. elapsed = time.time() - ctx['started']
  109. self.try_rename(ctx['tmpfilename'], ctx['filename'])
  110. fsize = os.path.getsize(encodeFilename(ctx['filename']))
  111. self._hook_progress({
  112. 'downloaded_bytes': fsize,
  113. 'total_bytes': fsize,
  114. 'filename': ctx['filename'],
  115. 'status': 'finished',
  116. 'elapsed': elapsed,
  117. })