Skip to content
quickthumb
All documentation

Quickthumb 0.8.0

Quickthumb 0.8.0 brings the work since v0.7.0 into one release: a shared Canvas/Deck output contract, more precise animation, vector paths, text selectors, compositing, bounded Lottie export, and a unified presentation UI. This is a breaking pre-1.0 release. Read the migration section before upgrading saved specs, scripts, or integrations.

Jump to migration from v0.7.0.

python -m pip install --upgrade "quickthumb[cli]==0.8.0"

Python 3.11+ is required. Video export needs an external FFmpeg installation; installing a Python extra does not install native programs or libraries.

One document and output workflow

Canvas and Deck now implement the structural Document protocol with validate(), inspect(), diagnose(), prefetch_assets(), sample(), and export(). There is no new document wrapper to construct.

from quickthumb import Canvas, Deck, VideoOptions

canvas = Canvas(320, 180).background(color="#102030").shape(
    "rectangle", position=(24, 24), width=96, height=64, color="#42CEB7",
)
receipt = canvas.export("card.png")
print(receipt.written_paths, receipt.pixel_metrics.width)
frames = canvas.sample(time=[0.0])
print(frames.frames[0].sha256)
Deck(slides=[canvas]).export("slides.mp4", animation=VideoOptions(hold=1.0))
  • export(path, ...) is the recommended file API and returns ExportResult: written paths, capability/fallback diagnostics, pixel and timing metrics, assets, and the observed render environment. render() remains available; it does not return a receipt. Both enforce the shared file-option and capability policy. Byte/string to_*() methods retain their own contracts.
  • sample() returns FrameSequence / CanonicalFrame data from the canonical renderer. inspect_document() combines layout, motion, diagnostics, and observed assets without writing an export.
  • Canonical to_json() / from_json() now cover both document kinds and plugin references. JSON describes the authored scene, not exported frames, callbacks, fonts, or an asset archive. Report models have model_dump_json().
  • Structured errors expose code and details with input paths and layer context. InputError, MissingAssetError, and ErrorDetail join the existing Quickthumb error types. Validation and diagnostic reports are JSON-safe.

Ordinary files may overwrite existing files. Deck raster output writes numbered slide files, reported in the receipt; this is not an atomic multi-file transaction. Unknown suffixes, incompatible option families, and unsupported format overrides are rejected. Explicit raster format overrides support PNG/JPEG/WebP without renaming the supplied path. JPEG quality is 1–95 and ordinary WebP quality is 1–100; PNG does not accept compression quality here.

Output contract · Public API · JSON/schema

Animation, audio, and rendering

  • Transform sampling uses subpixel affine geometry. anchor is a normalized 0–1 pivot, separate from layout align; ScaleXTrack and ScaleYTrack add independent axes. Image ScaleTrack now scales the outer object, like other layers. ImageZoomTrack and image pan/Ken Burns controls address content inside the image viewport. Static and animated transform handling is aligned.
  • PositionKeyframeSpec adds Bézier handles and constant-speed path sampling. Position values offset authored placement; handles offset their own keyframe, rather than declaring absolute canvas control points. Absolute TimingSpec.start values now survive JSON round trips. auto_orient adds path heading to authored rotation/rotation tracks, independent of track order. ColorTrack uses perceptual text/shape paint interpolation.
  • CubicBezierEasing, per-keyframe easing, hold keyframes, closed-form SpringEasing, and seeded, band-limited Wiggle expand motion authoring. Springs use a normalized one-second response with exact endpoints. Wiggle preserves the authored start and freezes its final perturbation after motion.
  • Canvas.null() / NullLayer and parent support scene-local transform hierarchies, including bounded nested group composition, counters, clips, and masks. .group() remains the simple auto-layout entry point. Parent-local placement does not change percentage coordinates into parent percentages: they still resolve against the canvas. Unsupported compositions are diagnosed.
  • Word/line staggering uses placed text layout. RangeSelector supports weighted selection and animated bounds; character targets are Unicode code points, not grapheme clusters. A requested exact fragment is rendered or rejected before output, including a single target; there is no approximate split path.
  • quickthumb.audio.analyze_audio() returns cached AudioAnalysis source-clock onset times and relative RMS windows. Use onsets as numeric timing anchors or analysis.scale_track() in AnimationSpec.timeline(). This is transient detection, not beat tracking, and does not attach a soundtrack for you.
  • Video sampling respects decoded timestamps; caption boundaries and encoded output clocks are tested separately from canonical scene time. Times are in seconds. GIF quantization and codec timing can differ from the canonical grid; receipts report timing rather than promising identical clocks in every codec.
  • GIF/video frame streaming, static-slide-prefix reuse, shared font faces, and bounded spawn-based frame workers reduce repeated work. workers=1 is the default; supported multi-worker output accepts 1–8 workers. Use render_quality="high" for spatial supersampling. Optional MotionBlur adds temporal exposure to file options and image sequences, not byte methods. It is distinct from spatial quality and has explicit composition limits. to_gif() also exposes proportional max_size and palette colors (2–256), previously available through GifOptions at the file boundary.

Motion and timing · Audio API and examples · Kinetic lower third

Paths, masks, and plugins

  • Canvas.path() / PathLayer author SVG path data in a fixed viewport: polygonal paths, adaptive quadratic/cubic curves and elliptical arcs, bounded gradient fills, solid/dashed strokes, caps and joins. Animated geometry is repainted at device resolution. DashOffsetTrack, TrimStartTrack, TrimEndTrack, and TrimOffsetTrack use canonical path-length sampling.
  • PathMorphTrack interpolates matched path contours. This is a different API from the existing cross-slide transitions.Morph; it does not imply every vector exporter can consume a morph track.
  • TrackMatte supports alpha/luma sources with explicit source visibility, inversion, and reuse rules. Owner-local path masks and MaskPositionTrack, MaskSizeTrack, and MaskFeatherTrack extend masking. Nested/dynamic group, caption, backdrop, and other unsupported combinations are rejected rather than silently approximated. Native HTML hard masks have narrower support than raster.
  • Versioned PluginDefinition / PluginRegistry registrations declare parameters, assets, and synchronous raster callbacks. Canvas.plugin() and JSON loading accept an explicit registry. Plugins are trusted Python, not a sandbox; JSON does not install callbacks. Raster rendering is required; timed plugins and plugin rendering with workers > 1 are not supported. Optional SVG hooks affect SVG only and require local pixel admission to be used as native output.

Path API · Masks and mattes · Plugin contract

Output formats and their boundaries

Output What changes in 0.8.0
PNG/JPEG/WebP Shared file policy, receipts, explicit quality validation; still raster output.
Image sequence export_image_sequence() writes PNG, JPEG, or WebP frames and a manifest to a new directory. Existing directories, even empty ones, are not overwritten. The parent must exist. Publication uses private staging and an exclusive atomic rename; it is not a crash-durable asset archive. No audio is included.
GIF/MP4 Shared animation clock and output controls. Deck MP4 plays animation by default and retains per-slide narration and soundtrack scheduling. MP4 is opaque; GIF has palette/transparency limits.
WebM/MOV transparent=True supports VP9 alpha WebM and ProRes 4444 MOV. Alpha support is not a promise of exact lossless RGB/RGBA across codec color conversion or players. MOV needs FFmpeg's prores_ks.
SVG Native bounded path/paint and admitted plugin routes; receipts distinguish actual native and raster layers. Static fidelity observation uses CairoSVG. No Deck to_svg() method.
HTML Presentation remains interactive; supported native path stroke phase/trim and hard-mask mappings are bounded. Path motion mapping uses a 120 Hz sampling grid plus authored knots. Unsupported animation has explicit static/fallback reporting, including per-slide Deck routes.
PDF Bounded paths become editable curves with explicit raster fallback for unsupported paint. Optional PDFium comparisons report actual executed routes. Deck PDF fidelity observation is rejected.
PPTX Receipts report actual editable/raster routes; support is feature-specific. Native PowerPoint playback/fidelity has not been certified by the Linux test suite.
Lottie .lot Canvas only, via export()/render() and LottieOptions; no Deck or byte API. Bounded solid paints on shapes/paths (including alpha), cubic geometry, native stroke trim, sampled held poses, Null/Shape parent hierarchies, and self-contained static ASCII text outlines with opaque text paint. No general raster fallback, audio, images, gradients, arbitrary text shaping, track mattes, or PathMorphTrack consumer. Unsupported features error even when another policy asks to rasterize.
from quickthumb import ImageSequenceOptions, LottieOptions

canvas.export_image_sequence(
    "frames", format="webp", lossless=True,
    options=ImageSequenceOptions(fps=30.0, hold=1.0),
)
canvas.export("card.lot", animation=LottieOptions(fps=30.0, hold=1.0))

Image sequence encoding arguments are separate from timing: PNG preserves canonical 8-bit straight RGBA; JPEG flattens to RGB over a default white matte; WebP can be lossy or lossless. Sequence JPEG/WebP quality is 1–95, unlike ordinary WebP's 1–100. lossless is WebP-only; incompatible parameters error. The manifest records encoding, frame names/count/timing, dimensions, alpha, mode, quality/lossless/matte accurately instead of asserting universal RGBA equality.

ExportPolicy makes unsupported-motion handling explicit. Native-route reports are observations of the route taken, not editability guarantees in all viewers. Opt-in static SVG/PDF fidelity limits are report-only: exceeding a limit does not automatically reject or switch renderers. Chrome comparison is a test tool, not a new production rendering backend. See the format-specific limits.

Assets, fonts, CLI, and presentation

  • Shared remote resolution adds prefetch_assets() and manifests with cache state, source/content identity, and stale reasons. Offline cached rendering is explicit; stale data is reported, not automatically refused. A document retains its first remote resolution; a new document resolves again under the current cache policy. Reports do not transfer bytes or own cache lifetimes. Keep local files and referenced cache files available through export. Cache controls are QUICKTHUMB_ASSET_CACHE_DIR, QUICKTHUMB_ASSET_MAX_AGE, and QUICKTHUMB_ASSET_OFFLINE.
  • Render environments record actual font bytes, face/variation/shaping identity, and renderer versions. Remote fonts use content-addressed copies. Default QUICKTHUMB_DEFAULT_FONT selection now honors weight, bold, and italic, so existing text can change wrapping, alignment, and visible pixels.
  • quickthumb doctor checks discovered requirements and write readiness; quickthumb inspect provides a common Canvas/Deck envelope. Schema, lint, render, and error output follow the stricter document contract. CLI raster quality follows the JPEG 1–95 / WebP 1–100 distinction.
  • quickthumb serve keeps slides-first presentation as the default. Presentation and Inspect motion share warm-white/teal chrome, real slide thumbnails, and the original slide aspect ratio. The motion panel scrubs export-renderer raster frames with timing/proxy controls; it is not playback, audio, or an editor. --timeline opens that inspection mode. Presenter view now matches the theme, with current/next slides, notes, elapsed time, and navigation. Keyboard/cue progression, audience synchronization, and source reload remain.
  • Installation/docs now distinguish base output from cli, svg, pdf, pptx, lottie, and rembg extras. CairoSVG needs native Cairo; text outlines need [lottie]/fontTools and supported fonts (RAQM for nonzero letter spacing). Background removal may download its model on first use.
  • Security/dependency maintenance includes patched rembg/ONNX Runtime, Pillow, certifi, filelock/virtualenv, and ty 0.0.84, plus current pinned CI actions, watchfiles, Typer, inline-snapshot, and diff-cover. Existing scan gates remain.

Installation · CLI and serve · Examples

Migration from v0.7.0

No compatibility aliases are provided for removed entry points.

Before 0.8.0
deck.to_animated_mp4(slide_duration=2.0) deck.to_mp4(hold=2.0); files use deck.export("out.mp4", animation=VideoOptions(hold=2.0)).
deck.to_mp4(...) / deck.render_mp4(...) for static narrated PNG slides General MP4 now plays motion. Use static canvases and explicit transitions.Cut() for cuts; narration/duration remain on .slide(). The old still-only method is removed, not silently preserved.
deck.to_gif(slide_duration=2.0) / deck.to_webm(slide_duration=2.0) Use hold=2.0 on either method. Update saved animation option JSON too.
Iterating canvas.diagnose() / deck.diagnose() directly Iterate .diagnose().findings; DiagnosticReport replaces the old result shapes.
deck.model_dump(mode="json") json.loads(deck.to_json()) for scene data. Result/report models still have Pydantic serialization.
Handwritten JSON without document kind, or with unknown fields/duplicate layer IDs Add kind: "canvas" / "deck" at document boundaries (including slides), retain layer type, and fix invalid fields/IDs. Regenerate schema with the installed version. Strict parsing also rejects non-finite/trailing JSON and malformed scalar fields.
Treating image ScaleTrack as viewport zoom Use ImageZoomTrack for viewport zoom; ScaleTrack transforms the outer image geometry. Review anchors and auto-orient plus rotation offsets.
Importing timeline/compiler/renderer helpers from quickthumb Use documented authoring types and document sample() / inspect_motion() operations. Low-level implementation helpers are no longer root-level public exports or a supported lifecycle.
Assuming default font style selects the regular face Select a font/style deliberately and review layout after the corrected style lookup.

For a static narrated cut sequence in the general MP4 path:

from quickthumb import Deck, VideoOptions, transitions

deck = Deck(320, 180, transition=transitions.Cut())
deck.slide(canvas, audio="narration.wav", duration=2.0)
deck.export("narrated.mp4", animation=VideoOptions(fps=30.0, hold=1.0))
mp4_bytes = deck.to_mp4(fps=30.0, hold=1.0)

duration overrides narration length, which otherwise supplies the static slide's duration before the default hold is used. This does not freeze an arbitrary animated Canvas. A None transition uses the existing default fade; use an explicit Cut for cuts. Reduced-motion handling is a separate policy.

For integrations built against unreleased intermediate checkouts, also replace export_png_sequence / PngSequence* with export_image_sequence / ImageSequence*, animation option quality with render_quality, and to_narrated_mp4 / render_narrated_mp4 with the general MP4 path. Those intermediate narrated/sequence names were not v0.7.0 public APIs. A monkeypatched render() hook is no longer a dispatch mechanism for export().

Validation and remaining limits

The pre-release integrated main passed Linux CI with 4,632 tests passed, 3 skipped, coverage 92.23%, and quality/type/build/security checks. The version PR and published wheel are checked separately by the release pipeline. Reference-player and pixel evidence exists for the documented bounded Lottie, HTML, vector, timing, and alpha routes; this does not certify every combination or native application.

The outstanding Linux default-font golden refresh is not newly verified by this release. Format-specific implementation and native-application limits are listed with each output above.

Full comparison: https://github.com/sjquant/quickthumb/compare/v0.7.0...v0.8.0