Merge branch 'jukebox' of https://github.com/remitamine/youtube-dl into remitamine...
[youtube-dl] / youtube_dl / downloader / external.py
1 from __future__ import unicode_literals
2
3 import os.path
4 import subprocess
5
6 from .common import FileDownloader
7 from ..utils import (
8     cli_option,
9     cli_valueless_option,
10     cli_bool_option,
11     cli_configuration_args,
12     encodeFilename,
13     encodeArgument,
14 )
15
16
17 class ExternalFD(FileDownloader):
18     def real_download(self, filename, info_dict):
19         self.report_destination(filename)
20         tmpfilename = self.temp_name(filename)
21
22         retval = self._call_downloader(tmpfilename, info_dict)
23         if retval == 0:
24             fsize = os.path.getsize(encodeFilename(tmpfilename))
25             self.to_screen('\r[%s] Downloaded %s bytes' % (self.get_basename(), fsize))
26             self.try_rename(tmpfilename, filename)
27             self._hook_progress({
28                 'downloaded_bytes': fsize,
29                 'total_bytes': fsize,
30                 'filename': filename,
31                 'status': 'finished',
32             })
33             return True
34         else:
35             self.to_stderr('\n')
36             self.report_error('%s exited with code %d' % (
37                 self.get_basename(), retval))
38             return False
39
40     @classmethod
41     def get_basename(cls):
42         return cls.__name__[:-2].lower()
43
44     @property
45     def exe(self):
46         return self.params.get('external_downloader')
47
48     @classmethod
49     def supports(cls, info_dict):
50         return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps')
51
52     def _option(self, command_option, param):
53         return cli_option(self.params, command_option, param)
54
55     def _bool_option(self, command_option, param, true_value='true', false_value='false', separator=None):
56         return cli_bool_option(self.params, command_option, param, true_value, false_value, separator)
57
58     def _valueless_option(self, command_option, param, expected_value=True):
59         return cli_valueless_option(self.params, command_option, param, expected_value)
60
61     def _configuration_args(self, default=[]):
62         return cli_configuration_args(self.params, 'external_downloader_args', default)
63
64     def _call_downloader(self, tmpfilename, info_dict):
65         """ Either overwrite this or implement _make_cmd """
66         cmd = [encodeArgument(a) for a in self._make_cmd(tmpfilename, info_dict)]
67
68         self._debug_cmd(cmd)
69
70         p = subprocess.Popen(
71             cmd, stderr=subprocess.PIPE)
72         _, stderr = p.communicate()
73         if p.returncode != 0:
74             self.to_stderr(stderr)
75         return p.returncode
76
77
78 class CurlFD(ExternalFD):
79     def _make_cmd(self, tmpfilename, info_dict):
80         cmd = [self.exe, '--location', '-o', tmpfilename]
81         for key, val in info_dict['http_headers'].items():
82             cmd += ['--header', '%s: %s' % (key, val)]
83         cmd += self._option('--interface', 'source_address')
84         cmd += self._option('--proxy', 'proxy')
85         cmd += self._valueless_option('--insecure', 'nocheckcertificate')
86         cmd += self._configuration_args()
87         cmd += ['--', info_dict['url']]
88         return cmd
89
90
91 class AxelFD(ExternalFD):
92     def _make_cmd(self, tmpfilename, info_dict):
93         cmd = [self.exe, '-o', tmpfilename]
94         for key, val in info_dict['http_headers'].items():
95             cmd += ['-H', '%s: %s' % (key, val)]
96         cmd += self._configuration_args()
97         cmd += ['--', info_dict['url']]
98         return cmd
99
100
101 class WgetFD(ExternalFD):
102     def _make_cmd(self, tmpfilename, info_dict):
103         cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies']
104         for key, val in info_dict['http_headers'].items():
105             cmd += ['--header', '%s: %s' % (key, val)]
106         cmd += self._option('--bind-address', 'source_address')
107         cmd += self._option('--proxy', 'proxy')
108         cmd += self._valueless_option('--no-check-certificate', 'nocheckcertificate')
109         cmd += self._configuration_args()
110         cmd += ['--', info_dict['url']]
111         return cmd
112
113
114 class Aria2cFD(ExternalFD):
115     def _make_cmd(self, tmpfilename, info_dict):
116         cmd = [self.exe, '-c']
117         cmd += self._configuration_args([
118             '--min-split-size', '1M', '--max-connection-per-server', '4'])
119         dn = os.path.dirname(tmpfilename)
120         if dn:
121             cmd += ['--dir', dn]
122         cmd += ['--out', os.path.basename(tmpfilename)]
123         for key, val in info_dict['http_headers'].items():
124             cmd += ['--header', '%s: %s' % (key, val)]
125         cmd += self._option('--interface', 'source_address')
126         cmd += self._option('--all-proxy', 'proxy')
127         cmd += self._bool_option('--check-certificate', 'nocheckcertificate', 'false', 'true', '=')
128         cmd += ['--', info_dict['url']]
129         return cmd
130
131
132 class HttpieFD(ExternalFD):
133     def _make_cmd(self, tmpfilename, info_dict):
134         cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']]
135         for key, val in info_dict['http_headers'].items():
136             cmd += ['%s:%s' % (key, val)]
137         return cmd
138
139 _BY_NAME = dict(
140     (klass.get_basename(), klass)
141     for name, klass in globals().items()
142     if name.endswith('FD') and name != 'ExternalFD'
143 )
144
145
146 def list_external_downloaders():
147     return sorted(_BY_NAME.keys())
148
149
150 def get_external_downloader(external_downloader):
151     """ Given the name of the executable, see whether we support the given
152         downloader . """
153     # Drop .exe extension on Windows
154     bn = os.path.splitext(os.path.basename(external_downloader))[0]
155     return _BY_NAME[bn]