`craft_lottie::model` is the Lottie document as the file has it: one struct per object of the lottie-spec 1.0 object model (animation, assets, layers by type, transform, masks, effects, shape items, text, markers, fonts, glyphs), one field per key. `Animation::read` reads within `Limits` (bytes, nesting, layers, assets, shape items); `to_json` writes. Keys the model does not name, values of another type than expected, and layers and shape items of an unknown type stay in `extra` and are written back, so a document survives a read and a write. Animatable properties stay JSON (`Animated`) and are read with `read_prop`.
5.7 KiB
craft-lottie
Lottie (Bodymovin) JSON: the document as the file has it, and the encodings its values use. Bytes in, a neutral model out, and back. Which layer, shape, effect and text feature becomes what in an application's document is the application's work.
No file access: callers pass bytes, so the crate builds for wasm32.
The document model
Animation::read(bytes) gives the document; to_json and to_vec write it. The model is the
lottie-spec 1.0 object model, one struct per object and one field per key:
Animation: version, frame rate, frame range, size,assets,layers,markers,fonts,chars,meta.Asset: an image (dir,path,embedded) or a precomposition (layers).Layer: the keys every layer has (name,index,parent, timing, blend mode,transform,masks,effects, matte keys) andcontent, byty:Precomp,Solid,Image,Null,Shape,Text, orOtherfor a type the model has no struct for.Shape: the keys every shape item has andkind, byty: group, rectangle, ellipse, polystar, path, fill, stroke, gradient fill and stroke, transform, trim, round corners, repeater, merge, pucker and bloat, twist, zig-zag, offset path, orOther.Transform,Mask,Effect(an effect and its values),Marker,Fonts,Font,Char.TextData: the documents over time (TextDocument), range animators (TextRange,TextSelector,TextStyle), alignment and path options.
Every field is an Option: files leave keys out, and a writer sets what it needs. An animatable
property is an Animated, the property's JSON; Animated::read gives its value or keyframes (see
below). What a value means depends on the property, so the model does not decode values.
Nothing a file holds is dropped because the model does not know it. A key the struct does not
name, and a key whose value has another type than the struct expects, stay in the struct's extra
and are written back. A layer or shape of an unknown ty keeps its ty and its keys. So a
document read and written back is the same JSON, with three exceptions: an entry of a list that is
not an object where objects are expected is left out, a whole number written 30.0 comes back
30, and a flag written 1 or 0 where the schema has a boolean comes back true or false.
Limits bounds what is read: bytes of JSON (256 MiB), nesting (128 levels, which is also where
serde_json stops), layers of the document and its precompositions together (100,000), assets
(20,000) and shape items (2,000,000). Passing one is LottieError::TooLarge. No reader panics on
any input.
The encodings
These work on serde_json values, for the values the model leaves as JSON.
| Part | Read | Write |
|---|---|---|
Document JSON and header (w, h, fr, ip, op, nm) |
parse_document, Header::read |
the application builds the JSON |
Animatable property ({a, k, x}) and keyframes |
read_prop |
write_prop, write_keys |
Ease of a segment (o, i) and speed / influence |
Tangents::read, to_speed |
Tangents::from_speed |
Bezier path ({c, v, i, o}) |
Bezier::from_json |
Bezier::to_json |
Gradient stop list (g.k) |
Gradient::from_flat |
Gradient::to_flat |
Numbers, colours, #rrggbb |
nums, color, hex_color |
round6, to_hex |
Embedded images (data: URIs, RFC 4648 base64) |
base64::data_uri |
base64::to_data_uri, image_mime |
Not here: dotLottie (.lottie), which is a ZIP archive around the same JSON, and layer styles
(sy), which lottie-spec 1.0 does not define (they stay in a layer's extra).
What the readers accept
Files in the wild come from many Bodymovin versions and other exporters, so the readers are tolerant where players are:
- a property that is a bare value (
"o": 100), a property withoutawhosekis a key list, and a key value wrapped once more ([[x, y]]); - keys before 5.0: a segment ends with
eand the last key has nos; the value of a key is then theeof the key before; - ease tangents whose
xandyare a number or a list, per dimension; a missingxis a third of the way and a missingyis linear; - a path without
c: Bodymovin 3.x and early 4.x keep the flag outside the path (clon a mask,closedon anshitem), so the caller passes it; tangent lists shorter or longer than the vertex list; - colours written 0..=255 instead of 0..=1;
- a gradient list without alpha pairs, and a colour count larger than the list.
Nothing is indexed by a number from the file, and no reader panics.
Ease
A Lottie keyframe holds the start value of a segment and the segment's normalised cubic Bezier
ease: o (out of this key) and i (into the next), each with x (time fraction) and y (value
progress fraction), per dimension. After Effects' temporal ease is speed (units per second) and
influence (fraction of the segment). For a segment of duration d and value change Δv:
o.x = influence_out o.y = speed_out · influence_out · d / Δv
i.x = 1 − influence_in i.y = 1 − speed_in · influence_in · d / Δv
Tangents::from_speed and Tangents::to_speed are this formula and its inverse. A spatial
property eases along its motion path: one dimension, with Δv the arc length, which the caller
computes.
Sources and clean room
Implemented from the public Lottie format description (the lottie-spec 1.0 schema and the
LottieFiles format documentation); no player or exporter source was used. The encodings come from
EffectCraft's effectcraft-lottie crate; the document model is written here from the same
description. The four fixtures in tests/fixtures are hand-written
JSON (original work), not exported from any product.