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.

130 lines
4.5 KiB

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