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.

123 lines
4.2 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, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
  34. args = [encodeArgument(opt) for opt in args]
  35. args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
  36. self._debug_cmd(args)
  37. proc = subprocess.Popen(args, stdin=subprocess.PIPE)
  38. try:
  39. retval = proc.wait()
  40. except KeyboardInterrupt:
  41. # subprocces.run would send the SIGKILL signal to ffmpeg and the
  42. # mp4 file couldn't be played, but if we ask ffmpeg to quit it
  43. # produces a file that is playable (this is mostly useful for live
  44. # streams)
  45. proc.communicate(b'q')
  46. raise
  47. if retval == 0:
  48. fsize = os.path.getsize(encodeFilename(tmpfilename))
  49. self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
  50. self.try_rename(tmpfilename, filename)
  51. self._hook_progress({
  52. 'downloaded_bytes': fsize,
  53. 'total_bytes': fsize,
  54. 'filename': filename,
  55. 'status': 'finished',
  56. })
  57. return True
  58. else:
  59. self.to_stderr('\n')
  60. self.report_error('%s exited with code %d' % (ffpp.basename, retval))
  61. return False
  62. class NativeHlsFD(FragmentFD):
  63. """ A more limited implementation that does not require ffmpeg """
  64. FD_NAME = 'hlsnative'
  65. def real_download(self, filename, info_dict):
  66. man_url = info_dict['url']
  67. self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
  68. manifest = self.ydl.urlopen(man_url).read()
  69. s = manifest.decode('utf-8', 'ignore')
  70. fragment_urls = []
  71. for line in s.splitlines():
  72. line = line.strip()
  73. if line and not line.startswith('#'):
  74. segment_url = (
  75. line
  76. if re.match(r'^https?://', line)
  77. else compat_urlparse.urljoin(man_url, line))
  78. fragment_urls.append(segment_url)
  79. # We only download the first fragment during the test
  80. if self.params.get('test', False):
  81. break
  82. ctx = {
  83. 'filename': filename,
  84. 'total_frags': len(fragment_urls),
  85. }
  86. self._prepare_and_start_frag_download(ctx)
  87. frags_filenames = []
  88. for i, frag_url in enumerate(fragment_urls):
  89. frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
  90. success = ctx['dl'].download(frag_filename, {'url': frag_url})
  91. if not success:
  92. return False
  93. down, frag_sanitized = sanitize_open(frag_filename, 'rb')
  94. ctx['dest_stream'].write(down.read())
  95. down.close()
  96. frags_filenames.append(frag_sanitized)
  97. self._finish_frag_download(ctx)
  98. for frag_file in frags_filenames:
  99. os.remove(encodeFilename(frag_file))
  100. return True