]> jfr.im git - yt-dlp.git/blob - yt_dlp/compat.py
Update to ytdl-commit-78ce962
[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 # Python 3.8+ does not honor %HOME% on windows, but this breaks compatibility with youtube-dl
138 # See https://github.com/yt-dlp/yt-dlp/issues/792
139 # https://docs.python.org/3/library/os.path.html#os.path.expanduser
140 if compat_os_name in ('nt', 'ce') and 'HOME' in os.environ:
141 _userhome = os.environ['HOME']
142
143 def compat_expanduser(path):
144 if not path.startswith('~'):
145 return path
146 i = path.replace('\\', '/', 1).find('/') # ~user
147 if i < 0:
148 i = len(path)
149 userhome = os.path.join(os.path.dirname(_userhome), path[1:i]) if i > 1 else _userhome
150 return userhome + path[i:]
151 else:
152 compat_expanduser = os.path.expanduser
153
154
155 try:
156 from Cryptodome.Cipher import AES as compat_pycrypto_AES
157 except ImportError:
158 try:
159 from Crypto.Cipher import AES as compat_pycrypto_AES
160 except ImportError:
161 compat_pycrypto_AES = None
162
163
164 WINDOWS_VT_MODE = False if compat_os_name == 'nt' else None
165
166
167 def windows_enable_vt_mode(): # TODO: Do this the proper way https://bugs.python.org/issue30075
168 if compat_os_name != 'nt':
169 return
170 global WINDOWS_VT_MODE
171 startupinfo = subprocess.STARTUPINFO()
172 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
173 try:
174 subprocess.Popen('', shell=True, startupinfo=startupinfo)
175 WINDOWS_VT_MODE = True
176 except Exception:
177 pass
178
179
180 # Deprecated
181
182 compat_basestring = str
183 compat_chr = chr
184 compat_filter = filter
185 compat_input = input
186 compat_integer_types = (int, )
187 compat_kwargs = lambda kwargs: kwargs
188 compat_map = map
189 compat_numeric_types = (int, float, complex)
190 compat_str = str
191 compat_xpath = lambda xpath: xpath
192 compat_zip = zip
193
194 compat_collections_abc = collections.abc
195 compat_HTMLParser = html.parser.HTMLParser
196 compat_HTTPError = urllib.error.HTTPError
197 compat_Struct = struct.Struct
198 compat_b64decode = base64.b64decode
199 compat_cookiejar = http.cookiejar
200 compat_cookiejar_Cookie = compat_cookiejar.Cookie
201 compat_cookies = http.cookies
202 compat_cookies_SimpleCookie = compat_cookies.SimpleCookie
203 compat_etree_Element = etree.Element
204 compat_etree_register_namespace = etree.register_namespace
205 compat_get_terminal_size = shutil.get_terminal_size
206 compat_getenv = os.getenv
207 compat_getpass = getpass.getpass
208 compat_html_entities = html.entities
209 compat_html_entities_html5 = compat_html_entities.html5
210 compat_http_client = http.client
211 compat_http_server = http.server
212 compat_itertools_count = itertools.count
213 compat_parse_qs = urllib.parse.parse_qs
214 compat_shlex_split = shlex.split
215 compat_socket_create_connection = socket.create_connection
216 compat_struct_pack = struct.pack
217 compat_struct_unpack = struct.unpack
218 compat_subprocess_get_DEVNULL = lambda: DEVNULL
219 compat_tokenize_tokenize = tokenize.tokenize
220 compat_urllib_error = urllib.error
221 compat_urllib_parse = urllib.parse
222 compat_urllib_parse_quote = urllib.parse.quote
223 compat_urllib_parse_quote_plus = urllib.parse.quote_plus
224 compat_urllib_parse_unquote = urllib.parse.unquote
225 compat_urllib_parse_unquote_plus = urllib.parse.unquote_plus
226 compat_urllib_parse_unquote_to_bytes = urllib.parse.unquote_to_bytes
227 compat_urllib_parse_urlencode = urllib.parse.urlencode
228 compat_urllib_parse_urlparse = urllib.parse.urlparse
229 compat_urllib_parse_urlunparse = urllib.parse.urlunparse
230 compat_urllib_request = urllib.request
231 compat_urllib_request_DataHandler = urllib.request.DataHandler
232 compat_urllib_response = urllib.response
233 compat_urlparse = urllib.parse
234 compat_urlretrieve = urllib.request.urlretrieve
235 compat_xml_parse_error = etree.ParseError
236
237
238 # Set public objects
239
240 __all__ = [
241 'WINDOWS_VT_MODE',
242 'compat_HTMLParseError',
243 'compat_HTMLParser',
244 'compat_HTTPError',
245 'compat_Match',
246 'compat_Pattern',
247 'compat_Struct',
248 'compat_asyncio_run',
249 'compat_b64decode',
250 'compat_basestring',
251 'compat_chr',
252 'compat_collections_abc',
253 'compat_cookiejar',
254 'compat_cookiejar_Cookie',
255 'compat_cookies',
256 'compat_cookies_SimpleCookie',
257 'compat_ctypes_WINFUNCTYPE',
258 'compat_etree_Element',
259 'compat_etree_fromstring',
260 'compat_etree_register_namespace',
261 'compat_expanduser',
262 'compat_filter',
263 'compat_get_terminal_size',
264 'compat_getenv',
265 'compat_getpass',
266 'compat_html_entities',
267 'compat_html_entities_html5',
268 'compat_http_client',
269 'compat_http_server',
270 'compat_input',
271 'compat_integer_types',
272 'compat_itertools_count',
273 'compat_kwargs',
274 'compat_map',
275 'compat_numeric_types',
276 'compat_ord',
277 'compat_os_name',
278 'compat_parse_qs',
279 'compat_print',
280 'compat_pycrypto_AES',
281 'compat_realpath',
282 'compat_setenv',
283 'compat_shlex_quote',
284 'compat_shlex_split',
285 'compat_socket_create_connection',
286 'compat_str',
287 'compat_struct_pack',
288 'compat_struct_unpack',
289 'compat_subprocess_get_DEVNULL',
290 'compat_tokenize_tokenize',
291 'compat_urllib_error',
292 'compat_urllib_parse',
293 'compat_urllib_parse_quote',
294 'compat_urllib_parse_quote_plus',
295 'compat_urllib_parse_unquote',
296 'compat_urllib_parse_unquote_plus',
297 'compat_urllib_parse_unquote_to_bytes',
298 'compat_urllib_parse_urlencode',
299 'compat_urllib_parse_urlparse',
300 'compat_urllib_parse_urlunparse',
301 'compat_urllib_request',
302 'compat_urllib_request_DataHandler',
303 'compat_urllib_response',
304 'compat_urlparse',
305 'compat_urlretrieve',
306 'compat_xml_parse_error',
307 'compat_xpath',
308 'compat_zip',
309 'windows_enable_vt_mode',
310 'workaround_optparse_bug9161',
311 ]