Compare commits
16 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a9ff01123 |
||
|
|
9c7f1f9f6d |
||
|
|
b860ff73b7 | ||
|
|
8eacb27463 | ||
|
|
b32c6119e7 | ||
|
|
58c7b223ae | ||
|
|
6b9deb5086 | ||
|
|
0b3bfa128f | ||
|
|
bb236482e6 | ||
|
|
9690485ec7 | ||
|
|
699b594e50 | ||
|
|
50a4a661a1 | ||
|
|
429a67881c | ||
|
|
c070263390 | ||
|
|
64d87fdda4 | ||
|
|
3093ca0390 |
8 changed files with 564 additions and 156 deletions
21
.gitignore
vendored
21
.gitignore
vendored
|
|
@ -53,24 +53,3 @@
|
|||
/yt-dlp-2025.02.19.tar.gz
|
||||
/yt-dlp-2025.03.26.tar.gz
|
||||
/yt-dlp-2025.03.31.tar.gz
|
||||
/yt-dlp-2025.04.30.tar.gz
|
||||
/yt-dlp-2025.05.22.tar.gz
|
||||
/yt-dlp-2025.06.09.tar.gz
|
||||
/yt-dlp-2025.06.25.tar.gz
|
||||
/yt-dlp-2025.06.30.tar.gz
|
||||
/yt-dlp-2025.07.21.tar.gz
|
||||
/yt-dlp-2025.08.11.tar.gz
|
||||
/yt-dlp-2025.08.22.tar.gz
|
||||
/yt-dlp-2025.08.27.tar.gz
|
||||
/yt-dlp-2025.09.05.tar.gz
|
||||
/yt-dlp-2025.09.26.tar.gz
|
||||
/yt-dlp-2025.10.22.tar.gz
|
||||
/yt-dlp-2025.12.08.tar.gz
|
||||
/yt-dlp-2026.01.29.tar.gz
|
||||
/yt-dlp-2026.02.04.tar.gz
|
||||
/yt-dlp-2026.02.21.tar.gz
|
||||
/yt-dlp-2026.03.03.tar.gz
|
||||
/yt-dlp-2026.03.13.tar.gz
|
||||
/yt-dlp-2026.03.17.tar.gz
|
||||
/yt-dlp-2026.06.09.tar.gz
|
||||
/yt-dlp-2026.08.19.tar.gz
|
||||
|
|
|
|||
119
0001-Revert-rh-websockets-Upgrade-websockets-to-13.0-1081.patch
Normal file
119
0001-Revert-rh-websockets-Upgrade-websockets-to-13.0-1081.patch
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
From fbc486f4fac78bfd49011c1b60362e656c679c71 Mon Sep 17 00:00:00 2001
|
||||
From: Maxwell G <maxwell@gtmx.me>
|
||||
Date: Mon, 6 Jan 2025 14:37:58 -0600
|
||||
Subject: [PATCH 1/2] Revert "[rh:websockets] Upgrade websockets to 13.0
|
||||
(#10815)"
|
||||
|
||||
This reverts commit 6f9e6537434562d513d0c9b68ced8a61ade94a64.
|
||||
---
|
||||
.github/workflows/core.yml | 1 -
|
||||
.github/workflows/quick-test.yml | 1 -
|
||||
pyproject.toml | 2 +-
|
||||
test/test_websockets.py | 8 ++++----
|
||||
yt_dlp/networking/_websockets.py | 5 ++++-
|
||||
5 files changed, 9 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
|
||||
index 9a4342a58..bed3af94b 100644
|
||||
--- a/.github/workflows/core.yml
|
||||
+++ b/.github/workflows/core.yml
|
||||
@@ -59,7 +59,6 @@ jobs:
|
||||
- name: Install test requirements
|
||||
run: python3 ./devscripts/install_deps.py --include test --include curl-cffi
|
||||
- name: Run tests
|
||||
- timeout-minutes: 15
|
||||
continue-on-error: False
|
||||
run: |
|
||||
python3 -m yt_dlp -v || true # Print debug head
|
||||
diff --git a/.github/workflows/quick-test.yml b/.github/workflows/quick-test.yml
|
||||
index 1a32bbfe3..235a2c64d 100644
|
||||
--- a/.github/workflows/quick-test.yml
|
||||
+++ b/.github/workflows/quick-test.yml
|
||||
@@ -17,7 +17,6 @@ jobs:
|
||||
- name: Install test requirements
|
||||
run: python3 ./devscripts/install_deps.py -o --include test
|
||||
- name: Run tests
|
||||
- timeout-minutes: 15
|
||||
run: |
|
||||
python3 -m yt_dlp -v || true
|
||||
python3 ./devscripts/run_tests.py --pytest-args '--reruns 2 --reruns-delay 3.0' core
|
||||
diff --git a/pyproject.toml b/pyproject.toml
|
||||
index 5e987a6fd..6810415d1 100644
|
||||
--- a/pyproject.toml
|
||||
+++ b/pyproject.toml
|
||||
@@ -52,7 +52,7 @@ default = [
|
||||
"pycryptodomex",
|
||||
"requests>=2.32.2,<3",
|
||||
"urllib3>=1.26.17,<3",
|
||||
- "websockets>=13.0",
|
||||
+ "websockets>=12.0",
|
||||
]
|
||||
curl-cffi = [
|
||||
"curl-cffi>=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.11; implementation_name=='cpython'",
|
||||
diff --git a/test/test_websockets.py b/test/test_websockets.py
|
||||
index dead5fe5c..5e0ce48cc 100644
|
||||
--- a/test/test_websockets.py
|
||||
+++ b/test/test_websockets.py
|
||||
@@ -88,7 +88,7 @@ def create_wss_websocket_server():
|
||||
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
||||
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
sslctx.load_cert_chain(certfn, None)
|
||||
- return create_websocket_server(ssl=sslctx)
|
||||
+ return create_websocket_server(ssl_context=sslctx)
|
||||
|
||||
|
||||
MTLS_CERT_DIR = os.path.join(TEST_DIR, 'testdata', 'certificate')
|
||||
@@ -103,7 +103,7 @@ def create_mtls_wss_websocket_server():
|
||||
sslctx.load_verify_locations(cafile=cacertfn)
|
||||
sslctx.load_cert_chain(certfn, None)
|
||||
|
||||
- return create_websocket_server(ssl=sslctx)
|
||||
+ return create_websocket_server(ssl_context=sslctx)
|
||||
|
||||
|
||||
def create_legacy_wss_websocket_server():
|
||||
@@ -112,7 +112,7 @@ def create_legacy_wss_websocket_server():
|
||||
sslctx.maximum_version = ssl.TLSVersion.TLSv1_2
|
||||
sslctx.set_ciphers('SHA1:AESCCM:aDSS:eNULL:aNULL')
|
||||
sslctx.load_cert_chain(certfn, None)
|
||||
- return create_websocket_server(ssl=sslctx)
|
||||
+ return create_websocket_server(ssl_context=sslctx)
|
||||
|
||||
|
||||
def ws_validate_and_send(rh, req):
|
||||
@@ -139,7 +139,7 @@ def setup_class(cls):
|
||||
cls.wss_thread, cls.wss_port = create_wss_websocket_server()
|
||||
cls.wss_base_url = f'wss://127.0.0.1:{cls.wss_port}'
|
||||
|
||||
- cls.bad_wss_thread, cls.bad_wss_port = create_websocket_server(ssl=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER))
|
||||
+ cls.bad_wss_thread, cls.bad_wss_port = create_websocket_server(ssl_context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER))
|
||||
cls.bad_wss_host = f'wss://127.0.0.1:{cls.bad_wss_port}'
|
||||
|
||||
cls.mtls_wss_thread, cls.mtls_wss_port = create_mtls_wss_websocket_server()
|
||||
diff --git a/yt_dlp/networking/_websockets.py b/yt_dlp/networking/_websockets.py
|
||||
index d29f8e45a..a2bdb9c82 100644
|
||||
--- a/yt_dlp/networking/_websockets.py
|
||||
+++ b/yt_dlp/networking/_websockets.py
|
||||
@@ -48,7 +48,10 @@
|
||||
# 2: "AttributeError: 'ClientConnection' object has no attribute 'recv_events_exc'. Did you mean: 'recv_events'?"
|
||||
import websockets.sync.connection # isort: split
|
||||
with contextlib.suppress(Exception):
|
||||
+ # > 12.0
|
||||
websockets.sync.connection.Connection.recv_exc = None
|
||||
+ # 12.0
|
||||
+ websockets.sync.connection.Connection.recv_events_exc = None
|
||||
|
||||
|
||||
class WebsocketsResponseAdapter(WebSocketResponse):
|
||||
@@ -162,7 +165,7 @@ def _send(self, request):
|
||||
additional_headers=headers,
|
||||
open_timeout=timeout,
|
||||
user_agent_header=None,
|
||||
- ssl=ssl_ctx if wsuri.secure else None,
|
||||
+ ssl_context=ssl_ctx if wsuri.secure else None,
|
||||
close_timeout=0, # not ideal, but prevents yt-dlp hanging
|
||||
)
|
||||
return WebsocketsResponseAdapter(conn, url=request.url)
|
||||
--
|
||||
2.48.1
|
||||
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
From b958f102848b73edeec7bb92fff8d4d002ae4b23 Mon Sep 17 00:00:00 2001
|
||||
From: Maxwell G <maxwell@gtmx.me>
|
||||
Date: Thu, 18 Jun 2026 21:19:45 -0500
|
||||
Subject: [PATCH 2/2] Restore compatibility with pytest < 9
|
||||
|
||||
---
|
||||
pyproject.toml | 4 +---
|
||||
test/test_downloader_external.py | 2 +-
|
||||
2 files changed, 2 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/pyproject.toml b/pyproject.toml
|
||||
index 8128fb3..8bb3631 100644
|
||||
--- a/pyproject.toml
|
||||
+++ b/pyproject.toml
|
||||
@@ -467,14 +467,12 @@ select = [
|
||||
]
|
||||
exclude = "*/extractor/lazy_extractors.py,*venv*,*/test/testdata/sigs/player-*.js,.idea,.vscode"
|
||||
|
||||
-[tool.pytest]
|
||||
+[tool.pytest.ini_options]
|
||||
addopts = [
|
||||
"-ra", # summary: all except passed
|
||||
"--tb=short",
|
||||
]
|
||||
verbosity_test_cases = "1"
|
||||
-strict_config = true
|
||||
-strict_markers = true
|
||||
markers = [
|
||||
"download",
|
||||
]
|
||||
diff --git a/test/test_downloader_external.py b/test/test_downloader_external.py
|
||||
index 0b51c96..d318d3c 100644
|
||||
--- a/test/test_downloader_external.py
|
||||
+++ b/test/test_downloader_external.py
|
||||
@@ -132,7 +132,7 @@ class TestDownloaderCookieBehavior:
|
||||
pytest.param(WgetFD, marks=pytest.mark.skipif(not WgetFD.available(), reason='wget unavailable')),
|
||||
pytest.param(Aria2cFD, marks=pytest.mark.skipif(not Aria2cFD.available(), reason='aria2c unavailable')),
|
||||
])
|
||||
- def test_cookie_behavior(self, /, downloader_cls):
|
||||
+ def test_cookie_behavior(self, downloader_cls):
|
||||
with FakeYDL() as ydl:
|
||||
downloader = downloader_cls(ydl, {})
|
||||
|
||||
--
|
||||
2.54.0
|
||||
|
||||
369
0002-Revert-rh-requests-Bump-minimum-requests-version-to-.patch
Normal file
369
0002-Revert-rh-requests-Bump-minimum-requests-version-to-.patch
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
From 86b38cb4301ee26dbea2c3ef1865d463aaf67721 Mon Sep 17 00:00:00 2001
|
||||
From: Maxwell G <maxwell@gtmx.me>
|
||||
Date: Mon, 6 Jan 2025 14:48:08 -0600
|
||||
Subject: [PATCH 2/2] Revert "[rh:requests] Bump minimum `requests` version to
|
||||
2.32.2 (#10079)"
|
||||
|
||||
This reverts commit db50f19d76c6870a5a13d0cab9287d684fd7449a.
|
||||
---
|
||||
pyproject.toml | 2 +-
|
||||
yt_dlp/networking/_requests.py | 163 ++++++++++++++++++++++-----------
|
||||
2 files changed, 109 insertions(+), 56 deletions(-)
|
||||
|
||||
diff --git a/pyproject.toml b/pyproject.toml
|
||||
index 6810415d1..2c1d485d4 100644
|
||||
--- a/pyproject.toml
|
||||
+++ b/pyproject.toml
|
||||
@@ -50,7 +50,7 @@ default = [
|
||||
"certifi",
|
||||
"mutagen",
|
||||
"pycryptodomex",
|
||||
- "requests>=2.32.2,<3",
|
||||
+ "requests>=2.31.0,<3",
|
||||
"urllib3>=1.26.17,<3",
|
||||
"websockets>=12.0",
|
||||
]
|
||||
diff --git a/yt_dlp/networking/_requests.py b/yt_dlp/networking/_requests.py
|
||||
index 5b6b264a6..98c755d57 100644
|
||||
--- a/yt_dlp/networking/_requests.py
|
||||
+++ b/yt_dlp/networking/_requests.py
|
||||
@@ -13,20 +13,21 @@
|
||||
from ..utils.networking import normalize_url
|
||||
|
||||
if requests is None:
|
||||
- raise ImportError('requests module is not installed')
|
||||
+ raise ImportError("requests module is not installed")
|
||||
|
||||
if urllib3 is None:
|
||||
- raise ImportError('urllib3 module is not installed')
|
||||
+ raise ImportError("urllib3 module is not installed")
|
||||
|
||||
-urllib3_version = tuple(int_or_none(x, default=0) for x in urllib3.__version__.split('.'))
|
||||
+urllib3_version = tuple(
|
||||
+ int_or_none(x, default=0) for x in urllib3.__version__.split(".")
|
||||
+)
|
||||
|
||||
if urllib3_version < (1, 26, 17):
|
||||
- urllib3._yt_dlp__version = f'{urllib3.__version__} (unsupported)'
|
||||
- raise ImportError('Only urllib3 >= 1.26.17 is supported')
|
||||
+ urllib3._yt_dlp__version = f"{urllib3.__version__} (unsupported)"
|
||||
+ raise ImportError("Only urllib3 >= 1.26.17 is supported")
|
||||
|
||||
-if requests.__build__ < 0x023202:
|
||||
- requests._yt_dlp__version = f'{requests.__version__} (unsupported)'
|
||||
- raise ImportError('Only requests >= 2.32.2 is supported')
|
||||
+if requests.__build__ < 0x023100:
|
||||
+ raise ImportError("Only requests >= 2.31.0 is supported")
|
||||
|
||||
import requests.adapters
|
||||
import requests.utils
|
||||
@@ -62,13 +63,14 @@
|
||||
from ..socks import ProxyError as SocksProxyError
|
||||
|
||||
SUPPORTED_ENCODINGS = [
|
||||
- 'gzip', 'deflate',
|
||||
+ "gzip",
|
||||
+ "deflate",
|
||||
]
|
||||
|
||||
if brotli is not None:
|
||||
- SUPPORTED_ENCODINGS.append('br')
|
||||
+ SUPPORTED_ENCODINGS.append("br")
|
||||
|
||||
-'''
|
||||
+"""
|
||||
Override urllib3's behavior to not convert lower-case percent-encoded characters
|
||||
to upper-case during url normalization process.
|
||||
|
||||
@@ -83,7 +85,7 @@
|
||||
|
||||
1: https://tools.ietf.org/html/rfc3986#section-2.1
|
||||
2: https://github.com/streamlink/streamlink/pull/4003
|
||||
-'''
|
||||
+"""
|
||||
|
||||
|
||||
class Urllib3PercentREOverride:
|
||||
@@ -102,14 +104,19 @@ def subn(self, repl, string, *args, **kwargs):
|
||||
# https://github.com/urllib3/urllib3/commit/a2697e7c6b275f05879b60f593c5854a816489f0
|
||||
import urllib3.util.url
|
||||
|
||||
-if hasattr(urllib3.util.url, 'PERCENT_RE'):
|
||||
+if hasattr(urllib3.util.url, "PERCENT_RE"):
|
||||
urllib3.util.url.PERCENT_RE = Urllib3PercentREOverride(urllib3.util.url.PERCENT_RE)
|
||||
-elif hasattr(urllib3.util.url, '_PERCENT_RE'): # urllib3 >= 2.0.0
|
||||
- urllib3.util.url._PERCENT_RE = Urllib3PercentREOverride(urllib3.util.url._PERCENT_RE)
|
||||
+elif hasattr(urllib3.util.url, "_PERCENT_RE"): # urllib3 >= 2.0.0
|
||||
+ urllib3.util.url._PERCENT_RE = Urllib3PercentREOverride(
|
||||
+ urllib3.util.url._PERCENT_RE
|
||||
+ )
|
||||
else:
|
||||
- warnings.warn('Failed to patch PERCENT_RE in urllib3 (does the attribute exist?)' + bug_reports_message())
|
||||
+ warnings.warn(
|
||||
+ "Failed to patch PERCENT_RE in urllib3 (does the attribute exist?)"
|
||||
+ + bug_reports_message()
|
||||
+ )
|
||||
|
||||
-'''
|
||||
+"""
|
||||
Workaround for issue in urllib.util.ssl_.py: ssl_wrap_context does not pass
|
||||
server_hostname to SSLContext.wrap_socket if server_hostname is an IP,
|
||||
however this is an issue because we set check_hostname to True in our SSLContext.
|
||||
@@ -118,7 +125,7 @@ def subn(self, repl, string, *args, **kwargs):
|
||||
|
||||
This has been fixed in urllib3 2.0+.
|
||||
See: https://github.com/urllib3/urllib3/issues/517
|
||||
-'''
|
||||
+"""
|
||||
|
||||
if urllib3_version < (2, 0, 0):
|
||||
with contextlib.suppress(Exception):
|
||||
@@ -134,8 +141,12 @@ def subn(self, repl, string, *args, **kwargs):
|
||||
class RequestsResponseAdapter(Response):
|
||||
def __init__(self, res: requests.models.Response):
|
||||
super().__init__(
|
||||
- fp=res.raw, headers=res.headers, url=res.url,
|
||||
- status=res.status_code, reason=res.reason)
|
||||
+ fp=res.raw,
|
||||
+ headers=res.headers,
|
||||
+ url=res.url,
|
||||
+ status=res.status_code,
|
||||
+ reason=res.reason,
|
||||
+ )
|
||||
|
||||
self._requests_response = res
|
||||
|
||||
@@ -152,12 +163,21 @@ def read(self, amt: int | None = None):
|
||||
# IncompleteRead is always contained within ProtocolError
|
||||
# See urllib3.response.HTTPResponse._error_catcher()
|
||||
ir_err = next(
|
||||
- (err for err in (e.__context__, e.__cause__, *variadic(e.args))
|
||||
- if isinstance(err, http.client.IncompleteRead)), None)
|
||||
+ (
|
||||
+ err
|
||||
+ for err in (e.__context__, e.__cause__, *variadic(e.args))
|
||||
+ if isinstance(err, http.client.IncompleteRead)
|
||||
+ ),
|
||||
+ None,
|
||||
+ )
|
||||
if ir_err is not None:
|
||||
# `urllib3.exceptions.IncompleteRead` is subclass of `http.client.IncompleteRead`
|
||||
# but uses an `int` for its `partial` property.
|
||||
- partial = ir_err.partial if isinstance(ir_err.partial, int) else len(ir_err.partial)
|
||||
+ partial = (
|
||||
+ ir_err.partial
|
||||
+ if isinstance(ir_err.partial, int)
|
||||
+ else len(ir_err.partial)
|
||||
+ )
|
||||
raise IncompleteRead(partial=partial, expected=ir_err.expected) from e
|
||||
raise TransportError(cause=e) from e
|
||||
|
||||
@@ -167,12 +187,14 @@ def read(self, amt: int | None = None):
|
||||
|
||||
|
||||
class RequestsHTTPAdapter(requests.adapters.HTTPAdapter):
|
||||
- def __init__(self, ssl_context=None, proxy_ssl_context=None, source_address=None, **kwargs):
|
||||
+ def __init__(
|
||||
+ self, ssl_context=None, proxy_ssl_context=None, source_address=None, **kwargs
|
||||
+ ):
|
||||
self._pm_args = {}
|
||||
if ssl_context:
|
||||
- self._pm_args['ssl_context'] = ssl_context
|
||||
+ self._pm_args["ssl_context"] = ssl_context
|
||||
if source_address:
|
||||
- self._pm_args['source_address'] = (source_address, 0)
|
||||
+ self._pm_args["source_address"] = (source_address, 0)
|
||||
self._proxy_ssl_context = proxy_ssl_context or ssl_context
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -181,14 +203,21 @@ def init_poolmanager(self, *args, **kwargs):
|
||||
|
||||
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
||||
extra_kwargs = {}
|
||||
- if not proxy.lower().startswith('socks') and self._proxy_ssl_context:
|
||||
- extra_kwargs['proxy_ssl_context'] = self._proxy_ssl_context
|
||||
- return super().proxy_manager_for(proxy, **proxy_kwargs, **self._pm_args, **extra_kwargs)
|
||||
+ if not proxy.lower().startswith("socks") and self._proxy_ssl_context:
|
||||
+ extra_kwargs["proxy_ssl_context"] = self._proxy_ssl_context
|
||||
+ return super().proxy_manager_for(
|
||||
+ proxy, **proxy_kwargs, **self._pm_args, **extra_kwargs
|
||||
+ )
|
||||
|
||||
# Skip `requests` internal verification; we use our own SSLContext
|
||||
+ # requests 2.31.0+
|
||||
def cert_verify(*args, **kwargs):
|
||||
pass
|
||||
|
||||
+ # requests 2.31.0-2.32.1
|
||||
+ def _get_connection(self, request, *_, proxies=None, **__):
|
||||
+ return self.get_connection(request.url, proxies)
|
||||
+
|
||||
# requests 2.32.2+: Reimplementation without `_urllib3_request_context`
|
||||
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
|
||||
url = urllib3.util.parse_url(request.url).url
|
||||
@@ -222,7 +251,7 @@ def rebuild_method(self, prepared_request, response):
|
||||
def rebuild_auth(self, prepared_request, response):
|
||||
# HACK: undo status code change from rebuild_method, if applicable.
|
||||
# rebuild_auth runs after requests would remove headers/body based on status code
|
||||
- if hasattr(response, '_real_status_code'):
|
||||
+ if hasattr(response, "_real_status_code"):
|
||||
response.status_code = response._real_status_code
|
||||
del response._real_status_code
|
||||
return super().rebuild_auth(prepared_request, response)
|
||||
@@ -256,23 +285,30 @@ def emit(self, record):
|
||||
|
||||
@register_rh
|
||||
class RequestsRH(RequestHandler, InstanceStoreMixin):
|
||||
-
|
||||
"""Requests RequestHandler
|
||||
https://github.com/psf/requests
|
||||
"""
|
||||
- _SUPPORTED_URL_SCHEMES = ('http', 'https')
|
||||
+
|
||||
+ _SUPPORTED_URL_SCHEMES = ("http", "https")
|
||||
_SUPPORTED_ENCODINGS = tuple(SUPPORTED_ENCODINGS)
|
||||
- _SUPPORTED_PROXY_SCHEMES = ('http', 'https', 'socks4', 'socks4a', 'socks5', 'socks5h')
|
||||
+ _SUPPORTED_PROXY_SCHEMES = (
|
||||
+ "http",
|
||||
+ "https",
|
||||
+ "socks4",
|
||||
+ "socks4a",
|
||||
+ "socks5",
|
||||
+ "socks5h",
|
||||
+ )
|
||||
_SUPPORTED_FEATURES = (Features.NO_PROXY, Features.ALL_PROXY)
|
||||
- RH_NAME = 'requests'
|
||||
+ RH_NAME = "requests"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Forward urllib3 debug messages to our logger
|
||||
- logger = logging.getLogger('urllib3')
|
||||
+ logger = logging.getLogger("urllib3")
|
||||
self.__logging_handler = Urllib3LoggingHandler(logger=self._logger)
|
||||
- self.__logging_handler.setFormatter(logging.Formatter('requests: %(message)s'))
|
||||
+ self.__logging_handler.setFormatter(logging.Formatter("requests: %(message)s"))
|
||||
self.__logging_handler.addFilter(Urllib3LoggingFilter())
|
||||
logger.addHandler(self.__logging_handler)
|
||||
# TODO: Use a logger filter to suppress pool reuse warning instead
|
||||
@@ -291,14 +327,14 @@ def close(self):
|
||||
self._clear_instances()
|
||||
# Remove the logging handler that contains a reference to our logger
|
||||
# See: https://github.com/yt-dlp/yt-dlp/issues/8922
|
||||
- logging.getLogger('urllib3').removeHandler(self.__logging_handler)
|
||||
+ logging.getLogger("urllib3").removeHandler(self.__logging_handler)
|
||||
|
||||
def _check_extensions(self, extensions):
|
||||
super()._check_extensions(extensions)
|
||||
- extensions.pop('cookiejar', None)
|
||||
- extensions.pop('timeout', None)
|
||||
- extensions.pop('legacy_ssl', None)
|
||||
- extensions.pop('keep_header_casing', None)
|
||||
+ extensions.pop("cookiejar", None)
|
||||
+ extensions.pop("timeout", None)
|
||||
+ extensions.pop("legacy_ssl", None)
|
||||
+ extensions.pop("keep_header_casing", None)
|
||||
|
||||
def _create_instance(self, cookiejar, legacy_ssl_support=None):
|
||||
session = RequestsSession()
|
||||
@@ -308,9 +344,11 @@ def _create_instance(self, cookiejar, legacy_ssl_support=None):
|
||||
max_retries=urllib3.util.retry.Retry(False),
|
||||
)
|
||||
session.adapters.clear()
|
||||
- session.headers = requests.models.CaseInsensitiveDict({'Connection': 'keep-alive'})
|
||||
- session.mount('https://', http_adapter)
|
||||
- session.mount('http://', http_adapter)
|
||||
+ session.headers = requests.models.CaseInsensitiveDict(
|
||||
+ {"Connection": "keep-alive"}
|
||||
+ )
|
||||
+ session.mount("https://", http_adapter)
|
||||
+ session.mount("http://", http_adapter)
|
||||
session.cookies = cookiejar
|
||||
session.trust_env = False # no need, we already load proxies from env
|
||||
return session
|
||||
@@ -325,7 +363,7 @@ def _send(self, request):
|
||||
|
||||
session = self._get_instance(
|
||||
cookiejar=self._get_cookiejar(request),
|
||||
- legacy_ssl_support=request.extensions.get('legacy_ssl'),
|
||||
+ legacy_ssl_support=request.extensions.get("legacy_ssl"),
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -345,7 +383,7 @@ def _send(self, request):
|
||||
requests_res = e.response
|
||||
|
||||
except requests.exceptions.SSLError as e:
|
||||
- if 'CERTIFICATE_VERIFY_FAILED' in str(e):
|
||||
+ if "CERTIFICATE_VERIFY_FAILED" in str(e):
|
||||
raise CertificateVerifyError(cause=e) from e
|
||||
raise SSLError(cause=e) from e
|
||||
|
||||
@@ -378,26 +416,33 @@ def requests_preference(rh, request):
|
||||
|
||||
# Use our socks proxy implementation with requests to avoid an extra dependency.
|
||||
class SocksHTTPConnection(urllib3.connection.HTTPConnection):
|
||||
- def __init__(self, _socks_options, *args, **kwargs): # must use _socks_options to pass PoolKey checks
|
||||
+ def __init__(
|
||||
+ self, _socks_options, *args, **kwargs
|
||||
+ ): # must use _socks_options to pass PoolKey checks
|
||||
self._proxy_args = _socks_options
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _new_conn(self):
|
||||
try:
|
||||
return create_connection(
|
||||
- address=(self._proxy_args['addr'], self._proxy_args['port']),
|
||||
+ address=(self._proxy_args["addr"], self._proxy_args["port"]),
|
||||
timeout=self.timeout,
|
||||
source_address=self.source_address,
|
||||
_create_socket_func=functools.partial(
|
||||
- create_socks_proxy_socket, (self.host, self.port), self._proxy_args))
|
||||
+ create_socks_proxy_socket, (self.host, self.port), self._proxy_args
|
||||
+ ),
|
||||
+ )
|
||||
except (socket.timeout, TimeoutError) as e:
|
||||
raise urllib3.exceptions.ConnectTimeoutError(
|
||||
- self, f'Connection to {self.host} timed out. (connect timeout={self.timeout})') from e
|
||||
+ self,
|
||||
+ f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
|
||||
+ ) from e
|
||||
except SocksProxyError as e:
|
||||
raise urllib3.exceptions.ProxyError(str(e), e) from e
|
||||
except OSError as e:
|
||||
raise urllib3.exceptions.NewConnectionError(
|
||||
- self, f'Failed to establish a new connection: {e}') from e
|
||||
+ self, f"Failed to establish a new connection: {e}"
|
||||
+ ) from e
|
||||
|
||||
|
||||
class SocksHTTPSConnection(SocksHTTPConnection, urllib3.connection.HTTPSConnection):
|
||||
@@ -414,12 +459,20 @@ class SocksHTTPSConnectionPool(urllib3.HTTPSConnectionPool):
|
||||
|
||||
class SocksProxyManager(urllib3.PoolManager):
|
||||
|
||||
- def __init__(self, socks_proxy, username=None, password=None, num_pools=10, headers=None, **connection_pool_kw):
|
||||
- connection_pool_kw['_socks_options'] = make_socks_proxy_opts(socks_proxy)
|
||||
+ def __init__(
|
||||
+ self,
|
||||
+ socks_proxy,
|
||||
+ username=None,
|
||||
+ password=None,
|
||||
+ num_pools=10,
|
||||
+ headers=None,
|
||||
+ **connection_pool_kw,
|
||||
+ ):
|
||||
+ connection_pool_kw["_socks_options"] = make_socks_proxy_opts(socks_proxy)
|
||||
super().__init__(num_pools, headers, **connection_pool_kw)
|
||||
self.pool_classes_by_scheme = {
|
||||
- 'http': SocksHTTPConnectionPool,
|
||||
- 'https': SocksHTTPSConnectionPool,
|
||||
+ "http": SocksHTTPConnectionPool,
|
||||
+ "https": SocksHTTPSConnectionPool,
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
45
17491.patch
45
17491.patch
|
|
@ -1,45 +0,0 @@
|
|||
From b6ef4e25fe882a82c02c12ca716f19fb3c1b89f3 Mon Sep 17 00:00:00 2001
|
||||
From: Mikel Olasagasti Uranga <mikel@olasagasti.info>
|
||||
Date: Thu, 20 Aug 2026 16:23:27 +0200
|
||||
Subject: [PATCH] tests: accept newer OpenSSL handshake error wording
|
||||
|
||||
Newer Python/OpenSSL combinations may report TLS handshake failures as
|
||||
"TLS_ALERT_HANDSHAKE_FAILURE" / "tls alert handshake failure" instead
|
||||
of the older "sslv3 alert handshake failure" wording expected by the
|
||||
test suite.
|
||||
|
||||
Relax the test regexes to accept both forms without changing the
|
||||
behavior being tested.
|
||||
|
||||
Signed-off-by: Mikel Olasagasti Uranga <mikel@olasagasti.info>
|
||||
---
|
||||
test/test_networking.py | 2 +-
|
||||
test/test_websockets.py | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/test/test_networking.py b/test/test_networking.py
|
||||
index 9dbe263b6fcd..57a7a21bd14b 100644
|
||||
--- a/test/test_networking.py
|
||||
+++ b/test/test_networking.py
|
||||
@@ -338,7 +338,7 @@ def test_ssl_error(self, handler):
|
||||
https_server_thread.start()
|
||||
|
||||
with handler(verify=False) as rh:
|
||||
- with pytest.raises(SSLError, match=r'(?i)ssl(?:v3|/tls).alert.handshake.failure') as exc_info:
|
||||
+ with pytest.raises(SSLError, match=r'(?i)(?:TLS_ALERT_HANDSHAKE_FAILURE|(?:sslv3|ssl/tls|tls).alert.handshake.failure)') as exc_info:
|
||||
validate_and_send(rh, Request(f'https://127.0.0.1:{https_port}/headers'))
|
||||
assert not issubclass(exc_info.type, CertificateVerifyError)
|
||||
|
||||
diff --git a/test/test_websockets.py b/test/test_websockets.py
|
||||
index caa026f44f3a..3b3814018eeb 100644
|
||||
--- a/test/test_websockets.py
|
||||
+++ b/test/test_websockets.py
|
||||
@@ -186,7 +186,7 @@ def test_verify_cert(self, handler):
|
||||
|
||||
def test_ssl_error(self, handler):
|
||||
with handler(verify=False) as rh:
|
||||
- with pytest.raises(SSLError, match=r'ssl(?:v3|/tls) alert handshake failure') as exc_info:
|
||||
+ with pytest.raises(SSLError, match=r'(?:TLS_ALERT_HANDSHAKE_FAILURE|(?:sslv3|ssl/tls|tls) alert handshake failure)') as exc_info:
|
||||
ws_validate_and_send(rh, Request(self.bad_wss_host))
|
||||
assert not issubclass(exc_info.type, CertificateVerifyError)
|
||||
|
||||
2
sources
2
sources
|
|
@ -1 +1 @@
|
|||
SHA512 (yt-dlp-2026.08.19.tar.gz) = 04f2dae28fe2b087fed3df38fc6787eac8f85a351bd72f3b010a84fb48bccf9bfde3a50ca4c005afb384f1219ee66a4cab24e5740eb1e5b8eb1a0d8ab773bafd
|
||||
SHA512 (yt-dlp-2025.03.31.tar.gz) = 3ac5e5d13e3d1d4965ec398901bea158f451e0aed3600dace706e9e1a8f89580349fe1249c76fe097966c8254eb1cc526075138fc16ea29cf933ab0019cfaf27
|
||||
|
|
|
|||
109
yt-dlp.spec
109
yt-dlp.spec
|
|
@ -2,30 +2,35 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-FileCopyrightText: Fedora Project Authors
|
||||
# SPDX-FileCopyrightText: 2022 Maxwell G <gotmax@e.email>
|
||||
# License text: https://spdx.org/licenses/MIT.html
|
||||
# See %%{name}.spec.license for the full license text.
|
||||
|
||||
%bcond_without tests
|
||||
|
||||
Name: yt-dlp
|
||||
Version: 2026.08.19
|
||||
Version: 2025.03.31
|
||||
Release: %autorelease
|
||||
Summary: A command-line program to download videos from online video platforms
|
||||
|
||||
License: Unlicense
|
||||
URL: https://github.com/yt-dlp/yt-dlp
|
||||
Source: %{url}/archive/%{version}/yt-dlp-%{version}.tar.gz
|
||||
URL: https://github.com/%{name}/%{name}
|
||||
Source: %{url}/archive/%{version}/%{name}-%{version}.tar.gz
|
||||
# License of the specfile
|
||||
Source: yt-dlp.spec.license
|
||||
|
||||
# https://github.com/yt-dlp/yt-dlp/pull/17491
|
||||
# Relax handshake error regexp
|
||||
Patch: 17491.patch
|
||||
# https://github.com/yt-dlp/yt-dlp/commit/6f9e6537434562d513d0c9b68ced8a61ade94a64
|
||||
# [rh:websockets] Upgrade websockets to 13.0 (#10815)
|
||||
# Revert this patch for compatibility with older Fedora versions
|
||||
# This patch is applied conditionally to Fedora <= 41
|
||||
Patch1000: 0001-Revert-rh-websockets-Upgrade-websockets-to-13.0-1081.patch
|
||||
|
||||
# Needed for compatibility with Fedora <= 44
|
||||
Patch: 0002-Restore-compatibility-with-pytest-9.patch
|
||||
# Revert "[rh:requests] Bump minimum `requests` version to 2.32.2 (#10079)"
|
||||
# Revert this patch for compatibility with older Fedora versions
|
||||
# This patch is applied conditionally to Fedora <= 40
|
||||
Patch1001: 0002-Revert-rh-requests-Bump-minimum-requests-version-to-.patch
|
||||
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: tomcli
|
||||
|
||||
%if %{with tests}
|
||||
# Needed for %%check
|
||||
|
|
@ -44,42 +49,58 @@ Recommends: /usr/bin/ffprobe
|
|||
|
||||
Suggests: python3dist(keyring)
|
||||
|
||||
# START: Remove in Fedora 46
|
||||
Provides: yt-dlp-bash-completion = %{version}-%{release}
|
||||
Obsoletes: yt-dlp-bash-completion < 2025.09.26-2
|
||||
|
||||
Provides: yt-dlp-fish-completion = %{version}-%{release}
|
||||
Obsoletes: yt-dlp-fish-completion < 2025.09.26-2
|
||||
|
||||
Provides: yt-dlp-zsh-completion = %{version}-%{release}
|
||||
Obsoletes: yt-dlp-zsh-completion < 2025.09.26-2
|
||||
# END: Remove in Fedora 46
|
||||
|
||||
%description
|
||||
%global _description %{expand:
|
||||
yt-dlp is a command-line program to download videos from many different online
|
||||
video platforms, such as youtube.com. The project is a fork of youtube-dl with
|
||||
additional features and fixes.
|
||||
additional features and fixes.}
|
||||
|
||||
%description %{_description}
|
||||
|
||||
%package bash-completion
|
||||
Summary: Bash completion for %{name}
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
Requires: bash-completion
|
||||
Supplements: (%{name} and bash-completion)
|
||||
|
||||
%description bash-completion
|
||||
Bash command line completion support for %{name}.
|
||||
|
||||
%package zsh-completion
|
||||
Summary: Zsh completion for %{name}
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
Requires: zsh
|
||||
Supplements: (%{name} and zsh)
|
||||
|
||||
%description zsh-completion
|
||||
Zsh command line completion support for %{name}.
|
||||
|
||||
%package fish-completion
|
||||
Summary: Fish completion for %{name}
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
Requires: fish
|
||||
Supplements: (%{name} and fish)
|
||||
|
||||
%description fish-completion
|
||||
Fish command line completion support for %{name}.
|
||||
|
||||
%prep
|
||||
%autosetup -p1
|
||||
|
||||
# TODO: Figure out https://bugzilla.redhat.com/show_bug.cgi?id=2405578
|
||||
# Users can configure JS challenges manually for now.
|
||||
tomcli set pyproject.toml arrays delitem \
|
||||
project.optional-dependencies.default 'yt-dlp-ejs.*'
|
||||
|
||||
# Remove -s from shebang so users can install extra deps or plugins (or yt-dlp-ejs) using pip.
|
||||
%undefine _py3_shebang_s
|
||||
%autosetup -N
|
||||
%autopatch -M 999 -p1
|
||||
%if %{defined fedora} && 0%{?fedora} <= 41
|
||||
# Revert patch for compatibility with older websockets
|
||||
%autopatch 1000 -p1
|
||||
%endif
|
||||
%if %{defined fedora} && 0%{?fedora} <= 40
|
||||
# Revert patch for compatibility with older requests
|
||||
%autopatch 1001 -p1
|
||||
%endif
|
||||
|
||||
# Remove unnecessary shebangs
|
||||
find -type f ! -executable -name '*.py' -print -exec sed -i -e '1{\@^#!.*@d}' '{}' +
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -x default,secretstorage
|
||||
|
||||
|
||||
%build
|
||||
# Docs and shell completions
|
||||
make yt-dlp.1 completion-bash completion-zsh completion-fish
|
||||
|
|
@ -87,30 +108,32 @@ make yt-dlp.1 completion-bash completion-zsh completion-fish
|
|||
# Docs and shell completions are also included in the wheel.
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files yt_dlp
|
||||
|
||||
|
||||
%check
|
||||
%if %{with tests}
|
||||
# See https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/run_tests.sh
|
||||
%pytest -k "not download and not test_verify_cert[Websockets]"
|
||||
%endif
|
||||
|
||||
|
||||
%files -f %{pyproject_files}
|
||||
%{_bindir}/%{name}
|
||||
%{_mandir}/man1/%{name}.1*
|
||||
%doc README.md
|
||||
%license LICENSE
|
||||
%{_bindir}/yt-dlp
|
||||
%{_mandir}/man1/yt-dlp.1*
|
||||
%{bash_completions_dir}/yt-dlp
|
||||
%{fish_completions_dir}/yt-dlp.fish
|
||||
%{zsh_completions_dir}/_yt-dlp
|
||||
|
||||
%files bash-completion
|
||||
%{bash_completions_dir}/%{name}
|
||||
|
||||
%files zsh-completion
|
||||
%{zsh_completions_dir}/_%{name}
|
||||
|
||||
%files fish-completion
|
||||
%{fish_completions_dir}/%{name}.fish
|
||||
|
||||
%pyproject_extras_subpkg -n yt-dlp default secretstorage
|
||||
|
||||
|
||||
%changelog
|
||||
%autochangelog
|
||||
|
|
|
|||
9
yt-dlp.spec.license
Normal file
9
yt-dlp.spec.license
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Loading…
Add table
Add a link
Reference in a new issue