Skip to content
quickthumb
All documentation

Image sequences and render quality

Export reference · Choose an output format

Image sequences

Canvas.export_image_sequence() and the matching Deck method write the actual animated timeline as independent PNG, JPEG, or WebP files plus one manifest. The destination must be new and its parent must exist. PNG is the default.

from quickthumb import ImageSequenceOptions

options = ImageSequenceOptions(fps=24, hold=2, render_quality="high", workers=2)
result = canvas.export_image_sequence("overlay_frames", options=options)
canvas.export_image_sequence("review_frames", format="jpeg", quality=85, matte="white", options=options)
canvas.export_image_sequence("web_frames", format="webp", lossless=True, options=options)
print(result.frame_count, result.duration, result.manifest_path)

Encoding controls are keyword arguments to the method, separate from timing and compositing in ImageSequenceOptions(fps=30, hold=3, workers=1, render_quality="standard"):

Control PNG JPEG WebP
format (case-insensitive) "png" (default) "jpeg" "webp"
quality Rejected Integer 1–95; default 90 Integer 1–95 for lossy output; default 80
matte Rejected Opaque color string; default white Rejected
lossless Rejected Rejected Boolean; default False

lossless=True and quality cannot be combined: quality names lossy compression, not lossless WebP encoder effort. Invalid types or incompatible options fail before creating output or staging. Options models contain only fps (finite, >0 and ≤120), hold (finite, nonnegative seconds), workers (strict integer 1–8), and render_quality ("standard" or "high").

PNG retains the renderer's exact 8-bit straight-alpha RGBA bytes, including hidden RGB. Lossless WebP also preserves those pixels, including hidden RGB. JPEG flattens alpha onto the resolved matte and encodes RGB; JPEG and lossy WebP can change colors and do not promise exact canonical pixels. WebP retains alpha, but fully opaque files may omit the redundant alpha plane and open as RGB in image readers. Convert those files to RGBA when consuming the manifest's logical mode: "RGBA". Authored backgrounds stay visible in every format. Odd and one-pixel dimensions retain every row and column. Differently sized slides fit the first canvas's full dimensions, with transparent padding before encoding (matte-colored padding for JPEG). Imported images are not ICC-normalized; output strips inherited profiles/metadata and makes no sRGB/color-profile claim.

The directory contains 000000.png, .jpg, or .webp, and so on, plus one compact manifest.json. It records version: "1", format: "image_sequence", document kind, encoding: "png"|"jpeg"|"webp", relative filename_pattern (%06d.png, %06d.jpg, or %06d.webp), and start_index: 0. Actual frame_count, fps, duration, width, and height describe the written files. Pixel fields describe logical decoding: PNG/WebP use mode: "RGBA" and alpha: "straight"; JPEG uses "RGB" and "none". bit_depth is 8. lossless reports the selected encoding; resolved quality and matte are recorded where applicable and otherwise null.

ImageSequenceResult adds absolute output_directory, manifest_path, and capability_report diagnostics to ImageSequenceManifest. Both models and ImageSequenceOptions support JSON round trips and model_json_schema(). They add no Canvas/Deck document fields or raster format enum values. There is no growing list of frame paths or timestamps. Repeated frames are encoded once per canonical shot and written as independent editable files, not hardlinks. Frame memory stays bounded; existing scene preparation and worker buffers still consume memory.

Timing follows animated exports, including explicit slide durations, advance_after, narration-derived visual timing, transitions, and Morph. Cumulative frame allocation avoids drift across fractional holds; short or zero-hold still slides receive at least one frame. The reported duration is exactly frame_count / fps, measured from the files actually written. This can differ from the independent timeline sampler's duration or sample count. validate_export("image_sequence") and inspect_motion(target="image_sequence") use video motion capabilities. Existing strict/reduced-motion policies and parent, Morph, caption, and worker restrictions apply.

Sequences are silent: no audio encoding, mux, soundtrack, or audio sidecars. Narration retains its existing file validation and duration inference, without the encoder's 64-audio-input limit. Image-only scenes need no FFmpeg; inferred narration lengths still require ffprobe, and video layers retain their normal probe/decode requirements.

All frames and manifest.json are rendered to an atomic staging directory before publication. The target directory must be non-existent; export fails safely if the destination directory already exists, preventing accidental overwrites.

Run examples/image_sequence_overlay.py for PNG, white-matted JPEG and lossless WebP lower-third sequences with no FFmpeg dependency. All three output directories must be fresh on every run.

High-quality animated compositing

GifOptions(render_quality="high") and VideoOptions(render_quality="high") opt into a 2× layer-compositing surface followed by one LANCZOS downsample. The default is render_quality="standard", which preserves existing output. This namespaced option is separate from render(..., quality=...) for JPEG/WebP. Animated byte methods also accept the keyword-only render_quality="high" argument.

canvas.render("smooth.mp4", animation=VideoOptions(fps=30, render_quality="high"))
deck.render("smooth.gif", animation=GifOptions(fps=12, colors=64, render_quality="high"))

High mode filters prepared layers and staggered targets directly into the larger surface without retaining enlarged copies of every source image. It improves integration of rotated/downscaled edges; dimensions, timing, audio, GIF palette/size controls and workers keep their existing meanings. Frames and cached background plates contain four times as many pixels, so high mode costs more rendering time and memory. Each worker adds its own buffers, so aggregate memory can increase with worker count.

This is supersampled layer compositing, not higher-resolution source layout: fonts/assets and backdrop-dependent source surfaces are still prepared at their native size. Authored static rotation is already baked into those sources. Captions retain native rendering after the downsample, while slide transitions and keyed Morph keep their existing native-resolution paths. High mode cannot restore details absent from a prepared source and may soften very small text. See the benchmark guide for matched cost measurements and before/after text crops.

Parallel animated rendering

GifOptions(workers=2) and VideoOptions(workers=2) opt into process-based frame rendering. The same keyword-only workers argument is available on animated byte methods. workers is a strict integer from 1 to 8; the default remains 1, with no automatic CPU scaling. Small/static exports may be faster with one worker because starting processes, preparing slides, and copying frames cost time. Encoding and audio mixing still run in the parent export path.

Use an importable script and Python's main guard: workers always use spawn, including on Linux, without changing the application's global start method. Interactive environments that cannot import the main module should use workers=1.

from quickthumb import Canvas, Fade, VideoOptions

def main():
    canvas = Canvas(640, 360).background(color="#123456")
    canvas.shape("rectangle", (40, 40), 200, 160, "#FF3355",
                 animation=Fade(duration=2))
    canvas.render("parallel.mp4", animation=VideoOptions(fps=30, workers=2))

if __name__ == "__main__":
    main()

Workers rebuild built-in canvases with separate fonts and video decoders. Only a bounded, ordered window of frame jobs is outstanding, preserving the existing shot durations, collapsed holds, caption boundaries, and transition order. Local video layers, captions, seeded grain and built-in motion work in parallel. Custom/plugin layers, Canvas subclasses, remote video sources and unseeded nonzero grain require workers=1. Remote images/fonts resolved by slide preparation are pinned for workers; keep local assets and font-cache files unchanged during export. A source or worker failure fails the export and preserves a pre-existing destination.

More workers require more memory: each has a Python runtime, fonts, prepared slide images and decoder caches, in addition to its in-flight RGB frame. Normal completion and errors wait for running jobs to finish and close their decoders. Pending jobs are canceled on failure; abruptly terminated workers are reported as rendering errors. An OS-level kill cannot run normal cleanup.