---
title: Animate a photo, or make a pet memorial from one picture
description: Bring a still photo to life with gentle motion, for a family photo or a pet memorial, and keep the face as it was.
---
You get a short video from one still photo: a grandparent who smiles and blinks, a dog whose ears lift in the wind, a slow drift of light across an old portrait. People call this "animate a photo", "photo to video" or "living photo". For a pet memorial, the clip can loop, with the pet's name and dates on screen.

Use invideo for this when the face must stay the same: your agent picks an image-to-video model, asks it for small motion only, compares the frames with your photo, and you can fall back to a free slow zoom that changes nothing.

## Quickest path

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

```text
Animate ./biscuit.jpg, our dog Biscuit. Gentle motion only: his ears
move, he blinks, soft light. Do not change his face or his markings.
Add "Biscuit, 2012 to 2026" at the end. Tell me the price before you
generate, and show me frames next to the photo.
```

In a chat app, you drag the photo into the project's Media panel in invideo, or attach it to the chat. In a terminal, you name the file and the agent uploads it.

## What you need

One good photo matters more than anything else.

- **One photo.** JPG or PNG, sharp, with the face clear and not cut at the edge.
- **The motion you want.** Small and slow works best: a blink, a breath, hair in the wind, a slow camera push.
- **The words, if any.** A name, dates, a short line.
- **The shape.** The model keeps the photo's shape. Say if you need 9:16 or 16:9.
- **Music or sound, if any.** Soft music from stock, or silence.

## Steps

Your agent prices every generation first and tells you what it made.

<Steps>
<Step title="Bring in the photo">
The agent makes a project with `new_project`, then brings the photo in with `start_upload` and `add_media`, or with `file` if you attached it to the chat. Expected result: a sequence URL for the photo.
</Step>
<Step title="Pick the model and read its inputs">
The agent runs `find_models` with `output` video and the job in words, like "animate one photo with small motion", then `get_model` on its pick. A good pick is `wan_27_alibaba.wan_27_i2v`. Your agent can search for others with `find_models`, such as `kling_v3_omni_video.frames` or `seedance_2_5.frames`, which also take the photo as the first frame. Expected result: the model's inputs.
</Step>
<Step title="Price it with a dry run">
The agent runs `generate` with `dry_run` true, the photo as the first frame, and a short prompt for small motion. It asks for no change to the face, in the prompt or in a negative prompt where the model has one. Expected result: the price, which the agent tells you.
</Step>
<Step title="Generate">
For a price of 1 credit or more it waits for your yes. Then the agent calls `generate` with `max_credits` set to that price and an `idempotency_key`. It reads the thread with `read`, and never calls `generate` again to check on it. Expected result: a thread URL, then the new video's sequence URL.
</Step>
<Step title="Compare the face with the photo">
The agent runs `media` `video.frames` on the new video at the start, the middle and the end, and compares each frame with the photo by eye. Expected result: the same face, eyes and markings.
</Step>
<Step title="Finish on a timeline">
The agent makes a timeline with `new_timeline`, places the clip with `place_media`, and adds the name and dates with `add_text`. For a loop, it runs `media` `video.loop` with `target_duration`. Expected result: a timeline URL with the clip and the words.
</Step>
<Step title="Check and export">
The agent runs `capture` at the start, the text and the end, and `check_timeline`. Then it runs `export` with `format` video. In a terminal, it waits with `invideo status` on the card's URL, then saves the file with `invideo notebook show <card-url> --out ./out`. Expected result: an export card with status `completed`.
</Step>
</Steps>

The same job from a terminal. The dry run comes first and spends nothing.

```bash
invideo project new --title "Biscuit"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo asset upload <project-url> ./biscuit.jpg
invideo gen models <project-url> --query "animate one photo with small motion"
invideo gen model <project-url> --model wan_27_alibaba.wan_27_i2v
invideo gen run <page-url> --model wan_27_alibaba.wan_27_i2v --dry-run \
  -i first_frame=<photo-sequence-url> -i duration=6 \
  -i prompt="ears move gently in a breeze, he blinks once"
invideo gen run <page-url> --model wan_27_alibaba.wan_27_i2v \
  --max-credits <price> --idempotency-key biscuit-1 \
  -i first_frame=<photo-sequence-url> -i duration=6 \
  -i prompt="ears move gently in a breeze, he blinks once"
invideo status <thread-url> --wait
invideo media video.frames <video-sequence-url> --at 0,3,5.9 --out ./frames
```

Where each value comes from:

- `<name>` is the name you choose in `invideo agent register`. `INVIDEO_AS` passes it to every later command.
- `<project-url>` comes from `invideo project new`. `<photo-sequence-url>` is the URL `invideo asset upload` returns for the photo.
- `<page-url>` is a notebook page; `invideo notebook show <project-url>` lists them. `<price>` is the dry run's `.estimated_credits`.
- `<thread-url>` is the `url` the paid run returns. `<video-sequence-url>` is the new video's sequence URL, which `invideo notebook show <thread-url>` lists once it lands.

The key MCP call is the dry run. The input names come from `get_model` for this model. `<page-url>` is a notebook page from `read` on the project, and `<photo-sequence-url>` is the URL `add_media` returns.

```json
{
  "url": "<page-url>",
  "model": "wan_27_alibaba.wan_27_i2v",
  "dry_run": true,
  "inputs": {
    "first_frame": "<photo-sequence-url>",
    "prompt": "Ears move gently in a breeze, he blinks once, soft light",
    "negative_prompt": "face change, new markings, extra limbs",
    "duration": "6",
    "resolution": "1080p"
  }
}
```

### The free option: a slow zoom

When the face must not change at all, the agent does not generate. It places the photo on a timeline and adds the `dynamic_zoom` effect with `add_effect`, for a slow push in. This is free, and every pixel stays your photo.

## 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.

- **Costs credits:** the image-to-video model, priced per second of video. A second try costs again.
- **Free:** the upload, the slow zoom, text and the export.
- **Tool ops:** `video.frames` and `video.loop`. The dry run shows its price before anything starts.

A generation takes a few minutes. It can take a few tries before the motion feels right. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems are too much motion or a face that changes.

| Symptom | Fix |
| --- | --- |
| `get_model` or `generate` refuses the model as not found | Models retire. Run `find_models` with the job in words, use the best match, and tell the person which model you used. |
| `video.frames` shows the face or the markings change | Ask for less motion, add the change to the negative prompt, or use the free slow zoom. |
| The motion looks wild | Shorten the prompt to one small action, and pick a shorter `duration`. |
| The model refuses the photo | Read the refusal `message`. Small or odd-shaped photos fail; resize with `media` `image.resizeFit` and try again. |
| CLI exit 5 | A credit limit. Use the free slow zoom, or check your balance. |
| Exit code 6, or `status` is `held` | A safety review, not a refusal. Wait, then run `invideo status <url> --wait` again. Do not resend. If it ends `failed`, change the input the review flagged. |
| Avoid: the agent calls `generate` again to check on it | Instead: it reads the thread URL, or calls `status`. |
| Avoid: saying done without comparing | Instead: `video.frames` next to the photo, then you look too. |

## Prompts to try

Each prompt is a real variation of the job.

```text
Animate this old wedding photo of my grandparents. Only a slow camera
push and a soft light flicker. No change to their faces. 16:9.
```

```text
Make a 30 second memorial loop for our cat Luna from ./luna.jpg. A
gentle blink and breathing, her name and "Forever in our hearts".
Soft piano. Price first.
```

```text
Make three takes of ./portrait.jpg with different small motions, name
them "Take 1 · blink", "Take 2 · smile", "Take 3 · breeze", and let me
pick one.
```

## When to use something else

invideo does not restore torn or faded prints. Use a photo restoration tool first, then animate the restored file.

If you want the person in the photo to talk, see [Make a personalized video message from Santa or an AI presenter](/how-to/personal/personalized-message-video). A talking photo needs a voice and an avatar model, not an image-to-video model.

## Related

- [Make a personalized video message from Santa or an AI presenter](/how-to/personal/personalized-message-video)
- [Make a memorial slideshow that loops and plays offline at the venue](/how-to/personal/memorial-slideshow)
- [Make a photo slideshow for any occasion: anniversary, graduation, retirement, baby's first year, family recap](/how-to/personal/occasion-slideshow)
- [Know the cost before you spend](/how-to/cost-before-you-spend)
- [Models](/reference/models)