PixelFlow User Book
PixelFlow is a Rust-first video processing framework inspired by VapourSynth.
What this book covers
This book documents current Phase 1 user-facing workflows for:
- rendering
.pfscripts withpixelflow, - writing scripts with
source(...)and filters, - using built-in
stdfilters, - configuring FFMS2-backed sources, and
- writing Rust plugins with
pixelflow-plugin-sdk.
Start here
- Read Getting started for first render.
- Read CLI for command behavior.
- Read Scripting for script rules.
- Read Built-in filters for filter reference.
- Read Plugins for plugin authoring limits and SDK path.
Getting started
Prerequisites
pixelflowbinary available locally.- An input video file readable through FFMS2.
- Write access to output path.
Create a minimal script
Create hello.pf:
output = source("input.mkv")
Render output
Run:
pixelflow hello.pf -o out.y4m
This command reads script, indexes reachable source(...) nodes, validates final graph, and writes an output stream selected from the final clip format. Integer Gray/YUV clips write Y4M; planar RGB clips write rawvideo.
Write to stdout instead of a file
Run:
pixelflow hello.pf -o - > out.y4m
Video bytes go to stdout. Progress, indexing messages, diagnostics, and timing output stay on stderr.
Next steps
- Read Render scripts for flags.
- Read Sources for path and source option behavior.
- Read FFMS2 source for exact
source(...)options.
CLI
Current command model
pixelflow is a render-oriented CLI. Normal invocation requires script path and -o output path. version subcommand is separate and does not require script.
Render pipeline
Current CLI flow is:
- read script source,
- evaluate script through
pixelflow_script::ScriptEngine, - index reachable FFMS2 sources,
- validate final graph,
- build built-in executors for reachable built-in filters,
- verify all reachable graph nodes have executors,
- render ordered frames to Y4M or rawvideo based on the final clip format.
Output limits
Current CLI output accepts only final clips with:
- fixed format,
- fixed resolution,
- finite frame count,
- constant frame rate, and
- either Y4M-compatible integer Gray/YUV format or planar RGB format.
Convert unsupported final clips explicitly before rendering.
Render scripts
Required arguments
pixelflow <SCRIPT> -o <PATH>
<SCRIPT>is path to script file.-o <PATH>selects output destination. PixelFlow chooses the stream format from the final clip format.-o -writes the selected stream to stdout.
Command reads script, indexes reachable source(...) nodes, validates final graph, and writes either Y4M or rawvideo output.
Flags
--set NAME=VALUE
Pass script parameters as named values.
--start N
Start render at zero-based frame index N.
--end N
Stop before frame index N.
--threads N
Override worker thread count. N must be positive non-zero integer.
--timings
Print per-node timing report to stderr after render completes.
Examples
pixelflow demo.pf -o out.y4m
pixelflow demo.pf -o - > out.y4m
pixelflow demo.pf -o out.y4m --set width=1280 --set fps=24000/1001
pixelflow demo.pf -o out.y4m --start 100 --end 200
pixelflow demo.pf -o out.y4m --threads 4 --timings
Output formats
PixelFlow auto-detects the final clip format:
- Integer Gray and YUV clips are written as Y4M.
- Planar RGB clips are written as FFmpeg-compatible rawvideo.
Rawvideo has no header. When piping or importing RGB output into ffmpeg, pass the pixel format, frame size, and frame rate explicitly. PixelFlow planar RGB maps to FFmpeg planar GBR names:
| PixelFlow format | FFmpeg -pixel_format |
|---|---|
rgbp8 | gbrp |
rgbp10 | gbrp10le |
rgbp12 | gbrp12le |
rgbp16 | gbrp16le |
rgbpf32 | gbrpf32le |
Example for a 1920x1080 RGB clip at 24000/1001 fps:
pixelflow rgb.pf -o - | ffmpeg -f rawvideo -pixel_format gbrp -video_size 1920x1080 -framerate 24000/1001 -i - out.mkv
Progress output
When stderr is connected to an interactive terminal, PixelFlow shows indexing and rendering progress with transient progress bars. Progress is hidden in non-interactive shells so CI logs and redirected diagnostics stay clean.
When rendering to stdout with -o -, indexing progress may still appear on interactive stderr, but rendering progress is suppressed so stdout piping workflows remain quiet apart from the Y4M stream.
Version output
Basic version output
Run:
pixelflow version
This prints current PixelFlow version string.
Verbose version output
Run:
pixelflow version --verbose
Current verbose output includes:
- PixelFlow core version,
- enabled feature summary,
- plugin ABI version, and
- loaded plugin list.
If no plugins load, verbose output prints plugins: none.
Scripting
PixelFlow scripts build a graph. They do not render frames directly.
Required output
Every valid script must assign exactly one top-level final clip to output.
These cases are errors:
- missing
output, - multiple top-level
outputassignments, - non-clip value assigned to
output.
Script responsibilities
Current script layer is responsible for:
- parameter injection,
- source and filter call coercion,
- namespace sugar rewrite,
- graph construction.
Real source probing and FFMS2 indexing happen later in source indexing, not while parsing script text.
Syntax and call forms
Use supported call form that best fits your script. Paired examples in same section are alternate syntax for same operation.
Generic filter calls
output = filter(source("input.mkv"), "resize", #{ width: 1280, height: 720 })
output = source("input.mkv").resize(#{ width: 1280, height: 720 })
Built-in std namespace
output = std.resize(source("input.mkv"), #{ width: 1280, height: 720 })
output = source("input.mkv").std.resize(#{ width: 1280, height: 720 })
Third-party plugin namespace
output = plugin.acme.blur(source("input.mkv"), #{ radius: 2 })
output = source("input.mkv").plugin.acme.blur(#{ radius: 2 })
Multi-input filters
Use array input form when filter consumes more than one clip:
y = source("input.mkv").std.split_plane(#{ plane: 0 })
u = source("input.mkv").std.split_plane(#{ plane: 1 })
v = source("input.mkv").std.split_plane(#{ plane: 2 })
output = std.merge_planes([y, u, v], #{ format: "yuv420p8" })
Metadata property reads
Use prop(clip, key) to read a metadata property from frame 0, or
prop(clip, frame, key) to read a specific zero-based frame.
clip = source("input.mkv")
output = clip
if prop(clip, 0, "core:matrix") == "bt709" {
output = clip.std.convert_colorspace(#{ matrix: "bt2020_ncl" })
}
The method form is equivalent:
clip = source("input.mkv")
frame_number = clip.prop(10, "core:frame_number")
output = clip
None metadata values return none(). Use is_none(value) before comparing or passing
optional metadata into filters.
Script parameters
Pass script parameters with --set NAME=VALUE.
Name rules
Parameter names must be valid script identifiers.
Value coercions
Current coercion rules are:
true/false-> bool,- whole numbers -> int,
- finite decimal numbers -> float,
- rational text such as
24000/1001-> rational, - anything else -> string.
After injection, script can use parameter names like normal variables.
Example
pixelflow demo.pf -o out.y4m --set width=1280 --set fps=24000/1001 --set title=sample
Sources
Use source("path") to create FFMS2-backed source nodes in scripts.
Relative paths
Relative source paths resolve against script directory during source indexing.
Script evaluation only records source requests in graph. Real probing and indexing happen later.
Source options
Pass source options as map:
output = source("input.mkv", #{ cache: "input.ffms2.pfidx", format: "yuv420p10" })
Read FFMS2 source for exact option rules.
Composing filters
Chaining one-input filters
output = source("input.mkv")
.std.resize(#{ width: 1280, height: 720 })
.std.crop(#{ left: 0, top: 0, width: 1280, height: 700 })
Combining multiple clips
y = source("input.mkv").std.split_plane(#{ plane: 0 })
u = source("input.mkv").std.split_plane(#{ plane: 1 })
v = source("input.mkv").std.split_plane(#{ plane: 2 })
output = std.merge_planes([y, u, v], #{ format: "yuv420p8" })
Multi-input filters use array input form. Every array entry must be clip.
Metadata-only multi-input filters also use array input form:
target = source("graded.mkv")
metadata_source = source("original.mkv")
output = std.copy_props([metadata_source, target])
Metadata-aware workflows
register_prop extends metadata schema at script time so metadata-validating filters can
plan against custom keys.
Register key before filter validates or writes plugin-defined metadata:
register_prop("acme/filter:enabled", "bool")
output = source("input.mkv")
.std.set_prop(#{ key: "acme/filter:enabled", value: true })
.std.require_prop(#{ key: "acme/filter:enabled", kind: "bool", value: true })
Use this when downstream filter needs custom metadata key with known type.
Reading metadata in scripts
prop retrieves a frame metadata value during script evaluation. This lets a script choose
graph structure or filter options from metadata on an already-built clip prefix.
register_prop("acme/filter:enabled", "bool")
clip = source("input.mkv")
.std.set_prop(#{ key: "acme/filter:enabled", value: true })
enabled = clip.prop("acme/filter:enabled")
output = clip
if enabled == true {
output = clip.std.resize(#{ width: 1280, height: 720 })
}
Use prop(clip, frame, key) when the value may differ by frame. Prop reads can index
sources and render the requested frame while the script is being evaluated, so prefer reading
only the metadata needed to choose the graph.
Built-in filters
Official built-in filters register under std. The FFMS2-backed source(...)
call is documented alongside them because it is the normal entry point for clips
used with those filters.
For the canonical pixel format and color metadata strings accepted by
source, convert_format, convert_colorspace, and merge_planes, see
Format and color metadata strings.
Call forms
- Use bare
source(...)for FFMS2 input. - Use
std.<filter>(input, #{ ... })orclip.std.<filter>(#{ ... })for one-input built-in filters such asresize,crop, andconvert_colorspace. - Use array input form for multi-input filters such as
std.merge_planes([y, u, v], #{ format: "yuv420p8" })andstd.copy_props([metadata_source, target]).
Filter families
- source input:
source(...) - conversion:
convert_bit_depth,convert_colorspace,convert_format - spatial:
crop,resize,split_plane,merge_planes - metadata:
set_prop,clear_prop,copy_props,require_prop - temporal:
assume_fps,select,trim
Format and color metadata strings
This reference lists the canonical string values used by built-in filters that
accept format, matrix, transfer, or primaries options.
Pixel format strings are matched case-insensitively and normalized to the
canonical lowercase names below. Color metadata strings use canonical lowercase
names only; aliases such as "rec709" are not accepted.
Pixel format strings
These canonical pixel format names are accepted by filters that use PixelFlow’s
general format parser, including convert_format and merge_planes.
| Canonical string | Family | Notes |
|---|---|---|
gray8 | Gray | Single-plane 8-bit integer gray. |
gray10 | Gray | Single-plane 10-bit integer gray. |
gray12 | Gray | Single-plane 12-bit integer gray. |
gray16 | Gray | Single-plane 16-bit integer gray. |
grayf32 | Gray | Single-plane 32-bit float gray. |
yuv420p8 | YUV 4:2:0 | Three-plane 8-bit integer YUV. |
yuv420p10 | YUV 4:2:0 | Three-plane 10-bit integer YUV. |
yuv420p12 | YUV 4:2:0 | Three-plane 12-bit integer YUV. |
yuv420p16 | YUV 4:2:0 | Three-plane 16-bit integer YUV. |
yuv420pf32 | YUV 4:2:0 | Three-plane 32-bit float YUV. |
yuv422p8 | YUV 4:2:2 | Three-plane 8-bit integer YUV. |
yuv422p10 | YUV 4:2:2 | Three-plane 10-bit integer YUV. |
yuv422p12 | YUV 4:2:2 | Three-plane 12-bit integer YUV. |
yuv422p16 | YUV 4:2:2 | Three-plane 16-bit integer YUV. |
yuv422pf32 | YUV 4:2:2 | Three-plane 32-bit float YUV. |
yuv444p8 | YUV 4:4:4 | Three-plane 8-bit integer YUV. |
yuv444p10 | YUV 4:4:4 | Three-plane 10-bit integer YUV. |
yuv444p12 | YUV 4:4:4 | Three-plane 12-bit integer YUV. |
yuv444p16 | YUV 4:4:4 | Three-plane 16-bit integer YUV. |
yuv444pf32 | YUV 4:4:4 | Three-plane 32-bit float YUV. |
rgbp8 | Planar RGB | Three-plane 8-bit integer RGB. |
rgbp10 | Planar RGB | Three-plane 10-bit integer RGB. |
rgbp12 | Planar RGB | Three-plane 12-bit integer RGB. |
rgbp16 | Planar RGB | Three-plane 16-bit integer RGB. |
rgbpf32 | Planar RGB | Three-plane 32-bit float RGB. |
Accepted pixel format aliases
These aliases resolve to the canonical names above anywhere PixelFlow accepts a pixel format string:
| Accepted alias | Canonical name |
|---|---|
gray | gray8 |
yuv420p | yuv420p8 |
yuv422p | yuv422p8 |
yuv444p | yuv444p8 |
rgbp | rgbp8 |
gbrp | rgbp8 |
gbrp8 | rgbp8 |
gbrp10 | rgbp10 |
gbrp12 | rgbp12 |
gbrp16 | rgbp16 |
gbrpf32 | rgbpf32 |
FFMS2 source() pixel format subset
source(..., #{ format: ... }) accepts only this subset of the pixel format
names above. The general aliases still work when they resolve to one of these
canonical names.
| Supported canonical string |
|---|
gray8 |
gray10 |
gray12 |
gray16 |
yuv420p8 |
yuv420p10 |
yuv420p12 |
yuv420p16 |
yuv422p8 |
yuv422p10 |
yuv422p12 |
yuv422p16 |
yuv444p8 |
yuv444p10 |
yuv444p12 |
yuv444p16 |
Matrix coefficients strings
Use these exact strings for matrix and source_matrix options.
| String | Meaning |
|---|---|
identity | Identity matrix coefficients. |
bt709 | ITU-R BT.709 matrix coefficients. |
bt470m | ITU-R BT.470 System M matrix coefficients. |
bt470bg | ITU-R BT.470 System B/G matrix coefficients. |
smpte170m | SMPTE 170M matrix coefficients. |
smpte240m | SMPTE 240M matrix coefficients. |
ycgco | YCgCo matrix coefficients. |
bt2020_ncl | ITU-R BT.2020 non-constant-luminance matrix coefficients. |
bt2020_cl | ITU-R BT.2020 constant-luminance matrix coefficients. |
smpte2085 | SMPTE ST 2085 matrix coefficients. |
chromaticity_derived_ncl | Chromaticity-derived non-constant-luminance matrix coefficients. |
chromaticity_derived_cl | Chromaticity-derived constant-luminance matrix coefficients. |
ictcp | ICtCp matrix coefficients. |
Transfer characteristic strings
Use these exact strings for transfer and source_transfer options.
| String | Meaning |
|---|---|
bt1886 | ITU-R BT.1886 transfer function. |
bt470m | ITU-R BT.470 System M transfer function. |
bt470bg | ITU-R BT.470 System B/G transfer function. |
smpte170m | SMPTE 170M transfer function. |
smpte240m | SMPTE 240M transfer function. |
linear | Linear-light transfer function. |
log100 | Logarithmic curve with 100:1 range. |
log316 | Logarithmic curve with 316:1 range. |
xvycc | xvYCC transfer function. |
bt1361e | ITU-R BT.1361 extended-color-gamut transfer function. |
srgb | IEC sRGB transfer function. |
bt2020_10 | ITU-R BT.2020 10-bit transfer function. |
bt2020_12 | ITU-R BT.2020 12-bit transfer function. |
pq | SMPTE ST 2084 perceptual quantizer. |
smpte428 | SMPTE ST 428 transfer function. |
hlg | ARIB STD-B67 hybrid log-gamma. |
Color primaries strings
Use these exact strings for primaries and source_primaries options.
| String | Meaning |
|---|---|
bt709 | ITU-R BT.709 primaries. |
bt470m | ITU-R BT.470 System M primaries. |
bt470bg | ITU-R BT.470 System B/G primaries. |
smpte170m | SMPTE 170M primaries. |
smpte240m | SMPTE 240M primaries. |
film | Film primaries. |
bt2020 | ITU-R BT.2020 primaries. |
smpte428 | SMPTE ST 428 primaries. |
dci_p3 | DCI-P3 primaries. |
display_p3 | Display P3 primaries. |
source
source(
path: String,
#{
cache?: String,
fps?: Rational,
vfr?: String,
format?: String,
track?: Int,
threads?: Int,
}
)
Creates a clip from an FFMS2-backed video source.
Relative source paths and relative cache paths resolve against the script
directory when one is known. If the source file does not report a usable frame
rate, you must supply fps. Variable-timestamp sources are normalized to CFR by
default, and vfr: "passthrough" is reserved but unsupported. When you do not
set cache, PixelFlow derives a default .ffms2.pfidx path automatically.
Examples
output = source("input.mkv")
output = source(
"input.mkv",
#{
cache: "custom.ffms2.pfidx",
fps: 24000/1001,
format: "yuv420p10",
track: 0,
threads: 4,
vfr: "normalize",
},
)
Input
A source file path plus optional FFMS2 indexing and decode options.
Output
A CFR clip decoded from the selected video track in the requested pixel format.
Options
cache(String) – Override the cache file path used for the FFMS2 index.fps(Rational) – Override the source frame rate when the file does not provide a usable one.vfr(String, default: implicit CFR normalization) – Select VFR handling; only"normalize"is accepted explicitly.format(String, default:"yuv420p8") – Select the output pixel format from the FFMS2source()pixel format subset.track(Int) – Select a specific zero-based video track; when omitted, PixelFlow uses the first indexed video track.threads(Int, default:1) – Set FFMS2 decode threads;0is clamped to1.
assume_fps
assume_fps(
input: Clip,
#{
fps: Rational | Int | String,
}
)
Changes a clip’s declared constant frame rate without adding, dropping, reordering, or modifying frames.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip. fps
must be positive. String values must use numerator/denominator syntax, integer
values are treated as n/1, and floats are rejected.
Examples
output = source("input.mkv").std.assume_fps(#{ fps: "24000/1001" })
Pass fps through a script parameter:
output = source("input.mkv").std.assume_fps(#{ fps: fps })
pixelflow retime.pf -o out.y4m --set fps=30000/1001
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with the same format, resolution, frame count, and per-frame metadata, but a new declared constant frame rate.
Options
fps(Rational | Int | String) – Set the output constant frame rate.
convert_bit_depth
convert_bit_depth(
input: Clip,
#{
bits: Int,
dither?: String,
}
)
Changes a clip’s bit depth without changing its resolution or timing.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip. The
target depth must be one of PixelFlow’s supported bit depths: 8, 10, 12,
16, or 32 for float output. dither only affects lossy integer
down-conversion and float-to-integer conversion.
Examples
output = source("input.mkv").std.convert_bit_depth(#{ bits: 10 })
output = source("input.mkv").std.convert_bit_depth(#{ bits: 8, dither: "none" })
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with the same family, resolution, timing, and metadata, converted to the requested sample depth.
Options
bits(Int) – Select the target bit depth.dither(String, default:"fruit") – Select the dither mode for lossy conversions; supported values are"fruit"and"none".
convert_colorspace
convert_colorspace(
input: Clip,
#{
range?: String,
matrix?: String,
transfer?: String,
primaries?: String,
chroma_siting?: String,
dither?: String,
}
)
Changes color metadata and, when needed, pixel values without changing the clip format or resolution.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip, and you
must set at least one colorspace option. PixelFlow expects canonical metadata
names such as "bt709" rather than aliases such as "rec709". Some
conversions are metadata-only, while others need render-time source metadata such
as core:range, core:matrix, core:transfer, core:primaries, and
core:chroma_siting. Missing or unsupported metadata can still fail render.
Examples
output = source("input.mkv").std.convert_colorspace(#{ range: "full" })
output = source("input.mkv").std.convert_colorspace(#{
matrix: "bt709",
transfer: "bt1886",
primaries: "bt709",
chroma_siting: "left",
})
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with the same format, resolution, and timing, with colorspace metadata rewritten and pixel values converted as needed.
Options
range(String) – Set the output color range; supported values are"full"and"limited".matrix(String) – Set the output matrix using PixelFlow’s canonical matrix names. See Matrix coefficients strings.transfer(String) – Set the output transfer characteristic using PixelFlow’s canonical transfer names. See Transfer characteristic strings.primaries(String) – Set the output color primaries using PixelFlow’s canonical primaries names. See Color primaries strings.chroma_siting(String) – Set the output chroma siting; supported values are"left","center","top_left","top","bottom_left", and"bottom".dither(String, default:"fruit") – Select the dither mode for lossy range conversions; supported values are"fruit"and"none".
convert_format
convert_format(
input: Clip,
#{
format: String,
range?: String,
matrix?: String,
transfer?: String,
primaries?: String,
chroma_siting?: String,
source_range?: String,
source_matrix?: String,
source_transfer?: String,
source_primaries?: String,
source_chroma_siting?: String,
}
)
Changes a clip’s pixel format without changing its resolution or timing.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip.
convert_format can change family and subsampling, but it cannot change bit
depth or sample storage type; use convert_bit_depth first when needed.
Cross-family conversions depend on correct source color metadata or explicit
source_* overrides, and some target or source metadata axes are only valid for
certain families. Planar RGB to YUV or Gray conversion also requires a target
matrix.
Examples
output = source("input.mkv").std.convert_format(#{ format: "yuv444p10" })
output = source("input.mkv").std.convert_format(#{
format: "rgbp10",
source_matrix: "bt709",
source_transfer: "bt1886",
source_primaries: "bt709",
})
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with the requested pixel format, the same resolution and timing, and metadata preserved or normalized for the output family.
Options
format(String) – Select the target pixel format. See Pixel format strings.range(String) – Override the target output range.matrix(String) – Override the target output matrix. See Matrix coefficients strings.transfer(String) – Override the target output transfer characteristic. See Transfer characteristic strings.primaries(String) – Override the target output color primaries. See Color primaries strings.chroma_siting(String) – Override the target output chroma siting.source_range(String) – Override the source range metadata used during conversion.source_matrix(String) – Override the source matrix metadata used during conversion. See Matrix coefficients strings.source_transfer(String) – Override the source transfer metadata used during conversion. See Transfer characteristic strings.source_primaries(String) – Override the source primaries metadata used during conversion. See Color primaries strings.source_chroma_siting(String) – Override the source chroma siting metadata used during conversion.
crop
crop(
input: Clip,
#{
left?: Int,
top?: Int,
width?: Int,
height?: Int,
right?: Int,
bottom?: Int,
}
)
Returns a rectangular view of one clip without changing its format or timing.
The input must be one fixed-format, fixed-resolution planar Gray, YUV, or planar
RGB clip. left, top, right, and bottom must be non-negative. width and
height must be positive when supplied. Use exactly one of width and right,
and exactly one of height and bottom. The crop rectangle must stay inside the
source frame. For subsampled YUV formats, all crop edges must also stay aligned
to the chroma plane divisors.
Examples
output = source("input.mkv").std.crop(#{ left: 0, top: 0, width: 1280, height: 720 })
output = source("input.mkv").std.crop(#{ left: 8, top: 4, right: 8, bottom: 4 })
Input
One fixed-format, fixed-resolution planar Gray, YUV, or planar RGB clip.
Output
A clip with the same format, timing, and metadata, cropped to the requested rectangle.
Options
left(Int, default:0) – Set the left crop offset in full-resolution pixels.top(Int, default:0) – Set the top crop offset in full-resolution pixels.width(Int) – Set the cropped output width. Mutually exclusive withright.height(Int) – Set the cropped output height. Mutually exclusive withbottom.right(Int) – Set the number of full-resolution pixels to crop from the right side. Mutually exclusive withwidth.bottom(Int) – Set the number of full-resolution pixels to crop from the bottom side. Mutually exclusive withheight.
merge_planes
merge_planes(
input: [Clip, Clip, ...],
#{
format: String,
}
)
Combines Gray plane clips into one planar Gray, YUV, or planar RGB clip.
Use array input form: std.merge_planes([a, b, c], #{ ... }). Every input clip
must be Gray, use fixed format and fixed resolution, and match the frame count
and frame rate of plane 0. The number of inputs and each input plane’s
dimensions must match the requested target format.
Examples
y = source("input.mkv").std.split_plane(#{ plane: 0 })
u = source("input.mkv").std.split_plane(#{ plane: 1 })
v = source("input.mkv").std.split_plane(#{ plane: 2 })
output = std.merge_planes([y, u, v], #{ format: "yuv420p8" })
Input
One Gray clip per output plane, passed in array form.
Output
A planar Gray, YUV, or planar RGB clip assembled from the input planes, using plane 0 for timing and metadata.
Options
format(String) – Select the target pixel format and plane layout. See Pixel format strings.
resize
resize(
input: Clip,
#{
width: Int,
height: Int,
kernel?: String,
b?: Float,
c?: Float,
taps?: Int,
}
)
Rescales a clip to a new width and height with a selectable scalar kernel.
The input must be one fixed-format, fixed-resolution planar Gray, YUV, or planar
RGB clip. width and height must be positive. b and c are only valid with
kernel: "bicubic", and taps is only valid with kernel: "lanczos". YUV
resize uses core:chroma_siting metadata when present and defaults to center
when that metadata is missing or None; unsupported chroma-siting values fail
render.
Examples
output = source("input.mkv").std.resize(#{ width: 1280, height: 720, kernel: "lanczos" })
output = source("input.mkv").std.resize(#{
width: 1920,
height: 1080,
kernel: "bicubic",
b: 0.33333334,
c: 0.33333334,
})
Input
One fixed-format, fixed-resolution planar Gray, YUV, or planar RGB clip.
Output
A clip with the same format, timing, and metadata, resized to the requested dimensions.
Options
width(Int) – Set the output width.height(Int) – Set the output height.kernel(String, default:"bicubic") – Select the resize kernel; supported values are"nearest","bilinear","bicubic","lanczos","spline16", and"spline36".b(Float, default:0.33333334) – Set the bicubic blur parameter whenkernelis"bicubic".c(Float, default:0.33333334) – Set the bicubic ringing parameter whenkernelis"bicubic".taps(Int, default:3) – Set the Lanczos lobe count whenkernelis"lanczos".
select
select(
input: Clip,
#{
every?: Int,
offset?: Int,
drop_every?: Int,
drop_offset?: Int,
frames?: String,
}
)
Builds a new clip by remapping output frames to selected source frames from one input clip.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip with a
finite frame count and a positive constant frame rate. Choose exactly one of
every, drop_every, or frames. Frame expressions preserve the written order,
accept single frames such as 5 and inclusive ranges such as 8-10, 8..10,
or 8..=10, and reject duplicates and empty entries.
Examples
output = source("input.mkv").std.select(#{ every: 2 })
output = source("input.mkv").std.select(#{ frames: "0, 10, 20, 30" })
Input
One fixed-format planar Gray, YUV, or planar RGB clip with a finite frame count.
Output
A clip with the same format and resolution, containing the selected frames in the mapped order and using the frame count and frame rate implied by the selection form.
Options
every(Int) – Keep everynth frame.offset(Int, default:0) – Start theeveryselection at this source frame offset.drop_every(Int) – Drop one frame from everyninput frames.drop_offset(Int, default: last frame in each group) – Choose which frame in eachdrop_everygroup to drop.frames(String) – Provide an explicit frame expression.
Metadata property filters
Use these filters for schema-aware clip metadata workflows:
All four filters validate metadata keys and value kinds against the active
MetadataSchema. Core keys are available immediately. Plugin-defined keys usually
need register_prop(...) before script-time validation can succeed.
Example
register_prop("pixelflow/sample:enabled", "bool")
clip = source("input.mkv")
metadata_source = source("metadata.mkv")
copied = std.copy_props([metadata_source, clip])
flagged = copied.std.set_prop(#{ key: "pixelflow/sample:enabled", value: true })
validated = flagged.std.require_prop(#{ key: "pixelflow/sample:enabled" })
output = validated.std.clear_prop(#{ key: "pixelflow/sample:enabled" })
set_prop
set_prop(
input: Clip,
#{
key: String,
value: MetadataValue,
}
)
Writes one metadata key on each output frame.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip. key
must already exist in the active MetadataSchema, and value must match the
registered metadata kind for that key.
Examples
register_prop("acme/filter:enabled", "bool")
output = source("input.mkv").std.set_prop(#{
key: "acme/filter:enabled",
value: true,
})
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with the same media properties, with the requested metadata key written on each frame.
Options
key(String) – Select the registered metadata key to write.value(MetadataValue) – Provide the metadata value to store.
clear_prop
clear_prop(
input: Clip,
#{
key: String,
}
)
Clears one metadata key on each output frame.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip. key
must already exist in the active MetadataSchema, because clearing still uses
schema-aware metadata validation.
Examples
output = source("input.mkv").std.clear_prop(#{ key: "core:matrix" })
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with the same media properties, with the requested metadata key cleared on each frame.
Options
key(String) – Select the registered metadata key to clear.
copy_props
copy_props(
input: [Clip, Clip]
)
Copies all frame metadata from one clip onto another clip.
Use array input form: std.copy_props([metadata_source, target]). The target clip
must be a fixed-format planar Gray, YUV, or planar RGB clip. The metadata source
and target must have the same frame count, but they may differ in format,
resolution, and frame rate.
Examples
target = source("graded.mkv")
metadata_source = source("original.mkv")
output = std.copy_props([metadata_source, target])
Input
Two clips in array form: [metadata_source, target].
Output
A clip that keeps the target clip’s plane data and media properties, but replaces each frame’s metadata with metadata from the same-numbered source frame.
require_prop
require_prop(
input: Clip,
#{
key: String,
kind?: String,
value?: MetadataValue,
}
)
Validates that each input frame carries required metadata before passing the frame through unchanged.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip. key
must already exist in the active MetadataSchema. If you set kind, it must
match the registered kind for that key. If you set value, it must also match
the registered kind, and value: none is rejected. Missing keys, cleared keys,
type mismatches, and value mismatches fail render.
Examples
register_prop("acme/filter:enabled", "bool")
output = source("input.mkv")
.std.require_prop(#{ key: "acme/filter:enabled", kind: "bool", value: true })
Input
One fixed-format planar Gray, YUV, or planar RGB clip.
Output
A clip with unchanged media and frame data when every frame satisfies the metadata requirement.
Options
key(String) – Select the registered metadata key to require.kind(String) – Require a specific metadata kind; supported values are"bool","int","float","string","array","rational","blob", and"binary".value(MetadataValue) – Require an exact metadata value.
split_plane
split_plane(
input: Clip,
#{
plane: Int,
}
)
Extracts one plane from a planar Gray, YUV, or planar RGB clip as a Gray clip.
The input must be one fixed-format, fixed-resolution planar Gray, YUV, or planar
RGB clip. plane is zero-based and must be within the input plane count. Gray
clips only accept plane: 0. For subsampled YUV formats, the output resolution
matches the selected plane’s own dimensions.
Examples
y = source("input.mkv").std.split_plane(#{ plane: 0 })
output = y
Input
One fixed-format, fixed-resolution planar Gray, YUV, or planar RGB clip.
Output
A Gray clip with the selected plane’s data, matching the input sample type and bit depth.
Options
plane(Int) – Select the zero-based plane index to extract.
trim
trim(
input: Clip,
#{
start?: Int,
end?: Int,
}
)
Keeps one contiguous frame range from an input clip.
The input must be one fixed-format planar Gray, YUV, or planar RGB clip with a
finite frame count. start and end must be non-negative, start must not
exceed end, and end must not exceed the input frame count. start is
inclusive and end is exclusive, so start == end is valid and produces an
empty clip.
Examples
output = source("input.mkv").std.trim(#{ start: 100, end: 200 })
Input
One fixed-format planar Gray, YUV, or planar RGB clip with a finite frame count.
Output
A clip with the same format, resolution, frame rate, and per-frame metadata, containing frames from start up to but not including end.
Options
start(Int, default:0) – Select the first source frame to keep.end(Int, default: input frame count) – Select the exclusive end frame.
Plugins
Current Phase 1 model
- built-in filters register first,
- CLI then loads conventional plugins from platform plugin directories,
- plugin load or registration failures warn and are skipped,
- verbose version output reports loaded plugins and ABI version.
Supported authoring path
Current official authoring path is Rust through pixelflow-plugin-sdk.
What plugins can declare today
Plugins currently declare:
- filter descriptors through
RegistrationContext::register_filter, and - metadata keys through
RegistrationContext::register_metadata.
Those declarations extend host registry and metadata schema during plugin load.
Current limits
- This book covers current Rust SDK authoring path only.
- Direct C or C++ authoring is not supported in current Phase 1 workflow.
- Plugin failures do not stop CLI startup when host can safely skip failed plugin.
See also
Rust SDK authoring
Minimal plugin type
#![allow(unused)]
fn main() {
use pixelflow_plugin_sdk::{
FilterRegistration, MetadataKind, MetadataRegistration, Plugin, RegistrationContext, Result,
pixelflow_plugin,
};
#[derive(Default)]
pub struct SamplePlugin;
impl Plugin for SamplePlugin {
fn name(&self) -> &'static str {
"pixelflow-sample-plugin"
}
fn register(&self, registry: &mut RegistrationContext<'_>) -> Result<()> {
registry.register_filter(FilterRegistration::new(
"sample.identity",
"pixelflow",
"sample",
))?;
registry.register_metadata(MetadataRegistration::new(
"pixelflow/sample:enabled",
MetadataKind::Bool,
))
}
}
pixelflow_plugin!(SamplePlugin);
}
What register() does
register() declares filters and metadata keys through RegistrationContext.
In current SDK surface:
register_filter(FilterRegistration::new(...))declares one filter name plus publisher and plugin namespaces.register_metadata(MetadataRegistration::new(...))declares one metadata key and itsMetadataKind.- Registration is synchronous. Host accepts or rejects each declaration during plugin load.
Keep register() focused on capability declaration. Do not hand-write ABI tables or exported symbols.
Entry point export
pixelflow_plugin! exports pixelflow_plugin_entry_v1 and is supported path.
Macro requires plugin type to implement:
PluginDefault'static
SDK wraps entry-table export and registration callbacks with panic guards so plugin panics do not unwind across FFI boundary.
Source of truth
Start with examples/sample-rust-plugin/src/lib.rs for smallest working plugin.
Read crates/pixelflow-plugin-sdk/src/lib.rs for Plugin, pixelflow_plugin!, ABI versioning, and entry-point behavior.
Also useful while authoring:
crates/pixelflow-plugin-sdk/src/builders.rsforFilterRegistrationandMetadataRegistrationcrates/pixelflow-plugin-sdk/src/registration.rsforRegistrationContextcrates/pixelflow-plugin-sdk/tests/plugin_contracts.rsfor SDK behavior and failure contracts
Practical authoring notes
- Keep plugin
name()stable. Host uses it for diagnostics. - Register plugin metadata keys before filters or scripts rely on them.
- Use committed sample plugin as smallest working template, not handwritten FFI.
- If host rejects registration callback, SDK surfaces structured plugin error instead of silent success.
C ABI note
PixelFlow commits a generated ABI header at:
crates/pixelflow-plugin-sdk/include/pixelflow_plugin.h
This header exists for ABI review and compatibility checks.
Direct C or C++ plugin authoring is unsupported in current Phase 1 workflow. Use the Rust SDK instead.
What header is for
Use header when reviewing ABI layout, exported symbol shape, and compatibility drift across host and plugin changes.
What header is not for
Do not treat header as supported direct authoring workflow for production PixelFlow plugins today.
Rust SDK remains only documented authoring path in this book.