---
title: Rebuild a Remotion, HyperFrames or Tesseract composition as an editable timeline
description: Your agent reads a Remotion, HyperFrames or Tesseract composition and rebuilds it as clips, text, shapes and keyframes you can move, trim and restyle.
---
Turn a code composition into an editable timeline: convert a Remotion, HyperFrames or Tesseract project into tracks, clips and keyframes instead of one flat render. Your agent reads the source, a React component, an HTML page or a `.tsrct` project, and rebuilds each element with edit operations. You end up with a title you can retype, a logo you can move and a push-in you can retime by hand.

Use invideo for this when the person who asks for changes does not write code, and you want them to drag a clip in the editor instead of asking for a new render.

## Quickest path

Set up your agent with [invideo for AI agents](/agents) first. Then give it this prompt.

```text
Read the composition in ./video and rebuild it on a new invideo timeline,
one element per clip: text as text, images and video as clips, motion as
keyframes. Match the size and fps. List any layer you cannot rebuild, and
render only those as clips. Work on a branch and show me the timeline URL.
```

In a terminal, the agent reads the source folder and uploads the files itself. In a chat app, you drag the images, videos, fonts and source files into the chat, and the agent works from them there.

## What you need

The source is the input, not the render.

- The composition source: the Remotion project, the HyperFrames HTML, CSS and JavaScript, or the Tesseract project with its media.
- Every file the composition uses: images, videos, audio and font files.
- The size and frame rate of the composition.
- A list of the layers that must stay editable, if not all of them.

## How each element maps

The agent rebuilds each element with the op that matches it. Look up each op's fields with `timeline_catalog` before its first use.

| In the composition | On the timeline |
| --- | --- |
| Size and frame rate | `set_timeline_settings` with `width`, `height` and `fps` |
| Start and length | `start_f` and `end_f`, or `at_f` and `duration_f`, in frames at the timeline's rate |
| Text | `add_text`, then `set_text_style` and `style_text_range` |
| Fonts | `add_media` with the font file; its id goes in `font` |
| Images and video | `add_media`, then `insert_media` or `place_media`, with `set_position` and `set_zoom` |
| Rectangles, circles, SVG paths | `add_shape` with `preset` `rectangle`, `ellipse`, `star` or `path` |
| Gradients and strokes | `set_shape_paint`, `add_shape_paint`, `set_shape_path_points` |
| Position and scale over time | `add_keyframe_lane` on `position_x`, `position_y`, `zoom_x`, `zoom_y`, then `add_keyframes` |
| Opacity and rotation over time | `set_keyframes` with `property` `opacity` or `rotation` |
| Easing | `ease` on `set_keyframes`, or `set_keyframe_interpolation` on a lane's keyframe |
| Path morphs | `animate_shape_path`, then `set_shape_path_points` at each time |
| Layer order and blend | track order, `set_blend_mode`, `set_opacity` |
| Transitions | `add_transition`, `add_edge_transition`, or `add_custom_transition` in GLSL |
| A filter or shader | an effect from the catalog with `add_effect`, or `add_custom_effect` in GLSL |
| Audio | `add_media`, then `place_media` on `A1`, `A2` or `A3` |
| Canvas, WebGL, DOM tricks, third-party components | Render that layer alone and place it as a clip |

Times convert one way. Set the timeline's `fps` to the composition's frame rate, and Remotion frame numbers copy over as they are. HyperFrames times are in seconds, in `data-start` and `data-duration`, so the agent multiplies them by the frame rate. In HyperFrames, CSS `z-index` sets which layer is on top, not `data-track-index`.

## Steps

The agent works on a branch and checks the result before it says done.

<Steps>
<Step title="Read the composition">
The agent reads the source and writes a list of elements, each with its start, length, position, style and motion. It sees which elements map to an op and which do not.
</Step>
<Step title="Set up the timeline on a branch">
The agent creates a branch with `timeline_history` and `action: "create_branch"`, then sends `set_timeline_settings` with the composition's size and frame rate. It sees a `branch_url` and a new `state_token`.
</Step>
<Step title="Bring in the files">
The agent uploads each image, video, audio and font file with `start_upload` and `add_media` on the branch URL. It sees a media bin id for each, like `m1` and `m2`.
</Step>
<Step title="Place the elements">
The agent sends one `edit_timeline` call with the clips, text and shapes, in track order, plus a keyframe lane for each animated position or zoom. It sees the new clip ids and lane ids in `created`.
</Step>
<Step title="Add the motion">
The agent sends a second call with `add_keyframes` on each lane id, and `set_keyframe_interpolation` for the easing. It sees a new `state_token`.
</Step>
<Step title="Render what has no op">
For each layer that does not map, the agent renders that layer alone in the source tool, uploads it, and places it on its track. It sees one clip per rendered layer.
</Step>
<Step title="Check against the render">
The agent calls `capture` at the same times as frames from the source tool's own render, compares them, and fixes what differs. It sees frames that match.
</Step>
</Steps>

The first call places a background video, a title that fades in, a logo, and two lanes for a push-in. This is the full MCP call:

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "set_timeline_settings", "timeline": "T1",
      "width": 1920, "height": 1080, "fps": 30 },
    { "op": "insert_media", "asset": "m1", "track": "V1",
      "at_f": 0, "ripple": false },
    { "op": "add_text", "track": "V3", "start_f": 15, "end_f": 90,
      "text": "Spring launch",
      "style": { "font_size": 96, "font_weight": 700,
                 "color": "#ffffff" } },
    { "op": "set_keyframes", "clip": "t1", "property": "opacity",
      "points": [ { "at_f": 15, "value": 0, "ease": "ease_out" },
                  { "at_f": 30, "value": 1 } ] },
    { "op": "insert_media", "asset": "m2", "track": "V2",
      "at_f": 60, "ripple": false },
    { "op": "set_position", "clip": "c2", "x": 0, "y": 300 },
    { "op": "set_zoom", "clip": "c2", "x": 0.25, "y": 0.25 },
    { "op": "add_keyframe_lane", "parent": "c1",
      "parent_kind": "video_clip", "property": "zoom_x",
      "default_value": 1 },
    { "op": "add_keyframe_lane", "parent": "c1",
      "parent_kind": "video_clip", "property": "zoom_y",
      "default_value": 1 }
  ]
}
```

`<branch-url>` is the `branch_url` that `timeline_history` returns. `<state_token>` comes from `get_timeline` on that URL. `m1` and `m2` are the media bin ids that `add_media` returns for the background video and the logo. On a new timeline the clips are `c1`, `t1` and `c2`; on a timeline that already has clips, the agent reads the ids from `created`.

The second call is the push-in. A lane id exists only after the first call lands, so the keyframes go in their own call. This is the bare list of ops that `--operations @ops.json` takes:

```json
[
  { "op": "add_keyframes", "lane": "<zoom-x-lane>",
    "points": [ { "time_seconds": 0, "value": 1 },
                { "time_seconds": 4, "value": 1.15 } ] },
  { "op": "add_keyframes", "lane": "<zoom-y-lane>",
    "points": [ { "time_seconds": 0, "value": 1 },
                { "time_seconds": 4, "value": 1.15 } ] }
]
```

`<zoom-x-lane>` and `<zoom-y-lane>` are the `keyframe_lane` ids in `created` from the first call. The same zoom on both lanes keeps the picture's shape.

The same job from a terminal:

```bash
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo branch new <edit-url> --name rebuild --token <state_token>
invideo asset upload <branch-url> ./video/public/bg.mp4
invideo asset upload <branch-url> ./video/public/logo.png
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @place.json --dry-run
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @place.json
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @motion.json
invideo edit capture <branch-url> --at 1 --at 3
```

`<project-url>` comes from `invideo project new` or `list_projects`. `<name>` is the name you choose in `invideo agent register`. `<edit-url>` is the timeline's URL. `<branch-url>` is the `branch_url` that `invideo branch new` returns. `<state_token>` comes from `invideo edit show <branch-url>`, read again before each change. `place.json` holds the `operations` list of the first call, and `motion.json` the second list.

## Cost and time

Edits and exports are free. Generation and transcripts cost credits. Paid stock costs a small credit charge, or one of your workspace's iStock licenses. Your agent runs a dry run first and tells you the price before it spends anything.

A rebuild uses edit operations and uploads, so it spends no credits. A short title card takes a few minutes. A long composition with many layers takes longer, so the agent rebuilds it in parts. See [Credits](/account/credits).

## If something goes wrong

Each refusal carries a `code` and a `message`. Read the `message`: it names the operation by its place in the list.

| Symptom | Fix |
| --- | --- |
| `revision_conflict` | Someone's edit landed since the read. Read the timeline again, then send the call with the new `state_token`. |
| `target_not_found` on `add_keyframes` | The lane id is wrong or from a dry run. Use the `keyframe_lane` id from `created` of the call that landed. |
| `target_not_found` on `set_text_style` with `font` | The font is not in the media bin. Upload the font file with `add_media` on the timeline's URL, then use the id it returns. |
| `target_not_found` on a clip id like `t1` | The project has clips already, so the new ids differ. Read them from `created`. |
| `invalid_edit` on `add_edge_transition` | The clip is not first or last on its track. Use `add_transition` on the cut between two clips. |
| `add_custom_effect` is refused with compiler errors | Fix the GLSL at the line and column the refusal lists. Read `timeline_catalog` with `guides` `custom_shader` first. |
| A rendered layer shows a black or white box | Render that layer again with a transparent background, where the tool supports it, and check it with `capture`. |
| The motion feels different from the source | Easing differs, such as a Remotion `spring`. Add more keyframes along the curve, or tune `set_keyframe_interpolation`. |
| Avoid: render the whole composition to one MP4 | Instead: rebuild what maps, and render only the layers that do not. |
| Avoid: edit main | Instead: rebuild on a branch from `timeline_history`, and give the person the branch URL. |

## Prompts to try

Each prompt is a real variation of this job.

```text
Rebuild the Remotion composition "Promo" from src/Root.tsx as an invideo
timeline. Keep the headline and the price as editable text clips.
```

```text
Turn this HyperFrames index.html into an invideo timeline. Convert the
GSAP tweens on position and scale into keyframe lanes. Render the canvas
chart alone and place it as a clip.
```

```text
Read my Tesseract project and rebuild the title card and lower third on
the invideo timeline, so my editor can change the names by hand.
```

## When to use something else

Keep rendering to MP4 when nobody will edit the elements, or when most of the composition is canvas, WebGL, physics or a third-party React component. Bring that render in as footage, as [Use Remotion with invideo](/integrations/remotion) shows. invideo does not run React, CSS or JavaScript, so arbitrary CSS layout, DOM behavior and live code do not translate. For a video made from data at volume, stay in the source tool and render there.

## Related

- [Use Remotion with invideo](/integrations/remotion)
- [Use HyperFrames with invideo](/integrations/hyperframes)
- [Use Tesseract with invideo](/integrations/tesseract)
- [Timeline edit operations](/reference/edit-operations)
- [Make an intro, outro or logo animation](/how-to/creators/intro-outro-logo-animation)
- [Check your video before you say it is done](/how-to/check-your-video)

<Collapse title="Does invideo import a composition file by itself?">
No. Your agent reads the source and sends edit operations. The timeline then holds real clips, text and keyframes that you edit like any other.
</Collapse>

<Collapse title="Will the rebuild match the render frame for frame?">
Close, not exact. Position, zoom, opacity, rotation and timing match. Custom easing, filters and fonts that render differently can look slightly off, so the agent compares frames with `capture`.
</Collapse>