diff options
| -rwxr-xr-x | youtube_dl/YoutubeDL.py | 6 | ||||
| -rw-r--r-- | youtube_dl/__init__.py | 1 | ||||
| -rw-r--r-- | youtube_dl/compat.py | 28 | ||||
| -rw-r--r-- | youtube_dl/options.py | 34 | ||||
| -rw-r--r-- | youtube_dl/update.py | 2 | ||||
| -rw-r--r-- | youtube_dl/utils.py | 56 | 
6 files changed, 108 insertions, 19 deletions
diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 4cc3ec2fb..fd2c0e044 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -211,6 +211,7 @@ class YoutubeDL(object):                         - "warn": only emit a warning                         - "detect_or_warn": check whether we can do anything                                             about it, warn otherwise +    source_address:    (Experimental) Client-side IP address to bind to.      The following parameters are not used by YoutubeDL itself, they are used by @@ -1493,9 +1494,8 @@ class YoutubeDL(object):          proxy_handler = compat_urllib_request.ProxyHandler(proxies)          debuglevel = 1 if self.params.get('debug_printtraffic') else 0 -        https_handler = make_HTTPS_handler( -            self.params.get('nocheckcertificate', False), debuglevel=debuglevel) -        ydlh = YoutubeDLHandler(debuglevel=debuglevel) +        https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel) +        ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)          opener = compat_urllib_request.build_opener(              https_handler, proxy_handler, cookie_processor, ydlh)          # Delete the default user-agent header, which would otherwise apply in diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 659a92a3b..d74a304b7 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -327,6 +327,7 @@ def _real_main(argv=None):          'merge_output_format': opts.merge_output_format,          'postprocessors': postprocessors,          'fixup': opts.fixup, +        'source_address': opts.source_address,      }      with YoutubeDL(ydl_opts) as ydl: diff --git a/youtube_dl/compat.py b/youtube_dl/compat.py index 46d438846..44a902573 100644 --- a/youtube_dl/compat.py +++ b/youtube_dl/compat.py @@ -4,6 +4,7 @@ import getpass  import optparse  import os  import re +import socket  import subprocess  import sys @@ -307,6 +308,32 @@ else:      compat_kwargs = lambda kwargs: kwargs +if sys.version_info < (2, 7): +    def compat_socket_create_connection(address, timeout, source_address=None): +        host, port = address +        err = None +        for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): +            af, socktype, proto, canonname, sa = res +            sock = None +            try: +                sock = socket.socket(af, socktype, proto) +                sock.settimeout(timeout) +                if source_address: +                    sock.bind(source_address) +                sock.connect(sa) +                return sock +            except socket.error as _: +                err = _ +                if sock is not None: +                    sock.close() +        if err is not None: +            raise err +        else: +            raise error("getaddrinfo returns an empty list") +else: +    compat_socket_create_connection = socket.create_connection + +  # Fix https://github.com/rg3/youtube-dl/issues/4223  # See http://bugs.python.org/issue9161 for what is broken  def workaround_optparse_bug9161(): @@ -343,6 +370,7 @@ __all__ = [      'compat_parse_qs',      'compat_print',      'compat_str', +    'compat_socket_create_connection',      'compat_subprocess_get_DEVNULL',      'compat_urllib_error',      'compat_urllib_parse', diff --git a/youtube_dl/options.py b/youtube_dl/options.py index e5602bb3a..e25466c39 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -149,14 +149,6 @@ def parseOpts(overrideArguments=None):          action='store_true', dest='list_extractor_descriptions', default=False,          help='Output descriptions of all supported extractors')      general.add_option( -        '--proxy', dest='proxy', -        default=None, metavar='URL', -        help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection') -    general.add_option( -        '--socket-timeout', -        dest='socket_timeout', type=float, default=None, -        help='Time to wait before giving up, in seconds') -    general.add_option(          '--default-search',          dest='default_search', metavar='PREFIX',          help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for  youtube-dl "large apple". Use the value "auto" to let youtube-dl guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.') @@ -173,6 +165,31 @@ def parseOpts(overrideArguments=None):          default=False,          help='Do not extract the videos of a playlist, only list them.') +    network = optparse.OptionGroup(parser, 'Network Options') +    network.add_option( +        '--proxy', dest='proxy', +        default=None, metavar='URL', +        help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection') +    network.add_option( +        '--socket-timeout', +        dest='socket_timeout', type=float, default=None, metavar='SECONDS', +        help='Time to wait before giving up, in seconds') +    network.add_option( +        '--source-address', +        metavar='IP', dest='source_address', default=None, +        help='Client-side IP address to bind to (experimental)', +    ) +    network.add_option( +        '-4', '--force-ipv4', +        action='store_const', const='0.0.0.0', dest='source_address', +        help='Make all connections via IPv4 (experimental)', +    ) +    network.add_option( +        '-6', '--force-ipv6', +        action='store_const', const='::', dest='source_address', +        help='Make all connections via IPv6 (experimental)', +    ) +      selection = optparse.OptionGroup(parser, 'Video Selection')      selection.add_option(          '--playlist-start', @@ -652,6 +669,7 @@ def parseOpts(overrideArguments=None):          help='Execute a command on the file after downloading, similar to find\'s -exec syntax. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'')      parser.add_option_group(general) +    parser.add_option_group(network)      parser.add_option_group(selection)      parser.add_option_group(downloader)      parser.add_option_group(filesystem) diff --git a/youtube_dl/update.py b/youtube_dl/update.py index 3f9c5249d..d8be4049f 100644 --- a/youtube_dl/update.py +++ b/youtube_dl/update.py @@ -59,7 +59,7 @@ def update_self(to_screen, verbose):          to_screen('It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.')          return -    https_handler = make_HTTPS_handler(False) +    https_handler = make_HTTPS_handler({})      opener = compat_urllib_request.build_opener(https_handler)      # Check if there is a new version diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index a12b0a7de..42f0b07ce 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -10,6 +10,7 @@ import ctypes  import datetime  import email.utils  import errno +import functools  import gzip  import itertools  import io @@ -34,7 +35,9 @@ from .compat import (      compat_chr,      compat_getenv,      compat_html_entities, +    compat_http_client,      compat_parse_qs, +    compat_socket_create_connection,      compat_str,      compat_urllib_error,      compat_urllib_parse, @@ -391,13 +394,14 @@ def formatSeconds(secs):          return '%d' % secs -def make_HTTPS_handler(opts_no_check_certificate, **kwargs): +def make_HTTPS_handler(params, **kwargs): +    opts_no_check_certificate = params.get('nocheckcertificate', False)      if hasattr(ssl, 'create_default_context'):  # Python >= 3.4 or 2.7.9          context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)          if opts_no_check_certificate:              context.verify_mode = ssl.CERT_NONE          try: -            return compat_urllib_request.HTTPSHandler(context=context, **kwargs) +            return YoutubeDLHTTPSHandler(params, context=context, **kwargs)          except TypeError:              # Python 2.7.8              # (create_default_context present but HTTPSHandler has no context=) @@ -420,17 +424,14 @@ def make_HTTPS_handler(opts_no_check_certificate, **kwargs):                  except ssl.SSLError:                      self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23) -        class HTTPSHandlerV3(compat_urllib_request.HTTPSHandler): -            def https_open(self, req): -                return self.do_open(HTTPSConnectionV3, req) -        return HTTPSHandlerV3(**kwargs) +        return YoutubeDLHTTPSHandler(params, https_conn_class=HTTPSConnectionV3, **kwargs)      else:  # Python < 3.4          context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)          context.verify_mode = (ssl.CERT_NONE                                 if opts_no_check_certificate                                 else ssl.CERT_REQUIRED)          context.set_default_verify_paths() -        return compat_urllib_request.HTTPSHandler(context=context, **kwargs) +        return YoutubeDLHTTPSHandler(params, context=context, **kwargs)  class ExtractorError(Exception): @@ -544,6 +545,26 @@ class ContentTooShortError(Exception):          self.expected = expected +def _create_http_connection(ydl_handler, http_class, is_https, *args, **kwargs): +    hc = http_class(*args, **kwargs) +    source_address = ydl_handler._params.get('source_address') +    if source_address is not None: +        sa = (source_address, 0) +        if hasattr(hc, 'source_address'):  # Python 2.7+ +            hc.source_address = sa +        else:  # Python 2.6 +            def _hc_connect(self, *args, **kwargs): +                sock = compat_socket_create_connection( +                    (self.host, self.port), self.timeout, sa) +                if is_https: +                    self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file) +                else: +                    self.sock = sock +            hc.connect = functools.partial(_hc_connect, hc) + +    return hc + +  class YoutubeDLHandler(compat_urllib_request.HTTPHandler):      """Handler for HTTP requests and responses. @@ -562,6 +583,15 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):      public domain.      """ +    def __init__(self, params, *args, **kwargs): +        compat_urllib_request.HTTPHandler.__init__(self, *args, **kwargs) +        self._params = params + +    def http_open(self, req): +        return self.do_open(functools.partial( +            _create_http_connection, self, compat_http_client.HTTPConnection, False), +            req) +      @staticmethod      def deflate(data):          try: @@ -631,6 +661,18 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):      https_response = http_response +class YoutubeDLHTTPSHandler(compat_urllib_request.HTTPSHandler): +    def __init__(self, params, https_conn_class=None, *args, **kwargs): +        compat_urllib_request.HTTPSHandler.__init__(self, *args, **kwargs) +        self._https_conn_class = https_conn_class or compat_http_client.HTTPSConnection +        self._params = params + +    def https_open(self, req): +        return self.do_open(functools.partial( +            _create_http_connection, self, self._https_conn_class, True), +            req) + +  def parse_iso8601(date_str, delimiter='T'):      """ Return a UNIX timestamp from the given date """  | 
