Skip to main content
This example demonstrates how to convert static images into animated videos using the Decart Queue API. The image-to-video model brings still images to life with natural, AI-generated motion.

What You’ll Build

An image-to-video workflow that:
  • Loads an input image from a file or URL
  • Submits an image-to-video generation job
  • Polls for completion
  • Saves the generated video

Prerequisites

  • Node.js 18 or higher
  • A Decart API key
  • An input image (PNG, JPG, or WebP)

Quick Start

1

Install the SDK

npm install @decartai/sdk
2

Set your API key

export DECART_API_KEY=your-api-key-here
3

Create an image-to-video script

Create animate-image.js:
import fs from "node:fs";
import { createDecartClient, models } from "@decartai/sdk";

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

console.log("Generating video from image...");

const inputImage = fs.readFileSync("input.png");

const result = await client.queue.submitAndPoll({
  model: models.video("lucy-pro-i2v"),
  prompt: "The scene comes to life with gentle motion",
  data: new Blob([inputImage]),
  onStatusChange: (job) => {
    console.log(`Job ${job.job_id}: ${job.status}`);
  },
});

if (result.status === "completed") {
  const output = Buffer.from(await result.data.arrayBuffer());
  fs.writeFileSync("output.mp4", output);
  console.log("Video saved to output.mp4");
} else {
  console.log("Job failed:", result.error);
}
4

Run the script

node animate-image.js

From Local File

Load an image from the local filesystem:
import fs from "node:fs";
import { createDecartClient, models } from "@decartai/sdk";

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

// Read image file
const inputImage = fs.readFileSync("path/to/image.png");

// Generate video
const result = await client.queue.submitAndPoll({
  model: models.video("lucy-pro-i2v"),
  prompt: "Gentle wind blowing through the scene",
  data: new Blob([inputImage]),
});

if (result.status === "completed") {
  const output = Buffer.from(await result.data.arrayBuffer());
  fs.writeFileSync("output.mp4", output);
}

From URL

You can also use an image URL:
import { createDecartClient, models } from "@decartai/sdk";

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

const result = await client.queue.submitAndPoll({
  model: models.video("lucy-pro-i2v"),
  prompt: "The landscape comes alive with subtle movement",
  data: "https://example.com/image.jpg",
});

if (result.status === "completed") {
  const output = Buffer.from(await result.data.arrayBuffer());
  fs.writeFileSync("output.mp4", output);
}

From Browser (File Upload)

In a browser environment, use file input:
import { createDecartClient, models } from "@decartai/sdk";

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

// Get file from input element
const fileInput = document.getElementById("imageInput");
const file = fileInput.files[0];

const result = await client.queue.submitAndPoll({
  model: models.video("lucy-pro-i2v"),
  prompt: "Bring this image to life with natural motion",
  data: file, // File object is already a Blob
  onStatusChange: (job) => {
    console.log(`Processing: ${job.status}`);
  },
});

if (result.status === "completed") {
  // Create object URL for video playback
  const videoUrl = URL.createObjectURL(result.data);
  const videoElement = document.getElementById("output");
  videoElement.src = videoUrl;
}

Manual Job Control

For webhooks or background processing:
import { createDecartClient, models } from "@decartai/sdk";

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

const inputImage = fs.readFileSync("landscape.jpg");

// Submit job and get ID
const job = await client.queue.submit({
  model: models.video("lucy-pro-i2v"),
  prompt: "The sun moves across the sky",
  data: new Blob([inputImage]),
});

console.log("Job ID:", job.job_id);
// Store job.job_id in database for later retrieval

// Later, check status
const status = await client.queue.status(job.job_id);

if (status.status === "completed") {
  const blob = await client.queue.result(job.job_id);
  const buffer = Buffer.from(await blob.arrayBuffer());
  fs.writeFileSync("output.mp4", buffer);
}

Prompt Tips

Effective prompts for image-to-video:

Natural Motion

prompt: "Gentle wind rustling through the trees"
prompt: "Clouds slowly drifting across the sky"
prompt: "Waves lapping at the shore"

Camera Movement

prompt: "Slow zoom into the scene"
prompt: "Pan across the landscape from left to right"
prompt: "Camera slowly orbiting around the subject"

Bringing Scenes to Life

prompt: "The scene comes alive with activity and movement"
prompt: "Natural motion as if captured on video"
prompt: "Subtle ambient motion throughout the scene"

Express API Example

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

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

// Submit image-to-video job
app.post("/api/animate", upload.single("image"), async (req, res) => {
  try {
    const { prompt } = req.body;
    const imageBuffer = req.file.buffer;

    const job = await client.queue.submit({
      model: models.video("lucy-pro-i2v"),
      prompt,
      data: new Blob([imageBuffer]),
    });

    res.json({ jobId: job.job_id, status: job.status });
  } catch (error) {
    res.status(500).json({ error: String(error) });
  }
});

// Check job status
app.get("/api/status/:jobId", async (req, res) => {
  try {
    const status = await client.queue.status(req.params.jobId);
    res.json(status);
  } catch (error) {
    res.status(500).json({ error: String(error) });
  }
});

// Get video result
app.get("/api/result/:jobId", async (req, res) => {
  try {
    const blob = await client.queue.result(req.params.jobId);
    const buffer = Buffer.from(await blob.arrayBuffer());
    res.setHeader("Content-Type", "video/mp4");
    res.send(buffer);
  } catch (error) {
    res.status(500).json({ error: String(error) });
  }
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

Supported Image Formats

  • PNG
  • JPEG/JPG
  • WebP
  • GIF (first frame used)

Output Specifications

  • Format: MP4 (H.264)
  • Duration: 5 seconds
  • Frame Rate: 30 FPS
  • Resolution: Based on input image (maintains aspect ratio)

Best Practices

  1. Use high-quality input images - Better input yields better output
  2. Be specific with prompts - Describe the type of motion you want
  3. Consider image composition - Images with clear subjects work best
  4. Test different prompts - Experiment to find what works for your use case
  5. Cache results - Store generated videos to avoid regenerating

Build docs developers (and LLMs) love