mirror of
				https://github.com/ytdl-org/youtube-dl.git
				synced 2025-10-29 09:26:20 -07:00 
			
		
		
		
	Add new option --source-address
Closes #3618, fixes #721, fixes #2481, fixes #4551, closes #1020.
This commit is contained in:
		| @@ -211,6 +211,7 @@ class YoutubeDL(object): | |||||||
|                        - "warn": only emit a warning |                        - "warn": only emit a warning | ||||||
|                        - "detect_or_warn": check whether we can do anything |                        - "detect_or_warn": check whether we can do anything | ||||||
|                                            about it, warn otherwise |                                            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 |     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) |         proxy_handler = compat_urllib_request.ProxyHandler(proxies) | ||||||
|  |  | ||||||
|         debuglevel = 1 if self.params.get('debug_printtraffic') else 0 |         debuglevel = 1 if self.params.get('debug_printtraffic') else 0 | ||||||
|         https_handler = make_HTTPS_handler( |         https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel) | ||||||
|             self.params.get('nocheckcertificate', False), debuglevel=debuglevel) |         ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel) | ||||||
|         ydlh = YoutubeDLHandler(debuglevel=debuglevel) |  | ||||||
|         opener = compat_urllib_request.build_opener( |         opener = compat_urllib_request.build_opener( | ||||||
|             https_handler, proxy_handler, cookie_processor, ydlh) |             https_handler, proxy_handler, cookie_processor, ydlh) | ||||||
|         # Delete the default user-agent header, which would otherwise apply in |         # Delete the default user-agent header, which would otherwise apply in | ||||||
|   | |||||||
| @@ -327,6 +327,7 @@ def _real_main(argv=None): | |||||||
|         'merge_output_format': opts.merge_output_format, |         'merge_output_format': opts.merge_output_format, | ||||||
|         'postprocessors': postprocessors, |         'postprocessors': postprocessors, | ||||||
|         'fixup': opts.fixup, |         'fixup': opts.fixup, | ||||||
|  |         'source_address': opts.source_address, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     with YoutubeDL(ydl_opts) as ydl: |     with YoutubeDL(ydl_opts) as ydl: | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import getpass | |||||||
| import optparse | import optparse | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
|  | import socket | ||||||
| import subprocess | import subprocess | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| @@ -307,6 +308,32 @@ else: | |||||||
|     compat_kwargs = lambda kwargs: kwargs |     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 | # Fix https://github.com/rg3/youtube-dl/issues/4223 | ||||||
| # See http://bugs.python.org/issue9161 for what is broken | # See http://bugs.python.org/issue9161 for what is broken | ||||||
| def workaround_optparse_bug9161(): | def workaround_optparse_bug9161(): | ||||||
| @@ -343,6 +370,7 @@ __all__ = [ | |||||||
|     'compat_parse_qs', |     'compat_parse_qs', | ||||||
|     'compat_print', |     'compat_print', | ||||||
|     'compat_str', |     'compat_str', | ||||||
|  |     'compat_socket_create_connection', | ||||||
|     'compat_subprocess_get_DEVNULL', |     'compat_subprocess_get_DEVNULL', | ||||||
|     'compat_urllib_error', |     'compat_urllib_error', | ||||||
|     'compat_urllib_parse', |     'compat_urllib_parse', | ||||||
|   | |||||||
| @@ -148,14 +148,6 @@ def parseOpts(overrideArguments=None): | |||||||
|         '--extractor-descriptions', |         '--extractor-descriptions', | ||||||
|         action='store_true', dest='list_extractor_descriptions', default=False, |         action='store_true', dest='list_extractor_descriptions', default=False, | ||||||
|         help='Output descriptions of all supported extractors') |         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( |     general.add_option( | ||||||
|         '--default-search', |         '--default-search', | ||||||
|         dest='default_search', metavar='PREFIX', |         dest='default_search', metavar='PREFIX', | ||||||
| @@ -173,6 +165,21 @@ def parseOpts(overrideArguments=None): | |||||||
|         default=False, |         default=False, | ||||||
|         help='Do not extract the videos of a playlist, only list them.') |         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)', | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     selection = optparse.OptionGroup(parser, 'Video Selection') |     selection = optparse.OptionGroup(parser, 'Video Selection') | ||||||
|     selection.add_option( |     selection.add_option( | ||||||
|         '--playlist-start', |         '--playlist-start', | ||||||
| @@ -652,6 +659,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 {}\'') |         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(general) | ||||||
|  |     parser.add_option_group(network) | ||||||
|     parser.add_option_group(selection) |     parser.add_option_group(selection) | ||||||
|     parser.add_option_group(downloader) |     parser.add_option_group(downloader) | ||||||
|     parser.add_option_group(filesystem) |     parser.add_option_group(filesystem) | ||||||
|   | |||||||
| @@ -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.') |         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 |         return | ||||||
|  |  | ||||||
|     https_handler = make_HTTPS_handler(False) |     https_handler = make_HTTPS_handler({}) | ||||||
|     opener = compat_urllib_request.build_opener(https_handler) |     opener = compat_urllib_request.build_opener(https_handler) | ||||||
|  |  | ||||||
|     # Check if there is a new version |     # Check if there is a new version | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ctypes | |||||||
| import datetime | import datetime | ||||||
| import email.utils | import email.utils | ||||||
| import errno | import errno | ||||||
|  | import functools | ||||||
| import gzip | import gzip | ||||||
| import itertools | import itertools | ||||||
| import io | import io | ||||||
| @@ -34,7 +35,9 @@ from .compat import ( | |||||||
|     compat_chr, |     compat_chr, | ||||||
|     compat_getenv, |     compat_getenv, | ||||||
|     compat_html_entities, |     compat_html_entities, | ||||||
|  |     compat_http_client, | ||||||
|     compat_parse_qs, |     compat_parse_qs, | ||||||
|  |     compat_socket_create_connection, | ||||||
|     compat_str, |     compat_str, | ||||||
|     compat_urllib_error, |     compat_urllib_error, | ||||||
|     compat_urllib_parse, |     compat_urllib_parse, | ||||||
| @@ -391,13 +394,14 @@ def formatSeconds(secs): | |||||||
|         return '%d' % 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 |     if hasattr(ssl, 'create_default_context'):  # Python >= 3.4 or 2.7.9 | ||||||
|         context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) |         context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) | ||||||
|         if opts_no_check_certificate: |         if opts_no_check_certificate: | ||||||
|             context.verify_mode = ssl.CERT_NONE |             context.verify_mode = ssl.CERT_NONE | ||||||
|         try: |         try: | ||||||
|             return compat_urllib_request.HTTPSHandler(context=context, **kwargs) |             return YoutubeDLHTTPSHandler(params, context=context, **kwargs) | ||||||
|         except TypeError: |         except TypeError: | ||||||
|             # Python 2.7.8 |             # Python 2.7.8 | ||||||
|             # (create_default_context present but HTTPSHandler has no context=) |             # (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: |                 except ssl.SSLError: | ||||||
|                     self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23) |                     self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23) | ||||||
|  |  | ||||||
|         class HTTPSHandlerV3(compat_urllib_request.HTTPSHandler): |         return YoutubeDLHTTPSHandler(params, https_conn_class=HTTPSConnectionV3, **kwargs) | ||||||
|             def https_open(self, req): |  | ||||||
|                 return self.do_open(HTTPSConnectionV3, req) |  | ||||||
|         return HTTPSHandlerV3(**kwargs) |  | ||||||
|     else:  # Python < 3.4 |     else:  # Python < 3.4 | ||||||
|         context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) |         context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) | ||||||
|         context.verify_mode = (ssl.CERT_NONE |         context.verify_mode = (ssl.CERT_NONE | ||||||
|                                if opts_no_check_certificate |                                if opts_no_check_certificate | ||||||
|                                else ssl.CERT_REQUIRED) |                                else ssl.CERT_REQUIRED) | ||||||
|         context.set_default_verify_paths() |         context.set_default_verify_paths() | ||||||
|         return compat_urllib_request.HTTPSHandler(context=context, **kwargs) |         return YoutubeDLHTTPSHandler(params, context=context, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ExtractorError(Exception): | class ExtractorError(Exception): | ||||||
| @@ -544,6 +545,26 @@ class ContentTooShortError(Exception): | |||||||
|         self.expected = expected |         self.expected = expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _create_http_connection(ydl_handler, http_class, is_https=False, *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): | class YoutubeDLHandler(compat_urllib_request.HTTPHandler): | ||||||
|     """Handler for HTTP requests and responses. |     """Handler for HTTP requests and responses. | ||||||
|  |  | ||||||
| @@ -562,6 +583,15 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler): | |||||||
|     public domain. |     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), | ||||||
|  |             req) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def deflate(data): |     def deflate(data): | ||||||
|         try: |         try: | ||||||
| @@ -631,6 +661,18 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler): | |||||||
|     https_response = http_response |     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'): | def parse_iso8601(date_str, delimiter='T'): | ||||||
|     """ Return a UNIX timestamp from the given date """ |     """ Return a UNIX timestamp from the given date """ | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user