---
title: Make a Shopify and TikTok Shop product video that follows the no-stills rule
description: A vertical product video for your Shopify store and TikTok Shop that moves, shows the real product, and has captions that stay off it.
---
Make a product video, or shoppable video, for your Shopify product page and your TikTok Shop listing. You get a vertical video where the product moves, your real product stays exact, and captions sit clear of it. A slideshow of still photos with a zoom on each is the video TikTok Shop marks as low quality, so this page avoids it.

Use invideo for this when you have product photos and a few phone clips, and you want your agent to add motion, captions and the right shape on an editable timeline, then hand you the file to upload.

The agent never uploads to Shopify or TikTok Shop. You upload the file and publish the listing.

## Quickest path

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

```text
Make a vertical product video for my Shopify page and TikTok Shop from
the photos and clips in ./product. No slideshow of stills: animate the
photos into short moving shots, and keep my real product clip as it is.
Add captions away from the product. Price the generation first and show
me frames before you export.
```

In a chat app, you drag the photos and clips into the project. In a terminal, the agent names the folder and uploads the files.

## What you need

These are the inputs the agent asks for.

- Photos of the product, front and detail, with a plain background if you have them.
- At least one real clip of the product: in a hand, in use, or on a table while the camera moves.
- The words for the captions: the product name, what it does, one reason to buy.
- The shape. Both stores take vertical 9:16 for this job.
- A mood for the music, or no music.

## The store rules, in words

The two stores have different rules. Read their own pages before you upload, because the stores change them.

- **TikTok Shop** asks for videos with real on-screen activity. Its guidance names still images, screenshots, slideshows, animated content and GIF-style videos as content it does not want. It asks that the product in the video matches the product page, and that no text or stickers cover the product. Read the TikTok Shop Seller University guides on high-quality shoppable videos and on common mistakes for video content quality.
- **Shopify** takes product videos as MP4, MOV or WebM files, with its own limits on length and size. See <a href="https://help.shopify.com/en/manual/products/product-media/product-media-types" target="_blank">Shopify's product media types</a>.

For TikTok Shop, lead with your real footage. A clip that a model makes from a photo moves, but it is generated. Use it to support your real clip, not to replace it.

## Steps

The agent works on a branch, so the main timeline stays clean until you approve.

<Steps>
<Step title="Bring the files in">
The agent makes a project with `new_project` and uploads each file with `start_upload` and `add_media`. Expected result: one sequence URL per photo and clip.
</Step>
<Step title="Shape the photos for vertical">
The agent runs `media` `image.resizeAspectRatio` with `aspect_ratio` `9:16` on each photo. It is a tool op; the dry run shows its price before anything starts. An image-to-video model follows the shape of its first frame, so this step sets the shape of the moving shot. Expected result: a new 9:16 image sequence beside each photo.
</Step>
<Step title="Animate the photos">
The agent runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick is `seedance_2_5.frames`, and `kling_v4.kling_v4_frames` gives a second look. It calls `generate` with `dry_run: true`, the 9:16 photo as its first frame and a short `prompt` for slow camera motion around the product, and tells you the price. Then it runs it with `max_credits` and an `idempotency_key`. Expected result: one sequence URL per photo, each a short moving clip. The agent reads it with `read` and never calls `generate` again to check.
</Step>
<Step title="Build the vertical timeline">
The agent makes a 1080 by 1920 timeline with `new_timeline` and creates a branch with `timeline_history`. It places your real clip first with `place_media`, then the generated shots, and covers the frame with `fill_frame`. It joins shots with `add_transition` type `slide` or `fade`. Expected result: a new `state_token` and a vertical cut with no still photo on screen.
</Step>
<Step title="Add captions off the product">
The agent adds safe-zone guides with `create_guide`, then the words with `add_captions`, at the top or bottom of the frame where the product is not. If your clip has speech, it first prices `audio.transcript` on the clip's sequence URL with `dry_run`, starts it, and calls again after `retry_after_s` for the word times. Expected result: a caption clip that never covers the product.
</Step>
<Step title="Add music">
The agent finds a track with `search_library` in `stock_music`, brings it into the bin with `add_media` and `stock`, places it on `A2` (Music) with `insert_media`, and sets its level with `set_audio_gain`. Expected result: music under the cut, quieter than any speech.
</Step>
<Step title="Check, then export">
The agent runs `capture` at each shot and `check_timeline`. It compares each frame with your product photo by eye: the shape, the color and the label must match. Then it runs `export` with `format` video, `quality` 1080 and a `file_name`. Expected result: an export card with status `completed`.
</Step>
</Steps>

The terminal version of the same job:

```bash
invideo project new --title "Product video"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo asset upload <project-url> ./product/*
invideo media image.resizeAspectRatio <photo-seq-url> \
  --aspect-ratio 9:16 --dry-run
invideo media image.resizeAspectRatio <photo-seq-url> \
  --aspect-ratio 9:16 --max-credits <price> --idempotency-key bottle-9x16
invideo gen models <project-url> --query "animate a product photo"
invideo gen model <project-url> --model seedance_2_5.frames
invideo gen run <page-url> --model seedance_2_5.frames --dry-run \
  -i prompt="slow orbit around the bottle" -i first_frame=<sequence-url>
invideo gen run <page-url> --model seedance_2_5.frames \
  --max-credits <price> --idempotency-key bottle-orbit-v1 \
  -i prompt="slow orbit around the bottle" -i first_frame=<sequence-url>
invideo edit new <project-url> --name "Product 9x16" \
  --width 1080 --height 1920
invideo edit show <edit-url> --json
invideo edit change <edit-url> --timeline T1 --token <state_token> \
  --operations @ops.json
invideo edit capture <edit-url> --at 1,4,8,12 --out ./frames
invideo edit export <edit-url> --format video --quality 1080 \
  --file-name product-9x16 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./out
mv ./out/export.mp4 ./out/product-9x16.mp4
```

Where the values come from: `<project-url>` from `invideo project new`, and `<edit-url>` from `invideo edit new`. `<name>` is the name you choose in `invideo agent register`, and `<page-url>` is a notebook page from `invideo notebook show <project-url>`. `<photo-seq-url>` is the `url` that `invideo asset upload` returns for a photo. `<sequence-url>` is the 9:16 photo that `image.resizeAspectRatio` returns. Each `<price>` is the dry run right before it. `<state_token>` comes from `invideo edit show`. `<card-url>` is `.sequences[0]` of what the export returns. `invideo status` does not download; `invideo notebook show` saves the file as `export.mp4`.

The key edit puts the real clip first, fills the frame, and adds a caption at the bottom. This is `ops.json`, a bare list. At 30 fps, frame 90 is 3 seconds.

```json
[
  { "op": "place_media", "asset": "m1", "track": "V1", "at_f": 0,
    "source_start_seconds": 0, "source_end_seconds": 3,
    "mode": "overwrite", "linked_audio": false },
  { "op": "fill_frame", "clip": "c1" },
  { "op": "create_guide", "timeline": "T1",
    "orientation": "horizontal", "position": 0.8, "locked": true },
  { "op": "add_captions", "track": "V3",
    "slides": [
      { "text": "Keeps drinks cold all day", "start_f": 0,
        "end_f": 90 }
    ] }
]
```

The media bin id `m1` comes from `invideo edit show` with `--mode collection --collection asset`. The clip id `c1` is the one `place_media` makes in the same call. The guide marks where the store's buttons sit, so the agent keeps the captions above it.

## 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: every timeline edit, `capture` and `export`.
- `image.resizeAspectRatio`: the dry run shows its price before anything starts.
- Costs credits: each image-to-video take, `audio.transcript` if your clip has speech, and stock music.

One video takes under an hour, most of it the generation. See [Credits](/account/credits) for prices.

## If something goes wrong

Most problems are a redrawn product or text in the wrong place.

| 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. |
| The generated shot changes the product | Reject the take. Generate again with a shorter motion prompt, or use your real clip for that shot. |
| The generated shot comes out 16:9 | The first frame was not 9:16. Run `image.resizeAspectRatio` first, then generate again. |
| A file is missing from the folder | Name the missing file and ask the person for it. |
| A caption covers the product | Move the caption clip with `set_position`, or restyle it with `set_text_style`. Capture again. |
| `timeout` on `generate` | Do not call it again. `read` the thread or call `status`. |
| CLI exit 3 | The edit changed since your read. Run `invideo edit show` and rebuild the ops. |
| The download folder is empty | `invideo status` does not download. Run `invideo notebook show <card-url> --out ./out`. |
| CLI exit 5 | Credits ran out. Stop and tell the person. |
| Avoid: a slideshow of stills with zooms | Instead: real clips and moving shots. TikTok Shop names slideshows as low quality. |
| Avoid: saying done without frames | Instead: `capture` each shot and compare it with the product photo. |

## Prompts to try

Each prompt is a real variation of this job.

```text
I only have three photos of the candle. Animate each into a slow push-in,
show me the takes, and keep the label exact. Vertical, with captions.
```

```text
Cut my unboxing clip to 20 seconds for TikTok Shop. Captions at the top,
no text over the product, light music.
```

```text
Make the same video in 9:16 for TikTok Shop and 1:1 for my Shopify page,
one branch each.
```

## When to use something else

invideo does not upload to Shopify or TikTok Shop, and it does not check a listing against the store's rules. You upload the file in Shopify admin or TikTok Shop Seller Center and read their review result there. For a live shopping stream, use TikTok LIVE itself. For product photos you do not have, shoot them on a phone first: a model works best from a real photo.

## Related

- [Turn a product photo into a video without redrawing the product](/how-to/marketing/product-photo-to-video)
- [Make an Amazon listing or Sponsored Brands video ready for moderation](/how-to/marketing/amazon-listing-video)
- [Resize one master ad for every placement, with safe zones](/how-to/marketing/every-placement-from-one-master)
- [Label AI content so your ad runs on TikTok, Meta and Google](/how-to/marketing/ai-disclosure-for-ads)
- [Check your video before you say it is done](/how-to/check-your-video)
- [Know the cost before you spend](/how-to/cost-before-you-spend)