fragment.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  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. """
  17. def report_retry_fragment(self, fragment_name, count, retries):
  18. self.to_screen(
  19. '[download] Got server HTTP error. Retrying fragment %s (attempt %d of %.0f)...'
  20. % (fragment_name, count, retries))
  21. def _prepare_and_start_frag_download(self, ctx):
  22. self._prepare_frag_download(ctx)
  23. self._start_frag_download(ctx)
  24. def _prepare_frag_download(self, ctx):
  25. if 'live' not in ctx:
  26. ctx['live'] = False
  27. self.to_screen(
  28. '[%s] Total fragments: %s'
  29. % (self.FD_NAME, ctx['total_frags'] if not ctx['live'] else 'unknown (live)'))
  30. self.report_destination(ctx['filename'])
  31. dl = HttpQuietDownloader(
  32. self.ydl,
  33. {
  34. 'continuedl': True,
  35. 'quiet': True,
  36. 'noprogress': True,
  37. 'ratelimit': self.params.get('ratelimit'),
  38. 'retries': self.params.get('retries', 0),
  39. 'test': self.params.get('test', False),
  40. }
  41. )
  42. tmpfilename = self.temp_name(ctx['filename'])
  43. dest_stream, tmpfilename = sanitize_open(tmpfilename, 'wb')
  44. ctx.update({
  45. 'dl': dl,
  46. 'dest_stream': dest_stream,
  47. 'tmpfilename': tmpfilename,
  48. })
  49. def _start_frag_download(self, ctx):
  50. total_frags = ctx['total_frags']
  51. # This dict stores the download progress, it's updated by the progress
  52. # hook
  53. state = {
  54. 'status': 'downloading',
  55. 'downloaded_bytes': 0,
  56. 'frag_index': 0,
  57. 'frag_count': total_frags,
  58. 'filename': ctx['filename'],
  59. 'tmpfilename': ctx['tmpfilename'],
  60. }
  61. start = time.time()
  62. ctx.update({
  63. 'started': start,
  64. # Total complete fragments downloaded so far in bytes
  65. 'complete_frags_downloaded_bytes': 0,
  66. # Amount of fragment's bytes downloaded by the time of the previous
  67. # frag progress hook invocation
  68. 'prev_frag_downloaded_bytes': 0,
  69. })
  70. def frag_progress_hook(s):
  71. if s['status'] not in ('downloading', 'finished'):
  72. return
  73. time_now = time.time()
  74. state['elapsed'] = time_now - start
  75. frag_total_bytes = s.get('total_bytes') or 0
  76. if not ctx['live']:
  77. estimated_size = (
  78. (ctx['complete_frags_downloaded_bytes'] + frag_total_bytes) /
  79. (state['frag_index'] + 1) * total_frags)
  80. state['total_bytes_estimate'] = estimated_size
  81. if s['status'] == 'finished':
  82. state['frag_index'] += 1
  83. state['downloaded_bytes'] += frag_total_bytes - ctx['prev_frag_downloaded_bytes']
  84. ctx['complete_frags_downloaded_bytes'] = state['downloaded_bytes']
  85. ctx['prev_frag_downloaded_bytes'] = 0
  86. else:
  87. frag_downloaded_bytes = s['downloaded_bytes']
  88. state['downloaded_bytes'] += frag_downloaded_bytes - ctx['prev_frag_downloaded_bytes']
  89. if not ctx['live']:
  90. state['eta'] = self.calc_eta(
  91. start, time_now, estimated_size,
  92. state['downloaded_bytes'])
  93. state['speed'] = s.get('speed') or ctx.get('speed')
  94. ctx['speed'] = state['speed']
  95. ctx['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
  96. self._hook_progress(state)
  97. ctx['dl'].add_progress_hook(frag_progress_hook)
  98. return start
  99. def _finish_frag_download(self, ctx):
  100. ctx['dest_stream'].close()
  101. elapsed = time.time() - ctx['started']
  102. self.try_rename(ctx['tmpfilename'], ctx['filename'])
  103. fsize = os.path.getsize(encodeFilename(ctx['filename']))
  104. self._hook_progress({
  105. 'downloaded_bytes': fsize,
  106. 'total_bytes': fsize,
  107. 'filename': ctx['filename'],
  108. 'status': 'finished',
  109. 'elapsed': elapsed,
  110. })