]>
Commit | Line | Data |
---|---|---|
f0b5d6af PH |
1 | from __future__ import unicode_literals |
2 | ||
3bc2ddcc | 3 | import os |
f0b5d6af | 4 | import re |
3bc2ddcc | 5 | import subprocess |
56401e1e | 6 | import sys |
3bc2ddcc JMF |
7 | |
8 | from .common import FileDownloader | |
f9a5affa S |
9 | from .fragment import FragmentFD |
10 | ||
11 | from ..compat import compat_urlparse | |
12 | from ..postprocessor.ffmpeg import FFmpegPostProcessor | |
1cc79574 | 13 | from ..utils import ( |
027008b1 | 14 | encodeArgument, |
3bc2ddcc | 15 | encodeFilename, |
fcd9e423 | 16 | sanitize_open, |
94e8c804 | 17 | handle_youtubedl_headers, |
3bc2ddcc JMF |
18 | ) |
19 | ||
20 | ||
21 | class HlsFD(FileDownloader): | |
22 | def real_download(self, filename, info_dict): | |
23 | url = info_dict['url'] | |
24 | self.report_destination(filename) | |
25 | tmpfilename = self.temp_name(filename) | |
26 | ||
63948fc6 | 27 | ffpp = FFmpegPostProcessor(downloader=self) |
8ac27a68 | 28 | if not ffpp.available: |
0e44f90e | 29 | self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.') |
baf29075 | 30 | return False |
5f9b8394 | 31 | ffpp.check_version() |
027008b1 | 32 | |
f1028194 S |
33 | args = [ffpp.executable, '-y'] |
34 | ||
985e4fdc | 35 | if info_dict['http_headers'] and re.match(r'^https?://', url): |
f1028194 S |
36 | # Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv: |
37 | # [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header. | |
94e8c804 | 38 | headers = handle_youtubedl_headers(info_dict['http_headers']) |
f1028194 S |
39 | args += [ |
40 | '-headers', | |
94e8c804 | 41 | ''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())] |
f1028194 | 42 | |
7d106a65 JMF |
43 | args += ['-i', url, '-c', 'copy'] |
44 | if self.params.get('hls_use_mpegts', False): | |
45 | args += ['-f', 'mpegts'] | |
46 | else: | |
47 | args += ['-f', 'mp4', '-bsf:a', 'aac_adtstoasc'] | |
f1028194 S |
48 | |
49 | args = [encodeArgument(opt) for opt in args] | |
8a7bbd16 | 50 | args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True)) |
027008b1 | 51 | |
7393746d S |
52 | self._debug_cmd(args) |
53 | ||
9d90e7de JMF |
54 | proc = subprocess.Popen(args, stdin=subprocess.PIPE) |
55 | try: | |
56 | retval = proc.wait() | |
57 | except KeyboardInterrupt: | |
58 | # subprocces.run would send the SIGKILL signal to ffmpeg and the | |
59 | # mp4 file couldn't be played, but if we ask ffmpeg to quit it | |
60 | # produces a file that is playable (this is mostly useful for live | |
56401e1e S |
61 | # streams). Note that Windows is not affected and produces playable |
62 | # files (see https://github.com/rg3/youtube-dl/issues/8300). | |
63 | if sys.platform != 'win32': | |
64 | proc.communicate(b'q') | |
9d90e7de | 65 | raise |
3bc2ddcc JMF |
66 | if retval == 0: |
67 | fsize = os.path.getsize(encodeFilename(tmpfilename)) | |
4a3da4eb | 68 | self.to_screen('\r[%s] %s bytes' % (args[0], fsize)) |
3bc2ddcc JMF |
69 | self.try_rename(tmpfilename, filename) |
70 | self._hook_progress({ | |
71 | 'downloaded_bytes': fsize, | |
72 | 'total_bytes': fsize, | |
73 | 'filename': filename, | |
74 | 'status': 'finished', | |
75 | }) | |
76 | return True | |
77 | else: | |
0e44f90e | 78 | self.to_stderr('\n') |
73fac4e9 | 79 | self.report_error('%s exited with code %d' % (ffpp.basename, retval)) |
3bc2ddcc | 80 | return False |
f0b5d6af PH |
81 | |
82 | ||
f9a5affa | 83 | class NativeHlsFD(FragmentFD): |
f0b5d6af PH |
84 | """ A more limited implementation that does not require ffmpeg """ |
85 | ||
f9a5affa S |
86 | FD_NAME = 'hlsnative' |
87 | ||
f0b5d6af | 88 | def real_download(self, filename, info_dict): |
f9a5affa S |
89 | man_url = info_dict['url'] |
90 | self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME) | |
91 | manifest = self.ydl.urlopen(man_url).read() | |
f0b5d6af | 92 | |
f9a5affa S |
93 | s = manifest.decode('utf-8', 'ignore') |
94 | fragment_urls = [] | |
f0b5d6af PH |
95 | for line in s.splitlines(): |
96 | line = line.strip() | |
97 | if line and not line.startswith('#'): | |
98 | segment_url = ( | |
99 | line | |
100 | if re.match(r'^https?://', line) | |
f9a5affa S |
101 | else compat_urlparse.urljoin(man_url, line)) |
102 | fragment_urls.append(segment_url) | |
103 | # We only download the first fragment during the test | |
104 | if self.params.get('test', False): | |
b686fc18 | 105 | break |
f0b5d6af | 106 | |
f9a5affa | 107 | ctx = { |
f0b5d6af | 108 | 'filename': filename, |
f9a5affa S |
109 | 'total_frags': len(fragment_urls), |
110 | } | |
111 | ||
112 | self._prepare_and_start_frag_download(ctx) | |
113 | ||
114 | frags_filenames = [] | |
115 | for i, frag_url in enumerate(fragment_urls): | |
116 | frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i) | |
117 | success = ctx['dl'].download(frag_filename, {'url': frag_url}) | |
118 | if not success: | |
119 | return False | |
fcd9e423 S |
120 | down, frag_sanitized = sanitize_open(frag_filename, 'rb') |
121 | ctx['dest_stream'].write(down.read()) | |
133a2b4a | 122 | down.close() |
fcd9e423 | 123 | frags_filenames.append(frag_sanitized) |
f9a5affa S |
124 | |
125 | self._finish_frag_download(ctx) | |
126 | ||
127 | for frag_file in frags_filenames: | |
fcd9e423 | 128 | os.remove(encodeFilename(frag_file)) |
f9a5affa | 129 | |
f0b5d6af | 130 | return True |