---
title: Make static ads at volume from the same brief as your video ads
description: Make image ads in bulk from the same brief as your video ads: stills from the videos, new images from a model, headlines on top, every size named.
---
Make static ads, also called image ads or statics, in bulk from the same brief and footage as your video ads. Your agent pulls the best stills from each video, makes new images with an image model where you need them, puts the headline and offer on top, sizes each one for its placement, and exports every still with a name.

This is ordinary ad work for a real product. The agent makes the images. You upload them and switch the ads on in the ad platform. The agent never publishes an ad.

Use invideo for this when your statics should match your video ads: the same shots, the same brand kit and the same headlines, all in one project, with every still exported free.

## Quickest path

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

```text
For each of the 10 videos in <project-url>, make 3 static ads: two stills
from the video and one new image from the brief. Put the video's hook as
the headline, with my brand kit. Make each in 1:1 and 4:5, named like
v01_static1_1x1. Do the first video, show me, then do the rest.
```

In a chat app, you see each still in the project. In a terminal, the agent waits for each export with `invideo status`, then saves the image to a folder with `invideo notebook show` and `--out`. `invideo status` alone does not download.

## What you need

The agent needs the videos, the words and the sizes.

- The video ads, as timelines or sequences in one project.
- The brief, and the brand kit in `context/brands/`.
- The headlines and offers, one per static, or let the agent take them from each video's hook.
- The sizes: 1:1 and 4:5 for feeds, 9:16 for Stories, 16:9 or 1.91:1 for some link ads.
- Your naming scheme.

## Steps

The agent does one video first, waits for your OK, then runs the rest in parts.

<Steps>
<Step title="Pull stills from the video">
The agent picks frames by looking at `capture` on the timeline. It exports each frame with `export` mode `image` and `still_at`, which includes the timeline's text and grade. For a frame of a raw source, it uses `media` `video.extractFrameAtTime` with `time_seconds`. It sees one image per still.
</Step>
<Step title="Make new images where you need them">
The agent runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick is `nanobanana_pro`, with a `prompt`, `reference_images` and an `aspect_ratio` within the range `get_model` gives. The agent passes a still of the real product as a reference. It calls `generate` with `dry_run: true` and tells you the price. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. It sees one image sequence URL per take, and reads it with `read`.
</Step>
<Step title="Size the images">
For 9:16 or 16:9, the agent runs `media` `image.resizeAspectRatio`. For 1:1 and 4:5, it cuts the shape with `image.crop` (`x`, `y`, `w`, `h`), then sets the size with `image.resizeFit` (`max_width`, `max_height`). For a grid of products, it runs `image.collage` with `image_array`. It sees a new image sequence per call.
</Step>
<Step title="Lay out the first static">
The agent makes a timeline at the static's size with `new_timeline`. It places the image with `insert_media`, adds the headline and offer with `add_text`, and styles them from the brand kit with `set_text_style`. It sees a timeline with an image and text clips.
</Step>
<Step title="Copy the layout for each headline">
The agent runs `duplicate_timeline` once per static, swaps the image with `place_media` mode `replace`, and swaps the words with `set_text`. It sees one timeline per static.
</Step>
<Step title="Check, then export each still">
The agent calls `capture` at `0` on each timeline. It checks that the text is readable at phone size and the product is not covered. Then it runs `export` with mode `image`, `still_at` `0`, `timeline_id` and `file_name`. It sees one export card per static with status `completed`.
</Step>
</Steps>

The terminal version, for one static:

```bash
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo edit export <video-edit-url> --format video --mode image \
  --still-at 3.2 --file-name v01_frame_3s --json
invideo gen models <project-url> --query "product image for an ad"
invideo gen model <project-url> --model nanobanana_pro
invideo gen run <page-url> --model nanobanana_pro --dry-run \
  --input static-inputs.json
invideo gen run <page-url> --model nanobanana_pro \
  --max-credits <price> --idempotency-key brewly-static-01 \
  --input static-inputs.json
invideo media image.crop <sequence-url> --x 420 --y 0 --w 1080 \
  --h 1080 --idempotency-key brewly-static-01-crop
invideo media image.resizeFit <cropped-sequence-url> --max-width 1080 \
  --max-height 1080 --idempotency-key brewly-static-01-fit
invideo edit new <project-url> --name "Statics 1x1" --width 1080 \
  --height 1080
invideo edit show <edit-url> --json
invideo edit change <edit-url> --timeline T1 --token <state_token> \
  --operations @layout-1x1.json
invideo edit capture <edit-url> --at 0 --out ./frames
invideo edit export <edit-url> --format video --mode image \
  --still-at 0 --file-name v01_static1_1x1 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./statics
```

The file `static-inputs.json` holds the model's inputs alone, as `get_model` lists them, with no `inputs` wrapper. It carries the list the `-i` flags cannot carry. The dry run and the paid run pass the same file. `<product-still-url>` is the sequence URL of the real product still, from the still export or `add_media`.

```json
{
  "prompt": "Brewly can on a sunlit counter, clean, warm",
  "aspect_ratio": "4:5",
  "reference_images": ["<product-still-url>"]
}
```

Where the values come from: `<video-edit-url>` is the video's timeline URL, from `invideo edit new` or `invideo notebook show <project-url>`. `<project-url>` comes from `invideo project new`. `<name>` is the name you choose in `invideo agent register`, and `<page-url>` is a notebook page from `invideo notebook show <project-url>`. `<edit-url>` is the statics timeline from `invideo edit new`, and `<state_token>` comes from `invideo edit show`. `<price>` is the dry run's `estimated_credits`. `<sequence-url>` is the image the paid run returns, and `<cropped-sequence-url>` is what `image.crop` returns. `<card-url>` is `.sequences[0]` of what each export returns.

The layout of one 1:1 static is the ops file `layout-1x1.json`, a bare list. The image goes on `V1` and the text on `V3`, the Titles track of a new timeline. The text runs for one second, and the export takes the still at `0`.

```json
[
  { "op": "insert_media", "asset": "<sequence-url>", "track": "V1",
    "at_f": 0, "ripple": false },
  { "op": "add_text", "track": "V3", "start_f": 0, "end_f": 30,
    "text": "Cold brew in 30 seconds",
    "style": { "font_size": 88, "font_weight": 700, "color": "#ffffff",
               "shadow": true } }
]
```

## 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: each new image from a model, and an `image.upscale` if a still needs more pixels.
- Free: stills from `export` mode `image`, the layouts and every export.
- `video.extractFrameAtTime`, `image.crop`, `image.resizeFit`, `image.resizeAspectRatio` and `image.collage`: the dry run shows the price before anything starts.

Stills and layouts take a few minutes per static. Run a large batch in parts, one video at a time, and check each part before the next. See [Credits](/account/credits) for prices.

## If something goes wrong

Most problems come from a blurry still or a size the op does not take.

| 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. |
| `image.resizeAspectRatio` refuses 1:1 or 4:5 | It takes 16:9 and 9:16. Use `image.crop`, then `image.resizeFit`. |
| A still is blurry | Pick a frame with less motion from `capture`, or run `image.upscale` with `dry_run` first. |
| The generated image shows a fake product | Pass the real product still in `reference_images`, or use a real still instead. |
| `export` mode `image` refused | It needs `still_at`. Add the time in seconds. |
| Text covers the product | Move the text with `set_position`, or make it smaller with `set_text_style`. |
| The folder is empty after `invideo status` | `invideo status` does not download. Run `invideo notebook show <card-url> --out ./statics`. |
| The saved image has a fixed name, not the `file_name` | `notebook show` saves under a fixed name. Rename each file to the scheme name. |
| CLI exit 7 | Nothing ran. Run the same command again. |
| Avoid: making every static at once | Instead: do one video, get the person's OK, then run the rest in parts. |
| Avoid: a new layout for each static | Instead: `duplicate_timeline` from the approved layout, and swap image and words. |

## Prompts to try

Each prompt is a real variation of this job.

```text
Take the 3 best frames from video 4, put each of my 3 offers on them, and
export 9 statics in 4:5.
```

```text
Make a 1:1 collage of my 4 flavors from the product stills, with the
headline "Pick your morning", and export it.
```

```text
The headline on all 1:1 statics changes to "Now in 8 flavors". Change it
on every layout and export version 2.
```

## When to use something else

invideo exports a still from a timeline. It does not make layered design files. For a static that a designer will keep editing, with layers and smart objects, use Figma, Canva or Photoshop, and bring the final image back in. For dynamic product ads that pull from a catalog, use the ad platform's own catalog ads.

## Related

- [Make performance ads for Meta, TikTok and YouTube, named and iterated from results](/how-to/marketing/performance-ads)
- [Resize one master ad for every placement, with safe zones](/how-to/marketing/every-placement-from-one-master)
- [Make a brand ad film from a brief, with generated shots and your real product](/how-to/marketing/brand-ad-film)
- [Thumbnail](/how-to/creators/thumbnail)
- [CSV to videos](/how-to/developers/csv-to-videos)