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 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 returnsExportResult: 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/stringto_*()methods retain their own contracts.sample()returnsFrameSequence/CanonicalFramedata 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 havemodel_dump_json(). - Structured errors expose
codeanddetailswith input paths and layer context.InputError,MissingAssetError, andErrorDetailjoin 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.
anchoris a normalized 0–1 pivot, separate from layoutalign;ScaleXTrackandScaleYTrackadd independent axes. ImageScaleTracknow scales the outer object, like other layers.ImageZoomTrackand image pan/Ken Burns controls address content inside the image viewport. Static and animated transform handling is aligned. PositionKeyframeSpecadds 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. AbsoluteTimingSpec.startvalues now survive JSON round trips.auto_orientadds path heading to authored rotation/rotation tracks, independent of track order.ColorTrackuses perceptual text/shape paint interpolation.CubicBezierEasing, per-keyframe easing, hold keyframes, closed-formSpringEasing, and seeded, band-limitedWiggleexpand 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()/NullLayerandparentsupport 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.
RangeSelectorsupports 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 cachedAudioAnalysissource-clock onset times and relative RMS windows. Use onsets as numeric timing anchors oranalysis.scale_track()inAnimationSpec.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=1is the default; supported multi-worker output accepts 1–8 workers. Userender_quality="high"for spatial supersampling. OptionalMotionBluradds 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 proportionalmax_sizeand palettecolors(2–256), previously available throughGifOptionsat the file boundary.
Motion and timing · Audio API and examples · Kinetic lower third
Paths, masks, and plugins
Canvas.path()/PathLayerauthor 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, andTrimOffsetTrackuse canonical path-length sampling.PathMorphTrackinterpolates matched path contours. This is a different API from the existing cross-slidetransitions.Morph; it does not imply every vector exporter can consume a morph track.TrackMattesupports alpha/luma sources with explicit source visibility, inversion, and reuse rules. Owner-local path masks andMaskPositionTrack,MaskSizeTrack, andMaskFeatherTrackextend 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/PluginRegistryregistrations 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 withworkers > 1are 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. |
| 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 areQUICKTHUMB_ASSET_CACHE_DIR,QUICKTHUMB_ASSET_MAX_AGE, andQUICKTHUMB_ASSET_OFFLINE. - Render environments record actual font bytes, face/variation/shaping identity,
and renderer versions. Remote fonts use content-addressed copies. Default
QUICKTHUMB_DEFAULT_FONTselection now honorsweight,bold, anditalic, so existing text can change wrapping, alignment, and visible pixels. quickthumb doctorchecks discovered requirements and write readiness;quickthumb inspectprovides 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 servekeeps 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.--timelineopens 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, andrembgextras. 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