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