Compare commits

..

16 commits

Author SHA1 Message Date
Maxwell G
2a9ff01123
Update to 2025.03.31. Fixes rhbz#2355632. 2025-03-31 22:56:22 -05:00
Maxwell G
9c7f1f9f6d
Update to 2025.03.26. Fixes rhbz#2354242. 2025-03-27 11:35:55 -05:00
Maxwell G
b860ff73b7 Update to 2025.02.19. Fixes rhbz#2346539. 2025-02-22 18:41:46 -06:00
Mikel Olasagasti Uranga
8eacb27463 Update to 2025.01.26 - Closes rhbz#2342116 2025-01-26 15:31:33 +01:00
Mikel Olasagasti Uranga
b32c6119e7 Update to 2025.01.15 - Closes rhbz#2338387 2025-01-16 15:25:01 +01:00
Mikel Olasagasti Uranga
58c7b223ae Update to 2025.01.12 - Closes rhbz#2337284 2025-01-13 09:46:46 +01:00
Maxwell G
6b9deb5086 Preserve compatibility with requests 2.31 for Fedora 40
[skip changelog]

(cherry picked from commit b8d3a22583)
2025-01-06 17:16:13 -06:00
Maxwell G
0b3bfa128f Properly handle yt-dlp extras subpackages
(cherry picked from commit eacda9a5dd)
2025-01-06 17:16:13 -06:00
Maxwell G
bb236482e6 Recreate websockets 12 compat patch
Recreate the websockets 12 compat patch so it can be rebased and applied
in the normal way instead needing to use patch -R.

Also, remove the tomcli pyproject.toml version constraints command.
Brute forcing the version constraints in the pyproject.toml won't work,
as they are now also checked at runtime and the code itself needs to be
patched as well.

[skip changelog]

(cherry picked from commit 6308364583)
2025-01-06 17:16:13 -06:00
Mikel Olasagasti Uranga
9690485ec7 Update to 2024.12.23 - Closes rhbz#2330257
(cherry picked from commit 473142dd7d)
2025-01-06 17:16:13 -06:00
Mikel Olasagasti Uranga
699b594e50 Convert to %autorelease and %autochangelog
[skip changelog]

(cherry picked from commit 4b45f4c7c9)
2025-01-06 17:16:13 -06:00
Yaakov Selkowitz
50a4a661a1 Fix EPEL build
Undefined macros result in a parser error unless they are ?-prefixed, and
%fedora is not guaranteed to be defined (as in EPEL builds).

(cherry picked from commit d77c484ebe)
2025-01-06 17:16:13 -06:00
Mikel Olasagasti Uranga
429a67881c Update to 2024.11.04. Fixes rhbz#2323783
(cherry picked from commit b43afeaccb)
2025-01-06 17:16:13 -06:00
Mikel Olasagasti Uranga
c070263390 Update to 2024.11.04. Fixes rhbz#2323783
(cherry picked from commit 5d846b7f84)
2025-01-06 17:16:13 -06:00
Mikel Olasagasti Uranga
64d87fdda4 Update to 2024.10.22. Fixes rhbz#2321221
(cherry picked from commit 0b309bc750)
2025-01-06 17:16:13 -06:00
Maxwell G
3093ca0390
Update to 2024.10.07.
(cherry picked from commit 1fa052bd17)
2024-10-08 10:37:00 -05:00
8 changed files with 564 additions and 156 deletions

21
.gitignore vendored
View file

@ -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

View 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

View file

@ -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

View 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

View file

@ -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)

View file

@ -1 +1 @@
SHA512 (yt-dlp-2026.08.19.tar.gz) = 04f2dae28fe2b087fed3df38fc6787eac8f85a351bd72f3b010a84fb48bccf9bfde3a50ca4c005afb384f1219ee66a4cab24e5740eb1e5b8eb1a0d8ab773bafd
SHA512 (yt-dlp-2025.03.31.tar.gz) = 3ac5e5d13e3d1d4965ec398901bea158f451e0aed3600dace706e9e1a8f89580349fe1249c76fe097966c8254eb1cc526075138fc16ea29cf933ab0019cfaf27

View file

@ -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
View 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.