---
title: Make a photo slideshow for any occasion: anniversary, graduation, retirement, baby's first year, family recap
description: A photo slideshow, or photo video, for an anniversary, a graduation, a retirement, a baby's first year or a family recap, with music, titles and an optional loop.
---
You get a photo slideshow, also called a photo video or a photo montage, for an anniversary, a graduation, a retirement party, a baby's first year or a family year in review. The photos run in order with soft transitions and music, with a title at the start and short captions where you want them. For a party screen, the slideshow can loop for as long as the party runs.

Use invideo for this when you have many photos and want to fix one later: every photo stays a clip on an editable timeline, so you swap a photo or a caption without starting over.

## Quickest path

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

```text
Make a 16:9 photo slideshow for my parents' 40th anniversary from the
photos in ./anniversary, oldest first. Five seconds a photo, soft
fades, gentle music, and "40 years, Anna and Raj" at the start. Make
a one hour loop for the party screen. Show me frames before you export.
```

In a chat app, you drag your photos into the project's Media panel in invideo, then name them in the chat. In a terminal, you name the folder and the agent uploads it.

## What you need

Sort your photos into a folder first. File names like `01.jpg`, `02.jpg` keep the order you want.

- **Photos.** JPG, PNG or HEIC, scans of old prints too. Short clips work as well.
- **The order.** By file name, by date, or by chapter (school, wedding, kids).
- **The words.** A title, an end line, and captions such as a year or a place.
- **The music.** A mood for stock music, or your own licensed file.
- **The shape and the screen.** 16:9 for a TV or a projector, 9:16 for a phone, 1:1 for a feed.
- **A loop, if needed.** How long it must play at the event.

## Steps

Your agent edits on a branch and tells you what changed at each step.

<Steps>
<Step title="Make the timeline and bring in the photos">
The agent makes a project with `new_project`, a timeline with `new_timeline` in your shape, and a branch with `timeline_history`. It uploads each photo with `start_upload` and `add_media` on the branch URL. Expected result: a branch URL, and every photo in the media bin with an id.
</Step>
<Step title="Sharpen a small scan, if you need to">
For a small or soft scan, the agent runs `find_models` with the job in words, like "upscale an old photo scan", and `get_model` on its pick. A good pick is `crystal_upscaler`. Your agent can search for others with `find_models`. It calls `generate` with `source_image` set to the scan's sequence URL, `scale_factor` 2 and `dry_run: true`, and tells you the price. For a price of 1 credit or more it waits for your yes. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. The original stays. Expected result: a sequence URL for the sharper image, which the agent reads with `read`, never with a second `generate`.
</Step>
<Step title="Lay out the photos in order">
The agent places the photos with `insert_media` and `ripple` true, then sets each one's length with `trim_clip`. It sends them in one `edit_timeline` call. Portrait photos in a wide frame get the `fit_with_blur` effect with `add_effect`, so there are no black bars. Expected result: one clip per photo, in order, each the length you asked.
</Step>
<Step title="Add transitions and words">
The agent adds a `fade` or `light_leak` transition between photos with `add_transition`. It adds the title, captions and the end line with `add_text`. Expected result: soft changes between photos and text on the right photos.
</Step>
<Step title="Add the music">
The agent finds a track with `search_library` in `stock_music` and adds it with `add_media`. It reads the beats with `media` `audio.beats` if you want the changes on the beat, and fades the end with `set_audio_fades`. Expected result: music that ends with the last photo.
</Step>
<Step title="Make the loop">
For a party screen, the agent puts the whole slideshow into one clip with `nest_clips` in `mode` nest. It copies that clip with `duplicate_clip` until the timeline is long enough, and adds a `fade` between the copies with `add_transition`. Expected result: one timeline that plays as long as the event.
</Step>
<Step title="Check and export">
The agent runs `capture` at the title, a few photos, a loop seam and the end, and `check_timeline`, and looks for every face in frame, readable text and no black frame at the seam. Then it runs `export` with `format` video and reads the card. In a terminal, it saves the file with `invideo notebook show` on the card's URL. Expected result: an export card with status `completed`.
</Step>
</Steps>

The same job from a terminal:

```bash
invideo project new --title "Anna and Raj, 40 years"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo edit new <project-url> --name "Slideshow"
invideo asset upload <edit-url> ./anniversary/*.jpg
invideo gen models <project-url> --query "upscale an old photo scan"
invideo gen model <project-url> --model crystal_upscaler
invideo gen run <page-url> --model crystal_upscaler --dry-run \
  -i source_image=<scan-sequence-url> -i scale_factor=2
invideo gen run <page-url> --model crystal_upscaler \
  --max-credits <price> --idempotency-key scan-1 \
  -i source_image=<scan-sequence-url> -i scale_factor=2
invideo library search <project-url> --library stock_music \
  --query "gentle piano"
invideo asset add <project-url> --stock <id>
invideo edit show <edit-url>
invideo edit change <edit-url> --timeline T1 --token <state_token> \
  --operations @ops.json
invideo edit capture <edit-url> --at 0,30,120,600 --out ./frames
invideo edit export <edit-url> --format video --quality 1080 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./out
```

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`, and `<edit-url>` from `invideo edit new`. `<id>` comes from `library search`.
- The `crystal_upscaler` lines are only for a small scan. `<scan-sequence-url>` is the URL `invideo asset upload` returns for that photo. `<page-url>` is a notebook page; `invideo notebook show <project-url>` lists them. `<price>` is the dry run's `.estimated_credits`.
- `<state_token>` comes from `invideo edit show`. `ops.json` holds a bare list of operations, like the `operations` list in the calls below.
- `<card-url>` is `.sequences[0]` of the export result. `invideo status` waits and saves nothing. `invideo notebook show` saves the file as `./out/export.mp4`.

To loop a finished file instead of the timeline, the CLI runs `video.loop` on its sequence URL. The dry run shows its price before anything starts.

```bash
invideo media video.loop <sequence-url> --target-duration 3600 --dry-run
invideo media video.loop <sequence-url> --target-duration 3600 \
  --max-credits <price> --idempotency-key loop-1
```

`<sequence-url>` is an export's `<card-url>`, or the URL `invideo asset upload` returns. `<price>` is the `credits` value of the dry run.

The key MCP calls are full calls. They nest the slideshow into one clip, copy it, and fade between the copies. `<branch-url>` is the `branch_url` that `timeline_history` returns, and each `<state_token>` comes from `get_timeline` on that branch. The agent reads the timeline between calls, so each call uses the real id of the nested clip and its copy. The first call nests the photos:

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "nest_clips", "video_clips": ["c1", "c2", "c3", "c4"],
      "audio_clips": ["a1"], "name": "Slideshow", "mode": "nest" }
  ]
}
```

The second call copies the nested clip, here `c9` from `get_timeline`:

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [ { "op": "duplicate_clip", "clip": "c9" } ]
}
```

The third call adds the fade between `c9` and the copy, whose id, like `c10`, comes from `get_timeline`:

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "add_transition", "left_clip": "c9", "right_clip": "c10",
      "type": "fade", "in_f": 15, "out_f": 15 }
  ]
}
```

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

- **Free:** uploads, the layout, transitions, text, the loop with `nest_clips` and `duplicate_clip`, and the export.
- **Tool ops:** `audio.beats` and `video.loop`. The dry run shows its price before anything starts.
- **Costs credits:** paid stock music, and a sharper scan with `crystal_upscaler`. Your own music file costs nothing.

A slideshow of many photos takes the agent a few minutes. A long loop takes longer to export. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems are the photo order, black bars, or a jump at the loop seam.

| 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. |
| 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. |
| A photo is missing from the media bin | The agent names the file and asks you for it, then uploads it with `add_media`. |
| Photos are in the wrong order | Rename the files `01`, `02` and so on, or tell the agent the order. It moves clips with `reorder_clip`. |
| Black bars beside a portrait photo | Add the `fit_with_blur` effect, or fill the frame with `fill_frame` if the crop is fine. |
| The music stops before the loop ends | The agent nests the music with the photos, so each copy has its own music. |
| A jump at the loop seam | Lengthen the `fade` at the seam with `resize_transition`, and `capture` the seam again. |
| `add_transition` refused | The two clips must touch on one track with no gap. The agent closes the gap first. |
| CLI exit 3 | The timeline changed since your read. Run `invideo edit show` and rebuild the ops. |
| Avoid: one `edit_timeline` call per photo | Instead: one call with all the photo ops. It lands all or none. |
| Avoid: saying done without looking | Instead: `capture` the title, faces, and the loop seam. |

## Prompts to try

Each prompt is a different occasion.

```text
Make a graduation slideshow for Mia from ./mia, kindergarten to
graduation day. A caption with the school year on each chapter, and
"Congratulations, Class of 2027" at the end. 16:9.
```

```text
Make a baby's first year video, one photo per month from ./baby, with
"Month 1" to "Month 12" on each. Soft lullaby music. Vertical for my
phone.
```

```text
Make a retirement slideshow for Tom from the team photos in ./tom.
Fun music, a light leak between photos, and a two hour loop for the
party screen.
```

## When to use something else

invideo does not scan or restore damaged prints. Scan old photos with a phone scanning app first. For a sharper scan, the agent can run `media` `image.upscale`, which invents fine detail. The dry run shows its price before anything starts.

To play the loop at the venue without the internet, see [Make a memorial slideshow that loops and plays offline at the venue](/how-to/personal/memorial-slideshow) for the download and player checks.

## Related

- [Make a memorial slideshow that loops and plays offline at the venue](/how-to/personal/memorial-slideshow)
- [Make a birthday montage from photos and clips](/how-to/personal/birthday-montage)
- [Animate a photo, or make a pet memorial from one picture](/how-to/personal/animate-a-photo)
- [Make a video with ChatGPT, Claude or Gemini](/how-to/personal/with-chatgpt-claude-gemini)
- [Check your video before you say it is done](/how-to/check-your-video)