]> jfr.im git - yt-dlp.git/blob - yt_dlp/compat.py
Add brotli content-encoding support (#2433)
[yt-dlp.git] / yt_dlp / compat.py
1 # coding: utf-8
2
3 import asyncio
4 import base64
5 import collections
6 import ctypes
7 import getpass
8 import html
9 import html.parser
10 import http
11 import http.client
12 import http.cookiejar
13 import http.cookies
14 import http.server
15 import itertools
16 import optparse
17 import os
18 import re
19 import shlex
20 import shutil
21 import socket
22 import struct
23 import subprocess
24 import sys
25 import tokenize
26 import urllib
27 import xml.etree.ElementTree as etree
28 from subprocess import DEVNULL
29
30
31 # HTMLParseError has been deprecated in Python 3.3 and removed in
32 # Python 3.5. Introducing dummy exception for Python >3.5 for compatible
33 # and uniform cross-version exception handling
34 class compat_HTMLParseError(Exception):
35 pass
36
37
38 # compat_ctypes_WINFUNCTYPE = ctypes.WINFUNCTYPE
39 # will not work since ctypes.WINFUNCTYPE does not exist in UNIX machines
40 def compat_ctypes_WINFUNCTYPE(*args, **kwargs):
41 return ctypes.WINFUNCTYPE(*args, **kwargs)
42
43
44 class _TreeBuilder(etree.TreeBuilder):
45 def doctype(self, name, pubid, system):
46 pass
47
48
49 def compat_etree_fromstring(text):
50 return etree.XML(text, parser=etree.XMLParser(target=_TreeBuilder()))
51
52
53 compat_os_name = os._name if os.name == 'java' else os.name
54
55
56 if compat_os_name == 'nt':
57 def compat_shlex_quote(s):
58 return s if re.match(r'^[-_\w./]+$', s) else '"%s"' % s.replace('"', '\\"')
59 else:
60 from shlex import quote as compat_shlex_quote
61
62
63 def compat_ord(c):
64 if type(c) is int:
65 return c
66 else:
67 return ord(c)
68
69
70 def compat_setenv(key, value, env=os.environ):
71 env[key] = value
72
73
74 if compat_os_name == 'nt' and sys.version_info < (3, 8):
75 # os.path.realpath on Windows does not follow symbolic links
76 # prior to Python 3.8 (see https://bugs.python.org/issue9949)
77 def compat_realpath(path):
78 while os.path.islink(path):
79 path = os.path.abspath(os.readlink(path))
80 return path
81 else:
82 compat_realpath = os.path.realpath
83
84
85 def compat_print(s):
86 assert isinstance(s, compat_str)
87 print(s)
88
89
90 # Fix https://github.com/ytdl-org/youtube-dl/issues/4223
91 # See http://bugs.python.org/issue9161 for what is broken
92 def workaround_optparse_bug9161():
93 op = optparse.OptionParser()
94 og = optparse.OptionGroup(op, 'foo')
95 try:
96 og.add_option('-t')
97 except TypeError:
98 real_add_option = optparse.OptionGroup.add_option
99
100 def _compat_add_option(self, *args, **kwargs):
101 enc = lambda v: (
102 v.encode('ascii', 'replace') if isinstance(v, compat_str)
103 else v)
104 bargs = [enc(a) for a in args]
105 bkwargs = dict(
106 (k, enc(v)) for k, v in kwargs.items())
107 return real_add_option(self, *bargs, **bkwargs)
108 optparse.OptionGroup.add_option = _compat_add_option
109
110
111 try:
112 compat_Pattern = re.Pattern
113 except AttributeError:
114 compat_Pattern = type(re.compile(''))
115
116
117 try:
118 compat_Match = re.Match
119 except AttributeError:
120 compat_Match = type(re.compile('').match(''))
121
122
123 try:
124 compat_asyncio_run = asyncio.run # >= 3.7
125 except AttributeError:
126 def compat_asyncio_run(coro):
127 try:
128 loop = asyncio.get_event_loop()
129 except RuntimeError:
130 loop = asyncio.new_event_loop()
131 asyncio.set_event_loop(loop)
132 loop.run_until_complete(coro)
133
134 asyncio.run = compat_asyncio_run
135
136
137 try: # >= 3.7
138 asyncio.tasks.all_tasks
139 except AttributeError:
140 asyncio.tasks.all_tasks = asyncio.tasks.Task.all_tasks
141
142 try:
143 import websockets as compat_websockets
144 except ImportError:
145 compat_websockets = None
146
147 # Python 3.8+ does not honor %HOME% on windows, but this breaks compatibility with youtube-dl
148 # See https://github.com/yt-dlp/yt-dlp/issues/792
149 # https://docs.python.org/3/library/os.path.html#os.path.expanduser
150 if compat_os_name in ('nt', 'ce') and 'HOME' in os.environ:
151 _userhome = os.environ['HOME']
152
153 def compat_expanduser(path):
154 if not path.startswith('~'):
155 return path
156 i = path.replace('\\', '/', 1).find('/') # ~user
157 if i < 0:
158 i = len(path)
159 userhome = os.path.join(os.path.dirname(_userhome), path[1:i]) if i > 1 else _userhome
160 return userhome + path[i:]
161 else:
162 compat_expanduser = os.path.expanduser
163
164
165 try:
166 from Cryptodome.Cipher import AES as compat_pycrypto_AES
167 except ImportError:
168 try:
169 from Crypto.Cipher import AES as compat_pycrypto_AES
170 except ImportError:
171 compat_pycrypto_AES = None
172
173 try:
174 import brotlicffi as compat_brotli
175 except ImportError:
176 try:
177 import brotli as compat_brotli
178 except ImportError:
179 compat_brotli = None
180
181 WINDOWS_VT_MODE = False if compat_os_name == 'nt' else None
182
183
184 def windows_enable_vt_mode(): # TODO: Do this the proper way https://bugs.python.org/issue30075
185 if compat_os_name != 'nt':
186 return
187 global WINDOWS_VT_MODE
188 startupinfo = subprocess.STARTUPINFO()
189 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
190 try:
191 subprocess.Popen('', shell=True, startupinfo=startupinfo)
192 WINDOWS_VT_MODE = True
193 except Exception:
194 pass
195
196
197 # Deprecated
198
199 compat_basestring = str
200 compat_chr = chr
201 compat_filter = filter
202 compat_input = input
203 compat_integer_types = (int, )
204 compat_kwargs = lambda kwargs: kwargs
205 compat_map = map
206 compat_numeric_types = (int, float, complex)
207 compat_str = str
208 compat_xpath = lambda xpath: xpath
209 compat_zip = zip
210
211 compat_collections_abc = collections.abc
212 compat_HTMLParser = html.parser.HTMLParser
213 compat_HTTPError = urllib.error.HTTPError
214 compat_Struct = struct.Struct
215 compat_b64decode = base64.b64decode
216 compat_cookiejar = http.cookiejar
217 compat_cookiejar_Cookie = compat_cookiejar.Cookie
218 compat_cookies = http.cookies
219 compat_cookies_SimpleCookie = compat_cookies.SimpleCookie
220 compat_etree_Element = etree.Element
221 compat_etree_register_namespace = etree.register_namespace
222 compat_get_terminal_size = shutil.get_terminal_size
223 compat_getenv = os.getenv
224 compat_getpass = getpass.getpass
225 compat_html_entities = html.entities
226 compat_html_entities_html5 = compat_html_entities.html5
227 compat_http_client = http.client
228 compat_http_server = http.server
229 compat_itertools_count = itertools.count
230 compat_parse_qs = urllib.parse.parse_qs
231 compat_shlex_split = shlex.split
232 compat_socket_create_connection = socket.create_connection
233 compat_struct_pack = struct.pack
234 compat_struct_unpack = struct.unpack
235 compat_subprocess_get_DEVNULL = lambda: DEVNULL
236 compat_tokenize_tokenize = tokenize.tokenize
237 compat_urllib_error = urllib.error
238 compat_urllib_parse = urllib.parse
239 compat_urllib_parse_quote = urllib.parse.quote
240 compat_urllib_parse_quote_plus = urllib.parse.quote_plus
241 compat_urllib_parse_unquote = urllib.parse.unquote
242 compat_urllib_parse_unquote_plus = urllib.parse.unquote_plus
243 compat_urllib_parse_unquote_to_bytes = urllib.parse.unquote_to_bytes
244 compat_urllib_parse_urlencode = urllib.parse.urlencode
245 compat_urllib_parse_urlparse = urllib.parse.urlparse
246 compat_urllib_parse_urlunparse = urllib.parse.urlunparse
247 compat_urllib_request = urllib.request
248 compat_urllib_request_DataHandler = urllib.request.DataHandler
249 compat_urllib_response = urllib.response
250 compat_urlparse = urllib.parse
251 compat_urlretrieve = urllib.request.urlretrieve
252 compat_xml_parse_error = etree.ParseError
253
254
255 # Set public objects
256
257 __all__ = [
258 'WINDOWS_VT_MODE',
259 'compat_HTMLParseError',
260 'compat_HTMLParser',
261 'compat_HTTPError',
262 'compat_Match',
263 'compat_Pattern',
264 'compat_Struct',
265 'compat_asyncio_run',
266 'compat_b64decode',
267 'compat_basestring',
268 'compat_brotli',
269 'compat_chr',
270 'compat_collections_abc',
271 'compat_cookiejar',
272 'compat_cookiejar_Cookie',
273 'compat_cookies',
274 'compat_cookies_SimpleCookie',
275 'compat_ctypes_WINFUNCTYPE',
276 'compat_etree_Element',
277 'compat_etree_fromstring',
278 'compat_etree_register_namespace',
279 'compat_expanduser',
280 'compat_filter',
281 'compat_get_terminal_size',
282 'compat_getenv',
283 'compat_getpass',
284 'compat_html_entities',
285 'compat_html_entities_html5',
286 'compat_http_client',
287 'compat_http_server',
288 'compat_input',
289 'compat_integer_types',
290 'compat_itertools_count',
291 'compat_kwargs',
292 'compat_map',
293 'compat_numeric_types',
294 'compat_ord',
295 'compat_os_name',
296 'compat_parse_qs',
297 'compat_print',
298 'compat_pycrypto_AES',
299 'compat_realpath',
300 'compat_setenv',
301 'compat_shlex_quote',
302 'compat_shlex_split',
303 'compat_socket_create_connection',
304 'compat_str',
305 'compat_struct_pack',
306 'compat_struct_unpack',
307 'compat_subprocess_get_DEVNULL',
308 'compat_tokenize_tokenize',
309 'compat_urllib_error',
310 'compat_urllib_parse',
311 'compat_urllib_parse_quote',
312 'compat_urllib_parse_quote_plus',
313 'compat_urllib_parse_unquote',
314 'compat_urllib_parse_unquote_plus',
315 'compat_urllib_parse_unquote_to_bytes',
316 'compat_urllib_parse_urlencode',
317 'compat_urllib_parse_urlparse',
318 'compat_urllib_parse_urlunparse',
319 'compat_urllib_request',
320 'compat_urllib_request_DataHandler',
321 'compat_urllib_response',
322 'compat_urlparse',
323 'compat_urlretrieve',
324 'compat_websockets',
325 'compat_xml_parse_error',
326 'compat_xpath',
327 'compat_zip',
328 'windows_enable_vt_mode',
329 'workaround_optparse_bug9161',
330 ]