Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 .pf scripts with pixelflow,
  • writing scripts with source(...) and filters,
  • using built-in std filters,
  • configuring FFMS2-backed sources, and
  • writing Rust plugins with pixelflow-plugin-sdk.

Start here

Getting started

Prerequisites

  • pixelflow binary 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

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:

  1. read script source,
  2. evaluate script through pixelflow_script::ScriptEngine,
  3. index reachable FFMS2 sources,
  4. validate final graph,
  5. build built-in executors for reachable built-in filters,
  6. verify all reachable graph nodes have executors,
  7. 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 formatFFmpeg -pixel_format
rgbp8gbrp
rgbp10gbrp10le
rgbp12gbrp12le
rgbp16gbrp16le
rgbpf32gbrpf32le

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 output assignments,
  • 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, #{ ... }) or clip.std.<filter>(#{ ... }) for one-input built-in filters such as resize, crop, and convert_colorspace.
  • Use array input form for multi-input filters such as std.merge_planes([y, u, v], #{ format: "yuv420p8" }) and std.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 stringFamilyNotes
gray8GraySingle-plane 8-bit integer gray.
gray10GraySingle-plane 10-bit integer gray.
gray12GraySingle-plane 12-bit integer gray.
gray16GraySingle-plane 16-bit integer gray.
grayf32GraySingle-plane 32-bit float gray.
yuv420p8YUV 4:2:0Three-plane 8-bit integer YUV.
yuv420p10YUV 4:2:0Three-plane 10-bit integer YUV.
yuv420p12YUV 4:2:0Three-plane 12-bit integer YUV.
yuv420p16YUV 4:2:0Three-plane 16-bit integer YUV.
yuv420pf32YUV 4:2:0Three-plane 32-bit float YUV.
yuv422p8YUV 4:2:2Three-plane 8-bit integer YUV.
yuv422p10YUV 4:2:2Three-plane 10-bit integer YUV.
yuv422p12YUV 4:2:2Three-plane 12-bit integer YUV.
yuv422p16YUV 4:2:2Three-plane 16-bit integer YUV.
yuv422pf32YUV 4:2:2Three-plane 32-bit float YUV.
yuv444p8YUV 4:4:4Three-plane 8-bit integer YUV.
yuv444p10YUV 4:4:4Three-plane 10-bit integer YUV.
yuv444p12YUV 4:4:4Three-plane 12-bit integer YUV.
yuv444p16YUV 4:4:4Three-plane 16-bit integer YUV.
yuv444pf32YUV 4:4:4Three-plane 32-bit float YUV.
rgbp8Planar RGBThree-plane 8-bit integer RGB.
rgbp10Planar RGBThree-plane 10-bit integer RGB.
rgbp12Planar RGBThree-plane 12-bit integer RGB.
rgbp16Planar RGBThree-plane 16-bit integer RGB.
rgbpf32Planar RGBThree-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 aliasCanonical name
graygray8
yuv420pyuv420p8
yuv422pyuv422p8
yuv444pyuv444p8
rgbprgbp8
gbrprgbp8
gbrp8rgbp8
gbrp10rgbp10
gbrp12rgbp12
gbrp16rgbp16
gbrpf32rgbpf32

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.

StringMeaning
identityIdentity matrix coefficients.
bt709ITU-R BT.709 matrix coefficients.
bt470mITU-R BT.470 System M matrix coefficients.
bt470bgITU-R BT.470 System B/G matrix coefficients.
smpte170mSMPTE 170M matrix coefficients.
smpte240mSMPTE 240M matrix coefficients.
ycgcoYCgCo matrix coefficients.
bt2020_nclITU-R BT.2020 non-constant-luminance matrix coefficients.
bt2020_clITU-R BT.2020 constant-luminance matrix coefficients.
smpte2085SMPTE ST 2085 matrix coefficients.
chromaticity_derived_nclChromaticity-derived non-constant-luminance matrix coefficients.
chromaticity_derived_clChromaticity-derived constant-luminance matrix coefficients.
ictcpICtCp matrix coefficients.

Transfer characteristic strings

Use these exact strings for transfer and source_transfer options.

StringMeaning
bt1886ITU-R BT.1886 transfer function.
bt470mITU-R BT.470 System M transfer function.
bt470bgITU-R BT.470 System B/G transfer function.
smpte170mSMPTE 170M transfer function.
smpte240mSMPTE 240M transfer function.
linearLinear-light transfer function.
log100Logarithmic curve with 100:1 range.
log316Logarithmic curve with 316:1 range.
xvyccxvYCC transfer function.
bt1361eITU-R BT.1361 extended-color-gamut transfer function.
srgbIEC sRGB transfer function.
bt2020_10ITU-R BT.2020 10-bit transfer function.
bt2020_12ITU-R BT.2020 12-bit transfer function.
pqSMPTE ST 2084 perceptual quantizer.
smpte428SMPTE ST 428 transfer function.
hlgARIB STD-B67 hybrid log-gamma.

Color primaries strings

Use these exact strings for primaries and source_primaries options.

StringMeaning
bt709ITU-R BT.709 primaries.
bt470mITU-R BT.470 System M primaries.
bt470bgITU-R BT.470 System B/G primaries.
smpte170mSMPTE 170M primaries.
smpte240mSMPTE 240M primaries.
filmFilm primaries.
bt2020ITU-R BT.2020 primaries.
smpte428SMPTE ST 428 primaries.
dci_p3DCI-P3 primaries.
display_p3Display 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 FFMS2 source() 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; 0 is clamped to 1.

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 with right.
  • height (Int) – Set the cropped output height. Mutually exclusive with bottom.
  • right (Int) – Set the number of full-resolution pixels to crop from the right side. Mutually exclusive with width.
  • bottom (Int) – Set the number of full-resolution pixels to crop from the bottom side. Mutually exclusive with height.

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

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 when kernel is "bicubic".
  • c (Float, default: 0.33333334) – Set the bicubic ringing parameter when kernel is "bicubic".
  • taps (Int, default: 3) – Set the Lanczos lobe count when kernel is "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 every nth frame.
  • offset (Int, default: 0) – Start the every selection at this source frame offset.
  • drop_every (Int) – Drop one frame from every n input frames.
  • drop_offset (Int, default: last frame in each group) – Choose which frame in each drop_every group 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 its MetadataKind.
  • 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:

  • Plugin
  • Default
  • '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.rs for FilterRegistration and MetadataRegistration
  • crates/pixelflow-plugin-sdk/src/registration.rs for RegistrationContext
  • crates/pixelflow-plugin-sdk/tests/plugin_contracts.rs for 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.