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