lottie_fixup 1.0.1 copy "lottie_fixup: ^1.0.1" to clipboard
lottie_fixup: ^1.0.1 copied to clipboard

Fixes Lottie/Bodymovin exports that crash or freeze the lottie Flutter package: malformed layers, assets, masks, shapes, and unexecuted expressions.

lottie_fixup #

Fixes Lottie/Bodymovin exports that crash or freeze the lottie Flutter package: malformed layers/assets/masks/shape content, and expressions that lottie doesn't execute (loopOut()/loopIn(), wiggle(), random(), time-based motion, cross-layer links).

Features #

  • Stops structural crashes — a range of JSON shapes the schema allows but lottie's own parser/render-tree builder doesn't defensively guard against, each grounded in a specific non-null-assertion or unassigned- field crash confirmed in lottie's source: audio layers ("ty": 6, which ship without the transform block lottie expects — Null check operator used on a null value), a precomp layer ("ty": 0) whose refId doesn't resolve to any asset, a text layer ("ty": 5) missing its document-data block, an asset with no usable id, a malformed masksProperties entry, and a gradient-fill/gradient-stroke/solid-stroke shape item missing a required field (confirmed by lottie's own source to be a real shape non-After-Effects tools like Telegram's Lottie export ship) — this package removes each one specifically, leaving everything else in the file untouched. An out-of-range stroke line-cap/line-join value is patched in place instead of removed. An animatable value with no actual keyframes (which crashes lottie the same way, but has no safe default to substitute) is reported rather than guessed at — see SanitizeResult.propertiesWithEmptyKeyframes.
  • Bakes loopOut()/loopIn() expressions into real keyframes, so looping animations don't freeze after their first cycle (lottie doesn't execute expressions). All four After Effects loop modes are supported in both directions — 'cycle', 'pingpong', 'offset', 'continue' — the latter two on any numeric property (position, scale, rotation, opacity...) — plus the duration-based loopOutDuration()/loopInDuration() variants in 'cycle'/'pingpong' mode.
  • Bakes other expressions, on a never-animated property or one that's already keyframed (the expression's result is authoritative, same as After Effects — the original curve is only available through value/ valueAtTime()): continuous time-based motion (e.g. time * 180 for constant rotation), if/else branching with comparisons/booleans, local var bindings, cross-layer links (thisComp.layer('Name').transform.position, across all transform properties including skew/skewAxis, copied exactly when that's the whole expression on a never-animated property, sampled when combined with other math or via .valueAtTime(t)) and the same-layer equivalent (thisLayer.transform.position, or bare transform.position), the Math.* namespace, linear()/ease()/easeIn()/easeOut()/clamp(), add()/sub()/mul()/div()/value, posterizeTime(), and random()/wiggle() (a deterministic, seeded approximation — After Effects' own noise/PRNG can't be reproduced bit-for-bit, but this is reproducible across builds and beats a frozen property). A wiggle()-only expression on a shape path wiggles each vertex independently.
  • Prunes empty precomps and now-unreferenced assets left behind by the fixes above.
  • Use it at load time (drop-in decoder, no build step) or ahead of time (CLI, zero runtime cost).

Getting started #

Add the dependency:

dependencies:
  lottie_fixup: ^1.0.0

Usage #

At load time — no build step #

Drop fixupLottieDecoder into any lottie loading API that takes a decoder:

import 'package:lottie/lottie.dart';
import 'package:lottie_fixup/lottie_fixup.dart';

Lottie.asset('assets/character.json', decoder: fixupLottieDecoder)

Safe to apply unconditionally, even to files already fixed ahead of time — fix is a no-op when there's nothing left to do. This adds a JSON decode/walk/re-encode once per composition load, not per frame. For larger files, pass backgroundLoading: true to move that work off the UI isolate:

Lottie.asset(
  'assets/character.json',
  decoder: fixupLottieDecoder,
  backgroundLoading: true,
)

Ahead of time — CLI #

For an animation that ships in every build and never changes, fix it once and skip the runtime cost entirely:

dart pub global activate lottie_fixup
lottie_fixup diagnose assets/animations/*.json   # report only, no changes
lottie_fixup fix assets/animations/*.json        # fix in place

Library #

import 'dart:convert';
import 'dart:io';
import 'package:lottie_fixup/lottie_fixup.dart';

final file = File('animation.json');
final doc = jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;

final result = fix(doc); // mutates doc in place
if (result.changed) {
  file.writeAsStringSync(jsonEncode(doc));
}

Configuration: opting out of approximations #

A few parts of expression baking are an approximation or a judgment call rather than an exact match to what After Effects would render — see Features above. BakeOptions lets you turn any of them off individually; every option defaults to true (bake everything), and turning one off only ever makes baking more conservative — the affected expressions are reported as unsupported instead of altered:

Option Default Turn off to...
bakeRandomAndWiggle true Leave random()/wiggle() unbaked (also disables bakeShapePathWiggle).
bakeOnKeyframedProperties true Only bake never-keyframed ("a": 0) properties, matching versions before 0.3.0.
bakeShapePathWiggle true Leave wiggle() on a shape path unbaked.
bakeApproximateEasing true Leave ease()/easeIn()/easeOut() unbaked (linear() is unaffected — it's an exact formula, not an approximation).

It plugs into every entry point:

const options = BakeOptions(bakeOnKeyframedProperties: false);

// Library
fix(doc, options: options);
diagnose(rawJson, doc, options: options); // pass the same options you'll fix() with

// At load time
Lottie.asset(
  'assets/character.json',
  decoder: fixupLottieDecoderWithOptions(options),
)
# CLI
lottie_fixup fix --no-keyframed-properties assets/animations/*.json

What this does not fix #

  • Expressions this package's small evaluator doesn't understand are reported (diagnose, or FixResult.propertyBake.skippedExpressions) but left untouched, notably: effect(...) (Effects Controller references, e.g. a Slider/Angle/Checkbox control used as a rig parameter); a reference into a comp more than one thisComp.layer(...) hop away; for/while loops or user-defined functions (no such grammar exists); text-layer/sourceText animator expressions; comp marker.* references; Math.random() (as opposed to the supported top-level random()); the vector-math helpers length()/normalize()/cross()/dot(); and lookAt().
  • 'offset'/'continue' on a non-numeric value (a shape path, for example, rather than position/scale/rotation/opacity) is reported rather than baked, since those modes work by doing arithmetic directly on the value.
  • A non-wiggle() expression on a shape path (arithmetic directly on a path value) is reported rather than baked — After Effects doesn't support that either.
  • A duration variant (loopOutDuration/loopInDuration) whose duration is shorter than the keyframed segment itself is reported rather than baked — that would need interpolating a cut point in the middle of the real animation, which isn't implemented.
  • A property that calls both loopIn and loopOut (a manual "loop both ways" expression) is reported rather than guessed at.
  • random()/wiggle() are baked as a plausible approximation, not a bit-exact match to After Effects — see Features above.
  • A layer missing ks for a reason other than being an audio layer is flagged (SanitizeResult.layersMissingTransform) rather than silently removed, since that could be a real authoring mistake worth checking by hand.
  • Repeater (ty: "rp") and merge-paths (ty: "mm") shape-content items missing their own required fields have the same crash shape as the gradient/stroke items this package does check (an unguarded non-null assertion in lottie's parser) but aren't checked yet.
  • An animatable-value-shaped object with a missing/empty k is flagged (SanitizeResult.propertiesWithEmptyKeyframes) rather than fixed — see Features above.

Additional information #

If this package saved you a debugging session, consider buying me a coffee.

ko-fi

0
likes
160
points
309
downloads

Documentation

API reference

Publisher

verified publishermonlycute.id.vn

Weekly Downloads

Fixes Lottie/Bodymovin exports that crash or freeze the lottie Flutter package: malformed layers, assets, masks, shapes, and unexecuted expressions.

Repository (GitHub)
View/report issues

Topics

#lottie #animation #after-effects #cli

Funding

Consider supporting this project:

ko-fi.com

License

MIT (license)

Dependencies

flutter, lottie

More

Packages that depend on lottie_fixup