vapoursynth-imageseqs
a rust vapoursynth source plugin for reading an ordered list of images as a clip.
features
- one frame per file, in the order supplied to
ReadorReadAlpha; - separate color and alpha clips with depth-preserving opaque alpha fallback;
- lots of formats support
- native YUV output for supported WebP, HEIF/HEIC, AVIF, and JPEG 2000 files;
- nominal 9–15-bit preservation, including 10/12-bit gray, RGB, alpha, and YUV formats;
- EXIF and codestream orientation handling with an
apply_rotationswitch; - CICP/container color metadata and optional raw ICC export through
ICCProfile; - background prefetching, bounded lookahead memory, variable-format clips.
requirements
for normal use:
- VapourSynth R79 or newer;
- Python 3.12 or newer when installing the wheel;
- the plugin binary for the current operating system and architecture.
the wheel does not install VapourSynth or a Python helper module. Unix builds
also need the dav1d and libde265 shared libraries at runtime. Windows
artifacts use the static x64-windows-static-md vcpkg triplet.
for source builds:
- Rust 1.88 or newer and Cargo;
- CMake, Ninja, and pkg-config;
- Debian/Ubuntu:
libdav1d-dev,libde265-dev, andlibwebp-dev; - macOS: Homebrew
dav1d,libde265, andwebppackages; - Windows: vcpkg with the
x64-windows-static-mdtriplet.
JPEG 2000 uses bundled OpenJPEG sources, and libheif is built as part of the project. see the build section for platform commands and the legal files for native dependency obligations.
install
the python wheel is plugin-only. it installs the native library at
vapoursynth/plugins/ and does not add a python module.
python -m pip install vapoursynth-imageseqs
until the package is published, download the matching *-plugin ci artifact
and copy its native file into your vapoursynth plugin directory:
| platform | file |
|---|---|
| windows | vs_imageseqs.dll |
| linux | libvs_imageseqs.so |
| macos arm64 | libvs_imageseqs.dylib |
if vapoursynth does not find it automatically, load it explicitly:
core.std.LoadPlugin(r"path/to/vs_imageseqs.dll")
the ci artifact and wheel include LICENSE, THIRD_PARTY_NOTICES, and
LICENSES/.
use
pass files in the order they should become frames:
from pathlib import Path
import vapoursynth as vs
core = vs.core
files = sorted(Path(r"I:\images").glob("*.png"))
clip = core.imgseqs.Read(
files=[str(path) for path in files],
fpsnum=24,
fpsden=1,
)
Read returns the color clip. ReadAlpha returns a dictionary containing the
color clip and a separate gray alpha clip:
result = core.imgseqs.ReadAlpha(files=[str(path) for path in files])
clip, alpha = result["clip"], result["alpha"]
arguments
| argument | default | meaning |
|---|---|---|
files |
required | ordered image paths |
fpsnum, fpsden |
24/1 |
output frame rate |
mismatch |
False |
allow variable sizes and pixel formats |
apply_rotation |
True |
apply the file's exif/codestream orientation |
icc_profile |
False |
expose embedded ICC bytes as ICCProfile |
debug |
False |
log create and per-frame timings |
prefetch |
half the logical cores, capped at 4 | background decode workers; 0 disables lookahead |
prefetch_memory |
max of 192 MiB and one largest frame per worker | lookahead budget in MiB; 0 is invalid |
without mismatch, every file must have the same size and format. with it,
each frame keeps its own size and format.
rotation applies the displayed orientation by default. orientations 5–8 swap
width and height, and ImgSeqOrientation still reports the original code.
apply_rotation=False keeps the stored picture and stored size. a rotated and
an upright file therefore need mismatch=True when rotation is enabled.
debug=True logs probing, decoding, allocation, pixel conversion, properties,
and total frame time to the vapoursynth log.
formats
| input | notes |
|---|---|
| png, jpeg, bmp, gif, ico, tiff | standard image decoder |
| dds | image's DXT1, DXT3, and DXT5 decoder |
| farbfeld | 16-bit RGBA input |
| webp | libwebp; lossy opaque files use YUV420P8 |
| avif | dav1d; monochrome uses Gray8/Gray10/Gray12, color may use its YUV planes |
| heif, heic | libheif/libde265; monochrome uses Gray8/Gray10/Gray12, color may use its YUV planes |
| jxl | jpeg xl decoder |
| jp2, j2k, jpf, jpx, j2c | JPEG 2000 through OpenJPEG; gray/RGB at 8–16 bits, and sYCC may use YUV planes |
| exr, hdr, pnm, qoi, tga | standard image decoder |
supported output includes gray and rgb at 8–16 bits, rgb 32-bit float, and the
YUV formats needed by the source: YUV420P8, YUV420P10, YUV422P8,
YUV422P10, YUV444P8, YUV444P10, YUV444P12, and YUV444P16.
Read ignores alpha. ReadAlpha uses the source depth for alpha: Gray8–
Gray16, or GrayS. the alpha channel is the second channel of LA and the
fourth channel of RGBA. files without alpha get an opaque plane filled with
the format maximum (255, 1023, 4095, 65535, or 1.0).
both clips share the frame count, rate, and indexes. each file is decoded once,
and mismatch applies to both clips.
nominal bit depth
when a container states 9–16 bits, the frame format names that depth and the
samples are right-aligned in its word. for example, a ten-bit avif is RGB30
with samples in 0..1023, not RGB48 with sixteen-bit-scaled samples. this
also applies to gray and alpha. files with no stated depth, 8-bit files,
16-bit files, and float files keep their decoder format.
yuv output
the plugin keeps a file's own YUV planes when its container names a matrix that vapoursynth understands:
- lossy webp without alpha:
YUV420P8,_Matrix=5,_Range=0; - color heif/heic/avif: the source planes, such as
YUV420P8orYUV444P10, tagged from the file's matrix and range.
an unspecified matrix (2) or no matrix keeps the RGB frame the plugin builds.
lossless webp, webp with alpha, and other formats stay RGB or gray. use
resize when a downstream filter needs RGB:
rgb = core.resize.Bicubic(clip, format=vs.RGB24)
the frame's _Matrix and _Range are used automatically. for an untagged
source, provide them explicitly:
rgb = core.resize.Bicubic(
clip,
format=vs.RGB24,
matrix_in_s="470bg",
range_in_s="limited",
)
zimg cannot convert an odd-sized 4:2:0 frame directly. for a variable clip,
crop odd edges before conversion and add the border back. FrameEval can do
that per frame, then a final resize can declare one constant format for
filters such as std.GPUUpload:
source = core.imgseqs.Read(files=files, mismatch=True)
plain_rgb = core.resize.Bicubic(source, format=vs.RGB24)
def convert(n=0, **_):
frame = source.get_frame(n)
right = frame.width % 2 if frame.format.subsampling_w else 0
bottom = frame.height % 2 if frame.format.subsampling_h else 0
if not right and not bottom:
return plain_rgb
even = core.std.CropAbs(
source,
width=frame.width - right,
height=frame.height - bottom,
)
rgb = core.resize.Bicubic(even, format=vs.RGB24)
return core.std.AddBorders(rgb, right=right, bottom=bottom)
rgb = core.std.FrameEval(source, convert)
rgb = core.resize.Bicubic(rgb, format=vs.RGB24)
Crop and AddBorders need a constant size and format, so use CropAbs for
variable clips. the example restores odd edges with black pixels; stack the
last real row and column instead if edge extension is preferred. a variable
clip is converted frame by frame, and resize arguments are ignored for RGB
frames.
frame properties
| property | meaning |
|---|---|
ImgSeqPath |
original file path |
ImgSeqIndex |
input-list index |
ImgSeqOriginalColorType |
decoder's original color type |
ImgSeqHasICC |
whether the source has an ICC profile |
ICCProfile |
raw embedded ICC bytes when icc_profile=True |
ImgSeqOrientation |
orientation code carried by the source |
ImgSeqAlpha |
1 on frames from the alpha clip |
container color metadata is mapped to _Primaries, _Transfer, _Matrix,
_Range, and, when explicitly named, _ChromaLocation. sources include avif
and heif/heic nclx, png cICP, jxl codestream headers, and av1 sequence
headers. unknown or unspecified codes are left unset. RGB frames always keep
_Matrix=0 and _Range=1. ImgSeqHasICC reports an embedded profile;
icc_profile=True also exposes its raw bytes as ICCProfile without changing
pixels or applying a color transform.
performance
benchmark a sequence against bestsource with:
.venv/Scripts/python.exe tests/bench-imgseqs-vs-bestsource.vpy --reps 3 --dir images --pattern "i_%04d.webp"
.venv/Scripts/python.exe tests/bench-imgseqs-vs-bestsource.vpy --reps 3 --extra --prefetch 16 --dir sandbox/webp --pattern "snek - p%03d.webp"
see docs/BENCH.md for the measurements. on the same 35 pages stored in six containers, imgseqs is 2.73x faster than bestsource on jpeg (4.98x including open), 1.37x faster on png, and 1.80x slower on webp because ffmpeg slice-threads each vp8 frame while libwebp decodes one frame at a time. that bestsource build cannot open avif, heic, or jxl.
clip creation only probes container metadata; decoding happens when frames are
requested, using the background pool. for a fair bestsource comparison, use
cachemode=0, apply_rotation=False on both plugins, and omit fpsnum and
fpsden because ffmpeg's image-sequence rate is 25 fps.
opening 35 avif pages costs about 2 ms, 35 heic pages about 6 ms, and the other
listed formats less in the recorded benchmark.
build
python -m pip install ".[dev]"
python -m build
linux and macos need these native packages:
# debian/ubuntu
sudo apt-get install --yes cmake ninja-build pkg-config libdav1d-dev libde265-dev libwebp-dev
# macos with homebrew
brew install cmake ninja pkg-config dav1d libde265 webp
libwebp is linked from its static archive when available, so it is not needed
at runtime. JPEG 2000 uses the OpenJPEG sources bundled by openjpeg-sys.
dav1d and libde265 remain shared on unix; windows uses the static vcpkg triplet.
license
vs-imageseqs is licensed under the Mozilla Public License 2.0. see
LICENSE.
the plugin also contains native code under these licenses:
- dav1d and OpenJPEG: BSD-2-Clause;
- libwebp: BSD-3-Clause with its accompanying patent grant;
- libheif and libde265: LGPL-3.0.
see THIRD_PARTY_NOTICES and LICENSES/ for the dependency versions, notices, and complete license texts. when a binary uses static LGPL linkage, these files are not the entire obligation: the release must also provide the applicable corresponding source and a practical way to relink the plugin with modified LGPL libraries.
Metadata
Release files for vapoursynth-imageseqs 0.1.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 | |
|---|---|---|---|
| vapoursynth_imageseqs-0.1.0.tar.gz | 250.4 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vapoursynth_imageseqs-0.1.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| vapoursynth_imageseqs-0.1.0-py3-none-manylinux_2_43_x86_64.whl | Python 3 | none | Linux glibc 2.43+ x86-64 | Details |
| vapoursynth_imageseqs-0.1.0-py3-none-macosx_26_0_arm64.whl | Python 3 | none | macOS 26.0+ ARM64 | Details |
Total release size: 9.8 MB
Release files / vapoursynth_imageseqs-0.1.0.tar.gz
| Download URL | vapoursynth_imageseqs-0.1.0.tar.gz |
|---|---|
| Size | 250.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
06e9b43d79d85fd58a2670f7988722def70e3364d7894fa20b0af914d05070a8
|
|
BLAKE2b-256 checksum How to use checksums |
58ebc8c8683b20b946c4ef8456eeebf82b15052e3f8109b1d18e01fe94c1245a
|
| 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 Sep 20, 2026.
Transparency logRelease files / vapoursynth_imageseqs-0.1.0-py3-none-win_amd64.whl
| Download URL | vapoursynth_imageseqs-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
ad3643c886409aafa084407eec08ef86557154f4f8378b29b75d47d056312a13
|
|
BLAKE2b-256 checksum How to use checksums |
95cd6ea40649a419d51340136afc877f77d406f454bf5b8fb9f422a376c25fa3
|
| 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 Sep 20, 2026.
Transparency logRelease files / vapoursynth_imageseqs-0.1.0-py3-none-manylinux_2_43_x86_64.whl
| Download URL | vapoursynth_imageseqs-0.1.0-py3-none-manylinux_2_43_x86_64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | Linux glibc 2.43+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
6f5b43c35c751a5f878f11e3953262700f1cc57ce301f9efe93b5a0c7699a567
|
|
BLAKE2b-256 checksum How to use checksums |
d78ec5d2b69ea1427dcae5a5e4c2608c34abdaf0cbc40ac238d167058b3d5a4c
|
| 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 Sep 20, 2026.
Transparency logRelease files / vapoursynth_imageseqs-0.1.0-py3-none-macosx_26_0_arm64.whl
| Download URL | vapoursynth_imageseqs-0.1.0-py3-none-macosx_26_0_arm64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | Python 3 macOS 26.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e877d140c2cecdb9a5922c0bae0eb76a86b3d08279a0bf9fe1a7e4ae60d46f88
|
|
BLAKE2b-256 checksum How to use checksums |
d082197824013f0d5539af6cfde6f6c81a9270f642f7f0acdb5d6e3ee37ad20d
|
| 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 Sep 20, 2026.
Transparency log