Lucy 2.5 · Realtime video editing

From zero to live AI video

Ninety seconds of what one prompt line can do to a live video — swap characters, restyle the scene, change the background, add VFX, all in realtime.

Your turn

Now try it live

Take a 60-second sneak peek on your own camera — type a prompt, drop a reference image, tap a preset. Then scroll: the guide takes you from zero to this exact demo, running in your own app.

Start building
Step 01

Install the SDK

One package. This guide builds in the browser with JavaScript — everything maps 1:1 to Python and Android.

npm install @decartai/sdk

SDK references: JavaScript · Python · Android

Step 02 · Optional, but recommended

Point your coding agent at the docs

Building with Claude Code, Codex, Cursor, or another agent? Give it access to Decart's docs before you write a line, so it gets the SDK, prompts, and API right the first time. Two ways to do it — pick one; either works on its own.

Option A · Connect the docs MCP — recommended

Decart runs a docs MCP server so your agent can search the full documentation live, as it builds. Using Codex? Add it to ~/.codex/config.toml:

~/.codex/config.toml
[mcp_servers.decart-docs]
type = "http"
url = "https://docs.platform.decart.ai/mcp"

On Claude Code, Cursor, or another agent? See the setup for 30+ coding agents.

or
Option B · Hand over the guide

No MCP? Send your agent this page's URL — the entire guide is served as markdown at /llms.txtor copy it straight to your clipboard.

Step 03

Get your API key

Create a key in the platform dashboard — it takes under a minute. Already have one? Skip straight to step 04.

platform.decart.ai/api-keysSign in → API Keys → Create API key
.env.local
# .env.local — keep the key out of source control
DECART_API_KEY=your-api-key-here

How this page does itthe sneak peek above runs on this same flow. Our key lives on the server; your browser only ever received a short-lived token scoped to lucy-2.5 and a single 60-second session. Steal the pattern from step 07's production note.

Step 04

Connect and start editing

Grab a camera stream, open the connection, and Lucy starts editing. The transformed stream arrives in onRemoteStream — wire it to a video element and you're live.

import { createDecartClient, models } from "@decartai/sdk";

const model = models.realtime("lucy-2.5");

// 1. Capture the camera at the model's native format
const stream = await navigator.mediaDevices.getUserMedia({
  video: {
    frameRate: model.fps,
    width: model.width,
    height: model.height,
  },
});

// 2. Create a client and connect — the edited stream comes back via callback
const client = createDecartClient({
  apiKey: "your-api-key-here",
});

const realtimeClient = await client.realtime.connect(stream, {
  model,
  mirror: "auto",
  onRemoteStream: (transformedStream) => {
    document.getElementById("output").srcObject = transformedStream;
  },
  // Pass the first edit here so the very first frame is already transformed
  initialState: {
    prompt: {
      text: "Change the wall's color to light blue, natural consistent paint finish.",
      enhance: true,
    },
  },
});

// 3. Edit the live stream at any time
await realtimeClient.set({
  prompt: "Substitute the character in the video with the person in the reference image.",
  image: characterImage,
  enhance: true,
});

First frame, already transformedpass the prompt and/or reference image in initialState so the very first generated frame uses it — otherwise viewers briefly see the raw feed.

Step 05

Updating the reference image

Prompts describe the edit; a reference image shows it. Swap either at any time — mid-session, no reconnect.

// Change to a new character
await realtimeClient.set({
  prompt: "Substitute the character in the video with the person in the reference image.",
  image: newCharacterImage,
  enhance: true,
});

// Set a new image only (clears any previous prompt)
await realtimeClient.set({ image: newCharacterImage });

// Set a new prompt only (clears any previous image)
await realtimeClient.set({ prompt: "Add dark sunglasses to the person's face." });

// Clear the reference image (fall back to text-only editing)
await realtimeClient.set({ image: null });

set() replaces the entire statefields you omit are cleared. Always include every field you want to keep — that keeps prompt and image in sync with no intermediate states.

512×512 or larger

JPEG, PNG, or WebP. Sharper reference, sharper result.

Clear, even lighting

Avoid harsh shadows or reflections that hide the details you care about.

Match the framing

Full-body video? Use a full-body reference, not a head-and-shoulders crop.

No occlusion

Keep the key features fully visible — hidden parts can't be extracted.

Step 06

Prompt like a director

Updating the prompt is one call. Lucy responds best when the prompt follows the pattern for its edit type — steal from the cookbook below; every example copies with a click and works in the demo up top.

// Text-only edit — update the prompt mid-session, no reconnect needed
await realtimeClient.set({
  prompt: "Substitute the character in the video with a futuristic robot.",
  enhance: true, // Lucy expands terse prompts into rich visual direction
});

// Prompt + reference image — tell Lucy what to take from the image
await realtimeClient.set({
  prompt: "Substitute the character in the video with the warrior from the attached image.",
  image: referenceImage, // File | Blob
  enhance: true,
});

Character swap

Substitute the character in the video with <description>.

Replace

Change <object> with <description of replacement>.

Remove

Remove <object> from the scene.

Add

Add <description of object> to <where to add it>.

Background

Change the background to <description of new background>.

Style

Change the style of the video to <style description>.

VFX

Add <effect description> to <location in scene>.

Need a reference image? Grab one straight from the demo's presets — click to download — or bring your own (512×512 or larger; JPEG, PNG, WebP).

AnimatedHair on FireGreen SlimeRené DecartClaymationCapybaraTuxedoCoatBlonde BobShoulder ParrotRoswell Alien

Describe what you want to seeskip negative instructions (“don't add a hat”) and vague asks (“make it royal”) — name the object, the look, and the location. For layered edits and troubleshooting, see the full prompting guide.

See more examples in the prompt galleryShowcase reels, before/after results on a real frame, and dozens of copy-paste prompts with downloadable reference images — by edit type.
Step 07

The complete example

The demo at the top of this page, reduced to its skeleton: camera in on the left, Lucy out on the right, one prompt line underneath. Two files and a terminal — running in about a minute.

# Scaffold a tiny Vite app and add the SDK
npm create vite@latest my-lucy-demo -- --template vanilla
cd my-lucy-demo
npm install @decartai/sdk

# Add your key (from step 03)
echo "VITE_DECART_API_KEY=your-api-key-here" > .env.local

# Replace index.html and main.js with the two files below, then:
npm run dev

Before you shipa key in VITE_* env ends up in the browser bundle — fine on localhost, not in production. Mint short-lived client tokens on your backend instead:

client-side auth — the pattern this page uses
// Backend: mint a short-lived token with your permanent key
const token = await client.tokens.create();

// Frontend: connect with the token instead of the real key
const frontendClient = createDecartClient({ apiKey: token.apiKey });
More ways to use it

Beyond the camera

Lucy edits any MediaStream — the camera is just the default. Point it at an uploaded video, a shared screen, or anything else you can turn into a stream.

video file as input
// Use an uploaded video file as the input instead of the camera
const video = document.createElement("video");
video.src = URL.createObjectURL(fileInput.files[0]); // <input type="file" accept="video/*">
video.muted = true;
video.loop = true;
video.playsInline = true;
await video.play();

// captureStream() turns the playing <video> into a MediaStream Lucy can edit
// (Safari calls it webkitCaptureStream)
const fileStream = (video.captureStream ?? video.webkitCaptureStream).call(video);

const realtimeClient = await client.realtime.connect(fileStream, {
  model,
  onRemoteStream: (transformedStream) => {
    document.getElementById("output-video").srcObject = transformedStream;
  },
  initialState: {
    prompt: { text: "Change the style of the video to Claymation.", enhance: true },
  },
});
screen share as input
// Edit a shared screen, window, or tab in realtime
const screenStream = await navigator.mediaDevices.getDisplayMedia({
  video: { frameRate: model.fps, width: model.width, height: model.height },
});

const realtimeClient = await client.realtime.connect(screenStream, {
  model,
  onRemoteStream: (transformedStream) => {
    document.getElementById("output-video").srcObject = transformedStream;
  },
});

Same session, same controlseverything from steps 05–06 — prompts, reference images, set() — works identically whatever the input source is.

Use cases

What can you build with it?

Lucy is a realtime edit layer for any live video. Live video avatars that restyle themselves mid-conversation — ask yours to change its shirt or its whole appearance, and it happens on the stream. Video calls where you change yourself live on Zoom or Google Meet. Virtual try-on that lets shoppers wear the clothes before they buy, in store or on the web. New ad formats — creative variations at scale, live branded moments, product placement inside streams. Streaming where you transform on camera, or let the audience decide what you become next. And many more — if it produces a MediaStream, Lucy can edit it.

Live video calls
Keep going

This page is the fast path. The docs are the full map.

Connection lifecycle, client tokens, portrait mode, self-anchoring, virtual try-on, and the complete prompting guide — all in the platform docs.