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.

114 lines
3.8 KiB

9 years ago
9 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. retval = subprocess.call(args)
  38. if retval == 0:
  39. fsize = os.path.getsize(encodeFilename(tmpfilename))
  40. self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
  41. self.try_rename(tmpfilename, filename)
  42. self._hook_progress({
  43. 'downloaded_bytes': fsize,
  44. 'total_bytes': fsize,
  45. 'filename': filename,
  46. 'status': 'finished',
  47. })
  48. return True
  49. else:
  50. self.to_stderr('\n')
  51. self.report_error('%s exited with code %d' % (ffpp.basename, retval))
  52. return False
  53. class NativeHlsFD(FragmentFD):
  54. """ A more limited implementation that does not require ffmpeg """
  55. FD_NAME = 'hlsnative'
  56. def real_download(self, filename, info_dict):
  57. man_url = info_dict['url']
  58. self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
  59. manifest = self.ydl.urlopen(man_url).read()
  60. s = manifest.decode('utf-8', 'ignore')
  61. fragment_urls = []
  62. for line in s.splitlines():
  63. line = line.strip()
  64. if line and not line.startswith('#'):
  65. segment_url = (
  66. line
  67. if re.match(r'^https?://', line)
  68. else compat_urlparse.urljoin(man_url, line))
  69. fragment_urls.append(segment_url)
  70. # We only download the first fragment during the test
  71. if self.params.get('test', False):
  72. break
  73. ctx = {
  74. 'filename': filename,
  75. 'total_frags': len(fragment_urls),
  76. }
  77. self._prepare_and_start_frag_download(ctx)
  78. frags_filenames = []
  79. for i, frag_url in enumerate(fragment_urls):
  80. frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
  81. success = ctx['dl'].download(frag_filename, {'url': frag_url})
  82. if not success:
  83. return False
  84. down, frag_sanitized = sanitize_open(frag_filename, 'rb')
  85. ctx['dest_stream'].write(down.read())
  86. down.close()
  87. frags_filenames.append(frag_sanitized)
  88. self._finish_frag_download(ctx)
  89. for frag_file in frags_filenames:
  90. os.remove(encodeFilename(frag_file))
  91. return True