1 from __future__ 
import unicode_literals
 
  17 import xml
.etree
.ElementTree
 
  21     import urllib
.request 
as compat_urllib_request
 
  22 except ImportError:  # Python 2 
  23     import urllib2 
as compat_urllib_request
 
  26     import urllib
.error 
as compat_urllib_error
 
  27 except ImportError:  # Python 2 
  28     import urllib2 
as compat_urllib_error
 
  31     import urllib
.parse 
as compat_urllib_parse
 
  32 except ImportError:  # Python 2 
  33     import urllib 
as compat_urllib_parse
 
  36     from urllib
.parse 
import urlparse 
as compat_urllib_parse_urlparse
 
  37 except ImportError:  # Python 2 
  38     from urlparse 
import urlparse 
as compat_urllib_parse_urlparse
 
  41     import urllib
.parse 
as compat_urlparse
 
  42 except ImportError:  # Python 2 
  43     import urlparse 
as compat_urlparse
 
  46     import urllib
.response 
as compat_urllib_response
 
  47 except ImportError:  # Python 2 
  48     import urllib 
as compat_urllib_response
 
  51     import http
.cookiejar 
as compat_cookiejar
 
  52 except ImportError:  # Python 2 
  53     import cookielib 
as compat_cookiejar
 
  56     import http
.cookies 
as compat_cookies
 
  57 except ImportError:  # Python 2 
  58     import Cookie 
as compat_cookies
 
  61     import html
.entities 
as compat_html_entities
 
  62 except ImportError:  # Python 2 
  63     import htmlentitydefs 
as compat_html_entities
 
  66     import http
.client 
as compat_http_client
 
  67 except ImportError:  # Python 2 
  68     import httplib 
as compat_http_client
 
  71     from urllib
.error 
import HTTPError 
as compat_HTTPError
 
  72 except ImportError:  # Python 2 
  73     from urllib2 
import HTTPError 
as compat_HTTPError
 
  76     from urllib
.request 
import urlretrieve 
as compat_urlretrieve
 
  77 except ImportError:  # Python 2 
  78     from urllib 
import urlretrieve 
as compat_urlretrieve
 
  82     from subprocess 
import DEVNULL
 
  83     compat_subprocess_get_DEVNULL 
= lambda: DEVNULL
 
  85     compat_subprocess_get_DEVNULL 
= lambda: open(os
.path
.devnull
, 'w') 
  88     import http
.server 
as compat_http_server
 
  90     import BaseHTTPServer 
as compat_http_server
 
  93     compat_str 
= unicode  # Python 2 
  98     from urllib
.parse 
import unquote_to_bytes 
as compat_urllib_parse_unquote_to_bytes
 
  99     from urllib
.parse 
import unquote 
as compat_urllib_parse_unquote
 
 100     from urllib
.parse 
import unquote_plus 
as compat_urllib_parse_unquote_plus
 
 101 except ImportError:  # Python 2 
 102     _asciire 
= (compat_urllib_parse
._asciire 
if hasattr(compat_urllib_parse
, '_asciire') 
 103                 else re
.compile('([\x00-\x7f]+)')) 
 105     # HACK: The following are the correct unquote_to_bytes, unquote and unquote_plus 
 106     # implementations from cpython 3.4.3's stdlib. Python 2's version 
 107     # is apparently broken (see https://github.com/rg3/youtube-dl/pull/6244) 
 109     def compat_urllib_parse_unquote_to_bytes(string
): 
 110         """unquote_to_bytes('abc%20def') -> b'abc def'.""" 
 111         # Note: strings are encoded as UTF-8. This is only an issue if it contains 
 112         # unescaped non-ASCII characters, which URIs should not. 
 114             # Is it a string-like object? 
 117         if isinstance(string
, compat_str
): 
 118             string 
= string
.encode('utf-8') 
 119         bits 
= string
.split(b
'%') 
 124         for item 
in bits
[1:]: 
 126                 append(compat_urllib_parse
._hextochr
[item
[:2]]) 
 133     def compat_urllib_parse_unquote(string
, encoding
='utf-8', errors
='replace'): 
 134         """Replace %xx escapes by their single-character equivalent. The optional 
 135         encoding and errors parameters specify how to decode percent-encoded 
 136         sequences into Unicode characters, as accepted by the bytes.decode() 
 138         By default, percent-encoded sequences are decoded with UTF-8, and invalid 
 139         sequences are replaced by a placeholder character. 
 141         unquote('abc%20def') -> 'abc def'. 
 143         if '%' not in string
: 
 150         bits 
= _asciire
.split(string
) 
 153         for i 
in range(1, len(bits
), 2): 
 154             append(compat_urllib_parse_unquote_to_bytes(bits
[i
]).decode(encoding
, errors
)) 
 158     def compat_urllib_parse_unquote_plus(string
, encoding
='utf-8', errors
='replace'): 
 159         """Like unquote(), but also replace plus signs by spaces, as required for 
 160         unquoting HTML form values. 
 162         unquote_plus('%7e/abc+def') -> '~/abc def' 
 164         string 
= string
.replace('+', ' ') 
 165         return compat_urllib_parse_unquote(string
, encoding
, errors
) 
 168     from urllib
.request 
import DataHandler 
as compat_urllib_request_DataHandler
 
 169 except ImportError:  # Python < 3.4 
 170     # Ported from CPython 98774:1733b3bd46db, Lib/urllib/request.py 
 171     class compat_urllib_request_DataHandler(compat_urllib_request
.BaseHandler
): 
 172         def data_open(self
, req
): 
 173             # data URLs as specified in RFC 2397. 
 175             # ignores POSTed data 
 178             # dataurl   := "data:" [ mediatype ] [ ";base64" ] "," data 
 179             # mediatype := [ type "/" subtype ] *( ";" parameter ) 
 181             # parameter := attribute "=" value 
 182             url 
= req
.get_full_url() 
 184             scheme
, data 
= url
.split(":", 1) 
 185             mediatype
, data 
= data
.split(",", 1) 
 187             # even base64 encoded data URLs might be quoted so unquote in any case: 
 188             data 
= compat_urllib_parse_unquote_to_bytes(data
) 
 189             if mediatype
.endswith(";base64"): 
 190                 data 
= binascii
.a2b_base64(data
) 
 191                 mediatype 
= mediatype
[:-7] 
 194                 mediatype 
= "text/plain;charset=US-ASCII" 
 196             headers 
= email
.message_from_string( 
 197                 "Content-type: %s\nContent-length: %d\n" % (mediatype
, len(data
))) 
 199             return compat_urllib_response
.addinfourl(io
.BytesIO(data
), headers
, url
) 
 202     compat_basestring 
= basestring  
# Python 2 
 204     compat_basestring 
= str 
 207     compat_chr 
= unichr  # Python 2 
 212     from xml
.etree
.ElementTree 
import ParseError 
as compat_xml_parse_error
 
 213 except ImportError:  # Python 2.6 
 214     from xml
.parsers
.expat 
import ExpatError 
as compat_xml_parse_error
 
 216 if sys
.version_info
[0] >= 3: 
 217     compat_etree_fromstring 
= xml
.etree
.ElementTree
.fromstring
 
 219     # python 2.x tries to encode unicode strings with ascii (see the 
 220     # XMLParser._fixtext method) 
 221     etree 
= xml
.etree
.ElementTree
 
 224         _etree_iter 
= etree
.Element
.iter 
 225     except AttributeError:  # Python <=2.6 
 226         def _etree_iter(root
): 
 227             for el 
in root
.findall('*'): 
 229                 for sub 
in _etree_iter(el
): 
 232     # on 2.6 XML doesn't have a parser argument, function copied from CPython 
 234     def _XML(text
, parser
=None): 
 236             parser 
= etree
.XMLParser(target
=etree
.TreeBuilder()) 
 238         return parser
.close() 
 240     def _element_factory(*args
, **kwargs
): 
 241         el 
= etree
.Element(*args
, **kwargs
) 
 242         for k
, v 
in el
.items(): 
 243             if isinstance(v
, bytes): 
 244                 el
.set(k
, v
.decode('utf-8')) 
 247     def compat_etree_fromstring(text
): 
 248         doc 
= _XML(text
, parser
=etree
.XMLParser(target
=etree
.TreeBuilder(element_factory
=_element_factory
))) 
 249         for el 
in _etree_iter(doc
): 
 250             if el
.text 
is not None and isinstance(el
.text
, bytes): 
 251                 el
.text 
= el
.text
.decode('utf-8') 
 255     from urllib
.parse 
import parse_qs 
as compat_parse_qs
 
 256 except ImportError:  # Python 2 
 257     # HACK: The following is the correct parse_qs implementation from cpython 3's stdlib. 
 258     # Python 2's version is apparently totally broken 
 260     def _parse_qsl(qs
, keep_blank_values
=False, strict_parsing
=False, 
 261                    encoding
='utf-8', errors
='replace'): 
 262         qs
, _coerce_result 
= qs
, compat_str
 
 263         pairs 
= [s2 
for s1 
in qs
.split('&') for s2 
in s1
.split(';')] 
 265         for name_value 
in pairs
: 
 266             if not name_value 
and not strict_parsing
: 
 268             nv 
= name_value
.split('=', 1) 
 271                     raise ValueError("bad query field: %r" % (name_value
,)) 
 272                 # Handle case of a control-name with no equal sign 
 273                 if keep_blank_values
: 
 277             if len(nv
[1]) or keep_blank_values
: 
 278                 name 
= nv
[0].replace('+', ' ') 
 279                 name 
= compat_urllib_parse_unquote( 
 280                     name
, encoding
=encoding
, errors
=errors
) 
 281                 name 
= _coerce_result(name
) 
 282                 value 
= nv
[1].replace('+', ' ') 
 283                 value 
= compat_urllib_parse_unquote( 
 284                     value
, encoding
=encoding
, errors
=errors
) 
 285                 value 
= _coerce_result(value
) 
 286                 r
.append((name
, value
)) 
 289     def compat_parse_qs(qs
, keep_blank_values
=False, strict_parsing
=False, 
 290                         encoding
='utf-8', errors
='replace'): 
 292         pairs 
= _parse_qsl(qs
, keep_blank_values
, strict_parsing
, 
 293                            encoding
=encoding
, errors
=errors
) 
 294         for name
, value 
in pairs
: 
 295             if name 
in parsed_result
: 
 296                 parsed_result
[name
].append(value
) 
 298                 parsed_result
[name
] = [value
] 
 302     from shlex 
import quote 
as shlex_quote
 
 303 except ImportError:  # Python < 3.3 
 305         if re
.match(r
'^[-_\w./]+$', s
): 
 308             return "'" + s
.replace("'", "'\"'\"'") + "'" 
 311 if sys
.version_info 
>= (2, 7, 3): 
 312     compat_shlex_split 
= shlex
.split
 
 314     # Working around shlex issue with unicode strings on some python 2 
 315     # versions (see http://bugs.python.org/issue1548891) 
 316     def compat_shlex_split(s
, comments
=False, posix
=True): 
 317         if isinstance(s
, compat_str
): 
 318             s 
= s
.encode('utf-8') 
 319         return shlex
.split(s
, comments
, posix
) 
 329 if sys
.version_info 
>= (3, 0): 
 330     compat_getenv 
= os
.getenv
 
 331     compat_expanduser 
= os
.path
.expanduser
 
 333     # Environment variables should be decoded with filesystem encoding. 
 334     # Otherwise it will fail if any non-ASCII characters present (see #3854 #3217 #2918) 
 336     def compat_getenv(key
, default
=None): 
 337         from .utils 
import get_filesystem_encoding
 
 338         env 
= os
.getenv(key
, default
) 
 340             env 
= env
.decode(get_filesystem_encoding()) 
 343     # HACK: The default implementations of os.path.expanduser from cpython do not decode 
 344     # environment variables with filesystem encoding. We will work around this by 
 345     # providing adjusted implementations. 
 346     # The following are os.path.expanduser implementations from cpython 2.7.8 stdlib 
 347     # for different platforms with correct environment variables decoding. 
 349     if os
.name 
== 'posix': 
 350         def compat_expanduser(path
): 
 351             """Expand ~ and ~user constructions.  If user or $HOME is unknown, 
 353             if not path
.startswith('~'): 
 355             i 
= path
.find('/', 1) 
 359                 if 'HOME' not in os
.environ
: 
 361                     userhome 
= pwd
.getpwuid(os
.getuid()).pw_dir
 
 363                     userhome 
= compat_getenv('HOME') 
 367                     pwent 
= pwd
.getpwnam(path
[1:i
]) 
 370                 userhome 
= pwent
.pw_dir
 
 371             userhome 
= userhome
.rstrip('/') 
 372             return (userhome 
+ path
[i
:]) or '/' 
 373     elif os
.name 
== 'nt' or os
.name 
== 'ce': 
 374         def compat_expanduser(path
): 
 375             """Expand ~ and ~user constructs. 
 377             If user or $HOME is unknown, do nothing.""" 
 381             while i 
< n 
and path
[i
] not in '/\\': 
 384             if 'HOME' in os
.environ
: 
 385                 userhome 
= compat_getenv('HOME') 
 386             elif 'USERPROFILE' in os
.environ
: 
 387                 userhome 
= compat_getenv('USERPROFILE') 
 388             elif 'HOMEPATH' not in os
.environ
: 
 392                     drive 
= compat_getenv('HOMEDRIVE') 
 395                 userhome 
= os
.path
.join(drive
, compat_getenv('HOMEPATH')) 
 398                 userhome 
= os
.path
.join(os
.path
.dirname(userhome
), path
[1:i
]) 
 400             return userhome 
+ path
[i
:] 
 402         compat_expanduser 
= os
.path
.expanduser
 
 405 if sys
.version_info 
< (3, 0): 
 407         from .utils 
import preferredencoding
 
 408         print(s
.encode(preferredencoding(), 'xmlcharrefreplace')) 
 411         assert isinstance(s
, compat_str
) 
 416     subprocess_check_output 
= subprocess
.check_output
 
 417 except AttributeError: 
 418     def subprocess_check_output(*args
, **kwargs
): 
 419         assert 'input' not in kwargs
 
 420         p 
= subprocess
.Popen(*args
, stdout
=subprocess
.PIPE
, **kwargs
) 
 421         output
, _ 
= p
.communicate() 
 424             raise subprocess
.CalledProcessError(ret
, p
.args
, output
=output
) 
 427 if sys
.version_info 
< (3, 0) and sys
.platform 
== 'win32': 
 428     def compat_getpass(prompt
, *args
, **kwargs
): 
 429         if isinstance(prompt
, compat_str
): 
 430             from .utils 
import preferredencoding
 
 431             prompt 
= prompt
.encode(preferredencoding()) 
 432         return getpass
.getpass(prompt
, *args
, **kwargs
) 
 434     compat_getpass 
= getpass
.getpass
 
 436 # Old 2.6 and 2.7 releases require kwargs to be bytes 
 440     _testfunc(**{'x': 0}) 
 442     def compat_kwargs(kwargs
): 
 443         return dict((bytes(k
), v
) for k
, v 
in kwargs
.items()) 
 445     compat_kwargs 
= lambda kwargs
: kwargs
 
 448 if sys
.version_info 
< (2, 7): 
 449     def compat_socket_create_connection(address
, timeout
, source_address
=None): 
 452         for res 
in socket
.getaddrinfo(host
, port
, 0, socket
.SOCK_STREAM
): 
 453             af
, socktype
, proto
, canonname
, sa 
= res
 
 456                 sock 
= socket
.socket(af
, socktype
, proto
) 
 457                 sock
.settimeout(timeout
) 
 459                     sock
.bind(source_address
) 
 462             except socket
.error 
as _
: 
 469             raise socket
.error("getaddrinfo returns an empty list") 
 471     compat_socket_create_connection 
= socket
.create_connection
 
 474 # Fix https://github.com/rg3/youtube-dl/issues/4223 
 475 # See http://bugs.python.org/issue9161 for what is broken 
 476 def workaround_optparse_bug9161(): 
 477     op 
= optparse
.OptionParser() 
 478     og 
= optparse
.OptionGroup(op
, 'foo') 
 482         real_add_option 
= optparse
.OptionGroup
.add_option
 
 484         def _compat_add_option(self
, *args
, **kwargs
): 
 486                 v
.encode('ascii', 'replace') if isinstance(v
, compat_str
) 
 488             bargs 
= [enc(a
) for a 
in args
] 
 490                 (k
, enc(v
)) for k
, v 
in kwargs
.items()) 
 491             return real_add_option(self
, *bargs
, **bkwargs
) 
 492         optparse
.OptionGroup
.add_option 
= _compat_add_option
 
 494 if hasattr(shutil
, 'get_terminal_size'):  # Python >= 3.3 
 495     compat_get_terminal_size 
= shutil
.get_terminal_size
 
 497     _terminal_size 
= collections
.namedtuple('terminal_size', ['columns', 'lines']) 
 499     def compat_get_terminal_size(fallback
=(80, 24)): 
 500         columns 
= compat_getenv('COLUMNS') 
 502             columns 
= int(columns
) 
 505         lines 
= compat_getenv('LINES') 
 511         if columns 
is None or lines 
is None or columns 
<= 0 or lines 
<= 0: 
 513                 sp 
= subprocess
.Popen( 
 515                     stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
) 
 516                 out
, err 
= sp
.communicate() 
 517                 _lines
, _columns 
= map(int, out
.split()) 
 519                 _columns
, _lines 
= _terminal_size(*fallback
) 
 521             if columns 
is None or columns 
<= 0: 
 523             if lines 
is None or lines 
<= 0: 
 525         return _terminal_size(columns
, lines
) 
 528     itertools
.count(start
=0, step
=1) 
 529     compat_itertools_count 
= itertools
.count
 
 530 except TypeError:  # Python 2.6 
 531     def compat_itertools_count(start
=0, step
=1): 
 537 if sys
.version_info 
>= (3, 0): 
 538     from tokenize 
import tokenize 
as compat_tokenize_tokenize
 
 540     from tokenize 
import generate_tokens 
as compat_tokenize_tokenize
 
 548     'compat_etree_fromstring', 
 550     'compat_get_terminal_size', 
 553     'compat_html_entities', 
 554     'compat_http_client', 
 555     'compat_http_server', 
 556     'compat_itertools_count', 
 561     'compat_shlex_split', 
 562     'compat_socket_create_connection', 
 564     'compat_subprocess_get_DEVNULL', 
 565     'compat_tokenize_tokenize', 
 566     'compat_urllib_error', 
 567     'compat_urllib_parse', 
 568     'compat_urllib_parse_unquote', 
 569     'compat_urllib_parse_unquote_plus', 
 570     'compat_urllib_parse_unquote_to_bytes', 
 571     'compat_urllib_parse_urlparse', 
 572     'compat_urllib_request', 
 573     'compat_urllib_request_DataHandler', 
 574     'compat_urllib_response', 
 576     'compat_urlretrieve', 
 577     'compat_xml_parse_error', 
 579     'subprocess_check_output', 
 580     'workaround_optparse_bug9161',