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.

127 lines
4.3 KiB

10 years ago
  1. from __future__ import unicode_literals
  2. import os
  3. import re
  4. import subprocess
  5. from .common import FileDownloader
  6. from .fragment import FragmentFD
  7. from ..compat import compat_urlparse
  8. from ..postprocessor.ffmpeg import FFmpegPostProcessor
  9. from ..utils import (
  10. encodeArgument,
  11. encodeFilename,
  12. sanitize_open,
  13. handle_youtubedl_headers,
  14. )
  15. class HlsFD(FileDownloader):
  16. def real_download(self, filename, info_dict):
  17. url = info_dict['url']
  18. self.report_destination(filename)
  19. tmpfilename = self.temp_name(filename)
  20. ffpp = FFmpegPostProcessor(downloader=self)
  21. if not ffpp.available:
  22. self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
  23. return False
  24. ffpp.check_version()
  25. args = [ffpp.executable, '-y']
  26. if info_dict['http_headers'] and re.match(r'^https?://', url):
  27. # Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
  28. # [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
  29. headers = handle_youtubedl_headers(info_dict['http_headers'])
  30. args += [
  31. '-headers',
  32. ''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())]
  33. args += ['-i', url, '-c', 'copy']
  34. if self.params.get('hls_use_mpegts', False):
  35. args += ['-f', 'mpegts']
  36. else:
  37. args += ['-f', 'mp4', '-bsf:a', 'aac_adtstoasc']
  38. args = [encodeArgument(opt) for opt in args]
  39. args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
  40. self._debug_cmd(args)
  41. proc = subprocess.Popen(args, stdin=subprocess.PIPE)
  42. try:
  43. retval = proc.wait()
  44. except KeyboardInterrupt:
  45. # subprocces.run would send the SIGKILL signal to ffmpeg and the
  46. # mp4 file couldn't be played, but if we ask ffmpeg to quit it
  47. # produces a file that is playable (this is mostly useful for live
  48. # streams)
  49. proc.communicate(b'q')
  50. raise
  51. if retval == 0:
  52. fsize = os.path.getsize(encodeFilename(tmpfilename))
  53. self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
  54. self.try_rename(tmpfilename, filename)
  55. self._hook_progress({
  56. 'downloaded_bytes': fsize,
  57. 'total_bytes': fsize,
  58. 'filename': filename,
  59. 'status': 'finished',
  60. })
  61. return True
  62. else:
  63. self.to_stderr('\n')
  64. self.report_error('%s exited with code %d' % (ffpp.basename, retval))
  65. return False
  66. class NativeHlsFD(FragmentFD):
  67. """ A more limited implementation that does not require ffmpeg """
  68. FD_NAME = 'hlsnative'
  69. def real_download(self, filename, info_dict):
  70. man_url = info_dict['url']
  71. self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
  72. manifest = self.ydl.urlopen(man_url).read()
  73. s = manifest.decode('utf-8', 'ignore')
  74. fragment_urls = []
  75. for line in s.splitlines():
  76. line = line.strip()
  77. if line and not line.startswith('#'):
  78. segment_url = (
  79. line
  80. if re.match(r'^https?://', line)
  81. else compat_urlparse.urljoin(man_url, line))
  82. fragment_urls.append(segment_url)
  83. # We only download the first fragment during the test
  84. if self.params.get('test', False):
  85. break
  86. ctx = {
  87. 'filename': filename,
  88. 'total_frags': len(fragment_urls),
  89. }
  90. self._prepare_and_start_frag_download(ctx)
  91. frags_filenames = []
  92. for i, frag_url in enumerate(fragment_urls):
  93. frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
  94. success = ctx['dl'].download(frag_filename, {'url': frag_url})
  95. if not success:
  96. return False
  97. down, frag_sanitized = sanitize_open(frag_filename, 'rb')
  98. ctx['dest_stream'].write(down.read())
  99. down.close()
  100. frags_filenames.append(frag_sanitized)
  101. self._finish_frag_download(ctx)
  102. for frag_file in frags_filenames:
  103. os.remove(encodeFilename(frag_file))
  104. return True