diff --git a/.github/workflows/challenge-tests.yml b/.github/workflows/challenge-tests.yml index 26e4c8259e..751746d732 100644 --- a/.github/workflows/challenge-tests.yml +++ b/.github/workflows/challenge-tests.yml @@ -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 diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index be3dede00d..407eea1117 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -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 diff --git a/.github/workflows/issue-lockdown.yml b/.github/workflows/issue-lockdown.yml index 09f47ee622..fbaf9b4766 100644 --- a/.github/workflows/issue-lockdown.yml +++ b/.github/workflows/issue-lockdown.yml @@ -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}" diff --git a/.github/workflows/quick-test.yml b/.github/workflows/quick-test.yml index f8aae59d40..68b56dbe2c 100644 --- a/.github/workflows/quick-test.yml +++ b/.github/workflows/quick-test.yml @@ -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 diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index 219cb9d17f..6e7fa43ede 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -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 diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 126c0a901c..14ac9bfc53 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -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: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd7bcf2eb4..3c92dac3ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/test-workflows.yml b/.github/workflows/test-workflows.yml index 26a84eb615..d42fbcbf65 100644 --- a/.github/workflows/test-workflows.yml +++ b/.github/workflows/test-workflows.yml @@ -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 diff --git a/Makefile b/Makefile index de3eabd13b..d5af60e19c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 8a938c1a4c..bc98de424f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/devscripts/make_contributing.py b/devscripts/make_contributing.py deleted file mode 100755 index e76cf885c8..0000000000 --- a/devscripts/make_contributing.py +++ /dev/null @@ -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() diff --git a/devscripts/update_requirements.py b/devscripts/update_requirements.py index dac398969f..c5bc6ce2b2 100755 --- a/devscripts/update_requirements.py +++ b/devscripts/update_requirements.py @@ -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[^-]+)-', filename): + if mobj := re.fullmatch(rf'{normalized_name}-(?P[^-]+)(?:-.+\.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}`**]()', md_old, md_new, compare, diff --git a/pyproject.toml b/pyproject.toml index 6449678c75..c169d8330b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -180,9 +180,6 @@ exclude = [ path = "yt_dlp/version.py" pattern = "_pkg_version = '(?P[^']+)'" -[tool.hatch.metadata] -allow-direct-references = true - [tool.hatch.envs.default] features = [ "curl-cffi", diff --git a/uv.lock b/uv.lock index 97ea42f378..ed001a92ec 100644 --- a/uv.lock +++ b/uv.lock @@ -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 = [ diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 6236200153..31c0266d4a 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -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 diff --git a/yt_dlp/extractor/peertube.py b/yt_dlp/extractor/peertube.py index 4c8205f8ee..4445485ccf 100644 --- a/yt_dlp/extractor/peertube.py +++ b/yt_dlp/extractor/peertube.py @@ -1322,7 +1322,7 @@ class PeerTubeIE(InfoExtractor): ) (?P{_UUID_RE}) ''' - _EMBED_REGEX = [r'''(?x)]+\bsrc=["\'](?P(?:https?:)?//{_INSTANCES_RE}/videos/embed/{cls._UUID_RE})'''] + _EMBED_REGEX = [rf'''(?x)]+\bsrc=["\'](?P(?:https?:)?//{_INSTANCES_RE}/videos/embed/{_UUID_RE})'''] _TESTS = [{ 'url': 'https://framatube.org/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d', 'md5': '8563064d245a4be5705bddb22bb00a28', diff --git a/yt_dlp/options.py b/yt_dlp/options.py index 39bd16e57b..ab25540642 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -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', diff --git a/yt_dlp/utils/_jsruntime.py b/yt_dlp/utils/_jsruntime.py index 2c2ec9e7c6..740248533b 100644 --- a/yt_dlp/utils/_jsruntime.py +++ b/yt_dlp/utils/_jsruntime.py @@ -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}')