ThumbnailDocument model

September 5, 2026 · View on GitHub

Full field reference for model.py. All bounds are enforced structurally before anything touches a file, a font or ffmpeg-skill.

ThumbnailCanvas

FieldTypeBoundsNotes
width, heightint16..7680the rendered output is always exactly this size; never hard-coded
background#RRGGBB | #RRGGBBAAdefault #000000; fills any area no image element covers

ThumbnailAsset

FieldTypeNotes
asset_idid (^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$)unique within the document
kindimage | video_frame
pathstringresolved through PathPolicy at render time; untrusted
timestampnumber, secondsrequired iff kind: video_frame; forbidden otherwise; 0 <= timestamp <= 604800; checked against the source's own duration once probed

A video_frame asset never triggers a search: timestamp is used exactly as given. Choosing a timestamp (best frame, a moment after a cut, a specific slide) is the caller's job.

ThumbnailElement

FieldTypeNotes
element_ididunique within the document
typeimage | textselects which of image/text below is required
z_indexint, -1000..1000default 0; stacking order, ties broken by document order

image content

FieldTypeNotes
asset_ididmust reference a declared asset (MISSING_INPUT otherwise)
position{x, y}top-left of the target box, canvas pixels; may be negative (partial off-canvas placement is allowed, deliberately, not auto-clamped)
size{width, height}1..7680 each; the target box on the canvas
fitcover | contain | fill | nonedefault cover. fill stretches (no aspect preserved); cover scales to fully cover the box and centre-crops the overflow; contain scales to fit entirely inside the box (result may be smaller); none places the source at native size, centred in the box
crop{x, y, width, height} | nullsource-pixel space, applied before fit; validated against the asset's actual decoded size at render time (INVALID_REQUEST, reason crop_out_of_bounds, if it doesn't fit)
opacity0..1default 1
rotation0 | 90 | 180 | 270default 0; clockwise; axis-aligned only — arbitrary-angle rotation is not implemented (UNSUPPORTED_OPERATION), to keep output deterministic and free of resampling artefacts at the canvas edge

text content

FieldTypeNotes
textstring, 1..2000 chars, <= 50 linescontrol characters other than \n are rejected; line breaks are literal \n — there is no automatic word-wrap, no reflow, no layout decision made here
font_idone of the registry (fonts.py)never a path; unresolvable on this machine -> MISSING_INPUT
font_sizeint, 6..400pixels
color#RRGGBB | #RRGGBBAA
position{x, y}the anchor point; align says which corner/edge of the text block sits there
align`{horizontal: leftcenter
line_spacing0.5..5.0default 1.2, multiplies the font's own line height
opacity0..1default 1
background{color, padding} | nulla filled box drawn behind the text block, padding 0..200
stroke{color, width} | nulloutline, width 0..40
shadow{color, offset_x, offset_y} | nullflat offset shadow, each offset -200..200 (no blur: kept simple and deterministic)

OutputSpec

FieldTypeNotes
pathstringresolved through PathPolicy; must end in the extension the format expects
formatpng | jpegonly formats actually implemented are ever advertised
overwritebooldefault false
jpeg_quality1..100only accepted when format: jpeg

Options

FieldTypeNotes
allowed_input_rootsarray of directories | nulldefault: unrestricted (any readable regular file)
workspacedirectory | nulldefault: current directory; confines output, the reuse cache and temp files
reusebooldefault true
timeout1..3600 secondsdefault 120; per ffmpeg-skill invocation
ffmpeg_skilldirectory | nullexplicit ffmpeg-skill checkout, overriding the environment-variable/well-known-path discovery

What is deliberately not a field

command, commands, argv, args, cmd, shell, exec, executable, script, filter, filters, filter_complex, vf, af, ffmpeg, env, cwd, eval, html, css, javascript — rejected by name at any depth, including inside metadata, which otherwise accepts arbitrary caller data (capped at 8 KiB of canonical JSON) that this skill never reads or renders.