Add new option --source-address
Closes #3618, fixes #721, fixes #2481, fixes #4551, closes #1020.
This commit is contained in:
parent
6ce08764a1
commit
be4a824d74
6 changed files with 98 additions and 19 deletions
|
@ -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 """
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue