[cleanup] Misc (#16452)

* Include `pin*` extras in lockfile
* Fix and clean up `devscripts/update_requirements.py`
* Improve release channel documentation
* Remove false statement from `--prefer-insecure` documentation
* Assorted code cleanup
* Set `GH_TELEMETRY=false` in CI/CD whenever `gh` is used
* Add comments about required checks in CI workflows
* Run `test-workflows.yml` for every PR so its checks can be required
* Verify actionlint attestation in CI
* Remove zizmor version to reduce workflow maintenance burden
  (zizmor-action handles pinning on its end)

Authored by: bashonly
This commit is contained in:
bashonly 2026-05-03 17:19:08 -05:00 committed by GitHub
parent 27973bae5e
commit 35684c1171
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 158 additions and 108 deletions

View File

@ -1,6 +1,7 @@
name: Challenge Tests
on:
push:
branches: ['master']
paths:
- .github/workflows/challenge-tests.yml
- test/test_jsc/*.py
@ -9,6 +10,7 @@ on:
- yt_dlp/extractor/youtube/pot/**.py
- yt_dlp/utils/_jsruntime.py
pull_request:
branches: ['**']
paths:
- .github/workflows/challenge-tests.yml
- test/test_jsc/*.py
@ -25,7 +27,7 @@ concurrency:
jobs:
tests:
name: Challenge Tests
name: Challenge tests
if: ${{ !contains(github.event.head_commit.message, ':ci skip') }}
permissions:
contents: read

View File

@ -1,6 +1,7 @@
name: Core Tests
on:
push:
branches: ['master']
paths:
- pyproject.toml
- .github/**
@ -13,6 +14,7 @@ on:
- yt_dlp/extractor/common.py
- yt_dlp/extractor/extractors.py
pull_request:
branches: ['**']
paths:
- pyproject.toml
- .github/**
@ -33,7 +35,7 @@ concurrency:
jobs:
tests:
name: Core Tests
name: Core tests
if: ${{ !contains(github.event.head_commit.message, ':ci skip') }}
permissions:
contents: read

View File

@ -5,9 +5,12 @@ on:
permissions: {}
env:
GH_TELEMETRY: "false"
jobs:
lockdown:
name: Issue Lockdown
name: Issue lockdown
if: vars.ISSUE_LOCKDOWN
permissions:
issues: write # Needed to lock issues
@ -19,4 +22,4 @@ jobs:
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPOSITORY: ${{ github.repository }}
run: |
gh issue lock "${ISSUE_NUMBER}" -R "${REPOSITORY}"
gh issue lock -R "${REPOSITORY}" "${ISSUE_NUMBER}"

View File

@ -1,5 +1,10 @@
name: Quick Test
on: [push, pull_request]
on:
push:
branches: ['master']
# This workflow contains required checks and needs to run for EVERY pull_request
pull_request:
branches: ['**']
permissions: {}
@ -9,7 +14,8 @@ concurrency:
jobs:
tests:
name: Core Test
# Required check; do not change name
name: Core test
if: ${{ !contains(github.event.head_commit.message, ':ci skip all') }}
permissions:
contents: read
@ -31,7 +37,9 @@ jobs:
run: |
python3 -m yt_dlp -v || true
python3 ./devscripts/run_tests.py --pytest-args '--reruns 2 --reruns-delay 3.0' core
check:
# Required check; do not change name
name: Code check
if: ${{ !contains(github.event.head_commit.message, ':ci skip all') }}
permissions:
@ -43,7 +51,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
python-version: '3.14'
- name: Install dev dependencies
run: python ./devscripts/install_deps.py --omit-default --include-group static-analysis
- name: Make lazy extractors

View File

@ -19,7 +19,7 @@ permissions: {}
jobs:
release:
name: Publish Github release
name: Publish GitHub release
if: vars.BUILD_MASTER
permissions:
contents: write # May be needed to publish release

View File

@ -70,7 +70,7 @@ jobs:
run: echo "${HEAD}" | tee .nightly_commit_hash
release:
name: Publish Github release
name: Publish GitHub release
needs: [check_nightly]
if: needs.check_nightly.outputs.commit
permissions:

View File

@ -65,6 +65,9 @@ on:
permissions: {}
env:
GH_TELEMETRY: "false"
jobs:
prepare:
name: Prepare
@ -226,7 +229,7 @@ jobs:
verbose: true
publish:
name: Publish Github release
name: Publish GitHub release
needs: [prepare, build]
permissions:
contents: write # Needed by gh to publish release to Github

View File

@ -1,23 +1,10 @@
name: Test and lint workflows
on:
push:
branches: [master]
paths:
- .github/*.yml
- .github/workflows/*
- bundle/docker/linux/*.sh
- devscripts/setup_variables.py
- devscripts/setup_variables_tests.py
- devscripts/utils.py
branches: ['master']
# This workflow contains required checks and needs to run for EVERY pull_request
pull_request:
branches: [master]
paths:
- .github/*.yml
- .github/workflows/*
- bundle/docker/linux/*.sh
- devscripts/setup_variables.py
- devscripts/setup_variables_tests.py
- devscripts/utils.py
branches: ['**']
permissions: {}
@ -28,10 +15,12 @@ concurrency:
env:
ACTIONLINT_VERSION: "1.7.11"
ACTIONLINT_SHA256SUM: 900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a
ACTIONLINT_REPO: https://github.com/rhysd/actionlint
ACTIONLINT_REPO: rhysd/actionlint
GH_TELEMETRY: "false"
jobs:
check:
# Required check; do not change name
name: Check workflows
permissions:
contents: read
@ -45,19 +34,26 @@ jobs:
python-version: "3.13" # Keep this in sync with release.yml's prepare job
- name: Install requirements
env:
GH_TOKEN: ${{ github.token }}
ACTIONLINT_TARBALL: ${{ format('actionlint_{0}_linux_amd64.tar.gz', env.ACTIONLINT_VERSION) }}
shell: bash
run: |
python -m devscripts.install_deps --omit-default --include-group test
sudo apt -y install shellcheck
python -m pip install -U pyflakes
curl -LO "${ACTIONLINT_REPO}/releases/download/v${ACTIONLINT_VERSION}/${ACTIONLINT_TARBALL}"
gh release download \
--repo "${ACTIONLINT_REPO}" \
--pattern "${ACTIONLINT_TARBALL}" \
"v${ACTIONLINT_VERSION}"
gh attestation verify \
--repo "${ACTIONLINT_REPO}" \
"${ACTIONLINT_TARBALL}"
printf '%s %s' "${ACTIONLINT_SHA256SUM}" "${ACTIONLINT_TARBALL}" | sha256sum -c -
tar xvzf "${ACTIONLINT_TARBALL}" actionlint
chmod +x actionlint
sudo install -D --mode=755 actionlint /usr/bin/
- name: Run actionlint
run: |
./actionlint -color
actionlint -color
- name: Check Docker shell scripts
run: |
shellcheck bundle/docker/linux/*.sh
@ -66,6 +62,7 @@ jobs:
pytest -Werror --tb=short --color=yes devscripts/setup_variables_tests.py
zizmor:
# Required check; do not change name
name: Run zizmor
permissions:
contents: read
@ -80,4 +77,3 @@ jobs:
with:
advanced-security: false
persona: pedantic
version: v1.23.1

View File

@ -3,7 +3,7 @@ all-extra: lazy-extractors yt-dlp-extra doc pypi-files
clean: clean-test clean-dist
clean-all: clean clean-cache
completions: completion-bash completion-fish completion-zsh
doc: README.md CONTRIBUTING.md CONTRIBUTORS issuetemplates supportedsites
doc: README.md CONTRIBUTORS issuetemplates supportedsites
ot: offlinetest
tar: yt-dlp.tar.gz
@ -30,7 +30,7 @@ clean-test:
test/testdata/sigs/player-*.js test/testdata/thumbnails/empty.webp "test/testdata/thumbnails/foo %d bar/foo_%d."*
clean-dist:
rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ \
yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS \
yt_dlp/extractor/lazy_extractors.py *.spec yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS \
yt-dlp.zip .ejs-* yt_dlp_ejs/
clean-cache:
find . \( \
@ -132,9 +132,6 @@ yt-dlp: yt-dlp.zip
README.md: $(PY_CODE_FILES) devscripts/make_readme.py
COLUMNS=80 $(PYTHON) yt_dlp/__main__.py --ignore-config --help | $(PYTHON) devscripts/make_readme.py
CONTRIBUTING.md: README.md devscripts/make_contributing.py
$(PYTHON) devscripts/make_contributing.py README.md CONTRIBUTING.md
issuetemplates: devscripts/make_issue_template.py .github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml .github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml .github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml .github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml .github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml yt_dlp/version.py
$(PYTHON) devscripts/make_issue_template.py .github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml .github/ISSUE_TEMPLATE/1_broken_site.yml
$(PYTHON) devscripts/make_issue_template.py .github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml .github/ISSUE_TEMPLATE/2_site_support_request.yml

View File

@ -167,9 +167,9 @@ For other third-party package managers, see [the wiki](https://github.com/yt-dlp
There are currently three release channels for binaries: `stable`, `nightly` and `master`.
* `stable` is the default channel, and many of its changes have been tested by users of the `nightly` and `master` channels.
* The `nightly` channel has releases scheduled to build every day around midnight UTC, for a snapshot of the project's new patches and changes. This is the **recommended channel for regular users** of yt-dlp. The `nightly` releases are available from [yt-dlp/yt-dlp-nightly-builds](https://github.com/yt-dlp/yt-dlp-nightly-builds/releases) or as development releases of the `yt-dlp` PyPI package (which can be installed with pip's `--pre` flag).
* The `master` channel features releases that are built after each push to the master branch, and these will have the very latest fixes and additions, but may also be more prone to regressions. They are available from [yt-dlp/yt-dlp-master-builds](https://github.com/yt-dlp/yt-dlp-master-builds/releases).
* `stable` is the default channel, which offers releases published on a (mostly) monthly schedule. While it is named `stable` due to many of its changes having been tested by users of the `nightly` or `master` release channels, the latest `stable` release is often "stale" and prone to external breakage (i.e. sites changing things on their end and breaking yt-dlp).
* The `nightly` channel offers releases that publish shortly before midnight UTC on any day that sees changes to the codebase. This channel serves as a snapshot of the project's development, and it is the **recommended channel for regular users** of yt-dlp. The `nightly` releases are available from [yt-dlp/yt-dlp-nightly-builds](https://github.com/yt-dlp/yt-dlp-nightly-builds/releases) or as development releases of the `yt-dlp` PyPI package (which can be installed with pip's `--pre` flag).
* The `master` channel offers "canary" releases that publish after each push to the master branch. This channel will always provide the very latest fixes and features, but may be prone to bugs or regressions. The `master` releases are available from [yt-dlp/yt-dlp-master-builds](https://github.com/yt-dlp/yt-dlp-master-builds/releases).
When using `--update`/`-U`, a release binary will only update to its current channel.
`--update-to CHANNEL` can be used to switch to a different channel when a newer version is available. `--update-to [CHANNEL@]TAG` can also be used to upgrade or downgrade to specific tags from a channel.

View File

@ -1,32 +0,0 @@
#!/usr/bin/env python3
import optparse
import re
def main():
return # This is unused in yt-dlp
parser = optparse.OptionParser(usage='%prog INFILE OUTFILE')
_, args = parser.parse_args()
if len(args) != 2:
parser.error('Expected an input and an output filename')
infile, outfile = args
with open(infile, encoding='utf-8') as inf:
readme = inf.read()
bug_text = re.search(
r'(?s)#\s*BUGS\s*[^\n]*\s*(.*?)#\s*COPYRIGHT', readme).group(1)
dev_text = re.search(
r'(?s)(#\s*DEVELOPER INSTRUCTIONS.*?)#\s*EMBEDDING yt-dlp', readme).group(1)
out = bug_text + dev_text
with open(outfile, 'w', encoding='utf-8') as outf:
outf.write(out)
if __name__ == '__main__':
main()

View File

@ -47,12 +47,6 @@ PINNED_EXTRAS = {
'pin-deno': 'deno',
}
WELLKNOWN_PACKAGES = {
'deno': {'owner': 'denoland', 'repo': 'deno'},
'protobug': {'owner': 'yt-dlp', 'repo': 'protobug'},
'yt-dlp-ejs': {'owner': 'yt-dlp', 'repo': 'ejs'},
}
EJS_ASSETS = {
'yt.solver.lib.js': False,
'yt.solver.lib.min.js': False,
@ -120,10 +114,15 @@ PYINSTALLER_BUILDS_TARGETS = {
'win-arm64-pyinstaller': 'win_arm64',
}
PYINSTALLER_BUILDS_TMPL = '''\
{}pyinstaller @ {} \\
--hash={}
'''
WELLKNOWN_PACKAGES = {
'deno': {'owner': 'denoland', 'repo': 'deno'},
'protobug': {'owner': 'yt-dlp', 'repo': 'protobug'},
'yt-dlp-ejs': {'owner': 'yt-dlp', 'repo': 'ejs'},
**{
f'pyinstaller[{asset_tag}]': {'owner': 'pyinstaller', 'repo': 'pyinstaller'}
for asset_tag in PYINSTALLER_BUILDS_TARGETS.values()
},
}
def call_pypi_api(project: str) -> dict[str, dict[str, typing.Any]]:
@ -447,7 +446,7 @@ def parse_version_from_dist(filename: str, name: str, *, require: bool = False)
normalized_name = re.sub(r'[-_.]+', '-', name).lower().replace('-', '_')
# Ref: https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers
if mobj := re.match(rf'{normalized_name}-(?P<version>[^-]+)-', filename):
if mobj := re.fullmatch(rf'{normalized_name}-(?P<version>[^-]+)(?:-.+\.whl|\.tar\.gz)', filename):
return mobj.group('version')
if require:
@ -579,27 +578,33 @@ def update_requirements(
all_updates = package_diff_dict(old_packages, new_packages)
# Update Windows PyInstaller requirements; need to compare before & after .txt's for reporting
if not upgrade_only or upgrade_only.lower() == 'pyinstaller':
if not upgrade_only:
info = fetch_latest_github_release('yt-dlp', 'Pyinstaller-Builds')
for target_suffix, asset_tag in PYINSTALLER_BUILDS_TARGETS.items():
asset_info = next(asset for asset in info['assets'] if asset_tag in asset['name'])
pyinstaller_version = parse_version_from_dist(
asset_info['name'], 'pyinstaller', require=True)
pyinstaller_builds_deps = run_pip_compile(
'--no-emit-package=pyinstaller',
upgrade_arg,
input_line=f'pyinstaller=={pyinstaller_version}',
env=env)
requirements_path = REQUIREMENTS_PATH / REQS_OUTPUT_TMPL.format(target_suffix)
if requirements_path.is_file():
old_requirements_txt = requirements_path.read_text()
else:
old_requirements_txt = ''
new_requirements_txt = PYINSTALLER_BUILDS_TMPL.format(
pyinstaller_builds_deps, asset_info['browser_download_url'], asset_info['digest'])
requirements_path.write_text(new_requirements_txt)
all_updates.update(evaluate_requirements_txt(old_requirements_txt, new_requirements_txt))
run_pip_compile(
upgrade_arg,
input_line=f'pyinstaller @ {asset_info["browser_download_url"]}',
output_file=requirements_path,
env=env)
new_requirements_txt = requirements_path.read_text()
if asset_info['digest'] not in new_requirements_txt:
raise ValueError(
f'expected pyinstaller wheel hash {asset_info["digest"]} '
f'not found in {requirements_path}')
diff_dict = evaluate_requirements_txt(old_requirements_txt, new_requirements_txt)
if pyinstaller_diff := diff_dict.get('pyinstaller'):
# NB: this depends on 'pyinstaller[asset_tag]' keys in WELLKNOWN_PACKAGES
all_updates.update({f'pyinstaller[{asset_tag}]': pyinstaller_diff})
# Export bundle requirements; any updates to these are already recorded w/ uv.lock package diff
for target_suffix, target in BUNDLE_TARGETS.items():
@ -640,6 +645,10 @@ def update_requirements(
# Write the finalized pyproject.toml
modify_and_write_pyproject(pyproject_text, table_name=EXTRAS_TABLE, table=extras)
# Generate/upgrade final lockfile that includes pinned extras
print(f'Running: uv lock {upgrade_arg}', file=sys.stderr)
run_process('uv', 'lock', upgrade_arg, env=env)
return all_updates
@ -682,7 +691,9 @@ def generate_report(
if offset is not None:
md_old = '.'.join(old_parts[:offset]) + '.***' + '.'.join(old_parts[offset:]) + '***'
md_old = md_old.lstrip('.')
md_new = '.'.join(new_parts[:offset]) + '.***' + '.'.join(new_parts[offset:]) + '***'
md_new = md_new.lstrip('.')
compare = ''
if github_info:
@ -692,14 +703,15 @@ def generate_report(
new_tag = tags_info.get(new) and tags_info[new]['name']
github_url = 'https://github.com/{owner}/{repo}'.format(**github_info)
if new_tag:
md_new = f'[{md_new}]({github_url}/releases/tag/{new_tag})'
md_new = f'[{md_new}](<{github_url}/releases/tag/{new_tag}>)'
if old_tag:
md_old = f'[{md_old}]({github_url}/releases/tag/{old_tag})'
md_old = f'[{md_old}](<{github_url}/releases/tag/{old_tag}>)'
if new_tag and old_tag:
compare = f'[`{old_tag}...{new_tag}`]({github_url}/compare/{old_tag}...{new_tag})'
compare = f'[`{old_tag}...{new_tag}`](<{github_url}/compare/{old_tag}...{new_tag}>)'
yield ' | '.join((
f'[**`{package}`**](https://pypi.org/project/{package})',
# Strip the [win*] tag from package in the URL (e.g. pyinstaller[win32])
f'[**`{package}`**](<https://pypi.org/project/{package.split("[")[0]}/>)',
md_old,
md_new,
compare,

View File

@ -180,9 +180,6 @@ exclude = [
path = "yt_dlp/version.py"
pattern = "_pkg_version = '(?P<version>[^']+)'"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.envs.default]
features = [
"curl-cffi",

66
uv.lock generated
View File

@ -1166,6 +1166,42 @@ default = [
deno = [
{ name = "deno" },
]
pin = [
{ name = "brotli", marker = "implementation_name == 'cpython' and sys_platform != 'ios'" },
{ name = "brotlicffi", marker = "implementation_name != 'cpython'" },
{ name = "certifi" },
{ name = "cffi", marker = "implementation_name != 'cpython'" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "mutagen" },
{ name = "pycparser", marker = "implementation_name != 'PyPy' and implementation_name != 'cpython'" },
{ name = "pycryptodomex" },
{ name = "requests" },
{ name = "urllib3" },
{ name = "websockets" },
{ name = "yt-dlp-ejs" },
]
pin-curl-cffi = [
{ name = "certifi", marker = "implementation_name == 'cpython'" },
{ name = "cffi", marker = "implementation_name == 'cpython'" },
{ name = "curl-cffi", marker = "implementation_name == 'cpython'" },
{ name = "markdown-it-py", marker = "implementation_name == 'cpython'" },
{ name = "mdurl", marker = "implementation_name == 'cpython'" },
{ name = "pycparser", marker = "implementation_name == 'cpython'" },
{ name = "pygments", marker = "implementation_name == 'cpython'" },
{ name = "rich", marker = "implementation_name == 'cpython'" },
]
pin-deno = [
{ name = "deno" },
]
pin-secretstorage = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
{ name = "cryptography" },
{ name = "jeepney" },
{ name = "pycparser", marker = "implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'" },
{ name = "secretstorage" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
secretstorage = [
{ name = "secretstorage" },
]
@ -1200,19 +1236,47 @@ test = [
[package.metadata]
requires-dist = [
{ name = "brotli", marker = "implementation_name == 'cpython' and sys_platform != 'ios' and extra == 'default'" },
{ name = "brotli", marker = "implementation_name == 'cpython' and sys_platform != 'ios' and extra == 'pin'", specifier = "==1.2.0" },
{ name = "brotlicffi", marker = "implementation_name != 'cpython' and extra == 'default'" },
{ name = "brotlicffi", marker = "implementation_name != 'cpython' and extra == 'pin'", specifier = "==1.2.0.1" },
{ name = "certifi", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==2026.2.25" },
{ name = "certifi", marker = "extra == 'default'" },
{ name = "certifi", marker = "extra == 'pin'", specifier = "==2026.2.25" },
{ name = "cffi", marker = "platform_python_implementation != 'PyPy' and extra == 'pin-secretstorage'", specifier = "==2.0.0" },
{ name = "cffi", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==2.0.0" },
{ name = "cffi", marker = "implementation_name != 'cpython' and extra == 'pin'", specifier = "==2.0.0" },
{ name = "charset-normalizer", marker = "extra == 'pin'", specifier = "==3.4.6" },
{ name = "cryptography", marker = "extra == 'pin-secretstorage'", specifier = "==46.0.6" },
{ name = "curl-cffi", marker = "implementation_name == 'cpython' and extra == 'curl-cffi'", specifier = ">=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.16" },
{ name = "curl-cffi", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==0.15.0" },
{ name = "deno", marker = "extra == 'deno'", specifier = ">=2.6.6" },
{ name = "deno", marker = "extra == 'pin-deno'", specifier = "==2.7.8" },
{ name = "idna", marker = "extra == 'pin'", specifier = "==3.11" },
{ name = "jeepney", marker = "extra == 'pin-secretstorage'", specifier = "==0.9.0" },
{ name = "markdown-it-py", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==4.0.0" },
{ name = "mdurl", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==0.1.2" },
{ name = "mutagen", marker = "extra == 'default'" },
{ name = "mutagen", marker = "extra == 'pin'", specifier = "==1.47.0" },
{ name = "pycparser", marker = "implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' and extra == 'pin-secretstorage'", specifier = "==3.0" },
{ name = "pycparser", marker = "implementation_name != 'PyPy' and implementation_name != 'cpython' and extra == 'pin'", specifier = "==3.0" },
{ name = "pycparser", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==3.0" },
{ name = "pycryptodomex", marker = "extra == 'default'" },
{ name = "pycryptodomex", marker = "extra == 'pin'", specifier = "==3.23.0" },
{ name = "pygments", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==2.19.2" },
{ name = "requests", marker = "extra == 'default'", specifier = ">=2.32.2,<3" },
{ name = "requests", marker = "extra == 'pin'", specifier = "==2.33.0" },
{ name = "rich", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==14.3.3" },
{ name = "secretstorage", marker = "extra == 'pin-secretstorage'", specifier = "==3.5.0" },
{ name = "secretstorage", marker = "extra == 'secretstorage'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11' and extra == 'pin-secretstorage'", specifier = "==4.15.0" },
{ name = "urllib3", marker = "extra == 'default'", specifier = ">=2.0.2,<3" },
{ name = "urllib3", marker = "extra == 'pin'", specifier = "==2.6.3" },
{ name = "websockets", marker = "extra == 'default'", specifier = ">=13.0" },
{ name = "websockets", marker = "extra == 'pin'", specifier = "==16.0" },
{ name = "yt-dlp-ejs", marker = "extra == 'default'", specifier = "==0.8.0" },
{ name = "yt-dlp-ejs", marker = "extra == 'pin'", specifier = "==0.8.0" },
]
provides-extras = ["curl-cffi", "default", "deno", "secretstorage"]
provides-extras = ["curl-cffi", "default", "deno", "pin", "pin-curl-cffi", "pin-deno", "pin-secretstorage", "secretstorage"]
[package.metadata.requires-dev]
build = [

View File

@ -3,9 +3,7 @@ import collections
import contextlib
import functools
import getpass
import http.client
import http.cookiejar
import http.cookies
import inspect
import itertools
import json

View File

@ -1322,7 +1322,7 @@ class PeerTubeIE(InfoExtractor):
)
(?P<id>{_UUID_RE})
'''
_EMBED_REGEX = [r'''(?x)<iframe[^>]+\bsrc=["\'](?P<url>(?:https?:)?//{_INSTANCES_RE}/videos/embed/{cls._UUID_RE})''']
_EMBED_REGEX = [rf'''(?x)<iframe[^>]+\bsrc=["\'](?P<url>(?:https?:)?//{_INSTANCES_RE}/videos/embed/{_UUID_RE})''']
_TESTS = [{
'url': 'https://framatube.org/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d',
'md5': '8563064d245a4be5705bddb22bb00a28',

View File

@ -1180,7 +1180,7 @@ def create_parser():
workarounds.add_option(
'--prefer-insecure', '--prefer-unsecure',
action='store_true', dest='prefer_insecure',
help='Use an unencrypted connection to retrieve information about the video (Currently supported only for YouTube)')
help='Use an unencrypted connection to retrieve information about the video')
workarounds.add_option(
'--user-agent',
metavar='UA', dest='user_agent',

View File

@ -41,12 +41,12 @@ def _find_exe(basename: str) -> str:
else:
exts = tuple(ext for ext in pathext.split(os.pathsep) if ext)
visited = []
visited = set()
for path in map(os.path.realpath, paths):
normed = os.path.normcase(path)
if normed in visited:
continue
visited.append(normed)
visited.add(normed)
for ext in exts:
binary = os.path.join(path, f'{basename}{ext}')