Building from Source¶
This guide covers two separate build paths:
Building the native VapourSynth plugin on its own, to be dropped into a VapourSynth plugins directory.
Building Python distributions (sdist and wheel) that bundle the plugin together with the
vsanalogPython wrapper for installation viapip.
Both pull in libchromadec — the decoding library — as
a Meson git wrap
declared in subprojects/chromadec.wrap, and link it statically. Meson
fetches it (and the SQLite and hvd-core it bundles) on first configure, so the
first build of a fresh checkout needs network access.
Building the VapourSynth Plugin¶
The steps in this section produce a standalone plugin binary
(vsanalog.dll / .dylib / .so) for manual installation. If you only
intend to build a Python wheel for redistribution, skip ahead to
Building Python Distributions — those instructions invoke this build
internally.
Build Dependencies¶
Meson build system (not 1.11.0, which meson-python rejects) and Ninja
C++20 compiler (GCC 10+, Clang 12+, or MSVC 2019+)
git, for the libchromadec wrapVapourSynth (>= R55) development headers
Neither FFTW3 nor SQLite is a build dependency. Both are built from source and
linked statically into the plugin, via force_fallback_for=sqlite3,fftw3,fftw3f
in meson.build: SQLite from the trimmed amalgamation libchromadec ships, and
FFTW3 from subprojects/fftw3.wrap, which downloads
the upstream tarball and builds it with the Meson port in
subprojects/packagefiles/fftw3/, once in double precision for the
Transform-PAL and NN decoders and once in single precision (fftw3f) for
hvd-core, the engine behind hvd2d and hvd3d. hvd-core itself comes
from libchromadec’s wrap. Its solver loops thread with OpenMP where the
toolchain provides it (GCC/Clang’s libgomp/libomp, MSVC’s /openmp,
Homebrew libomp with Apple Clang) and fall back to serial loops otherwise;
-Dhvd-core:openmp=disabled forces the serial build, which is what the
standalone plugin tarballs use to stay a single file. Qt is no longer used at
all.
Building FFTW rather than installing it is partly a performance decision and
partly a correctness one. The packaged builds ship the wrong codelets for several
targets: Homebrew and Fedora both produce a scalar-only aarch64 FFTW, and
Fedora’s x86_64 build has no AVX2/FMA. The 3D decoders (transform3d,
nntransform3d) spend most of their time in FFTW, and the payoff tracks vector
width — roughly 17% on the arm64 targets, which go from scalar to 128-bit NEON,
but only about 1% on x86_64, where AVX to AVX2 is no change in width. The 2D path
is far less sensitive either way. On x86_64 the wrap therefore earns its place
through uniformity and through building at all where no FFTW is installed, rather
than through speed. The port enables SSE2/AVX/AVX2
on x86 (FFTW dispatches between them at plan time, so this stays safe on older
CPUs) and NEON on aarch64. SVE is deliberately left off: FFTW selects ARM
codelets at compile time with no runtime dispatch, so an SVE build would not run
on hardware without it.
To link system copies instead — the usual choice when packaging for a distro —
pass -Dforce_fallback_for=, optionally with --wrap-mode=nodownload:
meson setup build -Dforce_fallback_for= --wrap-mode=nodownload
That needs FFTW3 and SQLite3 development packages installed — both really are
required. libchromadec declares FFTW optional and reports it as
FFTW3 (Transform-PAL) in its feature summary, but that gate only selects the
CHD_WITH_FFTW feature flag: palcolour.h includes transform_pal.h, and
so <fftw3.h>, unconditionally, and every build pulls that in through
decoders/registry.cpp. With no FFTW headers present the build fails outright
rather than dropping the Transform PAL decoders.
The neural-network decoders need a backend, selected by the ep
(execution provider) option — see Neural-Network Backends below. On macOS
they are always built against the system CoreML frameworks; elsewhere they need
ONNX Runtime and are omitted with -Dep=none.
Installing Build Dependencies¶
macOS (Homebrew):
brew install meson ninja vapoursynth
Ubuntu/Debian:
sudo apt install meson ninja-build build-essential git vapoursynth-dev
Fedora:
sudo dnf install meson ninja-build gcc-c++ git vapoursynth-devel
Arch Linux:
sudo pacman -S meson ninja base-devel git vapoursynth
Compiling the Plugin¶
git clone https://github.com/JustinTArthur/vapoursynth-analog.git
cd vapoursynth-analog
meson setup build
meson compile -C build
There are no git submodules to initialize; meson setup fetches
libchromadec into subprojects/ itself.
The plugin will be built as:
build/vsanalog.dylib(macOS)build/vsanalog.so(Linux)build/vsanalog.dll(Windows)
Neural-Network Backends¶
-Dep=<provider> picks the execution provider libchromadec’s neural-network
decoders are built for: cpu (default), cuda, migraphx,
directml, coreml, or none.
macOS ignores the value and always builds the native CoreML backend, with no ONNX Runtime involved.
Linux and Windows need an ONNX Runtime to build anything but
none. Point the build at an unpacked ONNX Runtime release:meson setup build -Dep=cpu -Dchromadec:onnxruntime_root=/opt/ort
-Dep=nonedrops the neural-network decoders entirely. The analytical decoders (Comb, PalColour, Transform PAL, SECAM, mono) are always built.
Model weights are not part of the build. The wheels bundle them; a plugin built
this way takes an explicit model_path instead. tools/fetch_models.py
downloads and checksum-verifies the same weights from tools/models.lock.
On macOS the weights then go through tools/convert_models_macos.py, which
drives libchromadec’s scripts/convert_coreml.py to produce the
.mlpackage bundles the native CoreML backend loads. It converts at fp32
except the nntransform3d v2 model, which it converts at fp16 on Apple
silicon so the Apple Neural Engine — an fp16-only device — can take it. Pass
--fp16 never for an all-fp32 set, or --fp16 always to convert fp16 on
an Intel Mac too (where it lands on the GPU and measures slightly slower than
fp32). No other bundled model survives fp16: the v1 chroma_net series
overflows its range and the ldzeug2 models break on index math.
The CUDA and Windows wheels additionally run tools/convert_models_fp16.py,
which writes an fp16 copy of the same fp16-safe model beside the fp32 one
(chroma_net-v2-...-fp16.onnx, via onnxruntime.transformers.float16
with the graph’s inputs and outputs pinned to fp32). The CUDA and DirectML
execution providers have no engine-level fp16 mode of their own — TensorRT
does, and keeps using the fp32 file — so this sibling is what an explicit
onnx_provider="cuda" or "directml" loads at the default "fp16"
precision. It needs only the pip onnxruntime and onnx packages for
its converter.
Self-Contained Plugin Builds¶
A default build is already self-contained apart from ONNX Runtime: FFTW and
SQLite are static subprojects absorbed into the plugin. -Dprefer_static=true
takes the static archive of anything else that offers one, which is what the
release tarballs are built with:
meson setup build -Dep=none -Dprefer_static=true
meson compile -C build
The result depends on nothing outside the C++ runtime, VapourSynth, and — on macOS — the system CoreML frameworks.
Installing the Plugin¶
These steps apply to the standalone plugin built above. If you are installing
via a wheel produced by the Python distribution build, pip handles
placement automatically and you can skip this section.
Copy the built plugin to your VapourSynth plugins directory.
macOS:
cp build/vsanalog.dylib ~/Library/Application\ Support/VapourSynth/plugins64/
Linux:
cp build/vsanalog.so ~/.local/lib/vapoursynth/
Or load it explicitly in your VapourSynth script:
core.std.LoadPlugin("/path/to/vsanalog.dylib")
Building Python Distributions¶
vsanalog uses meson-python as
its build backend, so the standard build front end
produces both sdists and wheels.
Install the front end once:
pip install build
Source Distribution (sdist)¶
An sdist bundles this project’s own source and is platform independent:
python -m build --sdist
It carries the libchromadec and FFTW wraps rather than their sources, so building from it fetches both over the network. It carries no model weights either: install from an sdist and the neural-network decoders need models you supply yourself.
The resulting dist/vsanalog-<version>.tar.gz can be uploaded to PyPI or
used as input to a wheel build on another machine.
Wheel¶
Building a wheel compiles the native plugin, so the same build dependencies listed above must be available. Whatever shared libraries the plugin ended up linking (in practice only ONNX Runtime — FFTW and SQLite are static subprojects) are not present on end-user machines, so the platform-appropriate repair tool vendors them into the wheel.
After repair, the wheels are re-tagged as py3-none because the plugin does
not embed CPython’s ABI:
python -m wheel tags --python-tag py3 --abi-tag none <wheel> --remove
macOS:
pip install build delocate wheel
python -m build --wheel -o dist/ -Csetup-args="-Dprefer_static=true"
delocate-wheel -w wheelhouse/ dist/*.whl
python -m wheel tags --python-tag py3 --abi-tag none wheelhouse/*.whl --remove
PKG_CONFIG_PATH should resolve vapoursynth.pc —
/opt/homebrew/lib/pkgconfig on Apple silicon.
Linux (manylinux):
Linux wheels are built inside a quay.io/pypa/manylinux_2_28_x86_64 (or
manylinux_2_34_aarch64) container so that the resulting binaries are
compatible with older glibc versions. Inside the container, install the build
dependencies (gcc-c++, git, an unpacked ONNX Runtime, and the
VapourSynth headers), then:
pip install build auditwheel wheel meson ninja meson-python
python -m build --wheel --no-isolation -o /tmp/dist/ \
-Csetup-args="-Dep=cpu" \
-Csetup-args="-Dprefer_static=true" \
-Csetup-args="-Dchromadec:onnxruntime_root=/opt/ort"
auditwheel repair -w wheelhouse/ /tmp/dist/*.whl
python -m wheel tags --python-tag py3 --abi-tag none wheelhouse/*.whl --remove
See .github/workflows/build.yml for the full container setup used in CI.
Windows:
Windows wheels need an MSVC developer environment and an unpacked ONNX
Runtime. delvewheel vendors the DLLs from the ONNX Runtime directory:
pip install build delvewheel wheel
python -m build --wheel -o dist/ `
-Csetup-args="-Dep=cpu" `
-Csetup-args="-Dchromadec:onnxruntime_root=$env:ORT_ROOT"
delvewheel repair --analyze-existing `
--namespace-pkg "vapoursynth;vapoursynth.plugins;vapoursynth.plugins.vsanalog" `
--add-path "$env:ORT_ROOT/lib" `
dist/*.whl -w repaired/
python tools/colocate_plugin_libs.py (Get-ChildItem repaired/*.whl) wheelhouse/
python -m wheel tags --python-tag py3 --abi-tag none (Get-ChildItem wheelhouse/*.whl) --remove
As on other platforms, PKG_CONFIG_PATH must resolve vapoursynth.pc
before invoking the build.
The extra colocate_plugin_libs.py pass is what makes the plugin autoload.
delvewheel puts the vendored DLLs in a top-level vsanalog.libs/ reachable
only through a patch that runs on import vsanalog, which VapourSynth’s
autoloader never does; the script moves them into the plugin’s own directory,
where the loader finds them unaided. The last --namespace-pkg entry names
that directory so delvewheel does not mint an __init__.py inside it.
GPU-execution-provider wheels are built the same way with -Dep=cuda,
-Dep=migraphx or -Dep=directml and an ONNX Runtime carrying that
provider. They are too large for PyPI, so they are retagged with a build tag
(--build 0cuda13) to keep them installable while distinguishing them from
the CPU wheel.
-Dep=cuda covers both published CUDA variants: the toolkit major is not a
build option but a property of the nvcc and the ONNX Runtime package the build
is pointed at. Our cuFFT kernels and ORT have to agree on libcudart, so a
12.x toolkit is paired with ORT’s gpu_cuda12 package and a 13.x toolkit
with gpu_cuda13. Only the wheel’s build tag distinguishes the results.
-Dep=cuda and -Dep=migraphx also switch on libchromadec’s native cuFFT
and hipFFT pipelines, so they need the vendor toolkit at build time — nvcc plus
cuFFT, or hipcc plus hipFFT — not just an ONNX Runtime. -Dep=directml needs
neither, since DirectML inference goes through ONNX Runtime alone.
ONNX Runtime with the MIGraphX provider comes from AMD rather than Microsoft:
the onnxruntime-migraphx wheel on ROCm’s own package index (not
onnxruntime-rocm, which carries no MIGraphX provider). It ships shared
objects under onnxruntime/capi/ and no headers, so
-Dchromadec:onnxruntime_root needs a root assembled from those objects and
the headers of Microsoft’s matching release.
Versioning¶
VERSION.txt at the repository root holds the version, in PEP 440 form
(0.3.0, 0.3.0a4). meson.build reads it, the distribution metadata
follows from that, src/version.h is generated from it, and the release
workflow names its tarballs from it — so a bump is a one-line edit to that file.
Meson tracks it, so an edit alone makes the next meson compile regenerate
the header.
A release tag has to be bumped to match by hand. tools/check_version.sh
compares the two and fails a tag build if they disagree; it also rejects a
meson.build that has gone back to a literal version and a src/version.h
checked in again beside the generated one. Run it any time:
tools/check_version.sh # in-tree copies agree
tools/check_version.sh v0.3.0 # ...and a tag matches
The plugin’s registered version carries only the major and minor components,
since that is all VS_MAKE_VERSION accepts. The full string, prerelease
suffix included, is available to the plugin as
VS_ANALOG_PLUGIN_VERSION_STRING.