It now saves the url of the most recently completed segment to figure out from where it has to resume. Before, it kept ALL segments which resulted in using double the disk space neccessary.
127 lines
4.4 KiB
Python
127 lines
4.4 KiB
Python
from __future__ import unicode_literals
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
from .common import FileDownloader
|
|
from .fragment import FragmentFD
|
|
|
|
from ..compat import compat_urlparse
|
|
from ..postprocessor.ffmpeg import FFmpegPostProcessor
|
|
from ..utils import (
|
|
encodeArgument,
|
|
encodeFilename,
|
|
sanitize_open,
|
|
)
|
|
|
|
|
|
class HlsFD(FileDownloader):
|
|
def real_download(self, filename, info_dict):
|
|
url = info_dict['url']
|
|
self.report_destination(filename)
|
|
tmpfilename = self.temp_name(filename)
|
|
|
|
ffpp = FFmpegPostProcessor(downloader=self)
|
|
if not ffpp.available:
|
|
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
|
return False
|
|
ffpp.check_version()
|
|
|
|
args = [ffpp.executable, '-y']
|
|
|
|
if info_dict['http_headers']:
|
|
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
|
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
|
args += [
|
|
'-headers',
|
|
''.join('%s: %s\r\n' % (key, val) for key, val in info_dict['http_headers'].items())]
|
|
|
|
args += ['-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
|
|
|
args = [encodeArgument(opt) for opt in args]
|
|
args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
|
|
|
|
self._debug_cmd(args)
|
|
|
|
retval = subprocess.call(args)
|
|
if retval == 0:
|
|
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
|
self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
|
|
self.try_rename(tmpfilename, filename)
|
|
self._hook_progress({
|
|
'downloaded_bytes': fsize,
|
|
'total_bytes': fsize,
|
|
'filename': filename,
|
|
'status': 'finished',
|
|
})
|
|
return True
|
|
else:
|
|
self.to_stderr('\n')
|
|
self.report_error('%s exited with code %d' % (ffpp.basename, retval))
|
|
return False
|
|
|
|
|
|
class NativeHlsFD(FragmentFD):
|
|
""" A more limited implementation that does not require ffmpeg """
|
|
|
|
FD_NAME = 'hlsnative'
|
|
|
|
def real_download(self, filename, info_dict):
|
|
man_url = info_dict['url']
|
|
self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
|
|
manifest = self.ydl.urlopen(man_url).read()
|
|
|
|
last_downloaded_segment_filename = filename + ".last_downloaded_segment"
|
|
last_downloaded_segment = None
|
|
if os.path.isfile(last_downloaded_segment_filename):
|
|
segment_file = open(last_downloaded_segment_filename, 'r')
|
|
last_downloaded_segment = segment_file.readline().strip()
|
|
segment_file.close()
|
|
|
|
s = manifest.decode('utf-8', 'ignore')
|
|
fragment_urls = []
|
|
arrived_at_last_downloaded_segment = (last_downloaded_segment is None)
|
|
for line in s.splitlines():
|
|
line = line.strip()
|
|
if line and not line.startswith('#'):
|
|
segment_url = (
|
|
line
|
|
if re.match(r'^https?://', line)
|
|
else compat_urlparse.urljoin(man_url, line))
|
|
if arrived_at_last_downloaded_segment:
|
|
fragment_urls.append(segment_url)
|
|
elif segment_url == last_downloaded_segment:
|
|
arrived_at_last_downloaded_segment = True
|
|
# We only download the first fragment during the test
|
|
if self.params.get('test', False):
|
|
break
|
|
|
|
ctx = {
|
|
'filename': filename,
|
|
'total_frags': len(fragment_urls),
|
|
}
|
|
|
|
self._prepare_and_start_frag_download(ctx)
|
|
|
|
for i, frag_url in enumerate(fragment_urls):
|
|
frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
|
|
success = ctx['dl'].download(frag_filename, {'url': frag_url})
|
|
if not success:
|
|
return False
|
|
down, frag_sanitized = sanitize_open(frag_filename, 'rb')
|
|
ctx['dest_stream'].write(down.read())
|
|
down.close()
|
|
os.remove(encodeFilename(frag_sanitized))
|
|
segments_file = open(last_downloaded_segment_filename, 'w')
|
|
segments_file.write(frag_url + "\n")
|
|
segments_file.close()
|
|
|
|
|
|
self._finish_frag_download(ctx)
|
|
|
|
if last_downloaded_segment is not None:
|
|
os.remove(last_downloaded_segment_filename)
|
|
|
|
return True
|