Skip to content

feat: Extend Lottie specification with VideoFrame asset support#149

Open
stepancar wants to merge 9 commits intolottie:mainfrom
stepancar:feat/add-video
Open

feat: Extend Lottie specification with VideoFrame asset support#149
stepancar wants to merge 9 commits intolottie:mainfrom
stepancar:feat/add-video

Conversation

@stepancar
Copy link
Copy Markdown
Contributor

@stepancar stepancar commented Aug 21, 2025

Motivation

Currently, raster animations in Lottie are supported as sequences of frames where images are embedded directly. Since no interframe coding happens under the hood, raster animations are very large. Lottie is not just a vector format, it is also used as a universal animation format. Adding support for frames extracted from video resources makes animation size much smaller. Modern systems support a variety of codecs, so video processing and frame extraction will not be a problem.

Changes:
Add VideoAsset with id, resourceUrl, duration, width, height.
Add VideoFrame asset, similar to ImageAsset, but referencing a VideoAsset by id and specifying a timestamp.

This is a painless schema change. Players can keep their existing image-sequence engines: when encountering VideoFrame, simply extract the frame from the video, convert it to a base64 PNG, and treat it as a normal ImageAsset.

Effectively:

lottieWithVideoFrames -> lottieWithImageSequence

Before

 "assets": [
        {
            "id": "imgSeq_0",
            "w": 420,
            "h": 420,
            "t": "seq",
            "u": "",
            "p": "base64......loong string"
            "e": 1,
       },
       {
            "id": "imgSeq_0",
            "w": 420,
            "h": 420,
            "t": "seq",
            "u": "",
            "p": "base64...... .loong string"
            "e": 1,
       }, ....
       {
            "id": "sequence_0",
            "layers": [
                   {
                    "ty": 2,
                    "sc": "#00ffff",
                    "refId": "imgSeq_0",
                    "ks": {
                       ...
                    },
                    "ip": 0,
                    "st": 0,
                    "op": 1,
                    "sr": 1,
                    "bm": 0
                },
                 {
                    "ty": 2,
                    "sc": "#00ffff",
                    "refId": "imgSeq_0",
                    "ks": {
                       ...
                    },
                    "ip": 0,
                    "st": 0,
                    "op": 1,
                    "sr": 1,
                    "bm": 0
                }
            ]
       }
]

After

"assets": [
        {
            "id": "video_0",
            "ty": 2,
            "w": 420,
            "h": 420,
            "u": "",
            "p": "base64......loong string with compressed frames"
            "e": 1,
       },
       {
            "id": "imgSeq_0",
            "ty": 3,
            "w": 420,
            "h": 420,
            "t": 1000, // 1 second
            "u": "",
           "vsid": "video_0",
            "e": 1,
       },
       {
            "id": "imgSeq_1",
            "ty": 3,
            "w": 420,
            "h": 420,
            "t": 1040, // 1 second 40ms (next frame)
            "u": "",
           "vsid": "video_0",
            "e": 1,
       },
       {
            "id": "sequence_0",
            "layers": [
                   {
                    "ty": 2,
                    "sc": "#00ffff",
                    "refId": "imgSeq_0",
                    "ks": {
                       ...
                    },
                    "ip": 0,
                    "st": 0,
                    "op": 1,
                    "sr": 1,
                    "bm": 0
                },
                 {
                    "ty": 2,
                    "sc": "#00ffff",
                    "refId": "imgSeq_0",
                    "ks": {
                       ...
                    },
                    "ip": 0,
                    "st": 0,
                    "op": 1,
                    "sr": 1,
                    "bm": 0
                }
            ]
       }
]

CLA PR

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 21, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@mbasaglia mbasaglia added the Proposal (RFC 1) RFC Stage 1 label Aug 21, 2025
Comment thread schema/assets/video.json Outdated
Comment on lines +32 to +61
"p": {
"title": "File Name",
"description": "Name of the video file or a data url",
"type": "string"
},
"u": {
"title": "File Path",
"description": "Path to the video file",
"type": "string"
},
"e": {
"title": "Embedded",
"description": "If '1', 'p' is a Data URL",
"$ref": "#/$defs/values/int-boolean"
}
},
"allOf": [
{
"if": {
"properties": {
"e": {"const": 1}
},
"required": ["e"]
},
"then": {
"properties": {
"p": {"$ref": "#/$defs/values/data-url"}
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd extract this into an abstract file-asset schema so we can share the definition between image and video assets (and other future assets

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbasaglia Added file-asset

Comment on lines +27 to +36
"w": {
"title": "Width",
"description": "Width of the frame",
"type": "number"
},
"h": {
"title": "Height",
"description": "Height of the frame",
"type": "number"
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The video asset schema also has w and h, are they needed here as well? if so, what's their semantic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbasaglia I was thinking about extracting a frame from VideoResource, specifying width, height, x, y from the top left corner. But you are right, as a first iteration we can drop w/h from frame

},
{
"type": "object",
"properties": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should introduce a ty for assets (not strictly required for this proposal)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbasaglia , I agree. Added ty prop

Comment thread docs/specs/assets.md

{schema_object:assets/video-frame}

Video frame assets reference a video asset by ID and specify a timestamp to extract a specific frame. Players MUST extract the frame at the exact timestamp specified, or the closest available frame if the exact timestamp is not available. The extracted frame should be treated as a static image for rendering purposes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should

Is this a SHOULD or a MUST?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This point is up for discussion.

The advantage of using MUST is that Lottie players could extract frames on the fly, which speeds up animation initialization and reduces memory usage before playback starts. At the same time, if players don’t treat the extracted frame as a static image but instead process it in real time, it could impose constraints on the video itself - for example, requiring more keyframes to ensure fast seeking, which would increase file size.

That’s why I would vote for MUST.

In the future, if we want to support optimization for initialization we could extend videoFrame with initialization: static | dynamic param to achieve that.

@Aidosmf Aidosmf moved this to In Progress in lottie-spec Aug 26, 2025
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@stepancar
Copy link
Copy Markdown
Contributor Author

@heathmill or @jcgregorio Hello. I have signed CLA, but bot is still complaining in this PR. Could you check? Thank you!

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Aug 31, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @stepancar on file. In order for us to review and merge your code, please contact @heathmill or @jcgregorio to get yourself added.

@mbasaglia
Copy link
Copy Markdown
Member

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label Sep 1, 2025
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Sep 1, 2025

The cla-bot has been summoned, and re-checked this pull request!

@mbasaglia
Copy link
Copy Markdown
Member

ok I fixed the bot

@stepancar
Copy link
Copy Markdown
Contributor Author

ok I fixed the bot

Thank you!

@stepancar stepancar mentioned this pull request Sep 1, 2025
Copy link
Copy Markdown
Member

@fmalita fmalita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the authoring story for these asset types, are you planning to add support in existing or new tools?

Comment thread schema/assets/video-frame.json Outdated
"type": "integer",
"const": 3
},
"vsid": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming bike-shed: normally sid refers for "slot ID", which I guess could be adopted here if we squint. But since we're not dealing with real slots, that may be confusing.

How about refId, which is the normal way to reference assets?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, refId makes more sense, updated

},
"t": {
"title": "Timestamp",
"description": "Timestamp in seconds where to extract the frame from the video",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this interact with start-time/time-stretch/general time remapping in referencing layers?

Also actual time units seem a bit odd since everything else in Lottie is expressed as frame indices (ip, op, etc).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, you are right, we can define time in frames here

Comment thread docs/specs/assets.md

{schema_object:assets/video-frame}

Video frame assets reference a video asset by ID and specify a timestamp to extract a specific frame. Players MUST extract the frame at the exact timestamp specified, or the closest available frame if the exact timestamp is not available. The extracted frame should be treated as a static image for rendering purposes.
Copy link
Copy Markdown
Member

@fmalita fmalita Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the same functionality be achieved using time remapping to lock a video layer's time? IOW do we actually need video frame assets, or would video assets be sufficient?

I can think of some advantages to having a dedicated asset type, mostly on the implementation side (caching) - but there are also some downsides (more verbose than video asset + time-locked video layer).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main motivation of having VideoFrame is ability to set time or frame number, I didn't find better way to express it with existing lottie primitives

@stepancar
Copy link
Copy Markdown
Contributor Author

What is the authoring story for these asset types, are you planning to add support in existing or new tools?

These assets are created in any raster editor and then exported as Lottie files with base64-encoded individual frames.

I’m not sure how easy it would be to extend Bodymovin to support wrapping a sequence of images into a video/animated image.
I still haven’t figured out where the source code is located. Therefore, I was planning to make a website where a user could upload a Lottie file and receive a compressed Lottie. I also planned to support this spec extension in lottie-web.

@b-wils
Copy link
Copy Markdown
Contributor

b-wils commented Oct 16, 2025

The source code for bodymovin is here- https://github.com/bodymovin/bodymovin-extension. FYI it took me a bit to get the dev environment running, particularly if you are on an ARM system.

Also did you have the authoring site/updated player available? Part of the spec process is to have at least an end to end demo of the feature.

One other thing is we wanted to be sure that the video asset could also be used for video playback. I think this still looks good but we may want to flesh out that support as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants