]> jfr.im git - yt-dlp.git/blame - youtube_dl/downloader/hls.py
Merge remote-tracking branch 'lenaten/8tracks'
[yt-dlp.git] / youtube_dl / downloader / hls.py
CommitLineData
f0b5d6af
PH
1from __future__ import unicode_literals
2
3bc2ddcc 3import os
f0b5d6af 4import re
3bc2ddcc
JMF
5import subprocess
6
5f9b8394 7from ..postprocessor.ffmpeg import FFmpegPostProcessor
3bc2ddcc 8from .common import FileDownloader
1cc79574 9from ..compat import (
f0b5d6af 10 compat_urlparse,
b686fc18 11 compat_urllib_request,
1cc79574
PH
12)
13from ..utils import (
3bc2ddcc
JMF
14 encodeFilename,
15)
16
17
18class HlsFD(FileDownloader):
19 def real_download(self, filename, info_dict):
20 url = info_dict['url']
21 self.report_destination(filename)
22 tmpfilename = self.temp_name(filename)
23
75f2e25b
PH
24 args = [
25 '-y', '-i', url, '-f', 'mp4', '-c', 'copy',
26 '-bsf:a', 'aac_adtstoasc',
27 encodeFilename(tmpfilename, for_subprocess=True)]
3bc2ddcc 28
63948fc6
JMF
29 ffpp = FFmpegPostProcessor(downloader=self)
30 program = ffpp._executable
31 if program is None:
0e44f90e 32 self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
baf29075 33 return False
5f9b8394 34 ffpp.check_version()
63948fc6 35 cmd = [program] + args
5f9b8394 36
3bc2ddcc
JMF
37 retval = subprocess.call(cmd)
38 if retval == 0:
39 fsize = os.path.getsize(encodeFilename(tmpfilename))
0e44f90e 40 self.to_screen('\r[%s] %s bytes' % (cmd[0], fsize))
3bc2ddcc
JMF
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:
0e44f90e
PH
50 self.to_stderr('\n')
51 self.report_error('%s exited with code %d' % (program, retval))
3bc2ddcc 52 return False
f0b5d6af
PH
53
54
55class NativeHlsFD(FileDownloader):
56 """ A more limited implementation that does not require ffmpeg """
57
58 def real_download(self, filename, info_dict):
59 url = info_dict['url']
60 self.report_destination(filename)
61 tmpfilename = self.temp_name(filename)
62
63 self.to_screen(
64 '[hlsnative] %s: Downloading m3u8 manifest' % info_dict['id'])
65 data = self.ydl.urlopen(url).read()
66 s = data.decode('utf-8', 'ignore')
67 segment_urls = []
68 for line in s.splitlines():
69 line = line.strip()
70 if line and not line.startswith('#'):
71 segment_url = (
72 line
73 if re.match(r'^https?://', line)
74 else compat_urlparse.urljoin(url, line))
75 segment_urls.append(segment_url)
76
b686fc18
PH
77 is_test = self.params.get('test', False)
78 remaining_bytes = self._TEST_FILE_SIZE if is_test else None
f0b5d6af
PH
79 byte_counter = 0
80 with open(tmpfilename, 'wb') as outf:
81 for i, segurl in enumerate(segment_urls):
f0b5d6af
PH
82 self.to_screen(
83 '[hlsnative] %s: Downloading segment %d / %d' %
84 (info_dict['id'], i + 1, len(segment_urls)))
b686fc18 85 seg_req = compat_urllib_request.Request(segurl)
fec02bcc 86 if remaining_bytes is not None:
b686fc18
PH
87 seg_req.add_header('Range', 'bytes=0-%d' % (remaining_bytes - 1))
88
89 segment = self.ydl.urlopen(seg_req).read()
fec02bcc 90 if remaining_bytes is not None:
b686fc18
PH
91 segment = segment[:remaining_bytes]
92 remaining_bytes -= len(segment)
93 outf.write(segment)
94 byte_counter += len(segment)
fec02bcc 95 if remaining_bytes is not None and remaining_bytes <= 0:
b686fc18 96 break
f0b5d6af
PH
97
98 self._hook_progress({
99 'downloaded_bytes': byte_counter,
100 'total_bytes': byte_counter,
101 'filename': filename,
102 'status': 'finished',
103 })
104 self.try_rename(tmpfilename, filename)
105 return True