]>
Commit | Line | Data |
---|---|---|
f0b5d6af PH |
1 | from __future__ import unicode_literals |
2 | ||
3bc2ddcc | 3 | import os |
f0b5d6af | 4 | import re |
3bc2ddcc JMF |
5 | import subprocess |
6 | ||
5f9b8394 | 7 | from ..postprocessor.ffmpeg import FFmpegPostProcessor |
3bc2ddcc | 8 | from .common import FileDownloader |
1cc79574 | 9 | from ..compat import ( |
f0b5d6af | 10 | compat_urlparse, |
b686fc18 | 11 | compat_urllib_request, |
1cc79574 PH |
12 | ) |
13 | from ..utils import ( | |
3bc2ddcc JMF |
14 | encodeFilename, |
15 | ) | |
16 | ||
17 | ||
18 | class 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 | ||
55 | class 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 |