From 235ef8926e5f2bac5cd1075b98b48bc03328b047 Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Tue, 9 Jun 2026 23:00:28 +1000 Subject: [PATCH 1/6] Build TTempSmooth (ttmpsm) plugin for Linux deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit havsfunc's MCTemporalDenoise (used by the noise-reduction filter) calls core.ttmpsm.TTempSmooth, which wasn't built for Linux, so jobs using it crashed with "No attribute with the name ttmpsm". Add it via the existing build_plugin helper (meson, HomeOfVapourSynthEvolution/VapourSynth-TTempSmooth). Verified: the built plugin loads as core.ttmpsm and MCTemporalDenoise runs. Note: ttmpsm is also absent from the published macOS and Windows deps, and macOS additionally lacks fft3dfilter (which MCTemporalDenoise also needs) — so that filter is currently broken on those platforms too (tracked separately). Co-Authored-By: Claude Opus 4.8 (1M context) --- Scripts/download-deps-linux.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Scripts/download-deps-linux.sh b/Scripts/download-deps-linux.sh index 30c9da5..c90a08c 100755 --- a/Scripts/download-deps-linux.sh +++ b/Scripts/download-deps-linux.sh @@ -617,6 +617,12 @@ build_plugin "dfttest" \ "libdfttest.so" \ "$PLUGIN_BUILD_ENV meson setup build --buildtype=release && ninja -C build" +# TTempSmooth (core.ttmpsm.TTempSmooth - used by havsfunc MCTemporalDenoise) +build_plugin "ttempsmooth" \ + "https://github.com/HomeOfVapourSynthEvolution/VapourSynth-TTempSmooth.git" \ + "libttempsmooth.so" \ + "$PLUGIN_BUILD_ENV meson setup build --buildtype=release && ninja -C build" + # FFT3DFilter build_plugin "fft3dfilter" \ "https://github.com/myrsloik/VapourSynth-FFT3DFilter.git" \ From 23799f36edde0c221807f14bd1a9e99a1f95717a Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Tue, 9 Jun 2026 23:10:20 +1000 Subject: [PATCH 2/6] Add TTempSmooth (+ macOS FFT3DFilter); unify deps to 1.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit havsfunc MCTemporalDenoise needs core.ttmpsm.TTempSmooth and core.fft3dfilter.FFT3DFilter. Checking the published deps zips showed ttmpsm absent on every platform, and macOS additionally missing fft3dfilter — so the noise-reduction filter was broken everywhere. - Windows: add TTempSmooth (prebuilt r4.1 win64 7z); fft3dfilter already present. - macOS: add TTempSmooth + FFT3DFilter — arm64 from the yuygfgg prebuilt pool (mirrors dfttest, with the FFTW install-name fixup), x86_64 built from source via meson (mirrors descratch/nnedi3cl; FFT3DFilter repointed at the bundled FFTW like dfttest). - Linux already adds ttmpsm (prior commit); has fft3dfilter. Unify all platforms back to deps 1.4.0 (deps-v1.4.0) with null sha/size pending a rebuild — every platform's deps change, and 1.4.0 hasn't shipped in an app release yet, so the per-platform version split is dropped. NOTE: the macOS build steps are CI/maintainer-validated (build-deps-macos.yml) — they couldn't be built/tested on the Linux dev machine. Co-Authored-By: Claude Opus 4.8 (1M context) --- Scripts/download-deps-macos.sh | 58 +++++++++++++++++++++++++++++++ Scripts/download-deps-windows.ps1 | 6 ++++ app/assets/deps-version.json | 36 +++++++++---------- 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/Scripts/download-deps-macos.sh b/Scripts/download-deps-macos.sh index a75cc7c..643ef9b 100755 --- a/Scripts/download-deps-macos.sh +++ b/Scripts/download-deps-macos.sh @@ -401,6 +401,17 @@ if [ "$ARCH" = "arm64" ]; then install_name_tool -change "@rpath/libfftw3f_threads.3.dylib" "@loader_path/../../lib/libfftw3f_threads.3.dylib" "$PLUGINS_DIR/libdfttest.dylib" codesign -s - -f "$PLUGINS_DIR/libdfttest.dylib" 2>/dev/null + # TTempSmooth (core.ttmpsm.TTempSmooth - used by havsfunc MCTemporalDenoise) + curl -sL "$YUYGFGG_BASE/lib/libttempsmooth.dylib" -o "$PLUGINS_DIR/libttempsmooth.dylib" + install_name_tool -id "@loader_path/libttempsmooth.dylib" "$PLUGINS_DIR/libttempsmooth.dylib" 2>/dev/null || true + codesign -s - -f "$PLUGINS_DIR/libttempsmooth.dylib" 2>/dev/null + + # FFT3DFilter (also used by MCTemporalDenoise; links FFTW like dfttest) + curl -sL "$YUYGFGG_BASE/lib/libfft3dfilter.dylib" -o "$PLUGINS_DIR/libfft3dfilter.dylib" + install_name_tool -change "@rpath/libfftw3f.3.dylib" "@loader_path/../../lib/libfftw3f.3.dylib" "$PLUGINS_DIR/libfft3dfilter.dylib" 2>/dev/null || true + install_name_tool -change "@rpath/libfftw3f_threads.3.dylib" "@loader_path/../../lib/libfftw3f_threads.3.dylib" "$PLUGINS_DIR/libfft3dfilter.dylib" 2>/dev/null || true + codesign -s - -f "$PLUGINS_DIR/libfft3dfilter.dylib" 2>/dev/null + # VIVTC (inverse telecine - VFM + VDecimate) curl -sL "$YUYGFGG_BASE/lib/libvivtc.dylib" -o "$PLUGINS_DIR/libvivtc.dylib" install_name_tool -id "@loader_path/libvivtc.dylib" "$PLUGINS_DIR/libvivtc.dylib" @@ -657,6 +668,53 @@ PYEOF fi cd "$BUILD_DIR" fi + + # ttmpsm (TTempSmooth - meson; finds VapourSynth via pkg-config, no extra deps) + # Used by havsfunc MCTemporalDenoise. + if [ "$FORCE" = true ] || [ ! -f "$PLUGINS_DIR/libttempsmooth.dylib" ]; then + echo ""; echo "=== Building TTempSmooth (x86_64) ===" + rm -rf ttempsmooth + if git clone --depth 1 https://github.com/HomeOfVapourSynthEvolution/VapourSynth-TTempSmooth.git ttempsmooth \ + && PKG_CONFIG_PATH="$VS_PC_DIR:${PKG_CONFIG_PATH:-}" \ + meson setup ttempsmooth/build ttempsmooth --buildtype=release \ + && ninja -C ttempsmooth/build; then + lib=$(find ttempsmooth/build -name "*.dylib" -type f | head -1) + if [ -n "$lib" ]; then + cp "$lib" "$PLUGINS_DIR/libttempsmooth.dylib" + install_name_tool -id "@loader_path/libttempsmooth.dylib" "$PLUGINS_DIR/libttempsmooth.dylib" 2>/dev/null || true + codesign -s - -f "$PLUGINS_DIR/libttempsmooth.dylib" 2>/dev/null || true + echo " Built TTempSmooth" + else echo " no dylib"; FAILED_PLUGINS+=("ttempsmooth"); fi + else + echo " Failed to build TTempSmooth"; FAILED_PLUGINS+=("ttempsmooth") + fi + cd "$BUILD_DIR" + fi + + # fft3dfilter (meson; links Homebrew FFTW, repoint at the bundled lib/ copy). + # Also used by havsfunc MCTemporalDenoise. + if [ "$FORCE" = true ] || [ ! -f "$PLUGINS_DIR/libfft3dfilter.dylib" ]; then + echo ""; echo "=== Building FFT3DFilter (x86_64) ===" + rm -rf fft3dfilter + if git clone --depth 1 https://github.com/myrsloik/VapourSynth-FFT3DFilter.git fft3dfilter \ + && PKG_CONFIG_PATH="$VS_PC_DIR:$BREW_PREFIX/lib/pkgconfig:${PKG_CONFIG_PATH:-}" \ + meson setup fft3dfilter/build fft3dfilter --buildtype=release \ + && ninja -C fft3dfilter/build; then + lib=$(find fft3dfilter/build -name "*.dylib" -type f | head -1) + if [ -n "$lib" ]; then + cp "$lib" "$PLUGINS_DIR/libfft3dfilter.dylib" + install_name_tool -change "$BREW_PREFIX/opt/fftw/lib/libfftw3f.3.dylib" \ + "@loader_path/../../lib/libfftw3f.3.dylib" "$PLUGINS_DIR/libfft3dfilter.dylib" 2>/dev/null || true + install_name_tool -change "$BREW_PREFIX/opt/fftw/lib/libfftw3f_threads.3.dylib" \ + "@loader_path/../../lib/libfftw3f_threads.3.dylib" "$PLUGINS_DIR/libfft3dfilter.dylib" 2>/dev/null || true + codesign -s - -f "$PLUGINS_DIR/libfft3dfilter.dylib" 2>/dev/null || true + echo " Built FFT3DFilter" + else echo " no dylib"; FAILED_PLUGINS+=("fft3dfilter"); fi + else + echo " Failed to build FFT3DFilter"; FAILED_PLUGINS+=("fft3dfilter") + fi + cd "$BUILD_DIR" + fi else # MVTools (essential for QTGMC motion compensation) build_plugin "mvtools" \ diff --git a/Scripts/download-deps-windows.ps1 b/Scripts/download-deps-windows.ps1 index f6763c7..2b7dc33 100644 --- a/Scripts/download-deps-windows.ps1 +++ b/Scripts/download-deps-windows.ps1 @@ -207,6 +207,12 @@ $Plugins7z = @( Url = "https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest/releases/download/r7/DFTTest-r7.7z" Check = "DFTTest.dll" }, + @{ + # core.ttmpsm.TTempSmooth - used by havsfunc MCTemporalDenoise + Name = "ttempsmooth" + Url = "https://github.com/HomeOfVapourSynthEvolution/VapourSynth-TTempSmooth/releases/download/r4.1/TTempSmooth-r4.1-win64.7z" + Check = "TTempSmooth.dll" + }, @{ Name = "neo_f3kdb" Url = "https://github.com/HomeOfAviSynthPlusEvolution/neo_f3kdb/releases/download/r10/neo_f3kdb_r10.7z" diff --git a/app/assets/deps-version.json b/app/assets/deps-version.json index 8889e19..6324e1e 100644 --- a/app/assets/deps-version.json +++ b/app/assets/deps-version.json @@ -1,36 +1,32 @@ { - "version": "1.3.0", - "releaseTag": "deps-v1.3.0", - "releaseDate": "2026-04-07", + "version": "1.4.0", + "releaseTag": "deps-v1.4.0", + "releaseDate": "2026-06-09", "platforms": { "windows-x64": { - "filename": "VapourBox-deps-1.3.0-windows-x64.zip", - "sha256": "b404bc4434b47116dc8acb152c139327c7d59fa9eee4e30a8d8b1a1f09b0799d", - "size": 172721085 + "filename": "VapourBox-deps-1.4.0-windows-x64.zip", + "sha256": null, + "size": null }, "macos-arm64": { - "filename": "VapourBox-deps-1.3.0-macos-arm64.zip", - "sha256": "959ce05ad67449ca2b680add0092a3a117c9a3f78e3f474e80d6bc49d6a110d3", - "size": 98931909 + "filename": "VapourBox-deps-1.4.0-macos-arm64.zip", + "sha256": null, + "size": null }, "macos-x64": { - "filename": "VapourBox-deps-1.3.0-macos-x64.zip", - "sha256": "a326b662e34bb737269f6792b4b1b8518d8512bb05046cf430632a0213202abf", - "size": 106078101 + "filename": "VapourBox-deps-1.4.0-macos-x64.zip", + "sha256": null, + "size": null }, "linux-x64": { - "version": "1.4.0", - "releaseTag": "deps-v1.4.0", "filename": "VapourBox-deps-1.4.0-linux-x64.zip", - "sha256": "2c3e7cf458d5443fe827242e5bd8ce12a47db57aa19f1d51af248ad80f1a519c", - "size": 191028656 + "sha256": null, + "size": null }, "linux-arm64": { - "version": "1.4.0", - "releaseTag": "deps-v1.4.0", "filename": "VapourBox-deps-1.4.0-linux-arm64.zip", - "sha256": "45986e0d70acd0b93a59aecfd6a493716ccfe963608194e1cfaabc948680fbd8", - "size": 169664443 + "sha256": null, + "size": null } }, "githubRepo": "StuartCameronCode/VapourBox" From 860f4f040b8f847093dd75ab5025b22396486678 Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Wed, 10 Jun 2026 15:24:11 +1000 Subject: [PATCH 3/6] docs: handoff plan for the macOS arm64 deps build breakage A macOS instance can build the deps locally (the Linux dev box can't). Captures the pre-existing macos-15 runner env drift (Python 3.14 breaking mvtools/znedi3 VS-include probes), evidence, where to look, and a step-by-step fix/verify plan. The TTempSmooth/FFT3DFilter additions are validated on Linux and macOS x64; this unblocks the separate arm64 infra failure. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/macos-arm64-deps-build-handoff.md | 99 ++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/macos-arm64-deps-build-handoff.md diff --git a/docs/macos-arm64-deps-build-handoff.md b/docs/macos-arm64-deps-build-handoff.md new file mode 100644 index 0000000..3e297cd --- /dev/null +++ b/docs/macos-arm64-deps-build-handoff.md @@ -0,0 +1,99 @@ +# Handoff: Fix the macOS arm64 dependency build + +**For:** a Claude Code instance running on a **macOS (Apple Silicon)** machine — you can build the deps locally, which the Linux dev box could not. + +**Goal:** get `build-deps-macos.yml` (and `./Scripts/download-deps-macos.sh`) producing a complete **macOS arm64** dependency bundle again, with **all** plugins built — including the newly added `ttmpsm` (TTempSmooth) and `fft3dfilter`, which `havsfunc.MCTemporalDenoise` needs. + +--- + +## Background / why this exists + +A user hit a crash: `No attribute with the name ttmpsm` — `MCTemporalDenoise` calls `core.ttmpsm.TTempSmooth` (and `core.fft3dfilter.FFT3DFilter`), which were missing from the shipped deps. We verified by inspecting the published deps zips: + +- `ttmpsm` was missing on **all** platforms. +- macOS additionally lacked **`fft3dfilter`**. + +Fixes were added on branch **`fix-ttempsmooth-plugin`** (PR pending): +- **Linux** — `ttmpsm` via `build_plugin` in `download-deps-linux.sh`. ✅ Verified building in CI. +- **Windows** — `ttmpsm` prebuilt (`TTempSmooth-r4.1-win64.7z`) in `download-deps-windows.ps1`. `fft3dfilter` already present. +- **macOS** — `ttmpsm` + `fft3dfilter` added to `download-deps-macos.sh`: + - **arm64**: downloaded as prebuilt dylibs from the `yuygfgg/Macos_vapoursynth_plugins` pool (mirrors the existing `dfttest` handling, with the FFTW `install_name_tool` repoint). + - **x86_64**: built from source via meson (mirrors `descratch`/`nnedi3cl`). ✅ **macOS x64 CI build succeeded** — `Built TTempSmooth` and `Built FFT3DFilter`. + +deps version was unified to **1.4.0** (`deps-v1.4.0`) across all platforms (`app/assets/deps-version.json`, sha256/size currently `null` pending a clean rebuild). + +## The actual problem to fix + +The **macОS arm64** job of `build-deps-macos.yml` **fails**, but **not** because of the TTempSmooth/FFT3DFilter additions — those run earlier (prebuilt downloads) and aren't implicated. It fails on **pre-existing** plugin builds, due to **runner environment drift**: + +- The `macos-15` runner now ships **Homebrew Python 3.14**. +- `mvtools` (meson) fails: + ``` + meson.build:14:4: ERROR: Command + `/opt/homebrew/opt/python@3.14/bin/python3.14 -c 'import vapoursynth as vs; print(vs.get_include())'` + failed with status 1. + ModuleNotFoundError: No module named 'vapoursynth' + ``` + i.e. the plugin's meson uses a runtime `import vapoursynth` to locate VS headers, and the runner's python has no `vapoursynth` module. +- `znedi3` (make) fails: `vsznedi3/vsznedi3.cpp:7:10: fatal error: 'znedi3.h' file not found`. + +This is **not** caused by our change — recent `main` builds of `build-deps-macos.yml` were **also failing** (2026-06-08 had multiple failures). It's environmental and predates the branch. + +### Evidence / references +- Failing run (branch `fix-ttempsmooth-plugin`): `build-deps-macos.yml` run **27208818617** — `build-x64` ✅, `build-arm64` ❌. +- The matching **Linux** run **27208815698** is fully green (so the cross-platform plugin work itself is sound). +- `gh run list --workflow build-deps-macos.yml --limit 8` shows pre-branch failures on `main`. + +## Where things live +- Script: `Scripts/download-deps-macos.sh` + - `build_plugin()` helper (clones + `eval "$build_cmd"`, copies first `*.dylib`). + - arm64 prebuilt block (yuygfgg) — where `dfttest`/`ttmpsm`/`fft3dfilter` are pulled. + - x86_64 source-build block — `neo-f3kdb`, `nnedi3cl`, `vivtc`, `descratch`, and now `ttmpsm`/`fft3dfilter`. + - **Template for the fix already in the script:** the `vivtc` build patches its `meson.build` to replace the `run_command(python … vs.get_include())` probe with the VS headers path (search for `VS_INC_DIR` / the inline `python3 - vivtc/meson.build <<'PYEOF'` heredoc). `mvtools`/`znedi3` need the equivalent. +- Workflow: `.github/workflows/build-deps-macos.yml` (arm64 native on `macos-15`; `workflow_dispatch` with `version`, `release_tag`, `arch`). + +## Suggested investigation / fix steps + +1. **Reproduce locally** on the Apple Silicon machine: + ```bash + ./Scripts/download-deps-macos.sh --force + ``` + Watch for the `mvtools` and `znedi3` failures (and note any others — the build is flaky; `nnedi3`/`bm3d` have also failed in CI). + +2. **mvtools (and any meson plugin using the python VS-include probe):** make the build locate VapourSynth headers without `import vapoursynth`. Options, in order of preference: + - Pass the built VS via pkg-config: set `PKG_CONFIG_PATH` to the VS install's `lib/pkgconfig` for the `build_plugin` meson invocations, if the plugin's `meson.build` prefers `dependency('vapoursynth')`. + - Otherwise, **patch the plugin's `meson.build`** to hardcode the VS include dir — reuse the exact approach already used for `vivtc` in this script. Consider extending `build_plugin()` to apply this patch generically. + +3. **znedi3 (`znedi3.h` not found):** ensure the build clones submodules / sees its headers. Check the znedi3 build invocation in the script (it's a `make`-based build); the header is part of the repo/submodule — confirm `--recurse-submodules` and the include path. + +4. **Re-run `./Scripts/download-deps-macos.sh --force` until it completes with an empty/expected `FAILED_PLUGINS` list.** Important: `build_plugin` failures are **non-fatal** (the job can report "success" while plugins silently fail), so check the **`Failed plugins:` summary line**, not just the exit code. + +5. **Verify the new plugins actually load** (and the reported bug is fixed) on arm64: + ```python + import vapoursynth as vs + c = vs.core + print(c.ttmpsm.TTempSmooth) + print(c.fft3dfilter.FFT3DFilter) + import havsfunc as haf + clip = c.std.BlankClip(width=160, height=120, format=vs.YUV422P8, length=20, fpsnum=25, fpsden=1) + haf.MCTemporalDenoise(clip, settings='medium').get_frame(5) # must not raise + ``` + (Run with the bundled-deps environment: `PYTHONHOME`/`PYTHONPATH`/`VAPOURSYNTH_PLUGIN_PATH`/`DYLD_LIBRARY_PATH`/`VAPOURSYNTH_CONF_PATH` per `worker/src/dependency_locator.rs` macOS branch.) + +6. **Confirm the FFTW dylib path resolves at runtime for `fft3dfilter`** (the arm64 yuygfgg dylib and the x64 source build are repointed at `@loader_path/../../lib/libfftw3f.3.dylib`). The CI build only proves it *compiles*; loading `core.fft3dfilter` is the real check — do it in step 5. + +7. **Run CI** to confirm: + ```bash + gh workflow run build-deps-macos.yml --ref fix-ttempsmooth-plugin -f version=1.4.0 -f arch=both + ``` + Both `build-arm64` and `build-x64` should be green and ship all plugins. + +## Definition of done +- `build-deps-macos.yml` green for **both** arm64 and x64 on `fix-ttempsmooth-plugin`, with no unexpected `FAILED_PLUGINS`. +- `core.ttmpsm` and `core.fft3dfilter` load on macOS arm64 and `MCTemporalDenoise` runs. +- macОS deps published at `deps-v1.4.0`; fill `app/assets/deps-version.json` → `macos-arm64`/`macos-x64` `sha256`+`size`. + +## Notes / gotchas +- Don't re-add TTempSmooth/FFT3DFilter — they're already in the script; the work is unblocking the **pre-existing** `mvtools`/`znedi3` (and possibly `nnedi3`/`bm3d`) builds on the current runner image. +- The same Python-3.14 `import vapoursynth` probe issue may eventually hit other meson plugins — a generic fix in `build_plugin()` is preferable to per-plugin patches. +- macОS deps build is **flaky** in general; treat a single green run as necessary-but-confirm by checking the `Failed plugins:` line. From 4e7cc974c2df4253c562b44a2c0f2aa811770169 Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Wed, 10 Jun 2026 21:14:06 +1000 Subject: [PATCH 4/6] Fix macOS arm64 deps build on clean runners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macОS arm64 plugin builds failed on current macos-15 images (host Homebrew Python 3.14 has no `vapoursynth` module) and on pre-existing issues unrelated to the TTempSmooth/FFT3DFilter additions: - build_plugin: patch the meson `import vapoursynth; vs.get_include()` probe to the from-source VS include dir (mvtools, bm3d, eedi3m), and export PKG_CONFIG_PATH so `dependency('vapoursynth')` resolves (removegrain, cas, nnedi3cl, nnedi3 configure). VS_PC_DIR/VS_INC_DIR are now computed for both arches. - znedi3: clone --recurse-submodules (graphengine + vsxx carry the headers); without them: "'znedi3.h' file not found". - nnedi3: strip the hardcoded -mfpu=neon (clang rejects it on arm64; NEON is baseline on aarch64). - fft3dfilter: arm64 uses the yuygfgg prebuilt; guard the source build to non-arm64 (it needs fftw3f_threads). - Add a repoint pass before signing: rewrite plugins' absolute Homebrew fftw/boost references to the bundled @loader_path/../../lib copies and normalize each plugin's install id, so plugins load on users' Macs. Verified locally on Apple Silicon: 25/25 plugins build, no failed plugins, all refs bundled, and MCTemporalDenoise renders (ttmpsm / fft3dfilter / mvtools all load). Co-Authored-By: Claude Opus 4.8 (1M context) --- Scripts/download-deps-macos.sh | 89 ++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/Scripts/download-deps-macos.sh b/Scripts/download-deps-macos.sh index 643ef9b..56d50b8 100755 --- a/Scripts/download-deps-macos.sh +++ b/Scripts/download-deps-macos.sh @@ -486,7 +486,27 @@ build_plugin() { cd "$name" - if eval "$build_cmd"; then + # Some plugins' meson.build locates the VapourSynth headers by running + # `import vapoursynth as vs; print(vs.get_include())` in the host python + # (e.g. mvtools, bm3d, eedi3m). On a clean build machine the host python has + # no `vapoursynth` module (the runtime VS module is built against our + # embedded python, not the host's), so that probe fails. Replace it with the + # from-source VS include dir - same trick already used for vivtc below. + if [ -n "$VS_INC_DIR" ] && [ -f meson.build ] && grep -q "import vapoursynth" meson.build; then + VS_INC_DIR="$VS_INC_DIR" python3 - meson.build <<'PYEOF' +import os, re, sys +path = sys.argv[1] +inc = os.environ["VS_INC_DIR"] +s = open(path).read() +s = re.sub(r"run_command\(.*?\)\.stdout\(\)\.strip\(\)", repr(inc), s, flags=re.S) +open(path, "w").write(s) +PYEOF + fi + + # Expose the from-source VapourSynth to pkg-config so plugins that use + # `dependency('vapoursynth')` (removegrain, cas, ...) resolve against our + # R73 build rather than failing or finding a mismatched system install. + if PKG_CONFIG_PATH="${VS_PC_DIR:-}:${PKG_CONFIG_PATH:-}" eval "$build_cmd"; then # Find the built library local lib_path=$(find . -name "*.dylib" -type f 2>/dev/null | head -1) if [ -n "$lib_path" ]; then @@ -537,6 +557,13 @@ download_prebuilt_plugin() { STEFANOLT="https://github.com/Stefan-Olt/vs-plugin-build/releases/download/vsplugin" +# pkg-config dir + header dir of the from-source VapourSynth install. Used by the +# plugin builds on BOTH arches (see build_plugin and the x86_64 source builds): +# the host python has no `vapoursynth` module on a clean runner, so meson plugins +# can't probe it the usual way and must be pointed at these explicitly. +VS_PC_DIR="$VS_INSTALL_DIR/lib/pkgconfig" +VS_INC_DIR="$(dirname "$(find "$VS_INSTALL_DIR/include" -name 'VapourSynth4.h' 2>/dev/null | head -1)")" + if [ "$ARCH" = "x86_64" ]; then # ======================================================================== # x86_64 plugins: download pre-built darwin-x86_64 binaries from @@ -581,8 +608,6 @@ if [ "$ARCH" = "x86_64" ]; then # ---- The four plugins Stefan-Olt does not ship: build from source ---- cd "$BUILD_DIR" - VS_PC_DIR="$VS_INSTALL_DIR/lib/pkgconfig" - VS_INC_DIR="$(dirname "$(find "$VS_INSTALL_DIR/include" -name 'VapourSynth4.h' 2>/dev/null | head -1)")" # neo-f3kdb (cmake; uses its bundled VapourSynth headers, VCL2 submodule) if [ "$FORCE" = true ] || [ ! -f "$PLUGINS_DIR/libneo-f3kdb.dylib" ]; then @@ -727,7 +752,10 @@ echo "" echo "=== Building ZNEDI3 ===" if [ "$FORCE" = true ] || [ ! -f "$PLUGINS_DIR/libznedi3.dylib" ]; then rm -rf znedi3 - git clone --depth 1 https://github.com/sekrit-twc/znedi3.git znedi3 + # --recurse-submodules: znedi3 carries graphengine + vsxx (which bundles the + # VapourSynth headers) as submodules. Without them the build fails with + # "'znedi3.h' file not found" / missing vsxx4_pluginmain.o. + git clone --depth 1 --recurse-submodules --shallow-submodules https://github.com/sekrit-twc/znedi3.git znedi3 cd znedi3 # ZNEDI3 has its own makefile - need to disable x86 optimizations on arm64 if [ "$ARCH" = "arm64" ]; then @@ -752,10 +780,13 @@ else fi # NNEDI3 (CPU version) +# Its Makefile.am hardcodes `-mfpu=neon` for the NEON path, which clang rejects +# on arm64 ('unsupported option -mfpu='). NEON is baseline on aarch64, so the +# flag isn't needed - strip it before autogen regenerates the Makefiles. build_plugin "nnedi3" \ "https://github.com/dubhater/vapoursynth-nnedi3.git" \ "libnnedi3.dylib" \ - "./autogen.sh && ./configure && make -j\$(sysctl -n hw.ncpu) && cp .libs/libnnedi3.dylib . 2>/dev/null || cp src/.libs/libnnedi3.dylib . 2>/dev/null" + "sed -i '' 's/ -mfpu=neon//' Makefile.am && ./autogen.sh && ./configure && make -j\$(sysctl -n hw.ncpu)" # NNEDI3CL (OpenCL version) build_plugin "nnedi3cl" \ @@ -799,10 +830,16 @@ else fi # FFT3DFilter -build_plugin "fft3dfilter" \ - "https://github.com/myrsloik/VapourSynth-FFT3DFilter.git" \ - "libfft3dfilter.dylib" \ - "meson setup build --buildtype=release && ninja -C build" +# On ARM64 we use the pre-built version from yuygfgg (downloaded above); its +# from-source build needs fftw3f_threads which isn't reliably discoverable here. +if [ "$ARCH" != "arm64" ]; then + build_plugin "fft3dfilter" \ + "https://github.com/myrsloik/VapourSynth-FFT3DFilter.git" \ + "libfft3dfilter.dylib" \ + "meson setup build --buildtype=release && ninja -C build" +else + echo " FFT3DFilter: using pre-built ARM64 version from yuygfgg" +fi # MiscFilters build_plugin "miscfilters" \ @@ -1141,6 +1178,40 @@ else: EOF fi +# ============================================================================ +# Repoint plugin support-lib references at the bundled copies in lib/ +# ============================================================================ +# Source-built plugins (mvtools, bm3d, dctfilter, nnedi3cl, ...) link Homebrew's +# fftw/boost by absolute path (e.g. /opt/homebrew/opt/fftw/lib/libfftw3f.3.dylib). +# Those paths don't exist on users' machines, so the plugin would fail to load. +# The libs are already bundled in $LIB_DIR; rewrite every such reference at the +# bundled copy. Also normalize each plugin's own install id to @loader_path so +# the bundle is fully relocatable. +echo "" +echo "=== Repointing plugin support-lib references to bundled lib/ ===" +SUPPORT_LIBS=(libfftw3f.3.dylib libfftw3f_threads.3.dylib libboost_filesystem.dylib libboost_atomic.dylib liblzma.5.dylib libdvdread.dylib) +for plugin in "$PLUGINS_DIR"/*.dylib; do + [ -f "$plugin" ] || continue + plugin_base=$(basename "$plugin") + changed=false + # Normalize the plugin's own install id. + install_name_tool -id "@loader_path/$plugin_base" "$plugin" 2>/dev/null && changed=true + # Repoint any dependency that matches a bundled support lib by basename. + while IFS= read -r ref; do + ref_base=$(basename "$ref") + case "$ref_base" in "$plugin_base") continue ;; esac + for sl in "${SUPPORT_LIBS[@]}"; do + if [ "$ref_base" = "$sl" ] && [ -f "$LIB_DIR/$sl" ] && [[ "$ref" != @loader_path/* ]]; then + install_name_tool -change "$ref" "@loader_path/../../lib/$sl" "$plugin" 2>/dev/null && changed=true + fi + done + done < <(otool -L "$plugin" | tail -n +2 | awk '{print $1}') + if [ "$changed" = true ]; then + codesign -s - -f "$plugin" 2>/dev/null || true + echo " Repointed $plugin_base" + fi +done + # ============================================================================ # Sign all binaries and libraries (required for macOS code signing) # ============================================================================ From ff54b31097cc3a04b4fbe408c9740d598f9b2ae1 Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Wed, 10 Jun 2026 21:44:32 +1000 Subject: [PATCH 5/6] macOS arm64: ship static ffmpeg from martin-riedl.de, not Homebrew Homebrew's arm64 ffmpeg 8.1.1 is thinly dynamically-linked (~17 deps under /opt/homebrew/Cellar/ffmpeg, plus x264/x265/dav1d/openssl/...), so `cp $BREW_PREFIX/bin/ffmpeg` produced a 444 KB binary that can't run on a user's Mac (encoding would fail). Runner drift silently broke this; the 1.3.0 arm64 deps shipped a 61 MB static ffmpeg from martin-riedl.de. Download the static arm64 ffmpeg/ffprobe from martin-riedl.de (links only system frameworks), mirroring the evermeet.cx static build used for x64. Update docs accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 2 +- Scripts/download-deps-macos.sh | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8e9c72d..29e6a17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -524,7 +524,7 @@ The `download-deps-windows.ps1` and `download-deps-macos.sh` scripts apply these - Fully self-contained deps (no Homebrew at runtime): Python 3.12 (python-build-standalone), VS built from source - Worker sets: `PYTHONHOME`, `PYTHONPATH`, `VAPOURSYNTH_CONF_PATH`, `DYLD_LIBRARY_PATH` - `vspipe` is a wrapper script that generates config dynamically (needed because `VAPOURSYNTH_PLUGIN_PATH` is additive, not a replacement) -- **x64 FFmpeg** is sourced pre-built from evermeet.cx (static x86_64, links only system frameworks); arm64 FFmpeg comes from Homebrew. **x64 plugins** build from source under Rosetta, except `tmedian`/`bestsource` which come pre-built from Stefan-Olt/vs-plugin-build. +- **FFmpeg** is sourced pre-built as a static binary that links only system frameworks: **x64** from evermeet.cx, **arm64** from martin-riedl.de (Homebrew's arm64 ffmpeg is dynamically linked to ~17 Homebrew dylibs and is NOT self-contained, so it can't be bundled). **x64 plugins** build from source under Rosetta, except `tmedian`/`bestsource` which come pre-built from Stefan-Olt/vs-plugin-build. - **Code signing**: After `install_name_tool` modifications, binaries must be re-signed: `codesign -s - -f ` (exit code 137 = SIGKILL means invalid signature) - Quarantine removal: `xattr -cr` on deps after download - Show in Folder: `open -R ` diff --git a/Scripts/download-deps-macos.sh b/Scripts/download-deps-macos.sh index 56d50b8..c2595d2 100755 --- a/Scripts/download-deps-macos.sh +++ b/Scripts/download-deps-macos.sh @@ -93,7 +93,8 @@ BREW_DEPS=( # libdvdread -> libdvdread (DVD title extraction in the worker) # xz -> liblzma.5 (linked by Stefan-Olt's x86_64 bestsource) fftw boost libdvdread xz - # FFmpeg (arm64 copies this; x64 uses evermeet.cx static builds instead) + # FFmpeg (build-time convenience only; at runtime both arches ship static + # ffmpeg downloaded from evermeet.cx (x64) / martin-riedl.de (arm64)) ffmpeg ) @@ -346,10 +347,21 @@ if [ "$ARCH" = "x86_64" ]; then codesign -s - -f "$DEPS_DIR/ffmpeg/ffprobe" 2>/dev/null || true echo " Installed evermeet.cx FFmpeg" else - cp "$BREW_PREFIX/bin/ffmpeg" "$DEPS_DIR/ffmpeg/" - cp "$BREW_PREFIX/bin/ffprobe" "$DEPS_DIR/ffmpeg/" + # arm64: Homebrew's ffmpeg is dynamically linked against ~17 Homebrew dylibs + # (/opt/homebrew/Cellar/ffmpeg/.../lib*, x264, x265, dav1d, openssl, ...), so + # copying just the binary yields a bundle that won't run on users' Macs. + # Use the static arm64 build from martin-riedl.de (links only system + # frameworks, ~60 MB) - the same source the 1.3.0 deps shipped, and the + # arm64 analogue of the evermeet.cx static build used for x64 above. + echo " Downloading static arm64 FFmpeg from martin-riedl.de..." + curl -sL "https://ffmpeg.martin-riedl.de/redirect/latest/macos/arm64/release/ffmpeg.zip" -o "$BUILD_DIR/ffmpeg.zip" + curl -sL "https://ffmpeg.martin-riedl.de/redirect/latest/macos/arm64/release/ffprobe.zip" -o "$BUILD_DIR/ffprobe.zip" + unzip -q -o "$BUILD_DIR/ffmpeg.zip" -d "$DEPS_DIR/ffmpeg/" + unzip -q -o "$BUILD_DIR/ffprobe.zip" -d "$DEPS_DIR/ffmpeg/" chmod +x "$DEPS_DIR/ffmpeg/ffmpeg" "$DEPS_DIR/ffmpeg/ffprobe" - echo " Copied FFmpeg from Homebrew" + codesign -s - -f "$DEPS_DIR/ffmpeg/ffmpeg" 2>/dev/null || true + codesign -s - -f "$DEPS_DIR/ffmpeg/ffprobe" 2>/dev/null || true + echo " Installed static arm64 FFmpeg from martin-riedl.de" fi # ============================================================================ From ae22a79f83169c795fe9c9caace02ccf190212d2 Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Wed, 10 Jun 2026 21:46:36 +1000 Subject: [PATCH 6/6] Add build-deps-windows.yml CI workflow Windows deps had no CI (only macOS/Linux did), so the windows-x64 bundle had to be built by hand on a Windows box. download-deps-windows.ps1 fetches everything from upstream release URLs and uses the 7-Zip preinstalled on windows-latest, so it runs cleanly in CI. Mirrors build-deps-macos.yml / build-deps-linux.yml: version + optional release_tag inputs, uploads an artifact and (when release_tag is set) to the deps release. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/build-deps-windows.yml | 49 ++++++++++++++++++++++++ CLAUDE.md | 5 ++- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build-deps-windows.yml diff --git a/.github/workflows/build-deps-windows.yml b/.github/workflows/build-deps-windows.yml new file mode 100644 index 0000000..c82dffe --- /dev/null +++ b/.github/workflows/build-deps-windows.yml @@ -0,0 +1,49 @@ +name: Build Windows Deps + +# Builds the VapourSynth/FFmpeg/Python dependency bundle for Windows x64. +# Mirrors build-deps-macos.yml / build-deps-linux.yml. Everything is fetched +# from upstream release URLs by download-deps-windows.ps1 (7-Zip is preinstalled +# on the windows-latest runner), so no local deps checkout is needed. + +on: + workflow_dispatch: + inputs: + version: + description: 'Deps version (e.g., 1.4.0)' + required: true + type: string + release_tag: + description: 'Upload to this release tag (e.g., deps-v1.4.0). Leave empty to skip upload.' + required: false + type: string + +permissions: + contents: write + +jobs: + build-x64: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Build dependencies + shell: pwsh + run: ./Scripts/download-deps-windows.ps1 + + - name: Package dependencies + shell: pwsh + run: ./Scripts/package-deps-windows.ps1 -Version "${{ inputs.version }}" + + - uses: actions/upload-artifact@v4 + with: + name: VapourBox-deps-${{ inputs.version }}-windows-x64 + path: dist/VapourBox-deps-${{ inputs.version }}-windows-x64.zip + + - name: Upload to release + if: inputs.release_tag != '' + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload "${{ inputs.release_tag }}" ` + "dist/VapourBox-deps-${{ inputs.version }}-windows-x64.zip" --clobber diff --git a/CLAUDE.md b/CLAUDE.md index 29e6a17..472ab00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -641,8 +641,9 @@ Create the app-specific password at appleid.apple.com → Sign-In and Security - Flutter Windows can't build on macOS — use GitHub Actions - Flutter Linux can't build on macOS — use GitHub Actions or a Linux VM -- Windows deps can be zipped on macOS if `deps/windows-x64/` exists -- Linux deps must be built on Linux (`./Scripts/download-deps-linux.sh`) +- Deps for every platform are built in CI by their own workflow: `build-deps-macos.yml` (arm64 + x64), `build-deps-linux.yml` (x64 + arm64), `build-deps-windows.yml` (x64, on `windows-latest` — everything is fetched from upstream release URLs by `download-deps-windows.ps1`, so no local checkout is needed). All take `version` + optional `release_tag` (leave empty to skip the release upload). +- Windows deps can also be zipped on macOS if `deps/windows-x64/` exists (`Scripts/package-deps-windows.ps1` via pwsh), but the CI workflow is the canonical path. +- Linux deps must be built on Linux (`./Scripts/download-deps-linux.sh`) or via `build-deps-linux.yml` - The macOS CI build (`build-macos.yml`) signs with the Developer ID cert and notarizes — see "macOS Code Signing & Notarization" below. Windows and Linux CI builds remain unsigned. - macOS ships as a **universal** (arm64+x86_64) app by default. `build-macos.yml` takes an `arch` input (`universal` | `both` | `arm64` | `x64`, default `universal`) and fans out via a matrix. Universal = lipo'd worker + Runner built with `ARCHS="arm64 x86_64"`; `both` = two separate single-arch DMGs. The x64 slice cross-compiles on the `macos-15` (arm64) runner. - Deps are **not** bundled — the app downloads `macos-arm64` or `macos-x64` deps at runtime per `uname`, so a universal app needs **both** deps bundles published. macOS deps are built by `build-deps-macos.yml` (arm64 on `macos-15`, x64 natively on `macos-15-intel`).