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.

112 lines
3.8 KiB

10 years ago
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. )
  14. class HlsFD(FileDownloader):
  15. def real_download(self, filename, info_dict):
  16. url = info_dict['url']
  17. self.report_destination(filename)
  18. tmpfilename = self.temp_name(filename)
  19. ffpp = FFmpegPostProcessor(downloader=self)
  20. if not ffpp.available:
  21. self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
  22. return False
  23. ffpp.check_version()
  24. args = [ffpp.executable, '-y']
  25. if info_dict['http_headers'] and re.match(r'^https?://', url):
  26. # Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
  27. # [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
  28. args += [
  29. '-headers',
  30. ''.join('%s: %s\r\n' % (key, val) for key, val in info_dict['http_headers'].items() if key.lower() != 'accept-encoding')]
  31. args += ['-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
  32. args = [encodeArgument(opt) for opt in args]
  33. args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
  34. self._debug_cmd(args)
  35. retval = subprocess.call(args)
  36. if retval == 0:
  37. fsize = os.path.getsize(encodeFilename(tmpfilename))
  38. self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
  39. self.try_rename(tmpfilename, filename)
  40. self._hook_progress({
  41. 'downloaded_bytes': fsize,
  42. 'total_bytes': fsize,
  43. 'filename': filename,
  44. 'status': 'finished',
  45. })
  46. return True
  47. else:
  48. self.to_stderr('\n')
  49. self.report_error('%s exited with code %d' % (ffpp.basename, retval))
  50. return False
  51. class NativeHlsFD(FragmentFD):
  52. """ A more limited implementation that does not require ffmpeg """
  53. FD_NAME = 'hlsnative'
  54. def real_download(self, filename, info_dict):
  55. man_url = info_dict['url']
  56. self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
  57. manifest = self.ydl.urlopen(man_url).read()
  58. s = manifest.decode('utf-8', 'ignore')
  59. fragment_urls = []
  60. for line in s.splitlines():
  61. line = line.strip()
  62. if line and not line.startswith('#'):
  63. segment_url = (
  64. line
  65. if re.match(r'^https?://', line)
  66. else compat_urlparse.urljoin(man_url, line))
  67. fragment_urls.append(segment_url)
  68. # We only download the first fragment during the test
  69. if self.params.get('test', False):
  70. break
  71. ctx = {
  72. 'filename': filename,
  73. 'total_frags': len(fragment_urls),
  74. }
  75. self._prepare_and_start_frag_download(ctx)
  76. frags_filenames = []
  77. for i, frag_url in enumerate(fragment_urls):
  78. frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
  79. success = ctx['dl'].download(frag_filename, {'url': frag_url})
  80. if not success:
  81. return False
  82. down, frag_sanitized = sanitize_open(frag_filename, 'rb')
  83. ctx['dest_stream'].write(down.read())
  84. down.close()
  85. frags_filenames.append(frag_sanitized)
  86. self._finish_frag_download(ctx)
  87. for frag_file in frags_filenames:
  88. os.remove(encodeFilename(frag_file))
  89. return True