Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 44 additions & 65 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,7 @@ jobs:
libegl1 libopengl0 libxkbcommon-x11-0 \
libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 \
ffmpeg

- name: Detect FFmpeg version (Linux)
if: matrix.os == 'ubuntu-latest'
id: ffmpeg-version-linux
shell: bash
run: |
version="$(ffmpeg -version | head -n1 | awk '{print $3}')"
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Cache bundled FFmpeg (Linux)
if: matrix.os == 'ubuntu-latest'
id: cache-ffmpeg-linux
uses: actions/cache@v4
with:
path: vendor/ffmpeg/linux
key: ffmpeg-${{ matrix.os }}-${{ runner.image_version }}-${{ steps.ffmpeg-version-linux.outputs.version }}-bundle

- name: Bundle FFmpeg (Linux)
if: matrix.os == 'ubuntu-latest' && steps.cache-ffmpeg-linux.outputs.cache-hit != 'true'
run: |
mkdir -p vendor/ffmpeg/linux
cp "$(command -v ffmpeg)" vendor/ffmpeg/linux/ffmpeg
chmod +x vendor/ffmpeg/linux/ffmpeg
zip

- name: Cache bundled FFmpeg (Windows)
if: matrix.os == 'windows-latest'
Expand Down Expand Up @@ -128,46 +105,9 @@ jobs:
cd "$repo_root"
./scripts/build_ffmpeg_windows_msys2.sh

- name: Detect FFmpeg version (macOS)
if: matrix.os == 'macos-latest'
id: ffmpeg-version-macos
shell: bash
run: |
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_NO_ENV_HINTS=1
brew_info="$(brew info --json=v2 ffmpeg 2>/dev/null || true)"
version="$(BREW_INFO="$brew_info" python - <<'PY'
import json
import os
data = os.environ.get("BREW_INFO", "").strip()
if not data:
print("unknown")
else:
try:
parsed = json.loads(data)
print(parsed["formulae"][0]["versions"]["stable"])
except Exception:
print("unknown")
PY
)"
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Cache bundled FFmpeg (macOS)
if: matrix.os == 'macos-latest'
id: cache-ffmpeg-macos
uses: actions/cache@v4
with:
path: vendor/ffmpeg/macos
key: ffmpeg-${{ matrix.os }}-${{ runner.image_version }}-${{ steps.ffmpeg-version-macos.outputs.version }}-bundle

- name: Bundle FFmpeg (macOS)
if: matrix.os == 'macos-latest' && steps.cache-ffmpeg-macos.outputs.cache-hit != 'true'
run: |
brew install ffmpeg
mkdir -p vendor/ffmpeg/macos
cp "$(brew --prefix)/bin/ffmpeg" vendor/ffmpeg/macos/ffmpeg
chmod +x vendor/ffmpeg/macos/ffmpeg
- name: Stage portable FFmpeg (Linux/macOS)
if: matrix.os != 'windows-latest'
run: uv --native-tls run --extra packaging python scripts/stage_portable_ffmpeg.py

- name: Install dependencies
run: uv pip install --system -e . pyinstaller
Expand All @@ -180,9 +120,48 @@ jobs:
shell: bash
run: |
out="dist/RenderKit-${{ steps.meta.outputs.os_label }}${{ steps.meta.outputs.artifact_suffix }}.zip"
python -m zipfile -c "$out" dist/RenderKit
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
python -m zipfile -c "$out" dist/RenderKit
else
zip -qry "$out" dist/RenderKit
fi
echo "package_path=$out" >> "$GITHUB_OUTPUT"

- name: Smoke test bundled FFmpeg
shell: pwsh
run: |
$zipPath = "${{ steps.package.outputs.package_path }}"
$extractRoot = Join-Path $env:RUNNER_TEMP "renderkit-pyinstaller-smoke"
if (Test-Path $extractRoot) {
Remove-Item $extractRoot -Recurse -Force
}
New-Item -ItemType Directory -Path $extractRoot -Force | Out-Null
Expand-Archive -Path $zipPath -DestinationPath $extractRoot -Force

$ffmpegName = if ($IsWindows) { "ffmpeg.exe" } else { "ffmpeg" }
$escapedFfmpegName = [regex]::Escape($ffmpegName)
$ffmpeg = Get-ChildItem -Path $extractRoot -Recurse -File -Filter $ffmpegName |
Where-Object { $_.FullName -match "[/\\]ffmpeg[/\\]$escapedFfmpegName$" } |
Select-Object -First 1
if ($null -eq $ffmpeg) {
throw "Bundled FFmpeg was not found in $zipPath"
}

& $ffmpeg.FullName -version
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}

$encoders = & $ffmpeg.FullName -hide_banner -encoders
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
foreach ($encoder in @("libx264", "libx265", "libaom-av1")) {
if (-not ($encoders -match $encoder)) {
throw "Bundled FFmpeg is missing required encoder: $encoder"
}
}

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
Expand Down
103 changes: 91 additions & 12 deletions .github/workflows/nuitka-standalone-zip.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
name: Nuitka Standalone Zip

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:

permissions:
contents: read

env:
FFMPEG_VERSION: "8.0.1"

jobs:
build-windows:
name: Build Nuitka Standalone Zip (Windows)
runs-on: windows-latest
build:
name: Build Nuitka Standalone Zip (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
platform: windows
- os: ubuntu-latest
platform: linux
- os: macos-latest
platform: macos
steps:
- uses: actions/checkout@v4

Expand All @@ -24,15 +41,31 @@ jobs:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Cache bundled FFmpeg
- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libopenexr-dev libilmbase-dev \
libegl1 libopengl0 libxkbcommon-x11-0 \
libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 \
patchelf zip

- name: Stage portable FFmpeg (Linux/macOS)
if: matrix.os != 'windows-latest'
run: uv --native-tls run --extra packaging python scripts/stage_portable_ffmpeg.py

- name: Cache bundled FFmpeg (Windows)
if: matrix.os == 'windows-latest'
id: cache-ffmpeg
uses: actions/cache@v4
with:
path: vendor/ffmpeg/windows
key: ffmpeg-windows-8.0.1-${{ hashFiles('scripts/build_ffmpeg_windows_msys2.sh', 'scripts/build_ffmpeg_windows_msys2_bootstrap.sh') }}
key: ffmpeg-nuitka-${{ matrix.os }}-${{ env.FFMPEG_VERSION }}-${{ hashFiles('scripts/build_ffmpeg_windows_msys2.sh', 'scripts/build_ffmpeg_windows_msys2_bootstrap.sh') }}

- name: Set up MSYS2
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
- name: Set up MSYS2 (Windows)
if: matrix.os == 'windows-latest' && steps.cache-ffmpeg.outputs.cache-hit != 'true'
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
Expand All @@ -47,9 +80,11 @@ jobs:
mingw-w64-ucrt-x86_64-x265
mingw-w64-ucrt-x86_64-aom

- name: Build FFmpeg
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
- name: Build FFmpeg (Windows)
if: matrix.os == 'windows-latest' && steps.cache-ffmpeg.outputs.cache-hit != 'true'
shell: msys2 {0}
env:
FFMPEG_VERSION: ${{ env.FFMPEG_VERSION }}
run: |
set -euo pipefail
repo_root="$(cygpath -u "$GITHUB_WORKSPACE")"
Expand All @@ -58,10 +93,54 @@ jobs:

- name: Build Nuitka artifact
shell: pwsh
run: .\scripts\build_nuitka.ps1
run: ./scripts/build_nuitka.ps1

- name: Verify Nuitka artifact
shell: pwsh
run: |
$zipPath = "dist-nuitka/RenderKit-nuitka-${{ matrix.platform }}-standalone.zip"
if (-not (Test-Path $zipPath)) {
throw "Expected Nuitka artifact was not created: $zipPath"
}
Get-Item $zipPath

- name: Smoke test bundled FFmpeg
shell: pwsh
run: |
$zipPath = "dist-nuitka/RenderKit-nuitka-${{ matrix.platform }}-standalone.zip"
$extractRoot = Join-Path $env:RUNNER_TEMP "renderkit-nuitka-smoke"
if (Test-Path $extractRoot) {
Remove-Item $extractRoot -Recurse -Force
}
New-Item -ItemType Directory -Path $extractRoot -Force | Out-Null
Expand-Archive -Path $zipPath -DestinationPath $extractRoot -Force

$ffmpegName = if ($IsWindows) { "ffmpeg.exe" } else { "ffmpeg" }
$escapedFfmpegName = [regex]::Escape($ffmpegName)
$ffmpeg = Get-ChildItem -Path $extractRoot -Recurse -File -Filter $ffmpegName |
Where-Object { $_.FullName -match "[/\\]ffmpeg[/\\]$escapedFfmpegName$" } |
Select-Object -First 1
if ($null -eq $ffmpeg) {
throw "Bundled FFmpeg was not found in $zipPath"
}

& $ffmpeg.FullName -version
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}

$encoders = & $ffmpeg.FullName -hide_banner -encoders
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
foreach ($encoder in @("libx264", "libx265", "libaom-av1")) {
if (-not ($encoders -match $encoder)) {
throw "Bundled FFmpeg is missing required encoder: $encoder"
}
}

- name: Upload Nuitka artifact
uses: actions/upload-artifact@v4
with:
name: RenderKit-nuitka-windows-standalone
path: dist-nuitka/RenderKit-nuitka-windows-standalone.zip
name: RenderKit-nuitka-${{ matrix.platform }}-standalone
path: dist-nuitka/RenderKit-nuitka-${{ matrix.platform }}-standalone.zip
25 changes: 19 additions & 6 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,23 @@ release-style zip is written to `dist-nuitka/RenderKit-nuitka-<platform>-standal
Before using the Nuitka zip for a release, test it by extracting the zip on the
target platform and running a real conversion.

## Bundled FFmpeg (Windows, Hybrid)
## Bundled FFmpeg

The repo does not commit `vendor/ffmpeg/`. You can build and stage a minimal
GPL FFmpeg locally (x265 + AV1 only), and CI will also generate it for releases.
The repo does not commit `vendor/ffmpeg/`. Packaging workflows stage FFmpeg
into `vendor/ffmpeg/<platform>/` before building artifacts, and the packaged
app uses that binary ahead of any system `ffmpeg` on `PATH`.

Linux and macOS use the packaging extra's portable `imageio-ffmpeg` binary:

```bash
uv --native-tls run --extra packaging python scripts/stage_portable_ffmpeg.py
```

The staging helper verifies the binary starts and includes the required
`libx264`, `libx265`, and `libaom-av1` encoders.

Windows uses a minimal GPL FFmpeg build from MSYS2 so the required DLLs can be
copied alongside `ffmpeg.exe`.

### Prerequisites (MSYS2 UCRT64)

Expand All @@ -106,9 +119,9 @@ pacman -S --needed \
./scripts/build_ffmpeg_windows_msys2.sh
```

This script writes `ffmpeg.exe` and required DLLs to `vendor/ffmpeg/`, which the
PyInstaller spec bundles automatically. To build a different version, set
`FFMPEG_VERSION` (default: 8.0.1):
This script writes `ffmpeg.exe` and required DLLs to `vendor/ffmpeg/windows/`,
which the packaging workflows bundle automatically. To build a different
version, set `FFMPEG_VERSION` (default: 8.0.1):

```bash
FFMPEG_VERSION=8.0.1 ./scripts/build_ffmpeg_windows_msys2.sh
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ docs = [
]

packaging = [
"imageio-ffmpeg>=0.6.0,<0.7",
"nuitka>=4.1.0",
"ordered-set>=4.1.0",
"pyinstaller>=6.0.0",
Expand Down
Loading
Loading