vs-frameblender
Installing
pip install vapoursynth-blur-frameblender
The wheel drops the plugin into vapoursynth/plugins, which VapourSynth R74+ autoloads from.
Wheels are built for Windows x64, Linux x64/aarch64 and macOS x64/arm64.
This is a shamelessly AI-written modification of the original AverageFrames that removes the cap of 31 weights, adds a squared blend option, and attempts to improve performance.
Temporal weighted average for VapourSynth, with no cap on the number of weights.
std.AverageFrames is limited to 31 weights. This does the same job without that limit, and is
faster than every alternative once the window gets large.
core.frameblender.FrameBlend(clip, weights=[...], scale=None, planes=None, squared=False, transfer=None)
weights— one float per frame in the window, odd count, centred on the current frame. Any scale; they do not need to be normalised.scale— divisor. Defaults to the sum of the weights, which is what you want for an average.planes— which planes to process. Defaults to all.squared— average in a squared domain rather than a linear one, so the blend adds up light rather than gamma-encoded codes.False(the default) dispatches to the plain kernels and costs nothing. On YUV it runs in RGB; see below.transfer— blend the light under a specific transfer curve instead:"square"(the same assquared=True),"pq"or"hlg". See HDR curves.
Frames past the start and end of the clip are clamped to the first/last frame. A frame repeated in the window is read once, with its weights summed. 8–16 bit integer and 32 bit float are supported.
core.frameblender.Isa() reports which kernel the CPU dispatch selected (baseline, avx2 or
avx512) — useful for checking the dispatch is doing what you expect.
The squared blend
With squared=True the blend runs in a squared domain instead of a linear one:
dst = f^-1( sum w_i * f(src_i) / scale )
f(v) = v|v| f^-1(y) = sign(y) * sqrt(|y|)
in normalised units where 0 is reference black and 1 reference white.
The numbers in a video file are not brightness — they sit on a gamma curve, where code 128 is about a fifth of the light of code 255, not half. Averaging them directly does not give the average of what they represent, the same way averaging decibels does not give the average sound power. A real camera integrates light over the shutter, so a linear average of encoded codes comes out darker than a real exposure on every moving edge.
Squaring first is a close enough stand-in for undoing the transfer: against a true 2.2 blend the
worst case is ~0.02 in normalised units, and it costs one multiply instead of a pow. A static
image is returned exactly, since f^-1(f(v)) = v.
v|v| rather than v*v so sub-black stays ordered and invertible, and superwhite runs past 1.0
rather than folding back.
Where black and white are
The curve has to know where black and white are. That comes from the clip's own _ColorRange on
the first frame — 0 and the format maximum for full range, 16 and 235 at 8 bit for limited.
Float is already normalised either way, so it is always 0 and 1.
This matters more than it sounds. Converting limited range footage to full range first, so the curve can assume the whole container, looks equivalent and is not: it clips every sample above white on the way in, and those are exactly the samples a brightness curve exists to protect. Anchored to the real white instead, samples past it come out above 1.0, which the float accumulator carries without complaint, and the inverse puts them back where they were. Hand the filter a limited range clip and leave it limited.
Below black the curve is simply linear, so there is nothing to floor.
To anchor somewhere else, set _ColorRange on the clip — there is no separate argument for it.
Notes
A curve about the chroma midpoint has no meaning, and squaring luma alone desaturates, because
the chroma still averages linearly and a bright saturated thing comes out bright and washed out.
So on YUV each pixel is converted to RGB inside the blend with the clip's _Matrix (1, 5, 6 or
9), put through the curve, and converted back, which saves converting every frame of the window
outside it. planes has to be all of them, or just luma. 4:2:0 and 4:2:2 chroma is taken as left
sited and brought up bilinearly, and only what the curve changes goes back down, so a static frame
comes back exact.
The squared blend is not free — the accumulate loop is where all the work is, and it gains a normalise, a multiply and an fma per sample:
| weights | linear | squared |
|---|---|---|
| 21 | 619 | 138 |
| 81 | 179 | 71 |
720p RGB24 in and out, output frames/sec, best of 4 on a 9800X3D. Against the power mean this replaced, measured the same way on the same machine: 2.4× at 21 weights and 3.8× at 81 (58 and 18 fps), all of it from dropping the two tables.
HDR curves
The square is within a few levels of any SDR curve, but HDR curves are far from it, so transfer
undoes those exactly. Both are mirrored below black and run past 1.0 above white the
same way the square does.
pq— SMPTE ST 2084, with light normalised so 1.0 is 10000 nits.hlg— BT.2100 HLG, blended in scene light (its inverse OETF), so no display has to be assumed.
The curves are evaluated without libm calls so the loops still vectorise: pow is a log2 and an
exp2 by bit manipulation and short series. Against a float64 reference on random frames covering
sub-black and superwhite, both are within 1 level at 8 and 10 bit and 2 at 16 bit, and within 3e-5 in
float (pq is the loosest, being two steep powers).
They cost more than the square, all of it in the forward curve that runs once per source sample:
| transfer | yuv420p8 | rgb24 |
|---|---|---|
| none | 718 | 368 |
| square | 368 | 376 |
| hlg | 152 | 172 |
| pq | 42 | 42 |
1080p, 21 weights, windows tiling, output frames/sec, best of 3 on a 9800X3D.
Why it's fast
The obvious way to write this is to hold N row pointers and reduce across them for each pixel.
That only vectorises when N is a compile-time constant, which is exactly why upstream's
AverageFrames templates on the source count and stops at 31 — past that it falls back to a
runtime loop that doesn't vectorise at all and is ~24× slower.
This swaps the loops. The pixel loop is innermost over a row-sized int32 accumulator, so each
source frame is one clean vectorised pass and nothing depends on N being known at compile time:
for each row:
zero acc[0..w)
for k in 0..num_srcs:
for x in 0..w: acc[x] += (src[k][x] - bias) * weights[k]
for x in 0..w: dst[x] = clamp(round(acc[x] / scale) + bias)
The accumulator is w * 4 bytes, so it stays in L1. Removing the cap costs nothing — it is
faster than the templated path at every window size worth using, because that version's
register pressure grows with N while this one's doesn't.
Measured at 1080p yuv420p8, output frames/sec, on a 9800X3D:
| weights | akarin 1.5.0 | llvmexpr | std.AverageFrames (R72) | frameblender |
|---|---|---|---|---|
| 21 | 752 | 762 | 755 | 783 |
| 31 | 586 | 599 | 567 | 739 |
| 81 | 265 | 362 | cap | 495 |
Frames are requested with a stride, which is how the consumer of this actually pulls them — benchmarking consecutive frames instead makes the windows overlap, keeps everything in L3, and measures a different bottleneck.
Accuracy
Validated against a float64 reference. Max error is ≤1 LSB on yuv420p8, yuv420p10 and
rgb48, and bit-exact on RGBS and on 8-bit at ≥21 weights — the same accuracy as
akarin.Expr and llvmexpr.Expr.
8-bit uses an int32 accumulator with the weights quantised by the largest multiplier that cannot overflow it, which puts the quantisation error far below one level. 16-bit uses a float accumulator instead: int32 would only leave ~15 bits for the weights at that depth, which is visible.
With the squared blend, validated the same way against a float64 reference: bit-exact on RGB24 and RGB48 and ~3e-8 on RGBS. A static frame comes back unchanged to 0 levels. The accumulator is float on the squared path at all depths, not the int32 one the linear 8 bit path uses, so samples above white have somewhere to go.
Building
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
Produces frameblender.dll / libframeblender.so. The VapourSynth 4 headers are vendored in
include/, so there is nothing else to fetch.
Do not add -ffast-math. The rounding in kernel.h relies on an add/subtract pair that
fast-math would fold away.
Platforms
x86-64 (Windows, Linux, Intel macOS). The kernel is compiled three times — baseline, AVX2,
AVX-512 — into separate object files with different -march//arch flags, and the right one is
picked by CPUID at load. Same shape upstream VapourSynth uses for its own filters, and it
matters: the baseline kernel is ~25% slower than AVX2 at 21 weights and 2.4× slower at 81.
CPU feature detection uses __cpuid/_xgetbv under MSVC and clang-cl, and
__builtin_cpu_supports under GCC and clang elsewhere — <intrin.h> does not exist on GCC.
aarch64 (Apple Silicon, ARM Linux). NEON is the baseline, so there is nothing to dispatch
between: CMake skips the x86 flags, all three variants build identically, and detection always
returns baseline. The kernel is plain C++ with no intrinsics, so it auto-vectorises to NEON on
its own. Slightly redundant (three identical copies of a small function) but correct.
core.frameblender.Isa() tells you which one is live.
Only the Windows x86-64 build has been run and benchmarked. The Linux and aarch64 paths are compile-checked for the preprocessor branches but otherwise untested — CI is the real check.
Notes
- Requires VapourSynth API 4 (R55+). The previous version of this plugin used API 3; the
namespace and function name are unchanged, but the
logargument is gone andscale,planesandsquaredare new. - v3 replaced the
gammaargument withsquared. A power mean can only reweight the frames in the window, so past aboutgamma=3it hands one frame nearly the whole pixel and the output stops being a blur at all. Squaring is the transfer function the codes were encoded with, not a gain, so it corrects the average without trading blur away for brightness - and is faster. - The old version truncated instead of rounding, so output was up to one level darker.
- 3.2 added
transfer.squared=Truestill works and istransfer="square".
Metadata
Release files for vapoursynth-blur-frameblender 3.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vapoursynth_blur_frameblender-3.2.0.tar.gz | 36.1 kB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| vapoursynth_blur_frameblender-3.2.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| vapoursynth_blur_frameblender-3.2.0-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| vapoursynth_blur_frameblender-3.2.0-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| vapoursynth_blur_frameblender-3.2.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 | Details |
| vapoursynth_blur_frameblender-3.2.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.24+ ARM64, Linux glibc 2.28+ ARM64 | Details |
| vapoursynth_blur_frameblender-3.2.0-py3-none-macosx_13_0_x86_64.whl | Python 3 | none | macOS 13.0+ x86-64 | Details |
| vapoursynth_blur_frameblender-3.2.0-py3-none-macosx_13_0_arm64.whl | Python 3 | none | macOS 13.0+ ARM64 | Details |
Total release size: 3.8 MB
Release files / vapoursynth_blur_frameblender-3.2.0.tar.gz
| Download URL | vapoursynth_blur_frameblender-3.2.0.tar.gz |
|---|---|
| Size | 36.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
28f41e767d4d9e22cdfa72df253e541894f0703b731da347d2b84da33ca4b677
|
|
BLAKE2b-256 checksum How to use checksums |
d2a66e66b34b022cc6e876bdf175968a2aa6f1635c48edd0d92ea24c0cd00e6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-win_amd64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-win_amd64.whl |
|---|---|
| Size | 81.0 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
89f2f03e79eaad414847aa74127c03601ecb9e73de7ff2f4755807c1d9d834b8
|
|
BLAKE2b-256 checksum How to use checksums |
954b70c1c9c3207366d9819ad346a33a8355da5e26d122b2a689c1e9aecf8121
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
524ec99c6d654488446da5fcd57563b4249e90b843c6c575ef9d81e6b67e1f0b
|
|
BLAKE2b-256 checksum How to use checksums |
4ccc88860634086acb3dec46a06e025a946265b56ba5f6f5df771c19faf27c77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
bf22fb905e2bdda3835566f28015f37e39afd19ed32c068342d5283712e1048a
|
|
BLAKE2b-256 checksum How to use checksums |
cfc1bec10e7a7e9b4ebd4a498c3378190564381b9dc8a4ff04c0a2a3970ab53e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 341.3 kB |
| Tags | Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
4c2c549e432a3639e37fa6ba9fde21be042a168b8cbe831fedac15ad7ac0942e
|
|
BLAKE2b-256 checksum How to use checksums |
7859455324d9da4113d8e0b89e5a93da242075996388c54135a1b2a1446492f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 255.9 kB |
| Tags | Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
ca97d6b68e9d8f767dac8a8a3a47d4112194aaaf3da1b6fbbbe37cfb8140458c
|
|
BLAKE2b-256 checksum How to use checksums |
1584c6e9f8d38de0c33d8dd6b9fb7befed7289b7e5f1720564f0f887c0631960
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-macosx_13_0_x86_64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-macosx_13_0_x86_64.whl |
|---|---|
| Size | 270.9 kB |
| Tags | Python 3 macOS 13.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
bbe7aedf0341fbd215fee7fe37f1febfdbd7a0854af352ac5f465ed6143f6b8f
|
|
BLAKE2b-256 checksum How to use checksums |
dd60562d6b74067c4916dd5ac5de22c3d1fbd5599c3b3e3721ba7035e7565897
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / vapoursynth_blur_frameblender-3.2.0-py3-none-macosx_13_0_arm64.whl
| Download URL | vapoursynth_blur_frameblender-3.2.0-py3-none-macosx_13_0_arm64.whl |
|---|---|
| Size | 166.0 kB |
| Tags | Python 3 macOS 13.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
bb63080a385608115960bda2966d7379c3fcc80e44d741bab8287589f74afb81
|
|
BLAKE2b-256 checksum How to use checksums |
cb43895a0c40020692ca931cc12aa0dc74cccd4dadcf6758fe9f3bf010651cdb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logSecurity for vapoursynth-blur-frameblender 3.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 8 files in this release have been uploaded with provenance metadata.
Attestation for vapoursynth_blur_frameblender-3.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.