name: Build Artifacts on: workflow_call: inputs: version: required: true type: string channel: required: false default: stable type: string origin: required: true type: string unix: default: true type: boolean linux: default: true type: boolean linux_armv7l: default: true type: boolean musllinux: default: true type: boolean macos: default: true type: boolean windows: default: true type: boolean secrets: GPG_SIGNING_KEY: required: false workflow_dispatch: inputs: version: description: | VERSION: yyyy.mm.dd[.rev] or rev (default: auto-generated) required: false default: '' type: string channel: description: | SOURCE of this build's updates: stable/nightly/master/ required: true default: stable type: string unix: description: yt-dlp, yt-dlp.tar.gz default: true type: boolean linux: description: yt-dlp_linux, yt-dlp_linux.zip, yt-dlp_linux_aarch64, yt-dlp_linux_aarch64.zip default: true type: boolean linux_armv7l: description: yt-dlp_linux_armv7l.zip default: true type: boolean musllinux: description: yt-dlp_musllinux, yt-dlp_musllinux.zip, yt-dlp_musllinux_aarch64, yt-dlp_musllinux_aarch64.zip default: true type: boolean macos: description: yt-dlp_macos, yt-dlp_macos.zip default: true type: boolean windows: description: yt-dlp.exe, yt-dlp_win.zip, yt-dlp_x86.exe, yt-dlp_win_x86.zip, yt-dlp_arm64.exe, yt-dlp_win_arm64.zip default: true type: boolean permissions: contents: read jobs: process: runs-on: ubuntu-latest outputs: origin: ${{ steps.process_inputs.outputs.origin }} timestamp: ${{ steps.process_inputs.outputs.timestamp }} version: ${{ steps.process_inputs.outputs.version }} steps: - name: Process inputs id: process_inputs env: INPUTS: ${{ toJSON(inputs) }} REPOSITORY: ${{ github.repository }} shell: python run: | import datetime as dt import json import os import re INPUTS = json.loads(os.environ['INPUTS']) timestamp = dt.datetime.now(tz=dt.timezone.utc).strftime('%Y.%m.%d.%H%M%S.%f') version = INPUTS.get('version') if version and '.' not in version: # build.yml was dispatched with only a revision as the version input value version_parts = [*timestamp.split('.')[:3], version] elif not version: # build.yml was dispatched without any version input value, so include .HHMMSS revision version_parts = timestamp.split('.')[:4] else: # build.yml was called or dispatched with a complete version input value version_parts = version.split('.') assert all(re.fullmatch(r'[0-9]+', part) for part in version_parts), 'Version must be numeric' outputs = { 'origin': INPUTS.get('origin') or os.environ['REPOSITORY'], 'timestamp': timestamp, 'version': '.'.join(version_parts), } print(json.dumps(outputs, indent=2)) with open(os.environ['GITHUB_OUTPUT'], 'a') as f: f.write('\n'.join(f'{key}={value}' for key, value in outputs.items())) unix: needs: process if: inputs.unix runs-on: ubuntu-latest env: CHANNEL: ${{ inputs.channel }} ORIGIN: ${{ needs.process.outputs.origin }} VERSION: ${{ needs.process.outputs.version }} UPDATE_TO: yt-dlp/yt-dlp@2025.09.05 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Needed for changelog - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install Requirements run: | sudo apt -y install zip pandoc man sed - name: Prepare run: | python devscripts/update-version.py -c "${CHANNEL}" -r "${ORIGIN}" "${VERSION}" python devscripts/update_changelog.py -vv python devscripts/make_lazy_extractors.py - name: Build Unix platform-independent binary run: | make all tar - name: Verify --update-to if: vars.UPDATE_TO_VERIFICATION run: | chmod +x ./yt-dlp cp ./yt-dlp ./yt-dlp_downgraded version="$(./yt-dlp --version)" ./yt-dlp_downgraded -v --update-to "${UPDATE_TO}" downgraded_version="$(./yt-dlp_downgraded --version)" [[ "${version}" != "${downgraded_version}" ]] - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-bin-${{ github.job }} path: | yt-dlp yt-dlp.tar.gz compression-level: 0 linux: needs: process if: inputs.linux runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - exe: yt-dlp_linux platform: x86_64 runner: ubuntu-24.04 - exe: yt-dlp_linux_aarch64 platform: aarch64 runner: ubuntu-24.04-arm env: CHANNEL: ${{ inputs.channel }} ORIGIN: ${{ needs.process.outputs.origin }} VERSION: ${{ needs.process.outputs.version }} EXE_NAME: ${{ matrix.exe }} UPDATE_TO: yt-dlp/yt-dlp@2025.09.05 steps: - uses: actions/checkout@v4 - name: Build executable env: SERVICE: linux_${{ matrix.platform }} run: | mkdir -p ./dist pushd bundle/docker docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}" popd sudo chown "${USER}:docker" "./dist/${EXE_NAME}" - name: Verify executable in container if: vars.UPDATE_TO_VERIFICATION env: SERVICE: linux_${{ matrix.platform }}_verify run: | cd bundle/docker docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}" - name: Verify --update-to if: vars.UPDATE_TO_VERIFICATION run: | chmod +x "./dist/${EXE_NAME}" mkdir -p ~/testing cp "./dist/${EXE_NAME}" ~/testing/"${EXE_NAME}_downgraded" version="$("./dist/${EXE_NAME}" --version)" ~/testing/"${EXE_NAME}_downgraded" -v --update-to "${UPDATE_TO}" downgraded_version="$(~/testing/"${EXE_NAME}_downgraded" --version)" [[ "${version}" != "${downgraded_version}" ]] - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-bin-${{ github.job }}_${{ matrix.platform }} path: | dist/${{ matrix.exe }}* compression-level: 0 linux_armv7l: needs: process if: inputs.linux_armv7l permissions: contents: read runs-on: ubuntu-24.04-arm env: CHANNEL: ${{ inputs.channel }} ORIGIN: ${{ needs.process.outputs.origin }} VERSION: ${{ needs.process.outputs.version }} EXE_NAME: yt-dlp_linux_armv7l steps: - uses: actions/checkout@v4 - name: Cache requirements id: cache-venv uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 with: path: | ~/yt-dlp-build-venv key: cache-reqs-${{ github.job }}-${{ github.ref }}-${{ needs.process.outputs.timestamp }} restore-keys: | cache-reqs-${{ github.job }}-${{ github.ref }}- cache-reqs-${{ github.job }}- - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: linux/arm/v7 - name: Build executable env: SERVICE: linux_armv7l run: | mkdir -p ./dist mkdir -p ~/yt-dlp-build-venv cd bundle/docker docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}" - name: Verify executable in container if: vars.UPDATE_TO_VERIFICATION env: SERVICE: linux_armv7l_verify run: | cd bundle/docker docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}" - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-bin-${{ github.job }} path: | dist/yt-dlp_linux_armv7l.zip compression-level: 0 musllinux: needs: process if: inputs.musllinux runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - exe: yt-dlp_musllinux platform: x86_64 runner: ubuntu-24.04 - exe: yt-dlp_musllinux_aarch64 platform: aarch64 runner: ubuntu-24.04-arm env: CHANNEL: ${{ inputs.channel }} ORIGIN: ${{ needs.process.outputs.origin }} VERSION: ${{ needs.process.outputs.version }} EXE_NAME: ${{ matrix.exe }} steps: - uses: actions/checkout@v4 - name: Build executable env: SERVICE: musllinux_${{ matrix.platform }} run: | mkdir -p ./dist pushd bundle/docker docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}" popd sudo chown "${USER}:docker" "./dist/${EXE_NAME}" - name: Verify executable in container if: vars.UPDATE_TO_VERIFICATION env: SERVICE: musllinux_${{ matrix.platform }}_verify run: | cd bundle/docker docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}" - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-bin-${{ github.job }}_${{ matrix.platform }} path: | dist/${{ matrix.exe }}* compression-level: 0 macos: needs: process if: inputs.macos permissions: contents: read runs-on: macos-14 env: CHANNEL: ${{ inputs.channel }} ORIGIN: ${{ needs.process.outputs.origin }} VERSION: ${{ needs.process.outputs.version }} UPDATE_TO: yt-dlp/yt-dlp@2025.09.05 steps: - uses: actions/checkout@v4 # NB: Building universal2 does not work with python from actions/setup-python - name: Cache requirements id: cache-venv uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 with: path: | ~/yt-dlp-build-venv key: cache-reqs-${{ github.job }}-${{ github.ref }}-${{ needs.process.outputs.timestamp }} restore-keys: | cache-reqs-${{ github.job }}-${{ github.ref }}- cache-reqs-${{ github.job }}- - name: Install Requirements run: | brew install coreutils # We need to use system Python in order to roll our own universal2 curl_cffi wheel brew uninstall --ignore-dependencies python3 python3 -m venv ~/yt-dlp-build-venv source ~/yt-dlp-build-venv/bin/activate python3 devscripts/install_deps.py -o --include build python3 devscripts/install_deps.py --print --include pyinstaller > requirements.txt # We need to ignore wheels otherwise we break universal2 builds python3 -m pip install -U --no-binary :all: -r requirements.txt # We need to fuse our own universal2 wheels for curl_cffi python3 -m pip install -U 'delocate==0.11.0' mkdir curl_cffi_whls curl_cffi_universal2 python3 devscripts/install_deps.py --print -o --include curl-cffi > requirements.txt for platform in "macosx_11_0_arm64" "macosx_11_0_x86_64"; do python3 -m pip download \ --only-binary=:all: \ --platform "${platform}" \ -d curl_cffi_whls \ -r requirements.txt done ( # Overwrite x86_64-only libs with fat/universal2 libs or else Pyinstaller will do the opposite # See https://github.com/yt-dlp/yt-dlp/pull/10069 cd curl_cffi_whls mkdir -p curl_cffi/.dylibs python_libdir=$(python3 -c 'import sys; from pathlib import Path; print(Path(sys.path[1]).parent)') for dylib in lib{ssl,crypto}.3.dylib; do cp "${python_libdir}/${dylib}" "curl_cffi/.dylibs/${dylib}" for wheel in curl_cffi*macos*x86_64.whl; do zip "${wheel}" "curl_cffi/.dylibs/${dylib}" done done ) python3 -m delocate.cmd.delocate_fuse curl_cffi_whls/curl_cffi*.whl -w curl_cffi_universal2 python3 -m delocate.cmd.delocate_fuse curl_cffi_whls/cffi*.whl -w curl_cffi_universal2 for wheel in curl_cffi_universal2/*cffi*.whl; do mv -n -- "${wheel}" "${wheel/x86_64/universal2}" done python3 -m pip install --force-reinstall -U curl_cffi_universal2/*cffi*.whl - name: Prepare run: | python3 devscripts/update-version.py -c "${CHANNEL}" -r "${ORIGIN}" "${VERSION}" python3 devscripts/make_lazy_extractors.py - name: Build run: | source ~/yt-dlp-build-venv/bin/activate python3 -m bundle.pyinstaller --target-architecture universal2 --onedir (cd ./dist/yt-dlp_macos && zip -r ../yt-dlp_macos.zip .) python3 -m bundle.pyinstaller --target-architecture universal2 - name: Verify --update-to if: vars.UPDATE_TO_VERIFICATION run: | chmod +x ./dist/yt-dlp_macos cp ./dist/yt-dlp_macos ./dist/yt-dlp_macos_downgraded version="$(./dist/yt-dlp_macos --version)" ./dist/yt-dlp_macos_downgraded -v --update-to "${UPDATE_TO}" downgraded_version="$(./dist/yt-dlp_macos_downgraded --version)" [[ "$version" != "$downgraded_version" ]] - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-bin-${{ github.job }} path: | dist/yt-dlp_macos dist/yt-dlp_macos.zip compression-level: 0 windows: needs: process if: inputs.windows permissions: contents: read runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - arch: 'x64' runner: windows-2025 python_version: '3.10' suffix: '' - arch: 'x86' runner: windows-2025 python_version: '3.10' suffix: '_x86' - arch: 'arm64' runner: windows-11-arm python_version: '3.13' # arm64 only has Python >= 3.11 available suffix: '_arm64' env: CHANNEL: ${{ inputs.channel }} ORIGIN: ${{ needs.process.outputs.origin }} VERSION: ${{ needs.process.outputs.version }} SUFFIX: ${{ matrix.suffix }} UPDATE_TO: yt-dlp/yt-dlp@2025.09.05 BASE_CACHE_KEY: cache-reqs-${{ github.job }}_${{ matrix.arch }}-${{ matrix.python_version }} # Use custom PyInstaller built with https://github.com/yt-dlp/Pyinstaller-builds PYINSTALLER_URL: https://yt-dlp.github.io/Pyinstaller-Builds/${{ matrix.arch }}/pyinstaller-6.15.0-py3-none-any.whl steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} architecture: ${{ matrix.arch }} - name: Cache requirements id: cache-venv if: matrix.arch == 'arm64' uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 with: path: | /yt-dlp-build-venv key: ${{ env.BASE_CACHE_KEY }}-${{ github.ref }}-${{ needs.process.outputs.timestamp }} restore-keys: | ${{ env.BASE_CACHE_KEY }}-${{ github.ref }}- ${{ env.BASE_CACHE_KEY }}- - name: Install Requirements env: ARCH: ${{ matrix.arch }} shell: pwsh run: | python -m venv /yt-dlp-build-venv /yt-dlp-build-venv/Scripts/Activate.ps1 python devscripts/install_deps.py -o --include build if ("${Env:ARCH}" -eq "x86") { python devscripts/install_deps.py } else { python devscripts/install_deps.py --include curl-cffi } python -m pip install -U "${Env:PYINSTALLER_URL}" - name: Prepare shell: pwsh run: | python devscripts/update-version.py -c "${Env:CHANNEL}" -r "${Env:ORIGIN}" "${Env:VERSION}" python devscripts/make_lazy_extractors.py - name: Build shell: pwsh run: | /yt-dlp-build-venv/Scripts/Activate.ps1 python -m bundle.pyinstaller python -m bundle.pyinstaller --onedir Compress-Archive -Path ./dist/yt-dlp${Env:SUFFIX}/* -DestinationPath ./dist/yt-dlp_win${Env:SUFFIX}.zip - name: Verify --update-to if: vars.UPDATE_TO_VERIFICATION shell: pwsh run: | $name = "yt-dlp${Env:SUFFIX}" Copy-Item "./dist/${name}.exe" "./dist/${name}_downgraded.exe" $version = & "./dist/${name}.exe" --version & "./dist/${name}_downgraded.exe" -v --update-to "${Env:UPDATE_TO}" $downgraded_version = & "./dist/${name}_downgraded.exe" --version if ($version -eq $downgraded_version) { exit 1 } - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-bin-${{ github.job }}-${{ matrix.arch }} path: | dist/yt-dlp${{ matrix.suffix }}.exe dist/yt-dlp_win${{ matrix.suffix }}.zip compression-level: 0 meta_files: if: always() && !cancelled() needs: - process - unix - linux - linux_armv7l - musllinux - macos - windows runs-on: ubuntu-latest steps: - name: Download artifacts uses: actions/download-artifact@v4 with: path: artifact pattern: build-bin-* merge-multiple: true - name: Make SHA2-SUMS files run: | cd ./artifact/ # make sure SHA sums are also printed to stdout sha256sum -- * | tee ../SHA2-256SUMS sha512sum -- * | tee ../SHA2-512SUMS # also print as permanent annotations to the summary page while read -r shasum; do echo "::notice title=${shasum##* }::sha256: ${shasum% *}" done < ../SHA2-256SUMS - name: Make Update spec run: | cat >> _update_spec << EOF # This file is used for regulating self-update lock 2022.08.18.36 .+ Python 3\.6 lock 2023.11.16 (?!win_x86_exe).+ Python 3\.7 lock 2023.11.16 win_x86_exe .+ Windows-(?:Vista|2008Server) lock 2024.10.22 py2exe .+ lock 2024.10.22 zip Python 3\.8 lock 2024.10.22 win(?:_x86)?_exe Python 3\.[78].+ Windows-(?:7-|2008ServerR2) lock 2025.08.11 darwin_legacy_exe .+ lock 2025.08.27 linux_armv7l_exe .+ lockV2 yt-dlp/yt-dlp 2022.08.18.36 .+ Python 3\.6 lockV2 yt-dlp/yt-dlp 2023.11.16 (?!win_x86_exe).+ Python 3\.7 lockV2 yt-dlp/yt-dlp 2023.11.16 win_x86_exe .+ Windows-(?:Vista|2008Server) lockV2 yt-dlp/yt-dlp 2024.10.22 py2exe .+ lockV2 yt-dlp/yt-dlp 2024.10.22 zip Python 3\.8 lockV2 yt-dlp/yt-dlp 2024.10.22 win(?:_x86)?_exe Python 3\.[78].+ Windows-(?:7-|2008ServerR2) lockV2 yt-dlp/yt-dlp 2025.08.11 darwin_legacy_exe .+ lockV2 yt-dlp/yt-dlp 2025.08.27 linux_armv7l_exe .+ lockV2 yt-dlp/yt-dlp-nightly-builds 2023.11.15.232826 (?!win_x86_exe).+ Python 3\.7 lockV2 yt-dlp/yt-dlp-nightly-builds 2023.11.15.232826 win_x86_exe .+ Windows-(?:Vista|2008Server) lockV2 yt-dlp/yt-dlp-nightly-builds 2024.10.22.051025 py2exe .+ lockV2 yt-dlp/yt-dlp-nightly-builds 2024.10.22.051025 zip Python 3\.8 lockV2 yt-dlp/yt-dlp-nightly-builds 2024.10.22.051025 win(?:_x86)?_exe Python 3\.[78].+ Windows-(?:7-|2008ServerR2) lockV2 yt-dlp/yt-dlp-nightly-builds 2025.08.12.233030 darwin_legacy_exe .+ lockV2 yt-dlp/yt-dlp-nightly-builds 2025.08.30.232839 linux_armv7l_exe .+ lockV2 yt-dlp/yt-dlp-master-builds 2023.11.15.232812 (?!win_x86_exe).+ Python 3\.7 lockV2 yt-dlp/yt-dlp-master-builds 2023.11.15.232812 win_x86_exe .+ Windows-(?:Vista|2008Server) lockV2 yt-dlp/yt-dlp-master-builds 2024.10.22.045052 py2exe .+ lockV2 yt-dlp/yt-dlp-master-builds 2024.10.22.060347 zip Python 3\.8 lockV2 yt-dlp/yt-dlp-master-builds 2024.10.22.060347 win(?:_x86)?_exe Python 3\.[78].+ Windows-(?:7-|2008ServerR2) lockV2 yt-dlp/yt-dlp-master-builds 2025.08.12.232447 darwin_legacy_exe .+ lockV2 yt-dlp/yt-dlp-master-builds 2025.09.05.212910 linux_armv7l_exe .+ EOF - name: Sign checksum files env: GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} if: env.GPG_SIGNING_KEY run: | gpg --batch --import <<< "${{ secrets.GPG_SIGNING_KEY }}" for signfile in ./SHA*SUMS; do gpg --batch --detach-sign "$signfile" done - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-${{ github.job }} path: | _update_spec SHA*SUMS* compression-level: 0 overwrite: true