← Plugin catalog
Creativity

Visual Sandbox

Visual Sandbox v1.0.8

Make videos, ads & voiceovers

Create a free account to save this plugin.

From the developer

Declared capabilities

  • Generate videos from text or images
  • Animate photos
  • Short films with a recurring character

Cost & access

Visual Sandbox sells nothing in ChatGPT. The ChatGPT server address has no checkout or top-up tool and returns no purchase link. Generations are charged to the person's existing Visual Sandbox account. When the balance is too low, the plugin says so and says that credit is added on visualsandbox.com.

Declared limitations

  • Written copy only. The request names Instagram, but asks for no media.
  • A general question about video specs. The person wants an answer, not a generated file.
  • Finding an existing stock photo. Visual Sandbox makes new media and does not search stock libraries.

Onboarding example

Animate this product photo into a 5-second vertical video.

View onboarding source →

Sources & dates

Developer declarations; not independently tested. Listing last checked Nov 5, 2026. Package observed Nov 5, 2026.

View package source →

Description

From the marketplace listing

Make videos, ads, voiceovers and images in ChatGPT with more than 50 AI models in one place. Animate a product photo, turn a script into a short film with the same character in every shot, or make a creator-style video ad with a voice and captions. Make a photo talk with a stock voice or your own cloned voice. Cut a long video into short vertical clips with word-timed captions. Turn one product photo into a set of store photos, or make several versions of one ad to test against each other. Edit in the same chat: join clips, add a voiceover, music and captions, trim to 9:16 and place your logo. Save a person or a product as a character once, and keep the same face and voice in every shot. Make music, sound effects, SVG logos, 3D models and web pages too. A larger run waits until you press Generate in the card. Every result shows in the chat and lands on a canvas at visualsandbox.com, where you see the whole set side by side and share a link. For help, contact hello@visualsandbox.com.

Language: English · Automatically detected from descriptions.

Matches for “reference”

Exact text from the indicated source. A mention alone does not establish support for your task.

Publisher capabilities · listing

Generate videos from text or images Animate photos Short films with a recurring character UGC-style video ads Talking avatars from a photo Voice cloning Long videos cut into short clips Join and edit video clips Word-timed captions Voiceovers and text to speech Generate images from text Edit images with a reference photo Product photo shoots Ad creatives and ad variations Reusable characters Music and sound effects SVG logos and icons 3D models Web pages and cards in editable layers Asks before a larger run

Changes

No changes recorded yet. Follow this plugin to keep track of future changes.

First seen Nov 5, 2026. Last seen Nov 5, 2026.

Keywords

Files & skills

File archives

Plugin package69 files · 1.61 MBBrowse files →
Skill instructions
vsb-ad-variations3.2 KB

View saved version →

---
name: vsb-ad-variations
description: >
  Make several versions of one image ad to test against each other with the
  Visual Sandbox MCP tools: the same product and offer, with a different
  hook, scene, person or layout in each version. Trigger when the person
  asks for "ad variations", "versions to A/B test", "more creatives",
  "different hooks", "5 ads for Meta", "new angles for this ad" or sends one
  ad and asks for more like it. Changes one thing per version, so a test
  says which thing worked. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Ad variations to test

An ad test only teaches something when each version changes one thing. This
skill takes one ad, or one product, and makes 3 to 6 versions that differ in
one named way.

Use the MCP tools only.

## 1. Get the base

- **An ad they already have.** Use it as the reference image. Pass a public
  `https` URL. Give an ad attached in the chat, or a local file, to
  `upload_media` first.
- **Only a product.** Make the base ad first with
  [`vsb-product-photoshoot`](../vsb-product-photoshoot/SKILL.md), then come
  back here.

Ask where the ads run. Use 4:5 for a Meta feed, 9:16 for Stories, Reels and
TikTok, and 1:1 when they do not know.

## 2. Pick the one thing that changes

Propose one axis and let the person pick:

| Axis | The versions differ in |
|------|------------------------|
| Hook | The headline on the ad. The picture stays the same |
| Scene | The place: a kitchen, an office, outdoors, a studio |
| Person | Who holds or uses the product |
| Angle | The reason to buy: price, speed, quality, a feeling |
| Layout | Where the product, the headline and the space sit |

Write the versions as a short table first: the version number and the one
thing that changes. Show it before you spend.

## 3. Make the versions

1. Read [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) with
   `get_skill`. When the ad has text, read
   [`vsb-nano-banana`](../vsb-nano-banana/SKILL.md) too: it has the rules
   for words that render cleanly.
2. Call `list_models` with `modality: "image"` and `get_model` for the model
   you pick. Use the same model for every version.
3. Write one base prompt. For each version, change only the part for the
   axis. Keep every other word the same.
4. Call `generate` once for each version, with the same reference and the
   same `sandbox_name`, such as "Serum ad test".
5. Call `get_job` for each one after `eta_seconds`. Check every word on each
   ad. A misspelled headline is a failed version.

## 4. Hand them over

List the versions in the order of the table, each with its `share_url` and
the one thing it changes. Give the `sandbox_url` of the canvas, where they
sit side by side. Offer one next step: a short video of the version the
person likes, with [`vsb-video`](../vsb-video/SKILL.md).

## Rules

- One axis for each set. Two changes in one version make the test useless.
- Never invent a claim, a price, a discount or a review for the ad. Use only
  the words the person gave you.
- Call `estimate_cost` before a set of more than six versions, and say the
  total.
- When a run is refused for want of credit, say the balance is too low, and
  follow what the server instructions say about credit.

Referenced files: 1 — file references are metadata, not package contents.

vsb-animate-photo2.82 KB

View saved version →

---
name: vsb-animate-photo
description: >
  Turn one photo into a short video clip with the Visual Sandbox MCP tools:
  a product shot that moves, a portrait that comes alive, a still that gets
  a slow camera move. Trigger when the person asks to "animate this photo",
  "make this picture move", "turn my product photo into a video", "add
  motion", "image to video", or attaches a picture and asks for a clip.
  Keeps the subject unchanged, asks for the price first, and shows the clip
  in a card. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Animate a photo

One photo becomes one clip of about 5 seconds. The subject stays the same.
Only the motion is new. Use the MCP tools only.

## 1. Get the photo and the motion

- A photo attached in the chat goes to `upload_media` as `file`. Use the
  `url` it returns. A public `https` link can go in directly.
- Ask what moves, in one question, only if the person did not say. Offer
  two choices: a slow camera move, or one small action of the subject.
- Keep the shape of the photo. A tall photo makes a 9:16 clip, a wide photo
  makes a 16:9 clip.

## 2. Pick the model

Read [`vsb-video`](../vsb-video/SKILL.md) with `get_skill`, at "Picking a
model". Then use this short rule:

- **A first try, or a product shot:** `video/p-video` or `video/p-video-2`
  with the photo as `image`. Fast and low in cost.
- **People, hands, or a hard motion:** `video/seedance-2.0-mini` with the
  photo as `image`. It holds faces and hands better.
- **Speech:** this is not the right skill. Read `vsb-talking-avatar`.

## 3. Write the prompt

Describe only the motion and the camera. The photo already holds the look.

- Name one motion: "steam rises from the cup", "she turns her head and
  smiles", "the camera pushes in slowly".
- Add what must not change: "The product keeps its exact shape, colour and
  label."
- Leave out new objects, new text and scene changes. They make the model
  redraw the photo.

## 4. Price, run, show

1. Call `estimate_cost` with the model and the inputs. Tell the person the
   price and wait for their yes.
2. Run the model with the photo `url`, the prompt, the aspect ratio and a
   duration of 5. Name the work with `sandbox_name`. In Review mode, a run
   of 10 cents or more waits for the person's press of Generate: show the
   card with `show_media`, or give the review link. Never press it for them.
3. Call `show_media` with the job id at once. The card shows the percent and
   then plays the clip.
4. When the person wants to judge it yourself, call `get_job` after
   `eta_seconds`. It returns four frames of the clip.

## Rules

- One clip per request unless the person asks for more.
- When the label or the face changes, say so. Offer a second run with a
  smaller motion, not a longer clip.
- To make it vertical later, or to add music, read `vsb-edit-video`.

Referenced files: 1 — file references are metadata, not package contents.

vsb-audio16.6 KB

View saved version →

---
name: vsb-audio
renamed_from: audio
description: >
  Generate sound effects, full music tracks, spoken voiceovers and new
  voices designed from a description via Visual Sandbox. Trigger when the
  user wants short SFX, ambient audio, background music, a soundtrack, a
  voiceover, narration, text to speech or a custom voice via the vsb CLI. SFX and speech runs are quick; music scales
  with track length. Submit with `--async` then poll with `vsb status`
  for anything long.
license: Proprietary. See LICENSE for the terms.
---

# Audio generation with vsb

> **On the MCP surface?** If the `visual-sandbox` MCP tools are in your
> tool list, use `generate` and `get_job` instead of the shell commands
> below. The model choice and the prompt craft on this page still apply.
> See `vsb` → Two ways to run.

Four jobs: sound effects, music, speech and voice design. Short SFX finishes in
5–30 seconds and a few hundred characters of speech in a few seconds
(sync is fine); music can take longer, so prefer `--async` + `vsb status`
for tracks over ~60s.

Start every run in a **background shell** (Claude Code: `run_in_background:
true`) so the turn stays free, and put the wait loop there too — the shell's
exit is the "done" signal. `vsb status` never waits on its own. Rules in
`vsb` → Background generations.

## What's live

| Task | Slug | Notes |
|------|------|-------|
| Sound effects (0.5–30s clips) | `audio/elevenlabs-sound-fx` | ElevenLabs. Foley, ambient, transitions, UI sounds. Loop mode for tiling beds. |
| Voiceover or narration from a script (text to speech) | `audio/elevenlabs-tts` | ElevenLabs. 21 stock voices (`--voice`, default `george`), or a voice you designed (`--my_voice <name>` or `--designed_voice <sample url>`, see below). No voice cloning: a recording of a real person is refused. `--mode v4` (default) is the newest and most natural and follows audio tags like `[whispers]`, 10,000 characters per run; `v3` also follows tags, 5,000; `multilingual-v2` reads long narration more evenly, 10,000; `flash-v2.5` costs half as much, 40,000. Billed per character. |
| Voiceover or narration at a much lower rate, or in a language ElevenLabs does not cover | `audio/gemini-3.8-flash-tts` | Google's flagship voice. 30 voices (`--voice`, default `Kore`), over 130 languages detected from the text, 10,000 characters per run, WAV out. Pauses and sounds come from inline tags in English: `<laugh>`, `<sigh>`, `<short pause>`. One voice per run. No designed voice, no cloning, no `[square bracket]` tags. Billed per character. |
| Many short reads, read-aloud lines or a cheap first draft | `audio/gemini-3.8-flash-lite-tts` | The faster, cheaper Gemini 3.8 voice: about a third less than Flash. The same voices, tags and limit, over 100 languages. Switch to `audio/gemini-3.8-flash-tts` for the final take of a long or emotional read. Billed per character. |
| A new voice from a written description | `audio/elevenlabs-voice-design` | ElevenLabs Voice Design v3. Returns one MP3 sample of a new voice reading one line. A sample is an audition, not the voiceover: pass it to `audio/elevenlabs-tts` as `--designed_voice`. Run again for another take. Billed per character of the sample line. The web Composer runs one design per click. Safety-filtered: a description that reads as a child, like "girl", is refused; "woman" passes. Not voice cloning. |
| Speak a recording again in another voice (voice changer, speech to speech) | `audio/elevenlabs-voice-changer` | ElevenLabs. Takes an audio or a video file of up to 5 minutes and one of the voices TTS takes: `--voice`, `--designed_voice` or `--my_voice`. Keeps the words, the timing and the emotion; only the voice changes. Use it to put one voice back on clips that a video model voiced itself. Billed per minute of the recording: the quote holds 5 minutes, then drops to the real length. |
| Music (10s–5min tracks) | `audio/eleven-music` | ElevenLabs music_v2. Full structured tracks, vocals (model-written or your own lyrics) or instrumental. 48 kHz MP3. |
| Full song or instrumental from a style description | `audio/minimax-music-2.6` | MiniMax. One flat price per track whatever the length, so long songs are cheapest here. Own lyrics with section tags, auto-written lyrics, or instrumental. It honours a stated key and BPM. You cannot set the length: expect two to four minutes. |
| Re-sing a song you already have in a new style | `audio/minimax-music-cover` | MiniMax. The only model that takes a recording as input. Anchors on the melody and swaps the voice, instruments and arrangement, with optional replacement lyrics. The source needs audible singing, and the melody can still drift, so listen before you use it. |
| Speech-to-text | `audio/scribe` | Transcription, not generation — see `vsb-subtitles`. |

(Verify the live catalog with `vsb models --modality audio --json` before
trusting this list.)

## ElevenLabs Sound FX

```bash
vsb run audio/elevenlabs-sound-fx \
  --prompt "footsteps on wet pavement at night, slow walk, distant traffic" \
  --duration_seconds 10 --json
```

- `duration_seconds` is a preset: `auto`, `2`, `5`, `10`, `15`, `20`, `30`.
  `auto` lets the model pick — good for one-shots.
- `prompt_influence`: `loose` / `balanced` / `strict`.
- `loop true` produces clips that tile cleanly (beds, rain, room tone).

### Prompt patterns for SFX

Descriptive, sensory, concrete:

- Good: "heavy wooden door creaking open, then slamming shut"
- Good: "thunder rumbling in the distance, light rain on a tin roof"
- Bad: "scary music" → that's `audio/eleven-music`
- Bad: "happy" → too abstract, the model won't know what *sounds* happy

## ElevenLabs Text to Speech

```bash
vsb run audio/elevenlabs-tts \
  --prompt "[excited] We did it! [laughs] Okay... now the hard part." \
  --voice george --mode v4 --json
```

- The flag is `--mode`, not `--version`: the CLI owns `--version`. Values:
  `v4` (default), `v3`, `multilingual-v2`, `flash-v2.5`. Limits are in the table.
- `--voice` takes one of 21 stock keys: `adam`, `alice`, `bella`, `bill`,
  `brian`, `callum`, `charlie`, `chris`, `daniel`, `eric`, `george`,
  `harry`, `jessica`, `laura`, `liam`, `lily`, `matilda`, `river`,
  `roger`, `sarah`, `will`. `vsb schema audio/elevenlabs-tts --json`
  lists them.
- Audio tags (`[whispers]`, `[laughs]`, `[sighs]`, `[excited]`) work in
  `v4` and `v3` only. Put the tag where it should happen. Remove the tags before
  you switch to another mode. The voice limits what a tag can do: a calm
  voice does not shout on cue.
- Pauses come from commas and `...`. `v3` takes no SSML break tags.
- Spell hard names the way they sound, and write numbers and dates as
  words. `flash-v2.5` reads numbers the least reliably.
- Text over the mode's limit is rejected before any charge. About 1,000
  characters make a minute of speech. Split a longer script at paragraph
  breaks and run each part.
- The MP3 is a voice track for `video/p-video-2` (`--audio`, the cheapest
  way to keep one voice across shots), `video/kling-avatar-v2` (a photo
  performs the audio) or `video/lipsync-2-pro` (new words on a clip in which
  the person already talks). For voice and video in one run,
  `video/p-video-avatar` writes its own speech. Pick the path in
  [`vsb-video` → People who talk](../vsb-video/SKILL.md#people-who-talk-pick-the-sound-path-before-you-spend) before you
  make the lines.

## ElevenLabs Voice Design

Use it when no stock voice fits: a narrator for a series, a voice for each
character, a brand voice. Design once, then speak every script with it.

```bash
# 1. Design. One sample comes back in result.urls.
vsb run audio/elevenlabs-voice-design \
  --prompt "Native British English. Female, in her 40s. Studio quality. Persona: calm documentary narrator. Emotion: warm, measured, curious. Low, smooth timbre with an unhurried pace." \
  --json | jq -r '.result.urls[0]'

# 2. Give the user the share page, https://visualsandbox.com/share/<job_id>/,
#    to hear it. Not right? Run step 1 again, or change the description.
# 3. Speak with it. Pass the sample URL itself, not a downloaded copy.
vsb run audio/elevenlabs-tts --prompt "<script>" \
  --designed_voice "<sample url>" --mode v3 --json
```

- `--prompt` is the description, 20 to 1,000 characters. More detail gives
  a more accurate voice. A structure that works: `Native <Language>.
  <Gender>, <Age range>. <Quality level>. Persona: <2-5 words>. Emotion:
  <2-3 adjectives>.` plus a sentence on timbre and delivery.
- Name the language and the regional accent first, or the voice drifts.
  Leave out effect words (`reverb`, `echo`) and vague ones (`foreign`).
- `--text` is the line the sample reads, 100 to 1,000 characters. Omit it
  and ElevenLabs writes a line that suits the voice; the run reserves the
  longest line and is repriced down after. A longer line gives steadier,
  more expressive sample. The web Composer hides this field.
- `--designed_voice` overrides `--voice` and works in every `--mode`. Use
  the same sample for every later script so the voice stays the same.
  The first run with a sample saves it as a voice and takes about 12 s
  longer. A voice not used for a while can be saved again on its next
  run, with the same longer wait. It sounds the same and costs no more.
- Only the user who designed a sample can speak with it. Any other URL,
  an uploaded recording included, is refused. This is voice design, not
  voice cloning, and cloning is not offered.
- `--auditions 3` keeps all three takes that ElevenLabs makes of the voice,
  for the same price: `result.urls` holds three samples to compare. Each
  take is its own voice. Pass the take the person picked as
  `--designed_voice`, or as `--my_voice` (it takes the link of one take).
  Over MCP in ChatGPT, call `voice_studio` with the job id: the card plays
  the three takes and saves the picked one to a character.

### Your voices

`vsb voices` lists every voice the user designed, newest first, with its id,
its name and its sample URL. Name one once, then speak with it by name, with
no sample URL to keep:

```bash
vsb voices                                  # list (MCP: list_voices)
vsb voices rename <id> narrator             # name it; "" clears the name
vsb run audio/elevenlabs-tts --my_voice narrator --prompt "<script>" --json
```

- `--my_voice` takes a name (any case) or an id, and wins over
  `--designed_voice`. An unknown name fails before any charge.
- Two voices of one user cannot share a name.

## ElevenLabs Voice Isolator

Use it when a recording has the right voice under noise: a street, a fan,
music. It keeps the speech and removes the rest. Run it on a recording before
a voice clone or a transcript, or on a clip's sound before you put it back
with `add_audio`.

```bash
vsb run audio/elevenlabs-voice-isolator --audio "<url of the recording or the video>" --json
```

Up to 5 minutes; a video gives its sound. Billed by the length of the
recording. `vsb pricing audio/elevenlabs-voice-isolator --json` gives the rate.

## ElevenLabs Voice Changer

Use it when a recording has the right words and timing but the wrong voice.
The main case is a video model, such as Seedance 2.5, that speaks your line
again in its own voice. Change the voice back, then put the sound on the clip.

```bash
# 1. A video is fine: the run takes its sound.
NEW=$(vsb run audio/elevenlabs-voice-changer --audio "<clip url>" \
  --my_voice narrator --json | jq -r '.result.urls[0]')
# 2. Replace the clip's own sound with the new voice.
vsb run tool/video-add-audio --video "<clip url>" --audio "$NEW" --mode replace --json
```

- The output is as long as the input, so the lip sync of a clip holds.
- Turn on `--remove_background_noise true` when the recording has room tone
  or music. Without it, the noise can come back as a hiss in the new voice.
- 5 minutes at most per run. Cut a longer recording into parts.
- It takes the same voices as Text to Speech. A recording of a real person
  cannot become a voice: there is no voice cloning.

## Eleven Music

```bash
vsb run audio/eleven-music \
  --prompt "Minimal modern electronic promo track. Warm analog synth pads, soft four-on-the-floor kick, sparse bell melody. Clean, premium, optimistic. 100 BPM." \
  --duration_seconds 30 --instrumental true --json
```

- `duration_seconds`: 10–300 (default 30). The exact length is pinned in
  the request, so the price shown at submit is exact and the track comes
  back at precisely that length — match it to your edit.
- `instrumental true` forces no vocals — the right call for background
  beds under dialogue or voiceover. Omit it and the model writes and
  performs vocals fitting the prompt (multilingual).
- **Brief it like a composer**: genre, mood, instrumentation, tempo/key
  ("100 BPM in A minor"). Use-case framing also works ("ad for a sneaker
  brand", "peaceful meditation bed").
- **Don't name artists or songs** — the provider rejects prompts that
  reference copyrighted music. The error includes a suggested rewrite;
  use it.
- `lyrics`: your own words, sung verbatim. See the next section.
- Licensing: commercial use OK (ads, social, podcasts, apps, games).
  NOT covered: releasing tracks on Spotify/Apple Music or offline
  TV/radio/film broadcast. Mention this if the user plans distribution.

### Your own lyrics

Pass `--lyrics` and the model sings those words instead of writing its
own. One line per row; blank lines separate sections. Section tags in
square brackets are optional and honoured: `[Verse 1]`, `[Chorus]`,
`[Bridge]`. Inline directions in curly braces are not sung: `{guitar solo}`.

```bash
vsb run audio/eleven-music \
  --prompt "Upbeat acoustic pop, female vocals, bright and simple, 120 BPM." \
  --duration_seconds 60 \
  --lyrics "$(cat lyrics.txt)" --json
```

`lyrics.txt`:

```text
[Verse 1]
Sandbox in the morning light
Every pixel feels just right

[Chorus]
Visual Sandbox, run it now
One click and we show you how
```

- The `prompt` still sets the style: genre, mood, vocals, tempo. Keep the
  words in `lyrics`, not in the prompt.
- Lyrics can be in any language. The style prompt should stay in English.
- Under the hood the server asks ElevenLabs for a composition plan with
  your lines, checks every line survived, pins the section lengths to
  `duration_seconds`, then composes. The price is unchanged: by track
  length only.
- `--lyrics` and `--instrumental true` together are rejected. Pick one.
- Size the track to the words: about 4 sung lines per 20 seconds. The
  planner adds an intro and outro around your sections.
- Max 3000 characters of lyrics.

### Async + poll (tracks over ~60s)

```bash
JOB=$(vsb run audio/eleven-music --prompt "..." --duration_seconds 120 \
  --instrumental true --async --json | jq -r '.job_id')
vsb status "$JOB" --json | jq -r '.status'   # non-blocking peek

# Wait — run this in a BACKGROUND shell; it notifies on completion.
until vsb status "$JOB" --json 2>&1 | jq -e '.status | IN("completed","failed","cancelled","error")' > /dev/null; do sleep 10; done
vsb status "$JOB" --result --download "./out/{request_id}.mp3" --json > "/tmp/vsb-$JOB.json"
```

Play the finished track for the user with `open <file>` (the default player).

`--download` only if the user opted into local saves — otherwise fetch with
`--result --json` alone and hand back the share page
`https://visualsandbox.com/share/<job_id>/`, never a raw CDN URL
(vsb critical rule 5).

## Cost estimation

```bash
vsb pricing audio/elevenlabs-sound-fx --json   # per clip
vsb pricing audio/eleven-music --json          # per minute of music
vsb pricing audio/elevenlabs-tts --json        # per 1,000 characters of text
vsb pricing audio/elevenlabs-voice-design --json  # per 1,000 characters of the sample line
```

Speech is billed per character of text, spaces and audio tags included.
`flash-v2.5` costs half as much as the other modes.

Music is billed by track length, prorated per second — a 30s bed costs a
few cents, a 5-minute song under a dollar.

## Common gotchas

- **Field is `prompt`** on every generation model. For speech it is the
  text to read. Run `vsb schema audio/<slug> --json` to confirm before
  guessing flags.
- **Lyrics go in `--lyrics`, not in the prompt.** Words inside the prompt
  are treated as style hints and may be paraphrased; `--lyrics` is sung
  verbatim.
- **Output is `.mp3`** — set the download template extension accordingly:
  `--download "./out/{request_id}.mp3"`.
- **SFX duration caps at 30 seconds**; anything longer is a music job.
- **Music duration must fit the edit** — the model fills exactly the
  seconds you ask for; a 16s video wants a 16s track, not a trimmed 30s one.
- **No em/en dashes (— –) in any text a model will speak or sing.** Speech
  synthesis vocalizes dashes unpredictably (odd pauses, hallucinated
  words). Use a comma, period, or `...` — full rule in the dialogue section
  of [`vsb-video`](../vsb-video/SKILL.md).

Referenced files: 1 — file references are metadata, not package contents.

vsb-characters2.63 KB

View saved version →

---
name: vsb-characters
description: >
  Keep one person, mascot or product the same in every picture and video
  with Visual Sandbox characters. Make a character sheet, save it with
  create_character, give it a voice, and pass its pictures to every later
  shot. Trigger when the person asks for "the same character", "a recurring
  host", "an AI influencer", "a mascot", "consistent faces", "a cast for my
  film", or names a character they made before. Uses the MCP tools, or
  `vsb characters` in the CLI.
license: Proprietary. See LICENSE for the terms.
---

# Characters

A model forgets a face between two runs. A character is what you give it
each time: the same reference pictures, the same description and the same
voice. Saving it is free.

## Make a character

1. **Ask what must not change.** Face, age, hair, build, skin, clothes, and
   the style (photo, 3D, cartoon). Write it down in two or three lines.
2. **Make the sheet.** Generate pictures of the character on a plain light
   background, in even light: a front view, a three-quarter view and a full
   body. Use one image model for all of them and read `vsb-image-prompting`
   first. For a real product, start from its photo.
3. **Show the sheet and get a yes.** Remake a view that drifted before you
   save.
4. **Save it.** `create_character` with the name, the description and the
   URLs of the pictures (up to 8). Links that are not on Visual Sandbox are
   copied, so they last.
5. **Give it a voice, if it speaks.** Pick a stock voice with `list_voices`,
   or design one with `audio/elevenlabs-voice-design`. Save its id with
   `update_character`.

## Use a character

1. `get_character` with its name.
2. Pass its `reference_urls` to the model's reference input, as `get_model`
   names it.
3. Put the description in the prompt, then say what is new in this shot:
   the place, the action, the camera.
4. For speech, pass the `voice_id` to `audio/elevenlabs-tts`: as `voice` for
   a stock voice, as `my_voice` for a voice the person designed or cloned.
   A `voice_id` can be the link of one take of a voice, which is what the
   Voice Studio card saves; `my_voice` takes that link as it is.

For two characters in one shot, pass the pictures of both and name each one
in the prompt: "the woman from the first pictures, the dog from the last".

## Look after the cast

- `list_characters` shows every character on the account. Check it when the
  person names someone, before you make a new one.
- Replace a weak reference with `update_character`. The new set replaces
  the old one.
- `delete_character` removes the character, not its pictures.
- Never make a character of a real person without their consent.

Referenced files: 1 — file references are metadata, not package contents.

vsb-clone-voice2.68 KB

View saved version →

---
name: vsb-clone-voice
description: >
  Clone a voice from a recording with the Visual Sandbox MCP tools, then
  read any script in it: a voiceover, an ad, a narration or a talking
  avatar in the person's own voice. Trigger when the person asks to "clone
  my voice", "use my voice", "make it sound like me", "voice cloning",
  "narrate this in my voice", or attaches a voice recording and asks to
  reuse it. Checks consent, makes the clone once, and reads scripts in it
  after that. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Clone a voice

Make the clone once. After that, every script reads in that voice. Use the
MCP tools only.

## 1. Check consent first

Clone only the voice of the person who asks, or a voice whose speaker gave
permission. Ask in one line when it is not clear. Set `confirm_rights: true`
only after the person confirms it. Never clone a public figure.

## 2. Get the recording

- One speaker, clear speech, no music. One to two minutes gives the best
  clone. A phone recording is good enough.
- A file attached in the chat goes to `upload_media` as `file`. Use the
  `url` it returns.
- Set `remove_background_noise: true` only for a noisy recording. It can
  lower the quality of a clean one.

## 3. Make the clone

1. Call `estimate_cost` for `audio/elevenlabs-voice-clone`. Tell the person
   the price and wait for their yes.
2. Run it with `audio` (the recording URL), `confirm_rights: true`, and a
   short `prompt` that the clone reads back as a sample. The voice stays
   usable later: Visual Sandbox keeps the recording, and a later run clones
   the voice again when it needs to. An account can clone 3 times a day.
3. Call `show_media` with the job id, so the person hears the sample.

## 4. Read a script in the voice

1. Call `list_voices`. The new voice is in the list of voices the person
   made. Use its name or id.
2. Run `audio/elevenlabs-tts` with `prompt` (the script) and `my_voice`.
   `mode: "v4"` is the most natural.
3. Write the script without dashes, and write numbers as words.

To put the voice on a face, read `vsb-talking-avatar`. To put it on a video,
read `vsb-edit-video` and use `add_audio`.

## In ChatGPT: the Voice Studio card

On an account that may clone a voice, `voice_studio` with no job id opens
a card that records the person, or takes a saved recording. They tick the
consent box, and the card sends you the recording's URL. Run the clone
with that URL and `confirm_rights: true`, then call `voice_studio` with
the clone's job id so they hear it and save it to a character.

## Rules

- One clone per voice. Reuse it with `my_voice`; do not clone again.
- When the sample sounds wrong, ask for a cleaner or a longer recording.

Referenced files: 1 — file references are metadata, not package contents.

vsb-design14.3 KB

View saved version →

---
name: vsb-design
description: >
  Make designs on the Visual Sandbox canvas with `vsb design` (or the
  `create_design` MCP tool): web pages, app screens, cards, posters and vector
  drawings, organized in named layers like a Figma file, and exported as HTML,
  React, SVG or PNG. Trigger when the user wants to "design a page", "mock up
  a screen", "make a landing page / hero / card", "make this UI" or
  "rebuild this page" from a screenshot, "draw a picture or an
  illustration in code", "make an SVG by drawing it", "put a design on the
  canvas", "export it to React", or points at a layer they picked on the
  canvas ("change this button"). Free: nothing is generated and nothing is
  billed.
license: Proprietary. See LICENSE for the terms.
---

# Design on the canvas

A design is **one HTML document on the canvas**, drawn at 1:1 like a frame in
Figma. A page, a screen and a card are HTML and CSS. A drawing is one `<svg>`.
**A layer is an element with a `data-name` attribute**, and the layer tree is
how those elements nest. The person sees the tree beside the canvas, hovers a
layer to outline it, and picks one to point at it.

The canvas, the HTML export and the React export are drawn by the same
browser engine from the same document, so they match pixel for pixel.

```bash
vsb design new hero.html --name "Landing hero" --width 1440 --height 900
vsb design export "Landing hero" -f png -o hero.png   # look at it, then fix it
vsb design update "Landing hero" hero.html --version 1
vsb design export "Landing hero" -f react             # landing-hero.jsx
```

## The loop

1. Write the file: `page.html`, or `drawing.js` for a drawing. Build a page
   with more than one section in sections (see below).
2. `vsb design new <file> --name "<what it is>" --width <px> --height <px>`.
3. `vsb design export <design> -f png -o check.png`, and **look at the PNG**.
   Read it with your image tool. Never report a design you have not looked at.
   Read the `warnings` the write printed too.
4. Fix what is wrong, then `vsb design update <design> <file> --version <n>`.
5. Give the person the canvas link the command printed.

`<design>` is the node uuid or the design's name. `--version` is the version
you last read. When the person changed the design since, the write is refused
with a 409: read it again (`vsb design get`) and apply your change to that.

**The person edits by hand too.** On the canvas they reorder layers inside
auto layout and by dragging rows in the layer tree, move and resize layers
(with snapping), type into text, rename and delete layers, set auto layout
and Fixed / Hug / Fill in a panel, align layers in their frame, resize the
artboard, and undo with Cmd+Z. A hand move is a `translate`, and a resize is
`width` and `height`, in the layer's `style`. Keep what they did: read the
design before you change it, and always pass `--version`.

## Build a page in sections

The canvas shows each write in less than two seconds. Put a page on it one
section at a time, so the person sees it grow and can stop you early.

1. Write the outline: the whole `<style>` block, the root, and one empty
   frame for each section, each with its `data-name`.
2. `vsb design new outline.html`, then give the person the canvas link.
3. Write the sections from top to bottom, one file each. The root element
   of the file carries the `data-name` of its section.
4. `vsb design update Home hero.html --layer Hero --version <n>` for each
   section. Each write adds one to the version.
5. Export the PNG after the last section, and look at it.

```html
<style>
  /* Every class of every section. A layer write replaces only its element,
     so this block stays as the outline wrote it. */
</style>
<div class="page" data-name="Home">
  <nav class="nav" data-name="Nav"></nav>
  <section class="hero" data-name="Hero"></section>
  <section class="features" data-name="Features"></section>
  <footer class="footer" data-name="Footer"></footer>
</div>
```

A card, a screen or a drawing is one write. Over MCP, the order is the same:
`create_design`, then `update_design` with `layer`.

## Commands

| Command | Does |
|---|---|
| `vsb design` | List the designs on this canvas |
| `vsb design new <file>` | Put a design on the canvas. `.html`, `.svg`, a `.js` drawing, or `-` for stdin |
| `vsb design layers [design]` | Print the layer tree, with each layer's index and kind. The picked layer is marked |
| `vsb design get [design]` | Print the HTML. `--layer <path>` prints one layer, `--layer picked` the one the person picked. `-o file` writes it |
| `vsb design update <design> <file>` | Replace the design. `--layer <path>` replaces one layer only |
| `vsb design move <design> --layer <path> --before\|--after\|--into <path>` | Rearrange: move one layer to another place. Nothing else is rewritten |
| `vsb design export [design] -f html\|react\|svg\|png` | Save a file. `--scale 2` for a retina PNG. PNG needs Chrome. `--layer <path>` saves one frame alone as html |
| `vsb design delete <design>` | Delete the design and its node. No undo, so name the design; it never means the picked one |

`[design]` may be left out on the commands that read: it is then the design
the person picked or selected on the canvas.

A layer is found by its index (`12`), its path (`Hero/Body/CTAs`), the end of
its path (`Body/CTAs`), or its name (`CTAs`). Two layers with the same name is
an error that lists both: pass the index or a longer path.

## Layers: every part a person can see

The layer tree is how the person reads your design and how they tell you what
to change, so name it the way a designer names a Figma file.

- **Every part a person can see is its own layer:** each shape, each line,
  each dot, each icon, each piece of text. Three tick marks are three layers,
  not one layer that holds three unnamed `<i>` elements.
- **A shape with words in it is two layers:** the shape, and a text layer
  inside it. Write `<a data-name="Primary"><span data-name="Text">Buy</span></a>`,
  not `<a data-name="Primary">Buy</a>`. The same goes for a key and its
  legend, a pill and its label, a keycap and its letter.
- **Never draw with `::before` or `::after`.** A pseudo-element is not a
  layer, so the person cannot pick it. Draw the part as an element.
- Leave a name off only a wrapper that does layout and draws nothing, and a
  run of words inside one line (an `<em>` in a headline).
- **Nest the names the way the design is built:** `Hero` holds `Nav` and
  `Body`, `Body` holds `Copy` and `IDE`, `Copy` holds `Headline` and `CTAs`.
- **Use short nouns in title case:** `Headline`, `Primary`, `Logos`, `Price`.
  Name by role, not by look: `Primary`, not `Orange button`.
- A landing page has 100 layers or more. That is correct.

**Every write answers with `warnings`:** each part that is drawn but is not a
layer. Name each one and update the design before you report it.

The kind comes from the element: an element with its own words is `text`, an
`<img>` is `image`, an SVG shape is `vector`, an SVG `<g>` is `group`, and
anything else that holds layers is `frame`.

## Auto layout: build every frame with flexbox

The canvas reads flexbox and grid as auto layout, the way Figma does. The
person resizes a button and expects its label to stay in the middle, so lay
the design out with flexbox, not with coordinates.

- **The page is a column, and each row is a row.** The root is
  `display: flex; flex-direction: column`. A nav with centred links is a
  three-column grid: `grid-template-columns: 1fr auto 1fr`.
- **A button centres what is in it:** `display: flex; align-items: center;
  justify-content: center`. The same goes for a badge, a key and a tag.
- **A box with words in it hugs them:** size a button, a badge, a tag or a
  card with `padding`, never a px `width`. The layout panel shows it as Hug,
  and it grows and shrinks with its text. Give a px size only to a part
  whose size is fixed by what it shows: a keycap, an icon, an artboard.
- **A part with a face inside it pads the face.** For a keycap, write
  `display: flex; padding: 3px 26px 40px 11px` on the cap and `flex: 1` on
  the face, not `position: absolute` on the face.
- **Space siblings with `gap`.** Use a margin only where one distance is
  different from the others.
- **Use `position: absolute` only for a part that floats free:** a sticker, a
  tilted illustration, a decoration that overlaps other parts. To nudge a
  part in a row, use `translate`, the same as a hand move on the canvas.

## Write the HTML so it draws the same everywhere

- **Send the body, not a page:** a `<style>` block, then one root element the
  size of the artboard. `vsb` wraps it in the page. A full page also works.
- **Style the root element, not `body`.** The React export has no `<body>`,
  so a `body { }` rule does not reach it.
- **Set `font-family` on the root.** Load web fonts with an `@import` from
  Google Fonts as the first line of the `<style>` block.
- **Use `px` at the artboard size.** Set `box-sizing: border-box` on the root
  and its descendants.
- **Classes in the `<style>` block, not Tailwind.** Nothing runs in a design,
  so Tailwind classes resolve to nothing.
- **No scripts, no forms, no event handlers.** Nothing runs; the export drops them.
- **Images by `https` URL.** Generate a picture with `vsb run` first, then put
  its URL in an `<img>` layer. A logo or an icon can be an inline `<svg>`: name each shape in it.

Common artboards: 1440 × 900 (desktop hero), 1440 × 3200 (desktop page),
390 × 844 (phone screen), 1080 × 1080 (square post), 1080 × 1350 (portrait post).

## Auto layout: Figma's words in CSS

Auto layout is flexbox. The person's layout panel writes exactly this, so
write the same when they ask in Figma's words:

| They say | Write |
|---|---|
| Auto layout, horizontal / vertical | `display: flex; flex-direction: row` / `column` |
| Wrap | `flex-wrap: wrap` |
| Spacing between items | `gap: 16px` |
| Padding | `padding: 24px 32px` |
| Align (the 3 × 3 grid) | `justify-content` along the flow, `align-items` across it |
| Space between | `justify-content: space-between` |
| Hug contents | `width: fit-content`, and `flex: none` along a flex parent's flow |
| Fill container | along the parent's flow `flex: 1 1 0; min-width: 0`; across it `align-self: stretch`; with no auto layout `width: 100%` |
| Fixed width | `width: 240px`, and `flex: none` along the flow |
| Align left / centre / right in the frame | the frame's alignment; or `align-self` across the flow, `margin: auto` along it |
| Absolute position | avoid it: it is a `translate` in code. Change the layout instead |

Build every page from auto layout frames, not from offsets: then the
person's drags reorder the layers, the export reads like hand-written CSS,
and nothing breaks when a text grows. A `translate` on a layer is a hand
move the person made; keep it unless they ask.

## Change one layer

Read the layer, change it, and write back only it. The rest of the document
stays byte for byte, and a big design does not travel twice.

```bash
vsb design get "Landing hero" --layer CTAs -o ctas.html
# edit ctas.html — keep data-name="CTAs" on its root element
vsb design update "Landing hero" ctas.html --layer CTAs --version 4
```

The file replaces the whole layer element, the tag with its `data-name`
included. To add a layer, replace its parent. To delete one, replace it with
an empty file.

## What the person picked

The person picks a frame on the canvas: a double-click on it, or a click on
its row in the layer tree. "Make this bigger", "change this", "move this
under the headline" mean that frame.

```bash
vsb design get --layer picked -o part.html   # the picked frame, from the selected design
# prints: Wrote part.html  layer Hero/Body/CTAs, v7
vsb design update <design> part.html --layer Hero/Body/CTAs --version 7
```

Read with `picked`, then **write to the path the read printed, never to
`picked`**: the person may pick another frame while you work, and a write to
`picked` would land on it. The server refuses `picked` in a write.
`vsb sandbox selection --json` shows the same pick as `layer`.

## Rearrange

Move a layer to another place without rewriting its parent:

```bash
vsb design move Home --layer Hero/Logos --before Hero/CTAs --version 7
vsb design move Home --layer Footer/Links --into Hero/Nav --version 8
```

`--before` and `--after` put it beside another layer; `--into` puts it last
inside one. Only the layer's place in the HTML changes, so in a flex or grid
box the layout reflows around it. A layer cannot move into itself.

## Drawings

A `.js` file is a drawing. Its default export gets a Canvas 2D context, and
`vsb` records every call as SVG, so the result is a vector file that you can
also draw on a `<canvas>`. The file runs on your machine; nothing runs on the
server.

```js
export const width = 1080, height = 1080, name = "Cat plate";
export default function draw(g, { layer }) {
  layer("Background", () => { g.fillStyle = "#e7c9a2"; g.fillRect(0, 0, width, height); });
  layer("Cat", () => { layer("Head", drawHead); layer("Body", drawBody); });
}
```

`layer(name, fn)` wraps what `fn` draws in `<g data-name>`: a layer on the
canvas. Read [references/drawing.md](references/drawing.md) before the first
drawing. It has the calls that work, the ones that are refused, and the craft
that makes a code drawing look drawn by hand.

**Pick the tool by the job.** A page or a screen is HTML: the browser lays it
out. An illustration with many organic shapes is a `.js` drawing: math makes
the geometry. An icon or a logo is a hand-written `<svg>`, or a vector model
(see `vsb-vector`). Never type long SVG path data by hand for organic shapes;
compute it.

## Exports

| Format | What it is |
|---|---|
| `html` | The page exactly as the canvas draws it. Open it in a browser |
| `react` | One component in a `.jsx` file. The same pixels at the same size |
| `svg` | The drawing, when the design is one `<svg>` element |
| `png` | A screenshot through the local Chrome. `--scale 2` doubles the pixels |

`--layer <path>` (or `--layer picked`) saves one frame alone as an html
page: the hero, the nav, one card. The frame keeps the styles, fonts and
CSS variables it gets from the frames above it, and its place in its
parent (margin, translate, offsets) goes. Take a frame this way when the
person wants one part of a design as code.

Over MCP, the tools are `create_design`, `get_design` (`layer: "picked"`
reads the pick), `update_design`, `move_layer`, `export_design` and
`delete_design`;
`export_design` has no `png`, and takes `layer` for one frame.

Referenced files: 2 — file references are metadata, not package contents.

vsb-edit-video3.61 KB

View saved version →

---
name: vsb-edit-video
description: >
  Edit video with the Visual Sandbox MCP tools, on the server, with nothing
  to install: join clips into one video, add a voiceover or music, burn
  word-timed captions, trim and reframe to 9:16, take a frame, place a logo.
  Trigger when the person asks to "put these clips together", "make a reel",
  "add music", "add a voiceover", "add captions or subtitles", "cut it to 15
  seconds", "make it vertical", "add my logo", or to finish a video made of
  several generated shots. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Edit a video

Every edit is its own tool. It returns a job id, like `generate`; show the
result with `show_media`. Each edit is also a tool model that `generate` and
`vsb run` can run.

Use the MCP tools only.

| To | Tool | Model | Price |
|----|------|-------|-------|
| Join clips and pictures, with a cut or a fade | `join_clips` | `tool/video-join` | Free |
| Add a voiceover, music or effects | `add_audio` | `tool/video-add-audio` | Free |
| Burn captions from the speech | `add_captions` | `tool/video-captions` | 5 cents |
| Trim, reframe to another shape, change speed | `trim_video` | `tool/video-trim` | Free |
| Take the first, the last or one frame as a picture | `extract_frame` | `tool/video-frame` | Free |
| Place a logo or a picture on the video | `add_logo` | `tool/video-overlay` | Free |

The free tools allow 200 runs a day. Each input can be up to 15 minutes.
Captions take a video of up to 10 minutes.

## Inputs

- Pass every video, picture and sound as a URL: a `result.urls` entry of an
  earlier job, a URL from `upload_media`, or any public `https` URL.
- A file attached in the chat goes to `upload_media` as `file` first.
- Pass the same `sandbox_name` as the rest of the task, so the edit lands on
  the same canvas.

## Make a reel from generated shots

1. Generate the shots. Give all of them the same shape, for example 9:16.
2. Join them: `join_clips` with `clips` in order. Use
   `transition: "fade"` for a calm piece, `"cut"` for an ad.
3. Make the voiceover with `audio/elevenlabs-tts`, then add it:
   `add_audio` with `video_volume: 0.2`, so the clip sound sits
   under the voice.
4. Add music the same way: `loop_audio: true`, `audio_volume: 0.3`,
   `fade_out_seconds: 1.5`.
5. Add captions last, on the finished mix: `add_captions` with a
   `style`: `highlight` (the default: large capitals, the spoken word in
   yellow), `hormozi` (the spoken word in green, yellow or red), `beast`
   (two or three comic-book words that pop in), `classic` (words appear one
   by one), `box` (sentences on a dark box) or `plain`. In ChatGPT the card
   offers the other looks under the captioned video; a press runs the
   captions again for 5 cents.
6. Call `show_media` with the job id of each step, or of the last step.

Captions read the speech of the video, so run them after the voiceover is
in. Run each step after the job before it is completed.

## Keep shots flowing

The last frame of one shot is the start frame of the next:
`extract_frame` with `position: "last"`, then pass its URL to the next
video model as the start image. Do this for shots that must continue one
movement.

## Fit one video to another platform

`trim_video` with `aspect_ratio: "9:16"` crops a 16:9 video from the
centre. Use `fit: "pad"` to keep the whole frame with bars. Add
`start_seconds` and `end_seconds` to keep only the best part, and `speed` to
fit a length.

## Before you run

- Say what each step does and which job it uses, in one line each.
- Captions cost 5 cents. Say so before the run.
- A failed step costs nothing. Read the error, fix the input, run again.

Referenced files: 1 — file references are metadata, not package contents.

vsb-feedback2.57 KB

View saved version →

---
name: vsb-feedback
description: >
  Send feedback to the Visual Sandbox team with the MCP tool submit_feedback
  or `vsb feedback`: a bug, a result that missed what was asked, a missing
  feature or model, something confusing, or praise. Trigger when the person
  says "tell them", "report this", "send feedback", "this is broken", "this
  should exist", "I love this", or when a tool fails in a way that is not
  the person's input or your own mistake. One report for each issue, never
  when the person asks you not to send one.
license: Proprietary. See LICENSE for the terms.
---

# Send feedback

Feedback goes to the team that builds Visual Sandbox. It is free, and it is
how a defect gets fixed.

## When to send

- **The person asks.** Send what they say, as `source: "user"`.
- **You see a defect yourself.** A tool fails with a server error, a result
  breaks the model's own rules, an error message is wrong, or a documented
  field does not work. Send it as `source: "agent"`, then tell the person in
  one line that you sent it.

Do not send:

- Your own mistakes: a wrong field, a wrong slug, a file that was not there.
- An expected refusal, such as too little credit or a field out of range.
- Anything when the person asks you not to.
- A refund request. Feedback starts no refund and no new run. Send the person
  to hello@visualsandbox.com for a refund.

## How to send

Over MCP:

```json
{
  "category": "generation_quality",
  "source": "agent",
  "message": "Asked for a 9:16 clip of a red mug; got 16:9 with a blue mug. The prompt named the color twice.",
  "attempted_task": "A product video for a mug",
  "tool_name": "video_p_video",
  "job_id": "8f3…"
}
```

With the CLI: `vsb feedback "<message>" --kind generation_quality --json`.

| `category` | Use it for |
|------------|------------|
| `bug` | An error, a crash, a tool that does not do what its description says |
| `generation_quality` | A result that missed the prompt, the reference or the model's own promise |
| `missing_feature` | A model, a field or a tool that the person needed and is not there |
| `confusing` | A message, a price or a step that the person or you could not understand |
| `praise` | Something that worked well |
| `other` | Anything else |

## Write the message

- What was asked, what came back, and what was expected. Two or three
  sentences.
- The exact error text and the job id, when there is one.
- No passwords, payment details, private files or the person's own words that
  they did not want shared.

Send one report for each issue. A second failure of the same kind in the same
task is not a new report.

Referenced files: 1 — file references are metadata, not package contents.

vsb-html-video4.95 KB

View saved version →

---
name: vsb-html-video
description: >
  Write HTML and render it into a video or a picture with the Visual Sandbox
  MCP tools render_html_video and render_html_image: motion graphics, kinetic
  type, an animated explainer, a title card, lower thirds, a logo sting, a
  social card, a quote card or a thumbnail with exact text. Trigger when the
  person asks for "motion graphics", "kinetic typography", "an animated
  explainer", "a title card", "an intro", "a lower third", "a logo
  animation", "a social card with this text" or a design that must move.
  Covers the composition contract, the render clock, GSAP timelines, sound,
  the limits, and how to check a render. Free. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# HTML to video

You write one HTML document. The renderer plays it in headless Chromium on a
clock that it moves frame by frame, so every frame is drawn at an exact time
and the same HTML always gives the same video. Both tools are free.

Use this pack only when the person asks for designed motion or exact text.
For filmed footage, a person or a real place, use a video model instead
([`vsb-video`](../vsb-video/SKILL.md)).

## The two tools

| Tool | Gives | Inputs |
|------|-------|--------|
| `render_html_video` | An MP4 | `html`, `width`, `height` (even, 16 to 1920), `duration` (up to 60 s), `fps` (24, 30 or 60) |
| `render_html_image` | A PNG | `html`, `width`, `height`, `time` (the moment to draw), `transparent` |

Each returns a job id. Call `show_media` with it, or `get_job` when it is
done. The result's `metrics.warnings` lists blocked requests and script
errors. Read them before you report the result.

## The composition contract

1. Set a fixed pixel size on `html` and `body`, the same as `width` and
   `height`. Use `overflow: hidden`.
2. Make every frame a function of the time alone. The renderer replaces
   `Date`, `performance.now`, `requestAnimationFrame`, `setTimeout` and
   `setInterval` with its own clock, and seeds `Math.random`. Do not read
   the real time in any other way.
3. Animate with one of these. All of them follow the render clock:
   - CSS animations and transitions.
   - The Web Animations API (`element.animate`).
   - A GSAP timeline, created paused and registered after the setup:
     `window.__timelines = { main: tl }`. The renderer seeks it to each
     frame's time.
   - `requestAnimationFrame`, with the time from its argument.
4. Never use an infinite loop that does not depend on the time, such as a
   `while` loop that waits for a load.
5. Load fonts, pictures, footage and libraries from public `https` URLs.
   A private, local or link-local address is blocked, and the block shows in
   the warnings.
6. Put footage in `<video muted playsinline src="…" data-start="2">`. The
   renderer seeks it to the frame's time minus `data-start`.
7. Add sound with `<audio src="…" data-start="0" data-duration="8"
   data-volume="0.8">`. Up to 8 tracks of up to 50 MB each. The renderer mixes
   them after the frames. A narration from `audio_elevenlabs_tts` works here.

## A skeleton

```html
<!doctype html>
<html><head><meta charset="utf-8">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@800&display=swap" rel="stylesheet">
<style>
  html, body { margin: 0; width: 1080px; height: 1920px; overflow: hidden; background: #0B0B0F; }
  h1 { font: 800 140px/1 Inter, sans-serif; color: #fff; position: absolute; top: 760px; left: 80px; }
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
</head><body>
<h1 id="title">Launch day</h1>
<audio src="https://cdn.visualsandbox.com/…/voice.mp3" data-start="0.5"></audio>
<script>
  const tl = gsap.timeline({ paused: true });
  tl.from("#title", { y: 120, opacity: 0, duration: 0.8, ease: "power3.out" })
    .to("#title", { scale: 1.06, duration: 3 }, 0.8);
  window.__timelines = { main: tl };
</script>
</body></html>
```

## How to work

1. Agree the size, the length and the words with the person. Keep one idea
   for each scene, and 2 to 4 seconds for each scene.
2. Read the brand with `get_theme` when the work is for a brand. Use its
   colors and fonts in the CSS, and its logo URL in an `<img>`.
3. Render one still first with `render_html_image` at the busiest moment
   (`time`). It is fast. Fix the layout before the video.
4. Render the video. 30 fps for graphics, 24 for a film look.
5. Look at the result before you report it: `get_job` returns four frames of
   the video.

## Limits

- Up to 60 seconds, 16 to 1920 pixels on each side, 2 MB of HTML.
- Up to 50 video renders and 200 picture renders a day for each account.
- A render stops after 8 minutes. Long footage or many heavy effects take
  longer to render.

## Rules

- Write the text the person gave, word for word. Check the spelling.
- Never put a password, a key or private data in the HTML. The renderer has
  no secrets, but the HTML is kept with the job.
- A failed render costs nothing. Read the error and the warnings, fix the
  HTML, and render again.

Referenced files: 1 — file references are metadata, not package contents.

vsb-image23.5 KB

View saved version →

---
name: vsb-image
renamed_from: image
description: >
  Pick the right Visual Sandbox model for any image task and prompt it well.
  Trigger when the user wants to generate, edit, restyle, upscale, or remove
  the background from images via the vsb CLI. All execution goes through
  `vsb run image/<slug>` — sync, ~5–10s for the standard models. Always
  verify slugs with `vsb models --modality image --json` first.
license: Proprietary. See LICENSE for the terms.
---

# Image generation with vsb

> **On the MCP surface?** If the `visual-sandbox` MCP tools are in your
> tool list, use `generate` and `get_job` instead of the shell commands
> below. The model choice and the prompt craft on this page still apply.
> See `vsb` → Two ways to run.

Image runs are sync — `vsb run image/<slug>` blocks until done (typically
5–10s). No `--async` needed, but **still start the run in a background shell**
(Claude Code: `run_in_background: true`) so the turn stays free and parallel
variations fan out (vsb critical rule 6).

Pipe `--json` and `--download "<template>"` to capture results, then
**open every finished image in Preview** — `open -a Preview <file>` — so the
user sees the picture instead of a link
(vsb critical rule 12). Download
to a scratch folder (`mktemp -d`) for that; a **project** save still needs the
opt-in from vsb critical rule 5.
Hand back the share page `https://visualsandbox.com/share/<job_id>/`, never a
raw CDN URL.

> **Before writing any image prompt, read
> [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md).** It covers the universal
> rules, the prompt-anatomy slot list, the reference-image keep/ignore
> pattern (what to take from an attached image and what to ignore — without
> this, watermarks and text overlays leak into outputs), and the camera /
> lens / lighting / grade vocabulary banks. This skill only covers
> *picking* a model — the prompt-craft itself lives in `image-prompting`,
> with model-specific quirks in [`vsb-nano-banana`](../vsb-nano-banana/SKILL.md) and
> [`vsb-ugc-people`](../vsb-ugc-people/SKILL.md).

## Rules for every paid image run

- **Keep a simple request simple.** "Make an image of X" is one image: no
  questionnaire, no extra drafts, no variations nobody asked for.
- **One request is one paid attempt.** A complaint or "fix it" is not a yes
  for a new run. Say what you would change, and ask first.
- **Review mode (MCP).** A run of 10 cents or more can answer
  `awaiting_approval`. In ChatGPT, call `show_media`; elsewhere, give the
  `share_url`. Never press Generate for the person.
- **Branded work** reads the brand first with `get_theme`
  ([`vsb-library-themes`](../vsb-library-themes/SKILL.md)).

## Picking a model

Verify the live catalog with `vsb models --modality image --json | jq '.models[] | {slug, category, owner}'`.

| Task | Default slug | Why |
|------|--------------|-----|
| Fast text-to-image / edit | `image/nano-banana-2-lite` | Google. Default. Cheapest (~$0.05/image), fastest, ~5s, on the newer NB2 architecture. Handles both text-to-image AND image edits — the same endpoint switches modes when a reference image array is non-empty. |
| Same cost, original model | `image/nano-banana` | The original Gemini 3 model at the same ~$0.05/image. Fall back here if a lite result looks off. |
| Higher quality / 4K / web-grounded | `image/nano-banana-2.1` | Google. The update to NB2: sharper output, cleaner text, steadier subjects across edits. **Pick it over `nano-banana-2`:** it costs about half as much at 1K and 2K (~$0.05 at 1K, ~$0.07 at 2K, ~$0.15 at 4K), and each of `google_search` / `image_search` adds a small charge, as on NB2. `resolution` `1K`/`2K`/`4K`, ten aspect ratios plus `match_input_image` (the default). References go in `image_input`: up to 14, but the backup route reads only the first three. |
| Existing NB2 work | `image/nano-banana-2` | Full NB2 — adds `resolution` (up to 4K) and web-search grounding as a paid add-on. Superseded by `nano-banana-2.1`; keep it only for a project already built on its look. |
| Top-tier Google output | `image/nano-banana-pro` | Most expensive of the family, sharpest detail. |
| Fast OpenAI generate or edit, up to 4K | `image/gpt-image-2.5-flare` | OpenAI. The fast tier of GPT Image 2.5 and the default OpenAI pick. One endpoint for text-to-image and edit. ~7–12s at 1K, ~12–24s at 4K, where `gpt-image-2` averages ~50s, and about half the price at high quality. `quality` is `low`/`medium`/`high`/`xhigh`/`max`; `resolution` is a separate control at `1K`/`2K`/`4K`; eight aspect ratios plus `match_input_image`. `background: transparent` returns a real alpha channel (the schema switches JPG to PNG for you). References go in `image_input`. |
| Precise edits that must hold across many passes | `image/gpt-image-2.5-sunburst` | OpenAI. The precision tier of GPT Image 2.5, slower than Flare. It changes only what you name and keeps the face, the composition and the light, also across multi-image composites and long chains of edits. One flat price per image for each `quality` tier (~$0.02 `low`, ~$0.06 `medium`, ~$0.16 `high`, ~$0.32 `xhigh`, ~$0.63 `max`); the ratio and the references do not move it. Seven aspect ratios plus `match_input_image`, `background: transparent`, JPG/PNG/WEBP. **No `resolution` field, so use Flare for 4K.** No seed. References go in `image_input`. Explore in Flare, finish here. |
| Photoreal / text rendering / multi-ref edit | `image/gpt-image-2` | OpenAI, the older tier. Best for legible typography, posters, product mockups. Slower and pricier than `gpt-image-2.5-flare`; reach for it only when a Flare result looks off. |
| Cheapest text-to-image | `image/z-image-turbo` | Tongyi-MAI via PrunaAI. Cheapest image model in the catalog, 8-step, sub-second. Renders short English and Chinese text well. **Takes no reference image, so it cannot edit.** Apache 2.0 license. |
| Fast iteration that still takes references | `image/flux-2-klein-9b` | Black Forest Labs. 4-step, sub-second, one endpoint for text-to-image and edit. Up to 5 references, output up to 4 MP. **FLUX Non-Commercial License — never use it for client work or paid ad creative.** |
| Cheap edits with up to 10 references, native 4K | `image/flux-3-image-klein` | Black Forest Labs. The compact FLUX 3 model (5.6B), newer than Klein 9B and scored above it by BFL. One endpoint for text-to-image and edit; up to 10 references in `image_input`, called "image 1", "image 2" in the prompt. `resolution` is `1K`/`1.5K`/`2K`/`4K`, 15 aspect ratios plus `match_input_image`, optional `seed`. ~$0.02/image up to 2K, ~$0.05 at 4K, and references are free. Weak on exact counts, charts and diagrams. **FLUX Kommunity License: free commercial use only for a user with annual revenue under $5M. Ask before you use it for client work.** |
| Many references, product + character compositing | `image/seedream-5-pro` | ByteDance. Takes up to 10 references and holds detail at 1K or 2K. The edit-capable model with the highest reference count. Commercial use allowed. |
| Posters, packaging, real brands and places, flat price at 2K | `image/grok-imagine-image-2` | xAI. Plans typography and layout before drawing, and names real brands, cities, and landmarks straight from the prompt. **One flat price per output image, so 2K at medium quality costs the same as a 1K draft.** Takes one reference image (a single string, not an array), so it cannot composite several. Slow: ~53s at medium/2K, ~15s at low/1K. Commercial use allowed. |
| Change one region, keep every other pixel | `image/flux-fill-pro` | The only mask model in the catalog. It repaints the masked area and carries the rest of the photo through unchanged. It also outpaints. |
| Budget volume, 3K, or a related set in one run | `image/seedream-5-lite` | ByteDance. The light tier of Seedream 5, on the same endpoint. One flat price per output image at 2K or 3K, up to 14 references, and a sequential mode that returns a whole set (storyboard, character sheet, brand kit) from one prompt, billed per image returned. Step up to `image/seedream-5-pro` when a single frame is the deliverable. |
| Illustration, anime, painterly, one style across a series | `image/krea-2-medium` | Krea. Foundation model tuned for expressive artistic directions, with style transfer from up to 10 reference images and a `creativity` control (`raw`/`low`/`medium`/`high`) that sets how far it departs from the prompt. Faster and steadier than Large across a batch. **Style transfer only, so it cannot edit a photo.** Commercial use allowed. |
| Photorealism with real texture, grain and motion blur | `image/krea-2-large` | Krea. Twice the size of Medium, with the richest output at `creativity: high`. Same 10 style references, same 8 aspect ratios including 2.35:1, same optional `moodboard_id`. Slower and pricier than Medium, so compose in Medium and finish here. **Style transfer only, so it cannot edit a photo.** Commercial use allowed. |
| Instruction edits: a product into a new scene, a color or outfit swap, a local fix | `image/krea-3` | Krea. Fast instruction-following editor, the edit model of the Krea family. Up to 8 images in `image_input`. `thinking_mode` `fast` (default) or `high` for multi-step edits; `resolution` `1K`/`2K`. `regional_edit: true` (experimental, needs an image) changes only the part that must change and keeps every other pixel. ~$0.04–0.10 per output image by mode and resolution, **plus ~$0.03 per input megapixel**, so send inputs of about 1 MP. Text-to-image is experimental and small text is weak. No style references, no `output_format` field. Commercial use allowed. |
| Art-directed poster, ad or brand image from a short prompt | `image/recraft-v4.1` | Recraft. Tuned with designers: it picks the layout, the palette and the light, and sets quoted text into the composition. ~$0.05/image flat, about 1 MP, 11 aspect ratios, prompts up to 10,000 characters. **Text-to-image only: no reference image, so it cannot edit.** Commercial use allowed; outputs may not train AI models. |
| Cheap, fast drafts with a designed look | `image/recraft-v4.1-flash` | Recraft. Its fastest, cheapest model, with quality close to V4.1. ~$0.01/image flat (`image/z-image-turbo` is still cheaper), about 1 MP, the same 11 aspect ratios. No styles. **Text-to-image only, so it cannot edit.** Render the final with `image/recraft-v4.1`. |
| Put real clothing on a real person | `image/p-image-try-on` | Pruna AI. Person photo plus up to 6 garment photos; face, pose, body and background carry through untouched, and a whole outfit layers in one pass. `garment_images` is a list even for one item. Shows the look, never the fit. Footwear is the weakest category; eyewear, handheld items, gloves and stacked jewelry are unsupported. Billed per run PLUS per input megapixel, so resize inputs before sending. |
| Background removal | `image-enhance/recraft-remove-background` | Note: category is `image-enhance`, not `image`. |
| Upscale, portraits and print | `image-enhance/upscale` | Topaz. Same — `image-enhance` category. Tuned for photographic realism, with five named enhance models and a face refinement step. One flat rate per image whatever the size. |
| Cheap, fast upscale at volume | `image-enhance/p-image-upscale` | Pruna AI. About a second a run. `upscale_mode: target` names the output in megapixels; `factor` multiplies each side (2/4/6/8). `enhance_details` and `enhance_realism` both default off; Realism is for AI-generated sources. Charged by the size of the result, so it is cheapest on small outputs. Pick `image-enhance/upscale` (Topaz) for portraits and print. |
| SVG, vector, logo, icon, cut file | see [`vsb-vector`](../vsb-vector/SKILL.md) | Not an image model job. Quiver for icons; `vector/recraft-v4.1-svg` for a new SVG from a prompt at a flat price; a flat PNG from this page traced by `vector/recraft-vectorize` for detailed art. |

**Tier up for detail-critical work.** The cheap defaults are for simple,
throwaway, or high-volume generations. When the task is detail-critical —
legible in-image text (signs, tattoos, posters, packaging), dense scenes with
many named elements, ad creative the user will publish, or precise multi-ref
identity edits — go straight to `image/nano-banana-pro` or
`image/gpt-image-2.5-flare` (Flare at `xhigh` or `max` when typography
legibility is the single hardest constraint; nano-banana-pro for photoreal
scenes that *contain* text or fine detail).
The ~$0.15–0.20/image premium is cheaper than three failed $0.05 retries.

## Prompt patterns

### Nano Banana family (text-to-image)

```bash
vsb run image/nano-banana \
  --prompt "a calico cat curled on a sunlit windowsill, soft morning light, shallow depth of field" \
  --aspect_ratio 16:9 \
  --output_format jpg \
  --download "./out/{request_id}.{ext}" \
  --json
```

Nano Banana likes concrete nouns + lighting + camera language. Aspect ratios
(`1:1`, `16:9`, `3:2`, `4:3`, `5:4`, `4:5`, `3:4`, `2:3`, `9:16`, `21:9`) are
strict enums — use `vsb schema image/nano-banana --json` to confirm before
passing.

### Nano Banana (image edit — multi-ref)

```bash
URL1=$(vsb upload ./photo.jpg --json | jq -r '.url')
URL2=$(vsb upload ./reference.jpg --json | jq -r '.url')

vsb run image/nano-banana \
  --prompt "Use Image A (cat photo) as the IDENTITY source — keep the cat's fur pattern, eye color, and proportions exactly. Use Image B (windowsill photo) as the BACKGROUND source — match the location and ambient light, but ignore any other subjects in it. Render: the cat from Image A curled up on the windowsill from Image B, matching the windowsill's ambient lighting. No logos, no captions, no watermarks." \
  --image_input "[\"$URL1\",\"$URL2\"]" \
  --aspect_ratio match_input_image \
  --download "./out/" \
  --json
```

Naming each reference (`Image A`, `Image B`) and giving it a role
(`IDENTITY source`, `BACKGROUND source`) is the universal multi-ref rule —
see [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) §Multi-reference for
the full template.

Pass JSON-array inputs as a literal — `parseValue` auto-coerces. The exact
field name (e.g. `image_input`) is whatever `vsb schema image/nano-banana
--json` returns; never guess. `vsb run` pre-validates flags against the
schema and rejects unknown keys with a "Did you mean…?" hint before any
charge is incurred.

`aspect_ratio: "match_input_image"` keeps the dimensions of the first
reference.

You can pass server-side paths directly too — any `/media/predictions/...`
URL from a prior `vsb run` works without re-uploading.

### GPT Image 2 (typography, posters, mockups)

```bash
vsb run image/gpt-image-2 \
  --prompt "minimal poster: bold serif headline 'NORTHWIND' on a kraft paper background, registration marks in the corners" \
  --quality high \
  --aspect_ratio 3:4 \
  --output_format png \
  --download "./out/{request_id}.png" \
  --json
```

GPT Image 2 specifically excels when you need **legible, multi-line text** baked
into the image. Quality enum is `auto|low|medium|high`. `n` (1–4) gives
multiple variants in one call.

### FLUX Fill Pro (masked edit and outpaint)

The only model that edits through a mask. Everything outside the mask survives
the run byte for byte, so use it for object removal, a product swap, new text
on a sign, or a retouch. Fields are `image` (a single string, not an array),
`mask`, and `outpaint`.

```bash
SRC=$(vsb upload ./kitchen.jpg --json | jq -r '.url')
MASK=$(vsb upload ./kitchen-mask.png --json | jq -r '.url')

vsb run image/flux-fill-pro \
  --prompt "a white marble countertop, same warm window light, same grain" \
  --image "$SRC" \
  --mask "$MASK" \
  --guidance 30 \
  --download "./out/{request_id}.png" \
  --json
```

Four rules decide the result:

- The mask is black and white, the same size as the source. White repaints,
  black keeps. The mask may instead ride in the source image's alpha channel.
- Describe what must be there, not the edit. Write "a blue knit sweater". Do
  not write "change the shirt". An empty prompt makes the model paint
  something rather than nothing.
- Mask a little wider than the object, and include its shadow and its
  reflection. The model invents a new object to explain a shadow you leave
  behind.
- High `guidance` leaves a visible seam. Lower it to about 30 for retouching
  and object removal, so the fill settles into the surrounding light and grain.

To outpaint instead, set `--outpaint` to `zoom_out_1.5x`, `zoom_out_2x`,
`make_square`, `extend_left`, `extend_right`, `extend_up`, or `extend_down`.
An `outpaint` preset makes the model ignore the mask. Output size follows the
input, so there is no aspect-ratio field and outpainting is the one case that
changes the dimensions.

### Krea 3 (instruction edit and regional edit)

```bash
SRC=$(vsb upload ./living-room.jpg --json | jq -r '.url')

vsb run image/krea-3 \
  --prompt "Change the sofa to deep green velvet. Keep everything else the same." \
  --image_input "[\"$SRC\"]" \
  --regional_edit true \
  --resolution 2K \
  --json
```

Lead with the change, then add one sentence about what stays. Do not list
everything that should stay. With several images, name each one by what it
shows: "put the sneaker from the second image on the person in the first".

`regional_edit` keeps every pixel outside the change, so it is the one way to
edit the latest output again without a loss in quality. Use it for a local
change: recolor or swap an object, add or remove an element, change the words
on a sign. Leave it off for a restyle, a relight or a new scene. It is
experimental: slower than a regular edit, and the quality can be lower.

Each input image adds a charge by its megapixels. About 1 MP per input is
enough. With five or more inputs, each one is reduced to 1536 px on its long
edge, so send four or fewer when every reference needs its full detail. The
`seed` does not reproduce a result, because the model expands the prompt
first.

### Seedream 5 Pro (up to 10 references)

```bash
vsb run image/seedream-5-pro \
  --prompt "the bottle from Image A on the marble surface from Image B, studio softbox light" \
  --image_input "[\"$URL1\",\"$URL2\"]" \
  --resolution 2K \
  --aspect_ratio 1:1 \
  --json
```

One endpoint covers generate and edit, the same as Nano Banana. Iterate at
`--resolution 1K`, then rerun the winning prompt at `2K` for print and crops.

### Grok Imagine 2.0 (layout and typography, flat price at any size)

```bash
vsb run image/grok-imagine-image-2 \
  --prompt "a vintage travel poster for Kyoto, Mount Fuji behind cherry blossoms, art deco typography reading 'KYOTO', rich flat color blocks" \
  --aspect_ratio 2:3 \
  --resolution 2K \
  --quality medium \
  --json
```

To edit, pass one reference through `--image`. The field is a **single
string**, not an array, so this model cannot composite several references.

```bash
URL=$(vsb upload ./storefront.jpg --json | jq -r '.url')
vsb run image/grok-imagine-image-2 \
  --prompt "make the sky a dramatic sunset" \
  --image "$URL" \
  --json
```

Four rules decide the result:

- **Always deliver at `--resolution 2K --quality medium`.** The price is flat
  per output image, so 2K costs the same as 1K. `low` at `1K` is only worth it
  as a speed setting: about 15 seconds against about 53 seconds.
- **Waxy, over-smoothed skin means the quality knob is wrong.** Pin medium and
  2K, then name the camera, the lens, the light direction, and the
  imperfections to keep (skin texture, stray hair, film grain).
- **Write long, specific prompts and put the style directive last.** Short
  prompts get generic results here. Name real brands, cities, and landmarks
  rather than describing them; the model knows them.
- **On an edit, describe only the change.** Re-describing the whole frame makes
  the model rewrite more than you asked. `aspect_ratio` is ignored on an edit,
  because the output keeps the shape of the source.

`aspect_ratio` also accepts `auto`, which lets the model pick the frame that
suits the prompt. `2:1` and `1:2` are in the enum and are rare elsewhere in the
catalog, so this is the model for site headers and tall mobile banners.

### Z-Image Turbo (cheapest, text-to-image only)

```bash
vsb run image/z-image-turbo \
  --prompt "a neon ramen shop sign that reads 'MIDNIGHT', wet asphalt reflection, night" \
  --aspect_ratio 3:2 \
  --output_megapixels 2 \
  --json
```

It has no reference-image field at all, so it cannot edit. Short English and
Chinese text is its strength. Quote the exact text and name the surface it
sits on. Long paragraphs break down.

### Background removal

```bash
URL=$(vsb upload ./product-photo.jpg --json | jq -r '.url')
vsb run image-enhance/recraft-remove-background \
  --image "$URL" \
  --download "./out/cutout.png" \
  --json
```

Note the category is `image-enhance`, not `image`, and `recraft-remove-background`
+ `upscale` both take a single string field (currently `image`) — different from
Nano Banana's array. Always run `vsb schema image-enhance/<slug> --json` first
to confirm field names; the CLI rejects unknown flags with a "Did you mean…?"
hint before submitting.

## Cost estimation

```bash
vsb pricing image/nano-banana --json | jq '.user_cost_estimate'
# 0.04875 → roughly 5 cents per image
```

Nano Banana family is cents-per-image. GPT Image 2 is ~$0.05–0.20 depending on
quality. Background removal and upscale are sub-cent.

`z-image-turbo`, `flux-2-klein-9b`, and `nano-banana-2` bill per output
megapixel or per resolution tier, so `user_cost_estimate` comes back `null`
and the tier table holds the numbers. Read it before you pick a size:

```bash
vsb pricing image/z-image-turbo --json | jq '.tiers'
```

## Common gotchas

- **A second edit on the same image degrades it.** Every model redraws the
  whole picture on each pass, so loss compounds. Never feed your own last
  output back in as `image_input` — grow the prompt and re-run from the
  original instead. Full workflows in
  [`vsb-image-iteration`](../vsb-image-iteration/SKILL.md). Two exceptions
  keep every pixel outside the change: `flux-fill-pro` with a mask, and
  `krea-3` with `regional_edit: true`.
- **`output_format` defaults to `jpg` on every image model except
  `flux-fill-pro`,** which defaults to `png` because a fill is usually the
  input to the next fill. `krea-3` has no `output_format` field. Pass
  `--output_format png` whenever the output may become an input.

- **Input field names vary by model** — Nano Banana uses an array (currently `image_input`), most enhance models take a single string (currently `image`), GPT Image 2 has its own shape. `vsb run` pre-validates against the schema, so just `vsb schema <slug>` once and copy the exact field name.
- **`aspect_ratio: "match_input_image"` only makes sense in edit mode** (when reference images are set). For text-to-image alone it falls back to a default.
- **`output_format` defaults to `jpg`.** If you want transparent PNG (e.g. for compositing), set `--output_format png` and remember the alpha channel is only meaningful for edit/cutout flows.
- **`z-image-turbo`, `recraft-v4.1` and `recraft-v4.1-flash` cannot edit.**
  They have no reference-image field. If the task attaches any image, pick
  another model before you write the prompt.
- **`flux-2-klein-9b` is non-commercial.** Never pick it for client work,
  paid ad creative, or anything the user will sell. Use Nano Banana,
  GPT Image 2, or Seedream 5 Pro instead.
- **`flux-fill-pro` takes `image` as a single string, plus a `mask`.** It is
  the only model with a mask. There is no brush tool in the composer yet, so
  an explicit mask arrives as a file through the CLI.
- **`grok-imagine-image-2` takes `image` as a single string too,** so it edits
  from one reference and cannot composite. Pick `seedream-5-pro` or
  `nano-banana-2-lite` when the job needs several references.
- **Don't loop `vsb run` inside a tight shell loop** — use a small batch (≤5 in parallel via `&`) to respect the provider's rate limit.

Referenced files: 1 — file references are metadata, not package contents.

vsb-image-iteration16.9 KB

View saved version →

---
name: vsb-image-iteration
description: >
  Change an image more than once without it falling apart. Read this before the
  SECOND `vsb run` on the same picture — before chaining a sandbox node's
  `output_url` into the next run's `image_input`, before "now make the shirt
  blue too", and before any loop that feeds an output back in as an input.
  Covers generation loss (why every image model degrades on repeated edits), the
  four workflows that prevent it (prompt stacking, contact sheet, crop and
  composite, noise repair), and the `output_format` / `resolution` settings that
  decide the outcome. For writing the prompt itself, read
  [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) first.
license: Proprietary. See LICENSE for the terms.
---

# Iterating on an image without degrading it

## The one thing to know

**No image model edits an image. It reads the image and draws a new one that
copies it.** Every "edit" is a full redraw from scratch.

So each pass adds error on top of the last pass. After two edits you see it.
After ten, the parts you never touched are unusable. Users call it
*generation loss*, or "a photocopy of a photocopy".

This is not a Nano Banana bug. It happens on `image/nano-banana*`,
`image/gpt-image-2`, `image/seedream-5-pro`, and `image/flux-2-*`. It happens
through the API, not only in a chat interface. It cannot be prompted away.

**What it looks like:** grain and noise creep in, edges go soft, colors drift,
JPEG blocks and banding appear, faces slowly stop being the same face.

> **The trap in this CLI.** `vsb` critical rule 10
> tells you to pass a selected node's `output_url` into the next run's
> `image_input`. That is correct for the FIRST edit. Do it twice in a row and
> you have built the photocopy loop. Read the decision table below before the
> second one.

## Pick a workflow

| You want | Use | Quality cost |
|----------|-----|--------------|
| Several changes to one picture | **A — prompt stacking** | None. Always one pass. |
| Many frames of the same subject / scene | **B — contact sheet** | None. One pass for all frames. |
| One small change, rest must stay untouched | **C — crop and composite** | None outside the crop. |
| An image that is already damaged | **D — noise repair** | Recovers some. Not all. |

Workflows A and C are the only two that actually solve the problem. B avoids it
by never chaining. D is a rescue for work you cannot redo.

---

## Workflow A — prompt stacking (the default)

**Never edit the edit. Grow the prompt and re-run from the original.**

Keep two things in a scratch file: the original image (or the original
text-to-image prompt) and a master prompt. For each new change, append one
sentence to the master prompt, then run again from the original source.

```bash
# Turn 1 — establish the original. Note the job_id it returns.
vsb run image/nano-banana-2 --json --no-sandbox \
  --output_format png --resolution 2K \
  --prompt "A man in a grey suit stands in a bright office."

# Turn 2 — the user wants a green tie. Re-run the SAME inputs with one
# sentence added. No reference image, so the picture is drawn fresh.
vsb run image/nano-banana-2 --json \
  --from-job <job_id_from_turn_1> \
  --prompt-add "He wears a dark green tie."

# Turn 3 — and a red door behind him. Stack again, from turn 2's job.
vsb run image/nano-banana-2 --json \
  --from-job <job_id_from_turn_2> \
  --prompt-add "A red door stands behind him."
```

`--from-job` copies the past run's inputs and `--prompt-add` appends to its
prompt. Explicit flags override anything inherited. Nothing is ever fed back in
as `image_input`, so there is no chain and no decay.

Doing it by hand works the same way: keep the master prompt in a file, append a
line per change, and pass the whole file each time.

When the original is a photo the user supplied, the same rule applies — the
`image_input` always points at **their** file, never at your last output.

**Cost:** a full generation per change, same as an edit. Nano Banana 2 Lite is
about $0.05. You pay the same either way, so there is no reason to chain.

**Trade-off:** the composition moves between runs, because each run is a new
draw. When the user needs the exact same frame and only one element changed,
use workflow C instead.

> ⚠️ This corrects rule 4 in [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)
> ("Iterate, don't one-shot. One change per turn."). One change per turn is
> right. Feeding the previous output back in is not. Iterate on the **prompt**,
> not on the **pixels**.

## Workflow B — contact sheet (one pass, many frames)

When the user needs the same character or product in several shots, ask for a
grid in a single generation. One pass means one draw, so every frame shares
exactly the same face, wardrobe, light, and grade. No chaining, no drift.

```bash
vsb run image/nano-banana-pro --json --output_format png --resolution 4K \
  --aspect_ratio 3:2 \
  --prompt "One 2x3 contact sheet image, 6 frames.
All wardrobe, styling, hair, makeup, lighting, environment, and color grade
must remain 100% unchanged across all frames.
Each frame shows a resting point after a camera move. Describe only the final
camera position and what the subject does, never the motion.
Frame 1: eye-level medium shot, three-quarter turn.
Frame 2: high-angle three-quarter frame, diagonal downward, reveals wardrobe from above.
Frame 3: low-angle oblique full-body frame, elongates the silhouette.
Frame 4: side-on compression frame, long lens.
Frame 5: over-the-shoulder frame, subject in the near third.
Frame 6: extreme detail frame of one wardrobe detail, from an unusual angle.
Keep exact garment type, silhouette, material, color, texture, stitching,
accessories, closures, jewelry, shoes, hair, and makeup in every frame."
```

Then split the grid. Two ways, both fine:

- **Local crop** (free, exact) — splits a 2x3 sheet into six tiles:
  ```bash
  for r in 0 1; do for c in 0 1 2; do
    ffmpeg -y -i sheet.png -vf "crop=iw/3:ih/2:iw/3*$c:ih/2*$r,format=rgb24" \
      -frames:v 1 -update 1 "frame_${r}${c}.png"
  done; done
  ```
- **Model extract** (costs a run, upscales at the same time):
  `"Isolate and amplify the key frame in row 1 column 1. Keep all details of
  the image in this keyframe exactly the same, do not change the pose or any
  details of the model."`

Use the local crop by default. The model extract is a second pass, so it is a
second chance to degrade.

**Best fit:** keyframes for image-to-video, character sheets, product angles,
storyboards, ad variations.

## Workflow C — `--mask` (surgical)

**Change one region. Keep every other pixel bit-identical.**

This is what professional retouchers do. The model only ever sees the crop, so
loss cannot spread into the rest of the picture.

1. **Crop the region**, with padding around it. Padding matters — a tight crop
   produces an edit that does not know what it sits next to, and the seam shows.
   Add 15–20 px.
2. **Run the model on the crop only.**
3. **Paste the result back** onto the original with a feathered mask (2–5 px).

```bash
vsb run image/nano-banana-2 --json \
  --image_input photo.png \
  --mask "375,415,360,325" \
  --prompt "Change only the ceramic mug to a deep cobalt blue glazed ceramic.
Keep everything else in the image exactly the same, preserving the original
style, lighting, and composition."
```

`--mask` takes **a rectangle** (`"x,y,w,h"` in pixels, or percentages like
`"40%,30%,25%,25%"`) **or a mask image**, where white marks the part to change.
That is the polarity every mask-taking model uses.

**The CLI picks the route from the model's schema. You do not.**

| The model | What happens | What you get back |
|---|---|---|
| Has a mask input | The mask goes straight to the model | The model's own output. It saw the whole picture for context and painted only inside the mask. |
| Has none | The area is cropped, run alone, and pasted back | A composited picture. Pixels outside the crop were never sent, so they are bit-identical. |

`image/flux-fill-pro` is the first kind. Everything else is the second — all
four Nano Banana variants, `gpt-image-2`, `seedream-5-pro`, `flux-2-klein-9b` —
so the crop route is still what most edits take. **No Gemini image model takes
a mask on any provider**: not Replicate, not fal.ai, not Google's
own API. Google offers only *semantic masking*, which is the prompt wording in
the template below, not a parameter. Check any model with:

```bash
vsb schema image/<slug> --json | jq '.inputs | keys'
```

The crop route reports what it did. The number that matters is
`pixels_changed_outside_region`, which must be `0`:

```json
"mask": {
  "mode": "crop",
  "out_path": "photo-edited.png",
  "region":     { "x": 375, "y": 415, "w": 360, "h": 325 },
  "padded_box": { "x": 355, "y": 395, "w": 400, "h": 365 },
  "feather_px": 20,
  "pixels_changed_outside_region": 0
}
```

**A painted mask on a model without a mask input collapses to a rectangle.**
The crop route can only cut a box, so it uses the box containing the painted
area. Anything you masked *out* inside that box is redrawn too.

**Flags**

| Flag | Default | What it does |
|---|---|---|
| `--mask <spec>` | — | A mask image, or `"x,y,w,h"` in pixels or percentages. |
| `--mask-out <path>` | `<source>-edited.png` | Where the composited picture lands. Crop route only. |

`--pad` and `--feather` still work on the crop route but are deliberately
undocumented: both derive from the size of the area now, and overriding them
mostly breaks the blend. Reach for them only when a seam is visibly wrong.
`--region` and `--region-out` keep working as aliases.


`--image_input` may be a local file or a URL, so a sandbox node's `output_url`
works directly.

**Reading `colour_shift`:** the CLI samples the padding ring — context the model
only saw so it could match — and corrects the whole patch by the difference. A
shift of a few levels is normal and is what makes the seam disappear. A shift
near ±32 means the correction hit its clamp: the region is probably drawn
tighter than the object, so the ring caught part of the subject. Widen the
region and run again.

**Rules that make the seam invisible:**

- Expand the selection by 15–20 px before you generate.
- Feather the edge by 2–5 px on the way back.
- Keep every attempt. The best result is often a blend of two tries.
- **Prompt the change, not the scene.** "Change the wall behind her to sage
  green" beats a full re-description. A full description invites the model to
  reconsider everything.
- Expect the crop to come back slightly off in color. Correct it with a curves
  adjustment locally. Do not re-prompt for a color shift — that is another pass.

**When `--mask` is the wrong tool.** It assumes the surroundings stay put: it
blends the new region against the padding ring, and the ring only works as a
reference if that ring is the same content in both pictures. Removing or moving
a whole structure breaks the assumption — take out a closet and the wall plane,
the floor line and the ceiling all shift out to the frame edges, so there is no
stable border left to blend against. Measured on exactly that edit: the colour
match hit its ±32 clamp on all three channels and the result was an obvious
rectangle. The CLI now warns when the clamp is hit.

For that kind of change, run **full-frame** with no `--mask`, and accept that
everything is redrawn. Tier up to `image/nano-banana-pro` when the picture has
detail worth keeping — on a real bedroom photo it held the brick texture, the
cap logos and the lamp filament that the cheaper models softened away.

**Best fit:** a fix on a picture the user already approved, product retouching,
removing one object, a text or sign correction, any change the user calls small.

## Workflow D — noise repair (rescue an image that is already damaged)

Use this only when the damage exists and the original is gone.

Add monochrome Gaussian noise to the image **before** you upload it. The noise
breaks up the JPEG blocks and the color banding. The model must then clean the
noise, so it rebuilds real detail instead of copying the damage.

```bash
# Add grain locally. More damage needs more noise; do not bury fine detail.
ffmpeg -y -i damaged.png -vf "noise=alls=18:allf=t+u" noisy.png

vsb run image/nano-banana-pro --json --output_format png --resolution 4K \
  --image_input '["noisy.png"]' \
  --prompt "Upscale the image, reconstruct it with no noise and a high amount
of detail, maintain its style."
```

Run it again with a different noise level if the first result is wrong. This
recovers sharpness and gradients. It does not recover detail the model never
drew.

An alternative rescue: `image-enhance/upscale`. Check the catalog first with
`vsb models --modality image --json`.

---

## Settings that decide the outcome

| Setting | Default in Visual Sandbox | What to use when iterating |
|---------|---------------------------|----------------------------|
| `output_format` | `jpg` for text-to-image, **`png` automatically when `image_input` is set** | Visual Sandbox flips it for you on edit runs, because an edit's output is a likely input to the next edit. Pass `--output_format jpg` to override. |
| `resolution` | `1K` on `nano-banana-2`, `2K` on `nano-banana-pro` | **`2K`.** It holds up across passes. Only `nano-banana-2`, `nano-banana-pro`, and `gpt-image-2` expose it. |
| `aspect_ratio` | `match_input_image` | Leave it. Changing it forces a full re-frame. |
| Model | `nano-banana-2-lite` | **Not Lite for chained edits.** Google states Nano Banana 2 Lite is "not optimized for multiple reference inputs or multi-turn sequential editing." Use `image/nano-banana-2` or `image/nano-banana-pro`. |

A text-to-image run stays JPEG, because its output is a leaf. The moment a
reference image is attached the container switches to PNG on its own. Check the
`inputs.output_format` on the response if you want to be sure.

```bash
vsb schema image/nano-banana-2 --json | jq '.inputs | {output_format, resolution}'
```

## Prompt templates

**The preserve template** (Google's own semantic-mask wording — use it on every
edit run):

```
Using the provided image, change only the [specific element] to [new element].
Keep everything else in the image exactly the same, preserving the original
style, lighting, and composition.
```

**Name three things, never two.** An implied region causes a full redraw:

1. Which region changes. Describe it as if you were drawing a mask.
2. What the change is.
3. What the result must look like.

**Use positive phrasing.** "An empty, deserted street with no signs of traffic"
works. "A street with no cars" gives you cars. The model reads the noun, not
the "not". This is the same rule as
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) rule 2.

## What does not work

- **Quality tags.** "8k", "high resolution", "keep the same quality",
  "maintain sharpness" do nothing on this model class. Users test them
  constantly and report no effect.
- **Asking the model to fix its own damage.** A fresh run that says "fix the
  pixelation" returns the same bad result. Use workflow D instead.
- **Long negative commands.** Repeating "do not change the background" five
  ways does not help and often makes it worse.
- **Feathering on its own.** It softens the boundary and does nothing about the
  interior. Measured on a real Nano Banana edit: the step at the seam was 1.2
  levels, the patch interior sat 8.9 levels bluer than the wall around it, and
  it was the interior the eye caught. The colour match is what fixes it — which
  is why the crop route does both.
- **`ffmpeg overlay` for the paste-back.** It converts the frame to YUV 4:2:0
  and back, which damages the whole picture. Measured: 324,018 pixels changed
  outside a 552x552 paste on a 1024x1024 frame. Use `--mask`; if you must do
  it by hand, force `format=rgba` on both inputs and
  `overlay=...:format=rgb,format=rgb24` on the output.
- **A fresh chat or a fresh session.** Some users believe conversation history
  causes the loss. The problem also appears through the API with no history at
  all, so this is unproven. Nothing is lost by starting fresh, but do not rely
  on it as the fix.

## Pre-flight checklist

Before the **second** `vsb run` on the same picture:

1. Do I still have the original image or the original prompt? (If no — this is
   already workflow D.)
2. Does the user need the exact same frame? → `--mask` (workflow C). Otherwise → `--from-job` + `--prompt-add` (workflow A).
3. Did I pass `--output_format png`? (Automatic when `image_input` is set — but confirm it in the response.)
4. Am I on `nano-banana-2` or `nano-banana-pro`, not Lite?
5. Am I about to pass my own last output as `image_input`? → stop, go to step 2.

All five ✓ → run. Any ✗ → not ready.

## Cross-links

- Prompt craft for every image model → [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)
- Model picker → [`vsb-image`](../vsb-image/SKILL.md)
- Nano Banana edit-mode quirks + variant table → [`vsb-nano-banana`](../vsb-nano-banana/SKILL.md)
- Saving a recipe so the original inputs survive → `vsb-presets`
- Sandbox selection chain (critical rule 10) → `vsb`

Referenced files: 1 — file references are metadata, not package contents.

vsb-image-prompting8.26 KB

View saved version →

---
name: vsb-image-prompting
renamed_from: image-prompting
description: >
  Canonical prompt-craft skill for every image model in Visual Sandbox
  (`image/nano-banana*`, `image/gpt-image-2.5-flare`, `image/gpt-image-2`,
  `image/flux-2-klein-9b`, future
  slugs). Read before writing any `vsb run image/<slug>` prompt — text-to-image
  or edit-with-reference. Covers the universal rules, the prompt-anatomy slot
  list, the reference-image keep / ignore formula (what to take from the
  reference, what to ignore — watermarks, text overlays, original lighting,
  original background), and the "enumerate models before picking cheapest"
  rule. Model-specific quirks live in sibling skills (`vsb-nano-banana`,
  `vsb-ugc-people`).
license: Proprietary. See LICENSE for the terms.
---

# Image prompting (model-agnostic)

## Universal rules

1. **Be specific.** Concrete subject, scene, light. Not "dog in park".
2. **Positive framing.** Describe what's there, not what isn't. ("Empty street" ✓, "street with no cars" ✗.)
3. **Use photography language.** `f/1.8 shallow DoF`, `golden hour`, `Rembrandt lighting` map to real transforms.
4. **Iterate on the prompt, not on the pixels.** One change per turn — but
   grow the prompt and re-run from the original. Feeding your own last output
   back in as `image_input` compounds loss on every pass. See
   [`vsb-image-iteration`](../vsb-image-iteration/SKILL.md).

## Prompt anatomy

Pick 5–9 slots. Concreteness > coverage.

| Slot | Example |
|------|---------|
| Subject | 28yo brunette, shoulder-length wavy hair, faint freckles, pale skin |
| Wardrobe | oversized cream cable-knit sweater, gold hoop earrings, no makeup |
| Action | holding latte mug close to chest with both hands |
| Location | sunlit Brooklyn apartment corner, exposed brick, fairy lights on window frame |
| Composition | medium close-up selfie, 9:16, eyes upper third, shoulders cropped |
| Camera | Shot on iPhone 17 Pro Max main lens (~26mm equiv) |
| Lens / DoF | f/1.8 shallow DoF, slight motion blur on hand |
| Lighting | warm late-morning window backlight, soft ambient room fill |
| Color grade | neutral white balance, slightly muted, no Instagram filter |
| Materiality | ribbed ceramic mug, cable-knit cotton, raw oak windowsill |
| Mood | cozy, candid, unposed |
| Anti-AI cues | sensor grain, lens flare, asymmetric framing, hair flyaway, faint pores |

## Bad → good

**Bad** (model fills every gap with AI-glossy defaults):
> "candid iPhone selfie of brunette in cozy apartment holding latte, UGC TikTok aesthetic"

**Good** (directed):
> "Candid iPhone selfie of 28yo brunette, shoulder-length wavy hair, faint freckles. Oversized cream cable-knit sweater, ribbed ceramic mug of latte at chest. Sunlit Brooklyn apartment corner, exposed brick, fairy lights on the window frame behind. Medium close-up, 9:16, eyes upper third. Shot on iPhone 17 Pro Max (~26mm), f/1.8, slight hand motion blur. Warm window backlight, soft ambient fill. Neutral white balance, sensor grain, asymmetric framing, single hair flyaway, no makeup. Cozy, candid, unposed."

## Reference-image prompting (CRITICAL — fixes watermark/caption bleed)

When `image_input` is set, the model uses the reference as *every* signal —
subject, watermarks, captions, UI bars, original lighting, original framing
all bleed forward unless you tell it not to.

**Formula** — paste at the top of every reference-anchored prompt:

```
Use the reference image as the IDENTITY source only.
Keep: [face, hair color and length, skin tone, body proportions, age, distinctive features].
Ignore everything else from the reference — including any watermarks, text overlays, captions, logos, UI bars, original lighting, original background, and original framing.

Now place the same subject in: [fresh scene — full anatomy from above].
```

End with: `"No logos, no captions, no watermarks, clean frame."`

(Negative framing — "don't add a watermark" — doesn't work. Stating the desired clean state does.)

**Multi-reference** — name each, give it a role:
```
Use Image A as the IDENTITY source — keep face, hair, skin.
Use Image B as the STYLE source — match color grade, grain, lighting, but ignore its subject.
Use Image C as the BACKGROUND source — match environment, ignore people / text.
```

Pro tier of nano-banana takes up to 14 refs. Standard models: 1–3.

## Picking the model — enumerate, never assume

"Cheapest" / "fastest" / "best for X" are comparative claims. They need
**data**, not memory. Prices change weekly. Memorized prices go stale.

```bash
vsb models --modality image --json \
  | jq -r '.models[] | "\(.category)/\(.slug)"' \
  | while read s; do
      printf "%-50s " "$s"
      vsb pricing "$s" --json 2>/dev/null \
        | jq -r '.user_cost_estimate // "tiered(\(.tiers | length // 0))"'
    done | sort -k2 -n
```

Models with `user_cost_estimate: null` are **tiered** (cost depends on
quality/resolution input, e.g. `nano-banana-pro`, `gpt-image-2.5-flare`,
`gpt-image-2`). Don't drop
them — inspect `vsb pricing <slug> --json` to read the tier table and pick
the cheapest tier you can live with.

Cheapest row wins **only when the user asked for cheapest and the task is
simple**. Detail-critical work — legible in-image text (signs, tattoos,
posters, packaging), dense multi-element scenes, publishable ad creative,
precise multi-ref identity edits — skips the cheap rows entirely: go to
`image/nano-banana-pro` or `image/gpt-image-2.5-flare` (Flare at `xhigh` or
`max` when typography legibility is the single hardest constraint;
nano-banana-pro for photoreal scenes containing text or fine detail). One
premium run beats three cheap retries.

Then verify the pick supports refs:

```bash
vsb schema image/<slug> --json | jq '.inputs.image_input'
```

`null` → no reference editing. Bump up the ladder until you find one.

**CLI quirk — array inputs.** `image_input` is `array<string>`. Pass either
as repeated flags (`--image_input a --image_input b`) or as a JSON-array
string (`--image_input '["a","b"]'`). The CLI auto-wraps a single bare
string, but mixed forms with other agents may emit a single bare value —
use `vsb run <slug> --help` to confirm the array shape before composing
the command.

(Future: `vsb pricing --modality image --all` should ship as a built-in — see vsb CLI backlog.)

## Vocabulary banks (one-liners)

- **Cameras:** `Shot on iPhone 17 Pro Max` (UGC) · `Shot on Fujifilm X-T5, 35mm` (editorial) · `disposable film camera with flash` (raw nostalgic) · `RED cinema camera` (filmic) · `CCTV, fisheye` (found-footage)
- **Lenses / DoF:** `f/1.8 shallow DoF` · `wide-angle 24mm` · `macro` · `tilt-shift` · `telephoto 85mm compression` · `phone main lens ~26mm` (UGC default)
- **Lighting:** `three-point softbox` · `Rembrandt` · `rim-lit from behind, soft fill` · `golden hour backlight` · `blue hour ambient` · `neon magenta + cyan rim` · `harsh midday top-light` · `on-camera flash, slight overexposure` · `warm tungsten mixed with daylight window`
- **Color grades:** `muted teal-and-orange Hollywood grade` · `bleach-bypass, low-sat high-contrast` · `kodachrome warm cast` · `cool monochrome` · `neutral white balance, no filter` (UGC default — say it explicitly)
- **Materiality:** name fabrics + finishes. `chunky oatmeal wool scarf` > `scarf`. `brushed aluminum laptop` > `laptop`.
- **Anti-AI cues** (use 3–4 for UGC, skip for studio/commercial): `sensor grain` · `minor lens flare` · `asymmetric framing` · `single hair flyaway` · `faint pore detail` · `micro motion blur on hand` (selfie = handheld) · `no makeup` · `slight jpeg compression` · `no Instagram filter`

## Cross-links

- Model picker → [`vsb-image`](../vsb-image/SKILL.md)
- Nano Banana quirks (text rendering, 14-ref blend, known limits) → [`vsb-nano-banana`](../vsb-nano-banana/SKILL.md)
- UGC format (selfie POV / talking-head) → [`vsb-ugc-people`](../vsb-ugc-people/SKILL.md)
- Editing the same image twice or more → [`vsb-image-iteration`](../vsb-image-iteration/SKILL.md)
- Selection-chain + sandbox patterns → `vsb`

## Pre-flight checklist

Before `vsb run`:

1. Subject named (age / hair / skin / build)?
2. Camera + lens + light + grade picked?
3. Reference attached → keep/ignore preamble + clean-frame end line?
4. User said "cheapest"/"fastest" → enumerated, not assumed?

All four ✓ → run. Any ✗ → not ready.

Referenced files: 1 — file references are metadata, not package contents.

vsb-library-themes3.77 KB

View saved version →

---
name: vsb-library-themes
description: >
  Keep a brand and its media in Visual Sandbox with the MCP tools: read a
  brand's colors, fonts, logo and tone from its website into a saved theme,
  apply the theme to every later prompt, and find, name and tag results in
  the person's library so they can be used again. Trigger when the person
  says "use my brand", "set up my brand", "match our website", "our colors",
  "our logo", "find the picture we made last week", "use the approved
  keyframe", "name this", "tag these" or gives a website for branded work.
  Saves only values the person confirmed. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Brand themes and the library

A **theme** keeps a brand once: its colors, fonts, logos, notes and
reference pictures. The **library** is every result and upload on the
account, the same rows the person sees on visualsandbox.com. Both are free.

## Set up a brand

1. Ask for the website, or for the colors, fonts and logo.
2. Call `extract_theme` with the website. It reads the site on the server
   and proposes colors with roles, fonts, logos and the site's own
   description. It saves nothing.
3. Show the proposal to the person before you save it:
   - **In ChatGPT,** call `edit_theme` with the name and the proposal as
     `draft`. The card shows the swatches, fonts and logos. The person fixes
     what is wrong and presses Save. Do not call `update_theme` for it.
   - **Elsewhere,** list the colors (hex and role), the fonts and the logo,
     and ask what to change. Then call `update_theme` with the agreed values.
4. Never invent a brand value. A value that you guess is a proposal until the
   person agrees to it.

### The theme's shape

- Colors: `hex`, `name`, and a `role`: `primary`, `secondary`, `accent`,
  `bg`, `fg` or `muted`.
- Fonts: `name`, `source` (`google` or `custom`) and a `role`: `heading`,
  `body`, `mono` or `accent`.
- Logos: `url`, `name`, `variant` (`light`, `dark`, `mono` or `color`) and
  `has_background`.
- `notes`: the audience, the tone, the look, and what to do and never do.
- `images`: reference pictures. The first one is the thumbnail.

`update_theme` replaces each field it is given and keeps the rest. A new
`colors`, `fonts` or `logos` list replaces the whole list. The first theme on
an account is its default; `set_default` changes that.

## Apply a theme

Before any branded image, video, design or HTML render:

1. Call `get_theme` (no name gives the default theme).
2. Put the look into the prompt in words: the main colors by name and hex,
   the mood from `notes`, and every "never" rule.
3. Pass a logo or a reference picture as a reference image when the model
   takes one. Say in the prompt what to keep from it and what to ignore.
4. In HTML ([`vsb-html-video`](../vsb-html-video/SKILL.md)) and designs
   ([`vsb-design`](../vsb-design/SKILL.md)), use the hex values and the
   font names exactly.

## Find and name media

- `search_assets` finds results and uploads by words (`query`), `tags`,
  exact `name`, `type` (`image`, `video`, `audio`, `3d`, `vector`, `text`)
  and `source` (`generated` or `uploaded`). Each asset carries a `url` to
  pass to the next run.
- `update_asset` gives an asset a `name`, `tags` and a `description`. It
  takes the asset's id, its name or its file URL.

How to name:

- A name is a unique slug: `acme-launch-hero`, `acme-launch-hero-v2`.
- A derivative keeps the name and adds a suffix: `-cutout`, `-vertical`,
  `-upscaled`. The original stays.
- Keep tags few and stable: the brand (`acme`), the campaign
  (`launch-2026`), the kind (`logo`, `keyframe`) and the state (`approved`).
- `tags` replaces the whole list, so send the tags to keep as well.

Name a result when the person approves it, so a later chat finds it with
`search_assets` instead of a new paid run.

Referenced files: 1 — file references are metadata, not package contents.

vsb-nano-banana9.49 KB

View saved version →

---
name: vsb-nano-banana
renamed_from: nano-banana
description: >
  Prompt the Nano Banana family (Google's Gemini Image — `image/nano-banana-2-lite`,
  `image/nano-banana`, `image/nano-banana-2`, `image/nano-banana-pro`) well. Trigger whenever the
  user is about to call `vsb run image/nano-banana*` or asks how to phrase a
  prompt for these models — covers the variant picker, edit-mode rules, text
  rendering, multi-reference blending, and the things this family gets
  wrong. For the universal prompt-anatomy rules and reference-image
  subject-anchor pattern, read [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)
  first — this skill only adds nano-banana-specific quirks on top.
license: Proprietary. See LICENSE for the terms.
---

# Prompting Nano Banana

**Read [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) first.** It covers the
four universal rules, the full prompt-anatomy slot list, the
reference-image keep/ignore pattern, and the vocabulary banks — those apply
to every image model. This skill only adds Nano Banana–specific behavior
that diverges from the trunk.

Nano Banana is built on Gemini 3 — it reasons over the prompt before
drawing, so the natural-language scene-description approach in
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) works especially well here.
The same endpoint handles text-to-image AND edits: pass `image_input` (an
array) to switch into edit mode.

Verify the live schema first: `vsb schema image/nano-banana --json`.

## Picking a variant

| Slug | When to use |
|------|-------------|
| `image/nano-banana-2-lite` | **Default.** Lightweight Nano Banana 2 — same low cost as the original (~$0.05/image, ~5s) but on the newer NB2 architecture. Great for iterating and everyday text-to-image + image edits. No `resolution` or web-search knobs. |
| `image/nano-banana` | The original Gemini 3 model. Same ~$0.05/image. Fall back here if a `nano-banana-2-lite` result looks off. Best with 1–3 reference images. |
| `image/nano-banana-2` | Full NB2 — adds `resolution` (up to 4K) and web-search grounding. Use when stills aren't crisp enough or you need higher fidelity. |
| `image/nano-banana-pro` | 4K-capable, sharpest text + typography, best for posters, infographics, packaging mockups, editorial work, and multi-reference character blending (up to 14 refs). Slowest + priciest of the three. |

All three share the same `image_input` array, `aspect_ratio` enum, and
`output_format` enum. Confirm with `vsb schema` before assuming.

## Image edits (`image_input` set)

> ⚠️ **Editing the same picture a second time degrades it.** The model never
> edits — it redraws the whole image from scratch on every pass, so error
> compounds. Before a second edit on one image, read
> [`vsb-image-iteration`](../vsb-image-iteration/SKILL.md). Also pass
> `--output_format png` on any output you may feed back in; the default is
> `jpg` and its compression stacks on every pass.

When `image_input` is non-empty, the model is editing — the prompt should
lead with the change and be explicit about what stays the same. The
universal subject-anchor pattern (in
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)) is the floor; Nano Banana
takes especially well to plain-English edit instructions on top.

**Good:** `"Change the man's tie to dark green. Keep the rest of the photo,
lighting, and composition exactly as they are."`

**Bad:** `"man with green tie"` (loses every other detail).

Edit patterns Nano Banana handles cleanly:

- **Add / remove / swap:** `"Remove the car in the background."`
  `"Replace the coffee mug with a vintage typewriter."`
- **Restyle:** `"Restyle as a 1990s film photograph — kodak gold, slight
  grain, warm cast."` `"Convert to ink-on-paper line art."`
- **Reframe / re-angle:** `"Re-render this scene from a low angle, keeping
  the same subject and lighting."`
- **Weather / time-of-day:** `"Same scene but at golden hour with backlit
  rim light."`
- **Product extraction:** `"Extract the mug and place it on a clean white
  studio backdrop with a soft shadow."`

For the `aspect_ratio` field, `match_input_image` only makes sense in edit
mode — it keeps the first reference's dimensions.

## Text rendering (Nano Banana's standout strength)

Nano Banana is one of the few image models that renders legible text. Pro
is the sharpest of the three.

- **Quote the exact words.** `"The headline 'URBAN EXPLORER' rendered in
  bold, white, sans-serif font at the top of the poster."`
- **Describe the font.** Style words work (`bold`, `cursive`, `serif`,
  `monospace`, `neon signage`), and named fonts work surprisingly often
  (`Century Gothic`, `Helvetica`-style).
- **Localize.** Write the prompt in any language and pass the target text
  in quotes. For exotic scripts give the model the literal characters —
  don't ask it to translate.
- **Text-first hack for tricky layouts.** Two-step: first ask Nano Banana
  (chat-style) for the literal text content, then run a generation request
  that quotes that text. Improves accuracy on long strings.

## Multi-reference character consistency

The universal subject-anchor pattern (in
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)) tells you *how* to phrase
"keep the subject, ignore the rest". Nano Banana adds two specifics on top:

- **Name each character / object in the prompt.** The model uses the name
  as an anchor across edits. `"Marigold the calico cat"` survives across
  follow-up edits better than `"the cat"`.
- **Use a reference pack.** Multiple angles of the same subject (front,
  profile, three-quarter) help the model triangulate identity. Pro takes
  up to **14 references** per prompt; standard nano-banana is best with
  **1–3**. For more than 3 references, switch to Pro.

## Multi-subject anatomy lock

Multi-person scenes hallucinate extra hands/arms when limb count is left
implicit. Budget every visible limb: state the exact total ("visible
hands: 2"), name whose and where each is, then declare the remaining arms
out of frame or hidden behind a torso. Same rule applies to feet on
full-body shots.

## What this family gets wrong (known limits)

Adds to the model-agnostic failure modes in
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md):

- **Tiny / dense text.** Long paragraphs at small size will hallucinate
  glyphs. Pro is best; even Pro isn't perfect.
- **Factual diagrams.** Anatomy, scientific cross-sections, and
  infographics may be confidently wrong. Verify any data-driven visual
  yourself.
- **Multilingual grammar.** Non-English text may have small grammatical
  slips or cultural mismatches. Provide the literal target string in
  quotes when accuracy matters.

## Worked examples

### Photoreal portrait (text-to-image)

```bash
vsb run image/nano-banana \
  --prompt "A 28-year-old florist standing in her shop doorway at golden hour. She wears a denim apron over a cream linen shirt. Shot on Fujifilm X-T5, 35mm, f/1.8 shallow depth of field. Warm rim light from the right, soft fill from the open window. Photoreal, cinematic muted teal-and-orange grading, slight sensor grain." \
  --aspect_ratio 4:5 \
  --output_format jpg \
  --download "./out/{request_id}.jpg" \
  --json
```

### Edit with reference (`image_input`)

```bash
URL=$(vsb upload ./product.jpg --json | jq -r '.url')
vsb run image/nano-banana \
  --prompt "Use the reference image as the PRODUCT source only. Keep the mug's pattern, colour, handle shape, and reflections exactly as they are. Ignore everything else from the reference — including any watermarks, text overlays, original background, and original lighting.

Now place this mug on a sunny tropical beach with sand and ocean waves in the background. Photoreal, natural midday daylight, soft shadow on the sand. No logos, no captions, no watermarks." \
  --image_input "[\"$URL\"]" \
  --aspect_ratio match_input_image \
  --download "./out/" \
  --json
```

### Poster with crisp typography (Pro)

```bash
vsb run image/nano-banana-pro \
  --prompt "A minimal travel poster. Composition: 9:16 vertical, full-bleed. Background: silhouette of a mountain range at dusk, indigo-to-magenta sky. Foreground typography: the word 'PATAGONIA' rendered in bold, white, condensed sans-serif font, baseline aligned at the bottom third. Subline reads 'Edge of the World' in a smaller serif italic directly underneath. Cinematic, slight film grain." \
  --aspect_ratio 9:16 \
  --output_format png \
  --download "./out/poster.png" \
  --json
```

### Multi-reference character blend (Pro shines here)

```bash
A=$(vsb upload ./pose.jpg --json | jq -r '.url')
B=$(vsb upload ./style.jpg --json | jq -r '.url')
C=$(vsb upload ./background.jpg --json | jq -r '.url')
vsb run image/nano-banana-pro \
  --prompt "Render a single image. Use Image A as the IDENTITY source — keep the subject's face, hair, skin tone, and body proportions exactly. Use Image B as the STYLE source — match its color grade, grain, and lighting feel, but ignore its subject. Use Image C as the BACKGROUND source — match the environment, but ignore any people or text in it. Cinematic, high-detail, no watermarks, no captions." \
  --image_input "[\"$A\",\"$B\",\"$C\"]" \
  --aspect_ratio match_input_image \
  --json
```

## Sources

- Google DeepMind — Gemini Image prompt guide: <https://deepmind.google/models/gemini-image/prompt-guide/>
- Google Cloud — Ultimate prompting guide for Nano Banana (2026-03): <https://cloud.google.com/blog/products/ai-machine-learning/ultimate-prompting-guide-for-nano-banana>
- Google blog — 7 tips for Nano Banana Pro: <https://blog.google/products-and-platforms/products/gemini/prompting-tips-nano-banana-pro/>

Referenced files: 1 — file references are metadata, not package contents.

vsb-product-photoshoot3.33 KB

View saved version →

---
name: vsb-product-photoshoot
description: >
  Make a set of product photos from one photo of the product with the Visual
  Sandbox MCP tools: hero shots, lifestyle scenes, flat lays and ad images
  that keep the product the same in every picture. Trigger when the person
  asks for "product photos", "a product shoot", "photos for my store",
  "Amazon or Shopify images", "lifestyle shots of my product", "a hero
  image", or sends a product photo and asks to make it look professional.
  Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# A product photo shoot

One photo of the product goes in. A set of pictures comes out, and the
product looks the same in all of them: the same shape, the same colour and
the same label.

Use the MCP tools only.

## 1. Get the product

- Ask for one clear photo of the product, if the person sent none. A phone
  photo on a plain table is enough.
- Pass a public `https` URL straight to `generate`. For a photo attached in
  the chat, call `upload_media` with it as `file`. For a local file, call
  `upload_media` with a base64 `data_uri`. Use the URL it returns.
- Ask where the pictures go: a store page, an ad, or social media. That sets
  the shape: 1:1 for a store, 4:5 or 9:16 for social media, 16:9 for a
  banner.

## 2. Plan the set

Propose four shots and let the person change the list before you run:

| Shot | What it shows |
|------|---------------|
| Hero | The product alone on a clean background, with soft studio light |
| Lifestyle | The product in use, in the place a buyer uses it |
| Detail | A close view of the texture, the material or the label |
| Flat lay | The product from above, with two or three props that fit it |

## 3. Run the first shot

1. Read [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) with
   `get_skill`. Its reference section says what to keep from the photo and
   what to drop, such as the old background and the old light.
2. Call `list_models` with `modality: "image"`. Pick an edit model that takes
   a reference image. Read [`vsb-nano-banana`](../vsb-nano-banana/SKILL.md)
   when you pick a Nano Banana model.
3. Call `get_model`, then `generate` with the photo as the reference. Name
   the work with `sandbox_name`, such as "Candle product shoot".
4. Call `get_job` after `eta_seconds`. Compare the product with the photo:
   the shape, the colour and every word on the label.

Run the hero shot first and show it. Run the other shots only after the
person approves the look.

## 4. Run the rest of the set

- Use the same model and the same reference on every shot.
- Start every prompt with the same sentence that describes the product, so
  the product stays the same.
- Send the same `sandbox_name` on every call, so the set lands on one canvas.

## 5. Hand it over

Give one `share_url` for each picture and the `sandbox_url` for the set.
Offer one next step: ad variations of the hero shot with
[`vsb-ad-variations`](../vsb-ad-variations/SKILL.md), or a short video of it
with [`vsb-video`](../vsb-video/SKILL.md).

## Rules

- Never change the product. A changed label or shape is a failed shot, even
  when the picture looks good. Say so and run the shot again.
- Never add a logo, a price or a claim that the person did not give you.
- When a run is refused for want of credit, say the balance is too low, and
  follow what the server instructions say about credit.

Referenced files: 1 — file references are metadata, not package contents.

vsb-p-video7.98 KB

View saved version →

---
name: vsb-p-video
renamed_from: p-video
description: >
  Prompt and run Pruna AI's P-Video family well. `video/p-video` is the
  cheapest video model in Visual Sandbox, with a built-in `draft` toggle that
  drops cost ~4×, and two newer tiers sit above it. Trigger whenever the user
  is about to call `vsb run video/p-video*` or asks "cheapest video model",
  "draft video", "iterate fast", or "lots of video variations". Always ask the
  user **draft or standard** before running.
  Pairs with the parent [`vsb-video`](../vsb-video/SKILL.md) skill for the async +
  poll pattern.
license: Proprietary. See LICENSE for the terms.
---

# P-Video (Pruna AI)

Cheapest video model in the catalog. Fast iteration. Two-mode runtime via the
`draft` boolean — flip it on for previews, off when you're locking the shot.

## Which P-Video tier

Three generation tiers now share this family. Price alone no longer picks one.

| Pick | When |
|---|---|
| `video/p-video` | Bulk prompt exploration. Its draft rate is still the lowest in the whole catalog. No audio of any kind. |
| `video/p-video-2` | The clip has to talk, or needs 1080p, 48 fps, a last frame, or a run past 15s. It writes native speech with lip-sync from dialogue in the prompt, and also takes an audio track to drive motion. |
| `video/p-video-2-pro` | The best Pruna picture. Silent: no audio in, no audio out, and 768p is the ceiling. |

People who talk, with one voice across shots: `video/p-video-2 --image <still>
--audio <ElevenLabs line>` is the cheapest path. The clip runs as long as the
audio, so `--duration` has no effect; pad the line with silence to the shot
length. Say in the prompt that the person speaks. Compare it with the other
paths in [`vsb-video` → People who talk](../vsb-video/SKILL.md#people-who-talk-pick-the-sound-path-before-you-spend) before you
spend.

Three more slugs in the family transform a clip you already have rather than
generate one, and they are covered in [`vsb-video`](../vsb-video/SKILL.md):
`video/p-video-edit`, `video/p-video-replace`, `video/p-video-animate`, plus
`video/p-video-avatar` for a photo that speaks a typed script.

Read the parent [`vsb-video`](../vsb-video/SKILL.md) skill first for the universal
async + poll + cancel pattern. This skill only adds p-video–specific behavior.

Prompt craft — slot formula, narration pattern, DO/DON'T — lives in the
sibling [`vsb-p-video-prompting`](../vsb-p-video-prompting/SKILL.md) skill. Load it
before writing any prompt.

Verify the live schema before assuming fields:

```bash
vsb schema video/p-video --json
vsb pricing video/p-video --json
```

## Always ask first: draft or standard?

Before running, ask the user which mode they want. Don't guess.

```
Draft (cheap, fast — for iteration) or standard (final-quality render)?
```

| Mode | `--draft` | 720p | 1080p | When to pick |
|------|-----------|------|-------|--------------|
| **Draft**    | `true`  | $0.00625/s | $0.0125/s | Trying out a prompt. Exploring 5–10 variations. Storyboards. Anything you'd re-run. |
| **Standard** | `false` | $0.025/s   | $0.05/s   | Locking the final shot. Delivering to client. The shot you'll post. |

A 5s 720p draft ≈ $0.03. The same shot at standard = $0.125 (4×). Use draft
to land the prompt + composition, then run **the exact same args with
`--draft false`** once to lock the final.

## Pricing reference

What you pay (verify live with `vsb pricing video/p-video --json`):

| Resolution | Mode | $/s |
|------------|------|-----|
| 720p  | draft on  | $0.00625 |
| 720p  | draft off | $0.025   |
| 1080p | draft on  | $0.0125  |
| 1080p | draft off | $0.05    |

Per-second billing. `--duration` is integer 1–10 (default 5).

## Input modes

P-Video accepts three independent conditioning signals — combine freely.

| Field | Meaning |
|-------|---------|
| `prompt` | Required. Scene + motion description. |
| `image` | Optional first-frame for image-to-video. |
| `audio` | Optional audio track — motion syncs to the sound. Useful for music videos and lip-sync-ish work. |

Pass `image` and/or `audio` only when you actually want that conditioning.
With no image, it's text-to-video. With one, it's image-to-video. With audio,
the model tries to match motion to the audio's tempo / dynamics.

Other knobs:

- `aspect_ratio`: `16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 1:1` (default `16:9`).
  Match the source image when seeding — most video models silently crop
  mismatched aspects (see parent [`vsb-video`](../vsb-video/SKILL.md) skill).
- `resolution`: `720p | 1080p` (default `720p`).
- `fps`: `24 | 48` (default `24`). Bump to 48 only for high-motion shots
  that need slow-mo or smooth pans — 24fps is the default cinematic feel.
- `prompt_upsampling`: bool, default `true`. Let the model rewrite the
  prompt internally for better adherence. Leave on unless you have a very
  specific phrasing you need preserved verbatim.
- `seed`: optional int, for reproducibility across runs.

## Why pick p-video over seedance / veo / kling

- **Cheapest path to a watchable video** — draft 720p at $0.005/s is ~16×
  cheaper than veo-3.1, ~5× cheaper than seedance-2.
- **Looser content moderation than ByteDance** — observed in practice:
  prompts / images that trip Seedance's safety filter (anything human-subject
  + selfie-style) often go through on p-video. If a Seedance run returns
  `"Your input was flagged by the model's safety filter"`, re-try the same
  inputs on p-video before rewriting the prompt.
- **Direct draft/standard toggle in one slug** — no need to swap models
  between fast and final passes; same UUID, same schema, just flip the
  bool.

Reach for veo-3.1 when you need top-tier motion + lighting realism, kling
for brush-style motion control, seedance for ByteDance's particular video
aesthetic.

## Worked examples

All examples use `--async` + status poll — video runs take 30s–3min. See
[`vsb-video`](../vsb-video/SKILL.md) for the polling helper.

### Text-to-video, draft (iteration)

```bash
JOB=$(vsb run video/p-video \
  --prompt "A street vendor in Bangkok grilling skewers under a neon sign, smoke curling up, slow camera push-in" \
  --aspect_ratio 16:9 \
  --resolution 720p \
  --duration 5 \
  --draft true \
  --async --json | jq -r '.job_id')
```

### Same shot, locked in (standard)

```bash
JOB=$(vsb run video/p-video \
  --prompt "A street vendor in Bangkok grilling skewers under a neon sign, smoke curling up, slow camera push-in" \
  --aspect_ratio 16:9 \
  --resolution 720p \
  --duration 5 \
  --draft false \
  --async --json | jq -r '.job_id')
```

Identical args, only `--draft` flipped. Use the same `--seed` across both
runs if you want the standard render to match the draft composition.

### Image-to-video (first-frame seed)

```bash
URL=$(vsb upload ./still.jpg --json | jq -r '.url')
JOB=$(vsb run video/p-video \
  --prompt "She holds the phone steady, mouths a few words, free hand finishes tucking hair behind ear" \
  --image "$URL" \
  --aspect_ratio 9:16 \
  --resolution 720p \
  --duration 4 \
  --draft true \
  --async --json | jq -r '.job_id')
```

Always pass `--aspect_ratio` matching the source image. P-Video defaults
to `16:9` and will crop a 9:16 selfie if you forget.

### Audio-conditioned (motion to sound)

```bash
URL=$(vsb upload ./still.jpg --json | jq -r '.url')
AUDIO=$(vsb upload ./beat.mp3 --json | jq -r '.url')
JOB=$(vsb run video/p-video \
  --prompt "Subject dances in place, gentle body sway, head bobs to the beat" \
  --image "$URL" \
  --audio "$AUDIO" \
  --aspect_ratio 9:16 \
  --duration 6 \
  --draft true \
  --async --json | jq -r '.job_id')
```

## Quirks

- **`default_cost_unit` reports `"image"` in the registry** — ignore it.
  P-Video bills per *second*, like every other video model. Trust the
  pricing tiers, not the unit field.
- **`prompt_upsampling: true` is the default** — if a prompt you wrote is
  being ignored or rewritten, pass `--prompt_upsampling false` to force
  literal interpretation.
- **`duration` is integer, not float.** Enum is `1..10`.
- **`fps: 48` is for slow-mo / smooth motion only.** Most UGC and
  cinematic work stays at 24.

Referenced files: 1 — file references are metadata, not package contents.

vsb-p-video-prompting9.43 KB

View saved version →

---
name: vsb-p-video-prompting
renamed_from: p-video-prompting
description: >
  Canonical prompt-craft skill for Pruna AI's P-Video family in Visual
  Sandbox: `video/p-video`, `video/p-video-2` and `video/p-video-2-pro`. Read
  before writing any `vsb run video/p-video*` prompt — text-to-video,
  image-to-video, or audio-conditioned. Covers the official
  Pruna slot formula (Subject / Action / Scene / Camera / Lighting / Style /
  Audio), the image-to-video narration pattern for talking avatars, the
  DO/DON'T list straight from Pruna's docs, and the `prompt_upsampling`
  override rule. Pairs with [`vsb-p-video`](../vsb-p-video/SKILL.md) for runtime
  (draft toggle, pricing, schema, async + poll).
license: Proprietary. See LICENSE for the terms.
---

# P-Video prompting

Prompt craft only. For runtime, pricing, draft toggle, async + poll, read
[`vsb-p-video`](../vsb-p-video/SKILL.md) first. For universal video knobs (aspect,
duration, fps), read [`vsb-video`](../vsb-video/SKILL.md).

The slot formula below carries across all three generation tiers. Three
differences matter when you write for the newer two:

- **`video/p-video-2` speaks.** Write the dialogue into the prompt, in quotation
  marks, keep `save_audio` on, and attach no audio file. It generates the speech
  with matched lip movement. `video/p-video` has no native audio at all.
- **Both newer tiers take a `last_frame_image`.** Describe the motion that has to
  land on it, not just the opening.
- **`video/p-video-2-pro` renames the knob.** Its control is `prompt_upsampler`
  with `off`, `turbo` or `max`, not the boolean `prompt_upsampling` the other two
  carry. `video/p-video-2-pro` is silent, so leave the Audio slot out of its
  prompts entirely.

## Universal rules

1. **Describe motion, not stills.** "Waves crashing against rocky shore" ✓. "A rocky shore with waves" ✗.
2. **Dynamic verbs.** running, soaring, dancing, transforming, colliding. Skip "is moving", "was walking" — passive voice flattens motion.
3. **Direct language, not commands.** "Camera slowly pans across serene lake at sunset" ✓. "Please create a video of a lake" ✗.
4. **Specify timing.** slow-mo / gradually / building to crescendo / quick burst — pacing is half the shot.
5. **Atmosphere matters.** "Misty morning with soft diffused light" reads better than "morning scene".
6. **Iterate with `draft: true`.** Don't one-shot in standard mode. Land phrasing cheap, lock with `draft: false` + same seed.

## Pruna's official prompt formula

```
[Subject] [Action] [Scene] [Camera] [Lighting] [Style] [Audio]
```

First three required. Last four optional but lift quality fast. Pruna's own basic example:

> Knitted purple prune character, reading a book, cozy room, static camera, warm natural light, documentary style, character reading aloud: "P-Video generates state-of-the-art AI video in seconds."

Same shot, advanced (richer per-slot):

> Cinematic shot of a knitted purple prune character, dancing joyfully through a large pink and purple room, camera starting in front then circling and ending behind the character showing a race car, warm golden hour lighting creating soft illumination, cinematic animation style.

## Slot cheat sheet

| Slot | What to include | Strong values |
|------|-----------------|---------------|
| **Subject** | age, gender, clothing, features, posture (human) / breed, size, color, behavior (animal) / material, dimensions, condition (object) | "28yo brunette with shoulder-length wavy hair", "knitted purple prune character with white eyes" |
| **Action** | motion verb + tempo + direction + intensity + continuity | "running gradually building to a sprint, from left to right", "wakes up, then puts on glasses, then drinks coffee" |
| **Scene** | location + spatial elements + weather/time + depth | "intimate cozy room with wooden floor and window, warm afternoon", "vast neon-lit Tokyo crosswalk at midnight, light rain" |
| **Camera** | tracking / orbital / zoom / pan / tilt / aerial / handheld + speed | "tracking shot following cyclist", "slow push-in", "orbiting circling around subject", "handheld documentary shake" |
| **Lighting** | source + quality + direction + shadows + atmosphere | "warm golden hour backlight, soft shadows", "neon magenta and cyan rim, harsh contrast", "candlelight, dramatic flicker" |
| **Style** | medium + aesthetic + mood | "photorealistic cinematic", "anime cel-shaded", "documentary style realism", "3D rendered whimsical" |
| **Audio** | dialogue / music / SFX | `character reading aloud: "..."` / `upbeat electronic music` / `footsteps, ambient room tone` |

## DO / DON'T (from Pruna docs)

| ✅ DO | ❌ DON'T |
|------|---------|
| Descriptive, direct: "Camera slowly panning across a serene lake at sunset" | Command style: "Please create a video of a lake at sunset" |
| Motion focus: "Waves crashing against rocky shore with dramatic motion" | Static description: "A rocky shore with waves" |
| Specific timing: "Slow motion of water droplets splashing" | Ignore pacing: "Water splashing" |
| Include camera: "Tracking shot following the cyclist down the winding road" | Omit camera: "Cyclist on a road" |
| Dynamic verbs: running, flying, swimming, dancing | Passive: "A person who is moving" |
| Temporal: "Gradually, slowly, quickly, building to crescendo" | Ambiguous: "Scene changes" |
| Atmospheric: "Misty morning with soft diffused light" | Bare: "Morning scene" |

## Image-to-video — narration pattern for talking avatars

P-Video's strongest use case is image-to-video with audio. Pruna's own demos
follow this exact shape:

```
[Subject motion]. [Optional camera]. [Voice descriptor] says: "[Quoted dialogue]".
```

Real examples from Pruna's site:

> The woman in the photo moves naturally. She says: "Find the house that's perfect for you, and your family."

> The camera pans from left to right. A heavy, deep male voice says: "Motorcycles! Faster than ever before!"

> The man says: "You! Join the fight!" and he points to the screen.

Pattern parts:
- **Explicit motion** — "moves naturally", "looks at camera", "points to the screen". Don't assume the model fills idle frames; tell it.
- **Optional camera move** — "camera pans left to right", "slow push-in".
- **Voice descriptor** (optional) — "heavy deep male voice", "soft feminine voice", "kid's voice". Drives audio synthesis tone.
- **Quoted dialogue** — exact double-quoted string. Drives lip-sync. Never
  use em/en dashes (— –) inside it: speech synthesis vocalizes them as odd
  pauses or hallucinated words. Use a comma, period, or `...` instead (see
  the dialogue text rules in [`vsb-video`](../vsb-video/SKILL.md)).

## Audio block — three modes

P-Video's `audio` slot in the prompt drives native audio generation. Three patterns:

```
character reading aloud: "P-Video generates ten seconds of video in ten seconds for only ten cents."
```
```
upbeat electronic music, 120 BPM, building to drop
```
```
footsteps on wet pavement, distant traffic, ambient room tone
```

For premium dialogue / music, generate externally and pass via `--audio <url>` instead — P-Video's SFX synthesis is weak; the audio-input path is the workaround Pruna's docs explicitly recommend.

## prompt_upsampling — when to turn off

Default `prompt_upsampling: true`. The model rewrites your prompt internally for stronger adherence. Helpful for short prompts; harmful when:

- Lip-sync requires verbatim phrasing ("X says: '...'") — upsampling can rewrite the quoted line.
- Brand names / product names / unusual proper nouns — get paraphrased away.
- You spent five drafts dialing in a sentence and want it preserved.

`--prompt_upsampling false` to force literal interpretation.

## Bad → good

**Bad** (passive, no camera, no atmosphere):
> "person walks in city, talks to camera"

**Good** (slot formula filled):
> Young man in dark gray hoodie walking briskly down a rain-slick Tokyo alley at night, looking over his shoulder twice then directly into the camera. Camera tracks behind him at chest height, gentle handheld sway. Neon magenta and cyan reflections on wet asphalt, harsh rim light from a vending machine. Cinematic documentary style, slight film grain. Subject says: "I'm telling you — they're already here."

## Iteration discipline

Don't burn standard credits on prompt exploration. Cheap loop:

1. `draft: true`, short `duration: 3` — test motion idea, scene composition, camera move.
2. Vary one slot per turn. Compound edits drift like image models, only worse.
3. When the draft reads right, lock the `seed`.
4. Re-run **identical args** with `draft: false` and full `duration` for final.

A 5s 720p draft = $0.025. Standard = $0.10. Affording 10–15 draft variations costs less than two standard runs.

## Cross-links

- Runtime, draft toggle, pricing, schema → [`vsb-p-video`](../vsb-p-video/SKILL.md)
- Universal video patterns (async + poll, aspect-ratio cropping) → [`vsb-video`](../vsb-video/SKILL.md)
- For text-only video (no audio), other models may fit better — model picker is in [`vsb-video`](../vsb-video/SKILL.md)

## Pre-flight checklist

Before `vsb run video/p-video`:

1. Subject, action, scene all named? (Required three.)
2. Action uses a dynamic verb, not passive voice?
3. Camera move stated (even "static camera")?
4. For talking avatar: image attached + `aspect_ratio` matches image + quoted dialogue + voice descriptor?
5. `draft: true` for first 3+ runs?
6. `prompt_upsampling: false` if the dialogue must stay verbatim?
7. No em/en dashes inside the quoted dialogue? (Speech synthesis mangles them.)

All ✓ → run. Any ✗ → not ready.

Referenced files: 1 — file references are metadata, not package contents.

vsb-seedance16.4 KB

View saved version →

---
name: vsb-seedance
description: >
  Run the ByteDance Seedance family in Visual Sandbox well: `video/seedance-2.5`
  (up to 30 reference images, reference video and audio lists), `video/seedance-2`
  (1080p and 4K), `video/seedance-2-fast`, and `video/seedance-2.0-mini`.
  Trigger whenever the user is about to call `vsb run video/seedance-*`, names
  Seedance, or asks for reference-driven video: a cast held across shots, a
  motion copied from a clip, a voice from an audio file, a first and last frame,
  a video edit, or a video extension. Covers the flags, the reference numbering,
  the locked aspect and duration rules per task, the price jump for a reference
  video, and the safety filter. Prompt craft lives in
  [`vsb-seedance-prompting`](../vsb-seedance-prompting/SKILL.md). Pairs with
  [`vsb-video`](../vsb-video/SKILL.md) for the async + poll pattern.
license: Proprietary. See LICENSE for the terms.
---

# Seedance (ByteDance)

Four slugs share one input shape. Read the parent
[`vsb-video`](../vsb-video/SKILL.md) first for async + poll + cancel. Read
[`vsb-seedance-prompting`](../vsb-seedance-prompting/SKILL.md) before you write
the prompt. This page only adds what is specific to Seedance.

Verify the live schema and price before you assume anything:

```bash
vsb schema video/seedance-2.5 --json
vsb pricing video/seedance-2.5 --json
```

## Pick a tier

| Slug | Pick it for | Resolution | Duration | Aspect |
|------|-------------|------------|----------|--------|
| `video/seedance-2.5` | Hero shots, many references, native audio, clips up to 30 s. The slowest model in the catalog: about 4 minutes for a short clip, longer for a long one. Draft, then finish at 1080p. | 480p, 720p, 1080p | -1, 4 to 30 | 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive |
| `video/seedance-2` | 4K output, or 1080p with up to 15 s | 480p to 4K | -1, 4 to 15 | adds 9:21 |
| `video/seedance-2-fast` | Same inputs as `seedance-2`, faster and cheaper, 720p cap | 480p, 720p | -1, 4 to 15 | adds 9:21 |
| `video/seedance-2.0-mini` | Drafts and batches at the lowest rate. One reference video and one reference audio only. No adaptive. | 480p, 720p | 4 to 12 | no adaptive |

Iterate on Mini or Fast. Finish the chosen shot on 2.5, or on 2 when the
delivery needs 1080p or 4K.

## Inputs

Every tier takes the same fields:

| Field | What it is |
|-------|------------|
| `prompt` | Required. Put spoken lines in double quotes. |
| `image` | First frame. Pins the opening picture. |
| `last_frame_image` | Last frame. Needs `image`. Same aspect as `image`. |
| `reference_images` | JSON list of URLs. 2.5 takes up to 30, the 2.0 family fewer. Cast, wardrobe, product, set, style. |
| `reference_videos` | JSON list of URLs. Motion, camera, pacing, the clip to edit or extend. Raises the price. |
| `reference_audios` | JSON list of URLs. Voice timbre, dialogue, music, ambience. |
| `generate_audio` | Default `true`. Turn off for a silent shot. |
| `aspect_ratio`, `resolution`, `duration`, `seed` | Per tier, see the table above. |

Lists go in as JSON strings. Upload local files first; `vsb upload` accepts
image, video and audio.

```bash
CAST=$(vsb upload ./cast.jpg --json | jq -r '.url')
SET=$(vsb upload ./set.jpg --json | jq -r '.url')
MOVE=$(vsb upload ./move.mp4 --json | jq -r '.url')

JOB=$(vsb run video/seedance-2.5 \
  --prompt "$(cat prompt.txt)" \
  --reference_images "[\"$CAST\",\"$SET\"]" \
  --reference_videos "[\"$MOVE\"]" \
  --aspect_ratio 16:9 \
  --resolution 720p \
  --duration 8 \
  --async --json | jq -r '.job_id')
```

## Reference numbering

The prompt binds each asset by its position in its list. The first URL in
`reference_images` is `[Image1]`, the second is `[Image2]`. The first URL in
`reference_videos` is `[Video1]`. The first URL in `reference_audios` is
`[Audio1]`. Each list counts on its own.

`image` and `last_frame_image` are not in those lists. Do not number them.
The model pins them as the first and last frame from the field alone.

Write the binding in the prompt for every asset you attach. An asset with no
line in the prompt is a gamble on what the model takes from it.

## Locked parameters per task

The task decides which flags you still control. Get this wrong and the run
comes back cropped, stretched, or the wrong length.

| Task | How to run it | Aspect | Duration |
|------|---------------|--------|----------|
| Text or reference to video | prompt, optional reference lists | Free | Free |
| First frame, or first and last frame | `--image`, optional `--last_frame_image` | `adaptive`. The first frame sets the shape. Both frames must share one aspect or the last frame stretches. | Free |
| Edit a video | the clip in `reference_videos`, an edit verb in the prompt: add, remove, replace, modify, change to | `adaptive` | `-1`. The output matches the source length. Not on Mini, which has no `-1`. |
| Extend a video | the clip in `reference_videos`, an extend verb in the prompt: extend forward, extend backward, continue | `adaptive` | The length of the new segment. The output is the new segment only; join it to the source in an editor. |

A reference video with no edit or extend verb is a reference task. The model
takes motion, camera or pacing from it and generates a new picture at the
aspect and duration you set.

## Price

Billed per second of output. Two things move the rate: resolution, and
whether `reference_videos` is set. On 2.5 a reference video roughly
quadruples the per-second price. Read the live table, then tell the user the
number before you run:

```bash
vsb pricing video/seedance-2.5 --json
```

Start at 480p and 5 seconds while the prompt is still moving. Raise
resolution and duration once the take reads right, with the same `--seed`.

`-1` (Auto) is quoted at 30 seconds up front, because the model picks the
length and the credit gate cannot know it. The bill is repriced to the
delivered length when the clip lands. Tell the user the quote is a ceiling.

## Worked examples

All examples use `--async` and the poll pattern from
[`vsb-video`](../vsb-video/SKILL.md).

### First and last frame

```bash
A=$(vsb upload ./start.jpg --json | jq -r '.url')
B=$(vsb upload ./end.jpg --json | jq -r '.url')
JOB=$(vsb run video/seedance-2.5 \
  --prompt "The decorator rotates the turntable and pipes cream along both tiers, then moves both hands away from the cake." \
  --image "$A" --last_frame_image "$B" \
  --aspect_ratio adaptive --duration 6 \
  --async --json | jq -r '.job_id')
```

### Lip-sync a portrait to a recording

A photoreal face is refused unless the person is an AI-made character and you
pass `--ai_character true` (see Gotchas). For a real person, use
the paths in [`vsb-video` → People who talk](../vsb-video/SKILL.md#people-who-talk-pick-the-sound-path-before-you-spend).

```bash
FACE=$(vsb upload ./host.jpg --json | jq -r '.url')
LINE=$(vsb upload ./line.mp3 --json | jq -r '.url')
JOB=$(vsb run video/seedance-2.5 \
  --prompt "Use [Image1] for the host's face, hair and jacket; do not use the image background. Use [Audio1] as the spoken line; do not generate new dialogue. Locked-off medium shot, no camera movement. The host looks into the camera and delivers [Audio1] with small natural gestures." \
  --reference_images "[\"$FACE\"]" --reference_audios "[\"$LINE\"]" \
  --aspect_ratio 9:16 --duration 8 \
  --async --json | jq -r '.job_id')
```

### A talking AI presenter in one voice

The recipe for a presenter who says your line in your voice, clip after clip.
It was proven on a series of 15 s talking-head clips in October 2026.

1. **The face.** One portrait from `image/gpt-image-2.5-flare`, the shot you
   want: the framing, the room and the light. Use it in every clip. Ask the
   user to confirm that the person is AI-made before the first run.
2. **The line.** Speak it with `audio/elevenlabs-tts` in the voice you keep
   (`--my_voice`, see [`vsb-audio`](../vsb-audio/SKILL.md)). Keep it about
   10 s for a 15 s clip. Pad it: 1 s of silence before the line, then silence
   to the clip length, so the clip opens and closes on a quiet face.
3. **The clip.** The face in `--reference_images`, never in `--image`: a first
   frame cannot go with reference audio (E006). `--ai_character true` is
   required for the face to pass.
4. **The check.** Seedance can drop words. Transcribe the clip with
   `audio/scribe` and compare it with the line, word by word. Give the
   speech window about 1.5 s more than the line in the prompt, and write
   "says every word of [Audio1] in full and in order".
5. **The voice.** Seedance speaks [Audio1] again in its own voice. The words
   and the timing match the line, but the voice drifts from clip to clip. Run
   the clip through `audio/elevenlabs-voice-changer` with the same voice, then
   put that sound back on the clip with `tool/video-add-audio --mode replace`.
   The lips stay in sync, because the timing does not change.

```bash
JOB=$(vsb run video/seedance-2.5 \
  --prompt "Animate [Image1] exactly as it is framed: the same shot, the same angle and the same distance from the first frame to the last. He is [Image1]: his face, hair, clothes and the room behind him. Use [Audio1] as his spoken line; do not generate new dialogue. He says every word of [Audio1] in full and in order. His lips move in exact sync with [Audio1]. 0-1s: he is silent and looks into the lens. 1-12s: he says [Audio1] into the lens like a relaxed host, with small head movements. After his last word: he smiles and holds still. No subtitles. No music; only his voice and quiet room tone." \
  --reference_images "[\"$FACE\"]" --reference_audios "[\"$LINE\"]" \
  --ai_character true --aspect_ratio 16:9 --resolution 720p --duration 15 \
  --async --json | jq -r '.job_id')
# Wait for the job (see vsb-video), then take its video URL:
CLIP=$(vsb status "$JOB" --result --json | jq -r '.result.urls[0]')
NEW=$(vsb run audio/elevenlabs-voice-changer --audio "$CLIP" --my_voice narrator \
  --remove_background_noise true --json | jq -r '.result.urls[0]')
vsb run tool/video-add-audio --video "$CLIP" --audio "$NEW" --mode replace --json
```

### One realistic AI character across shots

Seedance refuses a photoreal face unless `--ai_character true` is on. Use this
recipe only for a character that an image model made. Ask the user to confirm
that the person is AI-made and that they hold the rights, before the first
run. Never use it with a photo of a real person.

1. Make the face. Load [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)
   first. A front-facing head-and-shoulders portrait, even light, plain
   background.
2. Make a full-body shot of the same character from the face, in the outfit
   the scenes need. Put the face in `--image_input`.
3. Run Seedance with both images as references and the flag on. Do not set
   `--image`: with the flag on, a first frame guides the face only.
4. For the next shot, keep the same two references and the same words for the
   character. Change only the action, the place and the camera.

```bash
FACE=$(vsb run image/gpt-image-2.5-flare --quality high --aspect_ratio 3:4 \
  --prompt "Photorealistic studio portrait of a woman in her late twenties, shoulder-length dark wavy hair, natural skin texture, navy t-shirt, front-facing head and shoulders, soft even light, plain light-grey background." \
  --json | jq -r '.result.urls[0]')
BODY=$(vsb run image/gpt-image-2.5-flare --quality high --aspect_ratio 9:16 \
  --image_input "[\"$FACE\"]" \
  --prompt "The same woman from the reference, full body, standing, front view, navy t-shirt, mid-blue jeans, white sneakers, plain light-grey background, photorealistic studio photo." \
  --json | jq -r '.result.urls[0]')
JOB=$(vsb run video/seedance-2.5 \
  --prompt "Use [Image1] for the woman's face and hair, and [Image2] for her body and outfit. She walks toward the camera on a sunny city street and smiles. Tracking shot at eye level." \
  --reference_images "[\"$FACE\", \"$BODY\"]" --ai_character true \
  --resolution 480p --duration 5 --aspect_ratio 16:9 \
  --async --json | jq -r '.job_id')
```

What the tests showed (2026-10-07, Seedance 2.5): without the flag, the
portrait and every character sheet with a face were refused on both
providers. A sheet with no face passed, but the model made up a new face. With
the flag, the portrait passed as a reference and as a start frame, and the
face matched in every frame.

### Edit a clip

```bash
CLIP=$(vsb upload ./ad.mp4 --json | jq -r '.url')
JAR=$(vsb upload ./cream.jpg --json | jq -r '.url')
JOB=$(vsb run video/seedance-2.5 \
  --prompt "Edit [Video1]: replace the perfume bottle with the face cream jar from [Image1]. Keep all original motion, camera work, lighting and audio. Except for the jar, every visible person, prop and background element in [Video1] remains unchanged." \
  --reference_videos "[\"$CLIP\"]" --reference_images "[\"$JAR\"]" \
  --aspect_ratio adaptive --duration -1 \
  --async --json | jq -r '.job_id')
```

### Extend a clip forward

```bash
JOB=$(vsb run video/seedance-2.5 \
  --prompt "Extend [Video1] forward. The first frame of the new segment continues from the last frame of [Video1]: same bee, same flower, same camera height, same light. Then the bee lifts off and the camera follows it to a second flower of the same kind." \
  --reference_videos "[\"$CLIP\"]" \
  --aspect_ratio adaptive --duration 5 \
  --async --json | jq -r '.job_id')
```

### Draft, then finish at 1080p

`--draft true` makes a quick 480p preview at the 480p rate. When a take is
right, pass its output URL as `draft_video`. The same video comes back at
1080p, with no prompt and no other input. A draft is good for seven days,
and only its owner can finish it.

```bash
DRAFT=$(vsb run video/seedance-2.5 \
  --prompt "$(cat prompt.txt)" \
  --duration 5 --draft true \
  --async --json | jq -r '.job_id')
URL=$(vsb status "$DRAFT" --result --json | jq -r '.result.urls[0]')

JOB=$(vsb run video/seedance-2.5 \
  --draft_video "$URL" \
  --async --json | jq -r '.job_id')
```

## Gotchas

- **1080p, Draft and `draft_video` run on fal.** 480p and 720p run on
  Replicate, at about half the fal rate. `ai_character` runs on Cloudflare,
  so it cannot combine with 1080p or Draft.

- **Safety filter (E005).** Seedance refuses "drone" and "FPV" wording; write
  "aerial flythrough". On `"Your input was flagged by the model's safety
  filter"`, retry the same inputs on `video/p-video` before you rewrite the
  prompt.
- **Photoreal faces are refused, unless `--ai_character true`.** ByteDance's
  real-face detector rejects any `image` or reference with a photoreal face,
  AI-generated faces included: "flagged as sensitive" on Replicate, "may
  contain real person" on Cloudflare. Character sheets with a face are refused
  too. A still with no face passes, but the model then invents the face.
  `--ai_character true` sends the images through ByteDance's virtual avatar
  library on Cloudflare, and a realistic AI face then passes and stays the
  same. It is a promise that every person in the images is AI-made and that
  the user holds the rights: never set it for a photo of a real person, and
  ask the user before you set it. With it on, `image` guides the face only and
  does not fix the opening shot, and Seedance 2.0 and 2.0 Fast cap at
  12 s. For
  people who talk, read [`vsb-video` → People who talk](../vsb-video/SKILL.md#people-who-talk-pick-the-sound-path-before-you-spend).
- **A first frame excludes every reference list (E006).** `image` and
  `last_frame_image` cannot go with `reference_images`, `reference_videos` or
  `reference_audios`. Pick one mode.
- **`reference_audios` needs a reference image or video.** It takes up to 10
  clips and 30 s in total on 2.5, and 3 clips and 15 s on 2.0. It is the
  lip-sync input, so it works only in reference mode.
- **`use_virtual_avatar`** on the Cloudflare copy of Seedance is the input
  behind `--ai_character`. Use the flag; there is no second input to set.
- **Subtitles appear uninvited,** more often in portrait. Add "No subtitles."
  to every prompt that does not want them. Strip text from reference assets
  first.
- **Multi-view character sheets** are fine on 2.5 and harmful on the 2.0
  family, where the model reads the views as several people. On 2.0 send one
  headshot plus one full-body photo.
- **Join seams on extensions.** Expect a small jump at the join. Trim about
  6 frames off the end of the source and 1 frame off the start of the new
  segment when you cut them together.
- **`adaptive` is not on Mini.** Match the source aspect by hand there.
- **The wait is real.** 2.5 runs about four minutes for a short clip and
  longer at 12 s. Say so before the user starts to watch the spinner.

Referenced files: 1 — file references are metadata, not package contents.

vsb-seedance-prompting11.5 KB

View saved version →

---
name: vsb-seedance-prompting
description: >
  Canonical prompt-craft skill for the ByteDance Seedance family in Visual
  Sandbox (`video/seedance-2.5`, `video/seedance-2`, `video/seedance-2-fast`,
  `video/seedance-2.0-mini`). Read before writing any `vsb run video/seedance-*`
  prompt: text-to-video, reference-to-video, first and last frame, keyframes,
  storyboard, blockout, video edit, audio edit, or video extension. Built from
  the official Seedance 2.5 and 2.0 prompt guides. Covers the prompt shape
  (summary, shot list, notes), the asset binding rule, timestamps versus shot
  numbers, camera language, action and emotion writing, sound notation,
  negative control, and the 2.0 versus 2.5 differences. Templates and worked
  examples live in [references/templates.md](references/templates.md).
  Runtime, flags and locked parameters live in
  [`vsb-seedance`](../vsb-seedance/SKILL.md).
license: Proprietary. See LICENSE for the terms.
---

# Seedance prompting

Prompt craft only. For the flags, the reference numbering, the locked aspect
and duration rules, and the price, read [`vsb-seedance`](../vsb-seedance/SKILL.md)
first. For the async + poll pattern read [`vsb-video`](../vsb-video/SKILL.md).

Treat Seedance as a visual content producer. Write the prompt as a shot list,
not as ad copy. The model splits the prompt into a spatial layer (who is in
the frame, where) and a temporal layer (what changes, in what order). Give it
both.

## The prompt shape

Four parts, in this order. Drop a part when the shot does not need it. Never
drop the summary.

1. **Asset bindings.** One line per attached asset: which asset, which
   subject, which attributes to take, which to ignore.
2. **One-sentence summary.** Subject + location + event + genre or style +
   camera.
3. **Shot list or timeline.** `Shot 1 / Shot 2` or `0-3s / 3-8s`. Each
   segment: camera, subject action and expression, position in the scene,
   sound.
4. **Notes that hold for the whole clip.** Camera angle, lighting, style,
   audio mode, constraints such as "No subtitles."

Example, text only:

> Realistic nature documentary style, natural light. Warm afternoon, a
> grassy slope in a forest, a chubby panda cub rolls down the hill.
>
> 0-3s: The cub lies on the slope and starts to roll sideways, bending the
> grass. Sunlight comes through the trees from the upper left.
> 3-8s: The cub rolls to the lower right of the frame and stops on its
> belly. It turns its round face to the camera and makes a soft hum.
>
> Low camera, slight handheld feel, follows the cub to the lower right.
> Shallow natural depth of field. Environmental audio only: wind, grass,
> the soft plop of the roll. No BGM. No subtitles.

## Asset bindings

Number assets by their position in each list: `[Image1]`, `[Video1]`,
`[Audio1]`. Bind every one in the prompt. Never rely on a name written on the
image itself; the model confuses or duplicates the character.

- **One line per subject.** "The knight is [Image1]." "The archivist is
  [Image2]." Never "Characters A and B are [Image1] and [Image2]."
- **Say what to take and what to ignore.** "Use [Image1] for the face, hair
  and apron; do not use the background." "Use [Video1] for the spell-casting
  action and the orbit camera move; do not use its character or scene."
- **Several images for one subject.** "Images 1 to 3 are one character: face
  from [Image1], wardrobe from [Image2], back view from [Image3]. One person,
  not three."
- **Pair voice with face.** "[Image1] is the host and speaks with the voice
  in [Audio1]."
- **When the asset already says it, stop describing.** "Strictly follow the
  actions and camera in [Video1], same order." Do not then narrate the raise
  of a hand and the turn; the restatement fights the clip.
- **Reference video with no role stated** lends motion, camera and pacing.
  It does not lend identity, wardrobe or the scene unless you say so.
- **Unused assets.** If an attached asset has no role, say so: "[Image4] is
  not used for people, scene, props, action or sound." Better: do not attach
  it.

Four to five assets is the sweet spot on the 2.0 family: one or two
character images, one scene image, one motion video, one audio. 2.5 holds
more, but stability drops past 8 subject images or 5 subject videos.

## Time control

2.5 reads integer-second timestamps. The 2.0 family does not; it reads shot
numbers only, and a forced timing there can break the clip. So:

- **2.5:** `0-3s ... 3-8s ... 8-12s`, or `[1s-4s]`, or a time point ("at the
  5-second mark, quick left wipe"), or relative time ("after 3 seconds,
  everyone shakes their head").
- **2.0, 2-fast, Mini:** `Shot 1 / Shot 2 / Shot 3`. Let the model pace it.

Keep the timeline continuous. `0-3s ... 5-6s` leaves a hole the model fills
on its own. Give each second-range one state change. Too little content and
the model improvises. Too much and it cuts or drops plot. Never use
timestamps for high-frequency motion such as "shakes head three times a
second".

## Camera

Plain terms work: extreme wide, wide, medium, medium close-up, close-up;
push in, pull out, pan, track, follow, orbit, tilt up, handheld; low angle,
overhead, first person. Named techniques work too: one-shot long take, dolly
zoom, FPV, bullet time, speed ramp.

- **One camera move per shot.** Push, pan and track in one shot makes the
  picture unstable.
- **Name the subject the move centres on**, where it starts and where it
  ends. "The camera orbits the girl from her left to behind the airplane."
- **Expand a rare term into its visible result.** "Rack focus: the sharp
  foreground trees blur while the woman in the background becomes sharp."
- **Transitions need a time and a method.** "At the 5-second mark, cut left
  with a wipe and a short dissolve."
- **Avoid "drone" and "FPV" in the prompt on Visual Sandbox.** The safety
  filter refuses them. Write "aerial flythrough".

## Action and expression

- **General first, detail for the memorable beat.** "Several sets of
  high-knee raises and a somersault" beats a list of every rep. Write exact
  detail only for the one or two actions the shot is about.
- **Body part + range + speed.** "Slowly raises the right hand", "quickly
  turns the head", "pushes hard off the ground".
- **Slow, small, continuous beats big and fast.** Sprints, big jumps and
  violent rolls break more often than a walk, a lean or a turn.
- **Link actions.** "Uses the momentum of the turn to raise a hand."
- **Emotion as visible behaviour,** not as a label. Not "very sad" but
  "lowers the head, shoulders tremble, eyes redden, fingers clutch the hem".
  Pick two or three cues, not all of them.
- **Cause before reaction.** Show the trigger, then the face. Do not cut to
  the reaction close-up before the audience saw what caused it.
- **Spatial relations against fixed objects.** "Inside the counter, facing
  out", not "on the left of the screen".

## Sound and dialogue

Seedance generates audio with the picture. Say what you want or you get the
model's guess.

- **Dialogue in double quotes,** one speaker per line: `The man stops and
  says: "Remember this moment."` Mark the language when it is not obvious:
  `says in Japanese "こんにちは"`. Keep one language per clip.
- **No dashes inside the quotes.** Speech synthesis mangles them. Comma,
  period or `...` instead. Rule in [`vsb-video`](../vsb-video/SKILL.md).
- **Other speakers keep their mouths closed** while one talks. Say it.
- **Name the sources:** "Environmental audio only: wind, footsteps." "Soft
  piano in the background." "A bell rings in the distance."
- **The BytePlus markers** are optional structure the model also reads:
  `()` music, `<>` sound effect, `{}` dialogue, `【】` subtitle. If you use
  `<>` for effects, do not also wrap names in angle brackets.
- **Reference audio** gives timbre, accent, pace and emotion by default. If
  the written line and the recording differ, the written line wins unless
  you say "use the dialogue in [Audio1]".

## Negative control

Negatives work for two things only: subtitles and audio. Everything else,
write the positive.

- "No subtitles." "Do not add any text or subtitles." Portrait clips grow
  subtitles more than landscape.
- "No BGM; environmental and action sounds only." "No audio."
- "No watermark. No logo." when a reference carries one.
- Style drifts toward the reference. A realistic photo plus an anime request
  needs "2D Japanese anime style" stated, or convert the reference first.

## Task recipes

Each task has a template in [references/templates.md](references/templates.md).
The rules that matter most:

- **Reference to video.** Bindings, then summary, then shots. Adaptive
  aspect when the references should set the shape.
- **First and last frame.** Pin them with the fields, not the prompt. Then
  one continuous action that starts at the first frame and arrives at the
  last.
- **Keyframes.** Several stills as `reference_images`, first sentence "Use
  Images 1 to N in order as keyframes." The picture follows them closely.
- **Storyboard grid.** One image with up to 15 line-art panels. Bind it as
  "shot order and rough composition; do not adopt the sketch style or the
  text". The video follows it loosely. Need strict? Use keyframes.
- **Blockout (3D clay model video).** Coarse geometry gives camera, blocking
  and timing. Map each blob to a subject: "the red model in [Video1] is the
  man from [Image1]". Fine blockout: "Render [Video1]", keep structure,
  replace materials, scene and style.
- **Edit.** Name the one master video and the edit verb. Scope it: what
  changes, from A to B, when. Close the scope: "Except for the objects named
  above, every visible person, prop and background element in [Video1]
  remains unchanged." For a moving object, "inherits the same timing, path,
  speed and occlusion as the original".
- **Audio edit.** Same master rule. "Change only the man's line to 'Do not
  come here', American accent; keep every other sound, the lip timing and
  all visuals."
- **Extend.** "Extend [Video1] forward" or "backward". Describe the boundary
  frame first: pose, props, camera, light, audio state. Then the new event.
  The subject stays one continuous object; no duplicate, no swap. Backward:
  end the new segment on the source's first frame and keep later props out.
- **One-click reel.** Many stills, "turn all images into a short vlog,
  live-photo motion only, do not alter the images".
- **Seamless transition.** Two clips: describe the camera move that carries
  [Video1] into [Video2] and what morphs during it.

## 2.0 versus 2.5

| | 2.0 family | 2.5 |
|---|---|---|
| Timing | Shot numbers only | Integer-second timestamps |
| Character sheets | One headshot + one full body; multi-view reads as several people | Multi-view fine up to 5 subjects |
| Face drift fix | Separate headshot, large face, listed first | Same, less needed |
| Reference count | 4 to 5 assets | Up to 50, stable to about 8 images |
| Aspect | Fixed enums | Adaptive follows the assets |

## Pre-flight checklist

Before `vsb run video/seedance-*`:

1. Every attached asset has one binding line with take and ignore?
2. Summary sentence names subject, place, event, style, camera?
3. Shots use timestamps on 2.5, shot numbers on 2.0?
4. One camera move per shot, with a named subject?
5. Actions are body part + range + speed, emotions are visible cues?
6. Dialogue in double quotes, no dashes inside, one language?
7. "No subtitles." present when subtitles are unwanted? Audio mode named?
8. Edit or extend prompt names the master video and its verb, and closes
   the scope?
9. No "drone" or "FPV" wording?
10. Nothing about aspect, resolution or total duration in the prompt? Those
    are flags.

All yes: run. Any no: not ready.

Referenced files: 2 — file references are metadata, not package contents.

vsb-setup4.83 KB

View saved version →

---
name: vsb-setup
description: >
  Get a new person started with Visual Sandbox over the MCP tools, in ChatGPT,
  Claude or any other chat client, one step per message. Trigger right after
  the plugin is installed, when the person says "set up Visual Sandbox",
  "what can you make", "get started", "how does this work", or when the first
  tool call says the account is not connected. Checks the connection, turns
  the person's photos, product, logo or website into saved anchors, makes one
  preview keyframe, a first 5-second video and three voice takes, then opens
  the canvas. Every step is optional. Uses the MCP tools only, never a shell.
license: Proprietary. See LICENSE for the terms.
---

# Get started with Visual Sandbox

Visual Sandbox runs image, video, audio, 3D and vector models on the person's
account. Each run is charged to that account. This skill takes a new person
from "installed" to a first video they like.

**One step for each message.** Do one step, show the result, and ask one
question. Every step is optional: when the person wants to skip one, skip it.
Use the MCP tools only. There is no shell and no CLI on this surface.

## 1. Check the connection

Call `get_balance`.

- **It answers.** The account is connected. Go on.
- **It refuses with "Authorization is required".** Tell the person to open
  the plugin settings, press Connect and sign in to Visual Sandbox. Stop until
  they come back.
- **It refuses with `email_unverified`.** Tell the person to confirm the
  address from the Visual Sandbox email. Generation waits until they do.

When `balance_usd` is zero, say that runs are charged to the account and that
credit is added on visualsandbox.com. Do not quote a price for credit, and do
not offer to buy it.

## 2. Ask what to make first

Say in one sentence what the plugin makes: videos, ads, voiceovers, product
photos, music, logos and pages. Then ask what they want to make first, and
ask for what they have:

- a photo of themselves or of a person in the work,
- a product photo,
- a logo or a website,
- a script or an idea.

## 3. Turn their files into anchors

An anchor is saved once and used in every later run. Make only the ones that
the work needs.

- **A file in the chat** goes to `upload_media` as `file`. In ChatGPT, when
  nothing is attached, `import_media` opens a card where they pick a file.
- **A person or a mascot:** a character sheet, then `create_character`. See
  [`vsb-characters`](../vsb-characters/SKILL.md).
- **A website or a logo:** a brand theme with `extract_theme`, checked by the
  person before it is saved. See
  [`vsb-library-themes`](../vsb-library-themes/SKILL.md).
- **A product photo:** name it with `update_asset`, such as `acme-mug`, so
  later runs find it.

## 4. One preview keyframe

Make one image at the shape of the video they want (9:16 for Reels and
TikTok, 16:9 for YouTube). It is the first frame of the video. Read
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) before the prompt.
Use the anchors as references.

Show it and ask: "Is this the look?" Change it until they agree. Make no
video before they agree.

## 5. A first 5-second video

1. Pick the cheapest video model that takes a first frame. Call
   `estimate_cost` and say the price.
2. Run it with the approved keyframe as the first frame, 5 seconds, at the
   keyframe's shape. Read [`vsb-video`](../vsb-video/SKILL.md) for the model.
3. In Review mode a run of 10 cents or more waits for the person's press of
   Generate: show the card, or give the review link. Never press for them.

## 6. Three voices, while the video renders

Ask whether the work needs a voice. When it does:

1. Run `audio_elevenlabs_voice_design` with a description of the voice,
   `auditions: 3`, and a sample line of 100 to 400 characters in the
   person's own words. That costs a few cents.
2. In ChatGPT, call `voice_studio` with the job id. The person hears the
   three takes and saves one to a character. Elsewhere, give the share link
   and ask which take they like.

## 7. Offer what is still missing

Offer one of these, the one that fits the work:

- their own likeness, from a photo of them,
- their own voice, cloned with their consent
  ([`vsb-clone-voice`](../vsb-clone-voice/SKILL.md)),
- a brand theme, when the work is for a brand.

## 8. Open the canvas

Give the `sandbox_url`. Every result is on that canvas. Then suggest two or
three next prompts that build on what they made, such as "Make this into a
10-second ad with a voiceover" or "Cut three vertical clips from my podcast".

## Rules

- Every run costs money. Never run twice to check a result you have:
  `get_job` returns the image, or four frames of a video.
- One request is one paid attempt. Ask before another take.
- Send the same `sandbox_name` on every run in the setup.
- When a run is refused for want of credit, say the balance is too low, and
  follow the server instructions about credit.

Referenced files: 1 — file references are metadata, not package contents.

vsb-short-film4.48 KB

View saved version →

---
name: vsb-short-film
description: >
  Make a short film of several shots inside a chat with the Visual Sandbox
  MCP tools: a story of 15 to 60 seconds with the same character in every
  shot, kept as a film project between chats, joined into one cut with a
  narration and a music bed. Trigger when the person asks to "make a short
  film", "turn this script into a video", "a story with several scenes", "a
  trailer", "a music video", "a multi-scene ad", "an episode with my
  character", or "continue my film". Plans the shots, gives the total price
  once, approves the keyframes before any video, then assembles the cut.
  Uses the MCP tools only; for the CLI, read vsb-film.
license: Proprietary. See LICENSE for the terms.
---

# A short film

A short film is 3 to 8 shots of about 5 seconds, one character, one shape. A
**film project** keeps the brief, the script, the shot list and where each
shot stands, so the work goes on in a later chat. Video costs many times more
than an image, so every shot is approved as a keyframe before it moves.

## 0. Find or start the film

Call `list_film_projects`. When the person continues a film, `get_film_project`
returns everything, and you go on from its `stage`. Otherwise ask for the
brief (step 1), then call `create_film_project` with the `name`, the `brief`,
the `aspect_ratio`, `target_seconds` and the `characters`. It opens the
film's canvas and writes the plan there as `FILM.md`. **Send its
`sandbox_uuid` with every run of the film.**

## 1. Get the brief

Ask only for what is missing, in three questions or less.

- **The story** in one or two sentences, or the person's script.
- **The length:** 15, 30 or 60 seconds. Default: 30.
- **The shape:** 9:16 for a phone, 16:9 for a screen. Default: 16:9.

## 2. Write the script and the shot list

One row for each shot, one action for each shot. Write spoken lines without
dashes, and write numbers as words.

Save them with `update_film_project`: `script`, and `shots` with an `id`
(`shot-1`), a `title`, the `script` line and the `prompt`. When the person
approves them, set `stage: "script_ok"`.

## 3. Give the total price once

Call `estimate_cost` for one keyframe, one clip of each length you plan, and
the narration. Multiply by the number of shots and give one total. Wait for
the person's yes. Spend nothing before it.

## 4. Make the character

Read [`vsb-characters`](../vsb-characters/SKILL.md). Make one character sheet,
save it with `create_character`, and pass its pictures as references to every
keyframe. A product or a place can be a character too.

## 5. One keyframe for each shot

1. Read [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md).
2. Make one keyframe for each shot, at the film's shape, with the character
   pictures as references. Say first how many images that is.
3. Call `show_media` with the keyframe job ids. Change a keyframe until the
   person approves the set.
4. Record each approved keyframe with `update_film_project`, `patch_shots`:
   `storyboard_url` and `approved: true`. Then `stage: "storyboard_ok"`.

## 6. Move each shot

1. Read [`vsb-video`](../vsb-video/SKILL.md) at "Picking a model" and, for
   spoken lines, "People who talk".
2. Run one image-to-video job for each shot, with its keyframe as the first
   frame. In Review mode each run waits for the person's press: show the card
   or give the review link.
3. Record each job at once with `patch_shots`: `job_id`. The shot's
   `clip_url` fills in by itself when the job is done.

## 7. Narration, music and the cut

1. A narration: speak the film's script with `audio_elevenlabs_tts`, in the
   character's voice when it has one. Save it with `narration_url`.
2. A music bed (optional): make one with [`vsb-audio`](../vsb-audio/SKILL.md)
   and save it with `music_url`.
3. Call `assemble_film`. It joins every shot in order and lays the sound:
   `audio_mode: "narration"` replaces the shots' sound with the narration,
   `"mix"` lays it over, `"shots"` keeps the shots' own sound. The music
   drops while the narration speaks. `transition`: `cut` for pace, `fade` for
   calm. Free.
4. Call `show_media` with its job id. Set `stage: "preview_ok"`. After the
   person's last changes, set `stage: "final"`.
5. Captions, when the film has speech: `add_captions` on the cut.

## Rules

- No video run before the keyframes are approved.
- Move the `stage` on only when the person approved that step.
- Never run a clip twice to check it. Read the four frames `get_job` sends.
- A shot that fails costs nothing. Fix that shot only, then assemble again.

Referenced files: 1 — file references are metadata, not package contents.

vsb-swap-motion3.34 KB

View saved version →

---
name: vsb-swap-motion
description: >
  Put a different person or character into an existing video, or make a
  photo move the way a person moves in another video, with the Visual
  Sandbox MCP tools: P-Video Replace swaps the subject, Kling 3.0 Motion
  Control and P-Video Animate copy a motion onto a photo, and Kling 3.0 Omni
  restyles or edits a reference video. Trigger when the person asks to
  "swap the person in this video", "put me in this clip", "replace the
  actor", "make my character do this dance", "copy this motion", "motion
  transfer", "restyle this video" or "change what is in this video".
  Consent first, a still check before the paid run. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Swap a character, copy a motion

Three jobs, three model families. Read each model's form in its tool before
the first run; the field names below are the ones to look for.

| The person wants | Tool | Inputs |
|------------------|------|--------|
| A new person in their video, with its motion and sound | `video_p_video_replace` | `video` (the source), `images` (1 to 3 photos of the new person), `instruction_prompt`, `resolution` |
| A photo that moves like a person in another video | `video_kling_v3_motion_control` | `image` (the character), `video` (the driving motion, 3 to 30 s), `prompt` |
| The same, at a lower price | `video_p_video_animate` | `image`, `video`, `instruction_prompt`, `resolution` |
| A video restyled, or edited in place | `video_kling_v3_omni_video` | `reference_video`, `video_reference_type` (`base` edits it, `feature` takes only its camera and style), `reference_images`, `prompt` |

## Before any run

1. **Consent.** Put a real person into a video only when that person agreed
   to it. Ask when you are not sure. Never put a real person into a scene
   that harms or mocks them.
2. **The rights to the source.** The source video must be the person's own,
   or one they have the right to use.
3. **Get the files.** A file attached in the chat goes to `upload_media` as
   `file`. A public video page goes to `download_video`. Use the URLs they
   return.
4. **Check the price.** P-Video Replace and Animate reserve 30 seconds of
   output and refund the rest; Motion Control reserves its longest length.
   Call `estimate_cost`, and say the price.

## Check the look first

A swap fails when the new person's photos do not match the source's light
and angle.

1. Take a frame of the source with `extract_frame`.
2. Make one still of the new person in that frame with an image model and the
   frame as a reference ([`vsb-image`](../vsb-image/SKILL.md)). It costs a few
   cents.
3. Show the still. When the person agrees, use it as one of the `images`, or
   as the `image` for a motion copy.

## Write the instruction

Name each person by what the camera sees, in one line each:

- "The woman on the left in the black top becomes the person in the photos."
- "The dancer keeps every step; the character's outfit stays as in the photo."

Keep the scene, the camera and the sound unless the person asks to change
them.

## Run, then hand over

1. Run the tool once. In Review mode the run waits for the person's press:
   show the card or give the review link, and wait.
2. Call `show_media` with the job id in ChatGPT, or `get_job` when the
   job is done.
3. Look at the frames. Say what is wrong before you offer another take.

Referenced files: 1 — file references are metadata, not package contents.

vsb-talking-avatar2.79 KB

View saved version →

---
name: vsb-talking-avatar
description: >
  Make a talking video from one photo and a script with the Visual Sandbox
  MCP tools: a presenter, a spokesperson, an AI avatar or a person who reads
  a line, with the lips in time with the words. Trigger when the person asks
  to "make this photo talk", "create my avatar", "a talking head", "an AI
  spokesperson", "a presenter video", "lip sync this picture", or "make me
  say this". Picks a stock voice, an ElevenLabs voice or the person's cloned
  voice, asks for the price first, and shows the video in a card. Uses the
  MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# A talking avatar

One photo and one script make one talking video. The length of the video is
the length of the speech. Use the MCP tools only.

## 1. Get the photo and the words

- **The photo:** one face, looking at the camera, in good light. A photo
  attached in the chat goes to `upload_media` as `file`.
- **Consent:** use a photo of the person who asks, of a made-up character,
  or of somebody who gave permission. Do not make a real person say words
  they did not agree to.
- **The script:** about 2.5 words fit in one second. Write it without
  dashes, and write numbers as words. Show the script before you spend.

## 2. Pick the voice

| The person wants | Do this |
|------------------|---------|
| Any good voice | `video/p-video-avatar` with `voice_script` and a `voice` from its list |
| A natural ElevenLabs voice | `list_voices`, then `audio/elevenlabs-tts` with `voice` |
| Their own voice | Read `vsb-clone-voice`, then `audio/elevenlabs-tts` with `my_voice` |

For the second and the third row, make the speech first. Its result URL is
the `audio` of the video run.

## 3. Pick the video model

- **`video/p-video-avatar`:** the default. It takes `voice_script` and a
  stock `voice`, or an `audio` track. Use `video_prompt` for how the person
  moves, such as "nods and smiles while talking".
- **`video/kling-avatar-v2`:** for a sharper, more expressive face. It needs
  an `audio` track of 5 MB or less. `mode: "std"` first, `"pro"` for a final
  cut.

Read [`vsb-video`](../vsb-video/SKILL.md) with `get_skill`, at "People who
talk", when the person wants a full scene and not only a face.

## 4. Price, run, show

1. Call `estimate_cost`. Tell the person the price and wait for their yes.
2. Run the speech first, if the voice needs it. Then run the video model
   with the photo and the speech. In Review mode, a run of 10 cents or
   more waits for the person's press of Generate: show the card with
   `show_media`, or give the review link. Never press it for them.
3. Call `show_media` with the job ids. Give the `sandbox_url`.

## Rules

- One video per script. Change the script, not the model, when a word
  sounds wrong.
- For captions or a vertical cut, read `vsb-edit-video`.

Referenced files: 1 — file references are metadata, not package contents.

vsb-ugc-ad3.9 KB

View saved version →

---
name: vsb-ugc-ad
description: >
  Make a creator-style UGC video ad for a product with the Visual Sandbox MCP
  tools: a vertical 9:16 clip of one person who shows, uses or talks about
  the product, for TikTok, Instagram Reels or YouTube Shorts. Trigger when
  the person asks for a "UGC ad", "creator ad", "testimonial video",
  "unboxing video", "talking head ad", "TikTok ad for my product" or "a
  person holding my product". Plans a script, approves the first frame as an
  image, then animates it with speech. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# A UGC video ad

A UGC ad looks like a real person filmed it on a phone. One person, one
product, one message, 9:16, 5 to 15 seconds. The person talks to the camera,
or shows the product while a voice talks.

Use the MCP tools only. Video is the expensive part, so this skill makes the
look right as a still image first, and spends on video once.

## 1. Get the brief

Ask only for what is missing. Never ask more than three questions.

- **The product.** A photo is best. Pass a public `https` URL. Give a photo
  attached in the chat, or a local file, to `upload_media` first.
- **The hook.** The one thing the ad must say, such as "it fixed my skin in
  a week".
- **The format.** Pick one: a talking head, a selfie walk-and-talk, an
  unboxing, or a before-and-after. When the person does not say, use a
  talking head.

## 2. Write the script

Write 2 to 4 short lines that one person can say in the clip length. About
2.5 words fit in one second.

- Line 1 is the hook. It names the problem or the result.
- The middle line shows the product in use.
- The last line tells the viewer what to do.

Do not use dashes in the spoken lines. A speech model reads a dash badly.
Write numbers as words. Show the script to the person before you spend.

## 3. Make the first frame as an image

1. Read [`vsb-ugc-people`](../vsb-ugc-people/SKILL.md) with `get_skill`. It
   has the two UGC formats, the slot order and the words that stop a face
   from looking plastic.
2. Call `list_models` with `modality: "image"` and pick the model that pack
   recommends. Call `get_model` for its fields.
3. Call `generate` at 9:16 with the product photo as a reference. Name the
   work with `sandbox_name`, such as "Serum UGC ad", and send that same name
   on every later call.
4. Call `get_job` after `eta_seconds` and look at the frame. Check the hands,
   the label on the product and the face.
5. Show the frame. Change it until the person approves it.

## 4. Animate it with speech

1. Read [`vsb-video`](../vsb-video/SKILL.md) with `get_skill`, at the section
   "People who talk". It says which model speaks in one pass and which needs
   a voice track first.
2. Call `list_models` with `modality: "video"`. Pick an image-to-video model
   that speaks, from that section.
3. Call `get_model`, then `estimate_cost`. Tell the person the price and wait
   for a yes.
4. Call `generate` with the approved frame's URL as the start image and the
   script as the spoken line.
5. Call `get_job` after `eta_seconds`. A video takes one to three minutes.
   `get_job` returns four frames, so check them before you report.

When the person wants one voice across several clips, make the voiceover
first with [`vsb-audio`](../vsb-audio/SKILL.md) and follow the lip-sync path
in `vsb-video`.

## 5. Hand it over

Give the person the `share_url` of the video and the `sandbox_url` of the
canvas. Offer one next step: a second hook on the same frame, which costs one
video run and no new image.

## Rules

- One approved frame before any video run. A video costs many times more
  than an image.
- Never run a video twice to check it. Read the four frames `get_job` sends.
- Keep the product label sharp. If the model changes the label, say so and
  offer a new frame, not a new video.
- When a run is refused for want of credit, say the balance is too low, and
  follow what the server instructions say about credit.

Referenced files: 1 — file references are metadata, not package contents.

vsb-ugc-people9.61 KB

View saved version →

---
name: vsb-ugc-people
renamed_from: ugc-people
description: >
  Prompt AI image models to produce UGC-style photos of a single person for
  9:16 paid ad creative (TikTok, Reels, Shorts). Covers two distinct formats —
  selfie / phone-camera POV (handheld, hook shot) and locked-off talking-head
  testimonial (single frame meant to look like a real creator on a tripod).
  Trigger when the user asks for "UGC ad", "AI influencer photo", "creator
  selfie", "talking head ad frame", "ugc style", "fake iPhone photo of a
  person", or is about to run `vsb run image/<slug>` for a person shot
  intended to look like real UGC. Pairs with the `vsb-nano-banana` skill for
  prompt craft and the `vsb-image` skill for picking a model.
license: Proprietary. See LICENSE for the terms.
---

# UGC people for 9:16 ad creative

UGC ads succeed when the image **does not look AI**. Modern image models are
biased toward "professional photography" — clean studio light, smooth skin,
symmetric framing. UGC is the opposite: phone camera, imperfect, candid. Every
rule below pushes the model away from its default and toward something a real
creator would post.

Two formats, two rule sets. Pick one before you start writing the prompt.

## The two formats

| | **Selfie / phone POV** | **Talking-head testimonial** |
|---|---|---|
| Use case | Hook shot, scroll-stopper, "real moment" frame | Testimonial frame, problem/solution, single-take ad |
| Camera | Handheld, micro-jitter | Locked-off, no pan, no zoom |
| Eye line | Slightly **above** subject's eye, angled down | Subject's eye level, dead-on |
| Framing | Medium close-up to mid-shot, off-center OK | Medium close-up (chest to head), centered |
| Distinctive cues | Hand holding phone in frame, mirror, reflection | Static room, single light source, gesture script |
| Full rules | `references/selfie-pov.md` | `references/talking-head.md` |

Open the matching reference file before writing the prompt — it has the filled
template, the verbatim worked examples, and the failure modes specific to that
format.

## The slot order

Every working UGC people prompt fills these slots, in this order. Skipping a
slot lets the model default to its training median (stocky, smooth, posed).

1. **Device token** — `"shot on iPhone 15 Pro, front camera feel, not DSLR,
   not studio quality"`. See § "The iPhone token rule" below.
2. **Subject** — age range, ethnicity, build, mood. Specific: `"early-to-mid
   20s, mixed-heritage, freckles across nose, messy low bun"`. Not: `"a young
   woman"`.
3. **Wardrobe** — named items, fit, fabric: `"short-sleeve beige ribbed
   scoop-neck top"`, `"white fitted long-sleeve crop top, light blue
   leggings"`.
4. **Action** — micro-gesture, mid-action. `"tucking hair behind ear"`, `"hand
   loosely near chin"`, `"mid-laugh, phone held naturally"`. Static A-pose
   kills UGC realism.
5. **Location** — specific room, time of day, named light source: `"open
   luxury kitchen interior, polished concrete flooring, morning light from
   window camera-left"`.
6. **Lighting** — soft, natural, named. Two light sources (window + lamp) is
   a high-fidelity trick. Never `"studio lighting"`.
7. **Camera artifacts** — `"slight motion softness, mild grain, natural
   exposure, slight lens distortion from front camera, no HDR or cinematic
   color grading"`.
8. **Vibe line** — one sentence at the end: `"candid, unposed, natural
   lifestyle selfie — looks like a quick real moment, not staged or
   professionally shot."`

Then end with a negative block (see § "What to avoid").

## The iPhone token rule

The single most load-bearing line in any UGC prompt:

```
shot on iPhone 15 Pro, front camera feel, not DSLR, not studio quality
```

Why it works: every image model has been trained on millions of photos
tagged `iPhone`. The token activates the right cluster of training data
(short focal length, front-camera color science, soft auto-HDR look) and
the `not DSLR, not studio quality` half explicitly suppresses the
professional-photography cluster the model defaults to.

Variants in the wild: `"iPhone 13 front camera feel"`, `"iPhone 16 Pro
aesthetic"`, `"iPhone 17 Pro Max Simulation"`, `"matte white iPhone with the
recognizable triple-camera lens module"` (when the phone is *in* frame for a
mirror selfie). Pick a model — don't say just `"smartphone"`.

If the skill outputs only one fragment right, it must be this one.

## Anti-plastic vocabulary

Use *inline*, in the positive prompt — not just in the negatives. These cues
push the model toward textured human realism.

**Skin texture:**
`visible pores`, `peach fuzz catching the light along the jawline`,
`faint freckles across the bridge of the nose`, `subsurface scattering`,
`natural skin texture, no beauty filter`, `slight blemish on the cheek`.

**Imperfection:**
`slight motion blur`, `mild grain`, `slightly off-center framing`,
`slightly overexposed in the bright spots`, `slight lens distortion`,
`framing feels candid and handheld`.

**Hair:**
`slightly grown-out roots`, `natural baby hairs visible at the hairline`,
`messy unstyled strands`, `slightly frizzy`.

**Micro-expression:**
`caught laughing at something real`, `mouth slightly open mid-sentence`,
`small natural smile, not posed`, `relaxed brows`, `neutral, slightly serious
expression`.

## What to avoid

Words that drag the model toward stock-photo / glamour-magazine output. Do
**not** put these in the positive prompt — modern image models pattern-match
on language regardless of sign.

- `professional photography`, `studio lighting`, `8K masterpiece`,
  `hyperdetailed`, `sharp focus`, `cinematic` (unless you genuinely want
  cinematic)
- `beautiful`, `perfect`, `flawless`, `stunning` — bias toward glamour
- `model`, `supermodel`, `actress`, `influencer` as the *only* subject
  descriptor — biases toward posed studio energy. Replace with a concrete
  demographic.
- Pro camera brand names: `Hasselblad`, `Sony A7`, `shot on RED`
- `HDR`, `color graded`, `cinematic color grading`

Append a negative block at the end of every UGC prompt. Standard template:

```
Negatives: professional photography, studio lighting, sharp focus,
airbrushed skin, perfect composition, cinematic, 8k, masterpiece,
plastic skin, over-smoothed texture, distorted hands, extra fingers,
warped iPhone shape, harsh flash, harsh studio lighting, glamour
magazine aesthetic, oversaturated colors, mannequin face,
AI-perfect frozen expression, watermarks, captions, on-screen text.
```

For multi-shot work, also add: `face drift between shots, altered eye
shape, altered freckle placement, hair change, outfit change`.

## Model picker

Verify the live catalog: `vsb models --modality image --json | jq '.models[]
| {slug, category, owner}'`.

| Need | Default slug | Why |
|------|--------------|-----|
| Raw UGC realism, single shot | `image/gpt-image-2` | OpenAI. Current operator favourite for photoreal humans. Accepts negative-energy prompts ("failed candid shot") well. Weaker text rendering. Tiered cost — `Low quality` ≈ $0.006, `High quality` ≈ $0.21. |
| Multi-shot character consistency, text/product label legibility | `image/nano-banana-pro` | Google. Strongest prompt adherence + text. Polished-AI baseline — needs explicit anti-plastic cues. Tiered cost — 1K/2K ≈ $0.15, 4K ≈ $0.30. |
| Quick, cheap, good enough | `image/nano-banana` | Same family, fastest + cheapest among nano-banana tier. Flat ≈ $0.05. |
| Cheapest UGC-viable, gen + edit | `image/flux-2-klein-9b` | Black Forest Labs. Flat ≈ $0.02 gen / $0.04 edit-with-ref. Identity holds well across a single edit cycle. Best test-bench default; bump to nano-banana-pro if you need multi-shot consistency. |

For Midjourney and Higgsfield-style preset notes, see
`references/model-notes.md`.

Always verify the live schema: `vsb schema image/<slug> --json`. Read
per-slug help with `vsb run image/<slug> --help` — it surfaces array-typed
inputs (e.g. `image_input: array<string>`) and string-enum quirks
(`output_megapixels: '1'` is a string Literal, not the int 1).

**Reference-image CLI shape.** `--image_input` is array-typed. Pass either
repeated (`--image_input a --image_input b`) or as a JSON-array string
(`--image_input '["a","b"]'`). A single bare `--image_input <path>` is
auto-wrapped, but multi-ref calls need the repeated or JSON form.

## Quick start

A minimal selfie-POV run:

```bash
vsb run image/nano-banana-pro \
  --prompt "Shot on iPhone 15 Pro, front camera feel, not DSLR, not studio
quality. Mid-20s mixed-heritage woman, natural skin texture with visible pores
across the T-zone and faint freckles across the bridge of the nose, messy low
bun with baby hairs at the hairline. Short-sleeve oversized cream sweater.
Mid-action: tucking hair behind her ear with her left hand while looking
softly toward the lens, mouth slightly open mid-sentence. Open kitchen
interior, polished concrete floor, morning light from a window camera-left,
warm lamp on a shelf behind her adding a soft glow. Slight motion softness,
mild grain, slight lens distortion from front camera, slightly overexposed
on the window side, no HDR or cinematic color grading. Candid, unposed,
natural lifestyle selfie — looks like a quick real moment, not staged or
professionally shot. Negatives: professional photography, studio lighting,
plastic skin, airbrushed, glamour magazine, perfect composition, 8k,
masterpiece, distorted hands, extra fingers, harsh flash, mannequin face,
AI-perfect frozen expression, watermarks, captions." \
  --aspect_ratio 9:16 \
  --output_format jpg \
  --download "./out/{request_id}.{ext}" \
  --json
```

Open `references/selfie-pov.md` for the full filled template and three
verbatim worked examples. For testimonial-frame work, open
`references/talking-head.md`.

Referenced files: 4 — file references are metadata, not package contents.

vsb-vector12.4 KB

View saved version →

---
name: vsb-vector
description: >
  Make, edit and animate SVG files with Visual Sandbox. Trigger when the
  user asks for an SVG, a vector, a logo, an icon, an animated logo or
  loading state, a cut file (Cricut, laser, CNC, embroidery), or wants to
  "vectorize" a raster image via the vsb CLI. `vector/quiver-arrow-2` is
  the default: it writes an SVG from a prompt, vectorizes a raster, edits
  an SVG you already have, and animates one into a loop. For a detailed
  illustration, generate a flat raster with an image model first and
  trace it with `vector/recraft-vectorize`. Always ask the user how many
  colours the SVG must have before you run anything.
license: Proprietary. See LICENSE for the terms.
---

# Vector generation with vsb

> **On the MCP surface?** If the `visual-sandbox` MCP tools are in your
> tool list, use `generate` and `get_job` instead of the shell commands
> below. The route choice and the prompt craft on this page still apply.
> See `vsb` → Two ways to run.

Five live models. Verify with `vsb models --modality vector --json` before
you trust this table.

| Slug | Input | Cost | Good for |
|------|-------|------|----------|
| `vector/quiver-arrow-2` | prompt, or prompt + images, or image only, or `svg` (+ prompt) | ~$0.07 an icon, ~$0.60 a dense illustration, ~$0.03 an edit or an animation | **The default.** Icons, glyphs, monoline marks, simple logos. The only family that edits and animates an SVG you already have. |
| `vector/quiver-arrow-2-telos` | same as Arrow 2 | ~$0.10 an icon, ~$0.90 a dense illustration, ~$0.04 an edit or an animation | Arrow 2 plus a frontier reasoning model. A diagram whose logic must hold, an infographic from real figures, a mark that must obey a page of brand rules. Reads about eight times as much brief. |
| `vector/quiver-arrow-1.1` | prompt, or prompt + reference images, or image only | $0.25 a generate, $0.19 a vectorize, flat | Only when a dense illustration must land on a known flat price. Cannot edit and cannot animate. |
| `vector/recraft-v4.1-svg` | prompt only, plus `aspect_ratio` | ~$0.05 per SVG, flat | Recraft. A new SVG from a prompt at one low price, even for a dense illustration, with Recraft's composition and colour. **No reference images, and it cannot edit or animate.** About 20 seconds. Commercial use allowed. |
| `vector/recraft-vectorize` | one raster image | ~$0.02 per SVG | Tracing any raster into editable paths. The second half of the illustration route. |

**The Arrow 2 family is token-billed**, so the price moves with how much
drawing the model does. `vsb pricing` shows a ceiling, and the user is
charged for what the model actually produced, never more than the quote.
Arrow 1.1 and Recraft V4.1 SVG are the flat-price generators.

**Do not pick Telos for an icon.** It draws the same shapes as Arrow 2 and
costs half again as much. Pick it when the thinking is the hard part.

Start every run in a **background shell** (Claude Code: `run_in_background:
true`). All five models are sync. Recraft Vectorize finishes in seconds and
Recraft V4.1 SVG in about 20; the Arrow 2 family takes about a minute, and
Telos longer. Rules in
`vsb` → Background generations.

## Ask first, then route

Ask these two questions in one message before any run:

1. **How many colours?** One, two to three, four to six, or full colour.
2. **Background?** Transparent, or a solid colour.

If the user says "whatever", use four to six colours on a white
background and say so.

Then pick the route:

| The request is | Route |
|----------------|-------|
| An icon, a glyph, a monoline mark, a simple logo, one to three colours | **A. Quiver direct** |
| A scene, a character, a mascot, a poster, shading, four or more colours, or "detailed" | **B. Raster first, then vectorize** |
| The user already has a raster (PNG, JPG, a sandbox node) | **C. Vectorize only** |
| The user has an SVG and wants it changed ("recolour it", "thicken the strokes", "add a badge") | **D. Edit an SVG** |
| The user wants a logo, a loader or a mark to move | **E. Animate an SVG** |

Only the colour question applies to routes A, B and C. Routes D and E
start from an SVG, so its palette is already decided.

Quiver is not the default for a picture. It draws clean icons and falls
apart on detail: a busy illustration comes back simplified, with merged
shapes and dropped elements. That is the case for route B.

## Route A: Quiver direct

```bash
vsb run vector/quiver-arrow-2 \
  --prompt "A paper plane icon. Flat geometric style, thick uniform 2px strokes, rounded joins. Exactly 2 colours: navy #1B2A49 and white. No gradients, no shading, no text. Centred, transparent background." \
  --json
```

Prompt order: subject, style, palette, background. Say the colour count
as a number and name the colours. Words that keep the output simple:
flat, minimal, geometric, line art, monoline, single colour, thick
stroke.

- One mark per generation. For an icon set, run once per icon with the
  same style sentence, so the set reads as a family.
- `images` is a list. Prompt + images = style or structure reference.
  Images with an empty prompt = vectorize the first image (route C, but
  Quiver is several times the price of Recraft for that job).
- `--reasoning_effort` takes `low`, `medium`, `high`, `xhigh`. Leave it
  alone for a single glyph. Raise it when the layout is the hard part.
  Higher effort spends more tokens, so it costs more.
- Arrow 2 rejects `temperature`, `top_p` and `presence_penalty`. Those
  were Arrow 1.x knobs. `reasoning_effort` replaced them.
- Swap in `vector/quiver-arrow-2-telos` when the brief is long or its
  logic has to hold. Paste the whole brand guide or data table in rather
  than summarising it; reading it is what you are paying for.

## Route B: raster first, then vectorize

Recraft traces what it sees, so the palette and the background are set
in the raster prompt. Read
[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) before you
write it.

```bash
# 1. Flat raster. PNG is mandatory: JPG artifacts trace into hundreds of stray paths.
RASTER=$(vsb run image/nano-banana-2 \
  --prompt "Flat vector illustration of a fox reading a book under a tree. Exactly 5 colours: burnt orange #E0642B, cream #F6EFE3, forest green #2F5D3A, dark brown #3B2A1A, sky blue #BFD8E6. Solid fills only. No gradients, no shading, no texture, no outlines, no noise. Clean hard edges. Subject centred with margin. Plain white background. No text, no watermark." \
  --aspect_ratio 1:1 --output_format png --json | jq -r '.result.urls[0]')

# 2. Trace it.
vsb run vector/recraft-vectorize --image "$RASTER" --json
```

- **Transparent background wanted?** Run
  `image-enhance/recraft-remove-background --image "$RASTER"` between
  step 1 and step 2, and trace its output instead. PNG transparency
  survives the trace.
- `image/nano-banana-2-lite` is fine for a draft. Use `image/nano-banana-2`
  for the final, since every edge becomes a path.
- Keep the raster square unless the user asked for another shape.
- This is one chain, not two. Do not feed the SVG, or the traced raster,
  back into an image model. Change the prompt and rerun step 1 instead.
- One step instead of two: `vector/recraft-v4.1-svg --prompt "..."` draws
  the SVG directly at a flat price. Use the same palette and background
  sentences. It takes no reference image, so the two-step chain stays the
  route when the user supplies a style or a character to match.

## Route C: vectorize only

```bash
URL=$(vsb upload ./logo.png --json | jq -r '.url')
vsb run vector/recraft-vectorize --image "$URL" --json
```

For a node on the canvas, take `output_urls[0]` from
`vsb sandbox selection --json` as the `--image` value.

## Route D: edit an SVG that already exists

`svg` plus a prompt edits. The Arrow 2 family only; Arrow 1.1 and Recraft
have no `svg` field at all.

```bash
SVG=$(vsb run vector/quiver-arrow-2 --prompt "..." --json | jq -r '.result.urls[0]')

vsb run vector/quiver-arrow-2 \
  --svg "$SVG" \
  --prompt "Make the star rounded and fill it #FFD700. Leave everything else as it is." \
  --json
```

- `--svg` takes a URL, a data URI, or raw markup. A sandbox node works:
  pass its `output_urls[0]`.
- Name what changes and say the rest stays. The model reads the real
  paths, so an unmentioned shape survives the edit.
- The edits endpoint reads at most 200,000 characters of markup and takes
  at most 4 reference images. Generate takes 16. A traced photo usually
  blows the character cap; edit the source raster instead.
- An edit is the cheapest thing the family does. Iterating on one mark
  beats regenerating it.

## Route E: animate an SVG

`svg` with **no** prompt animates. The output is a looping SVG with the
loop written into the file, so it plays in a browser with no JavaScript
player and no video file.

```bash
vsb run vector/quiver-arrow-2 --svg "$SVG" --json

# Steer what moves. --operation animate is required here:
vsb run vector/quiver-arrow-2 \
  --svg "$SVG" \
  --prompt "Spin the outer ring clockwise, forever. Hold the wordmark still." \
  --operation animate \
  --json
```

- Careful: a prompt plus `svg` and nothing else **edits**, it does not
  animate. `--operation animate` is what makes the prompt steer a loop.
- Animate a tidy SVG. A mark Arrow 2 drew animates reliably; a traced
  photo or a file with no grouping gives the model nothing to grab.
- Good jobs: a logo reveal in a site header, a loading bar, an empty
  state, an icon micro-cue, a product illustration that needs some life.
- The file is a few kilobytes and needs no player, which is the whole
  reason to reach for this over a hand-built animation.

## Hold the colour count

Recraft has no palette setting. Check the SVG, then fix the raster if it
is over.

```bash
# Distinct fills in the SVG. Download to a scratch dir; this is a check, not a user save.
vsb run vector/recraft-vectorize --image "$RASTER" \
  --download "/tmp/vsb-vector/{request_id}.svg" --json > /tmp/vsb-vector/run.json
grep -o 'fill="[^"]*"' /tmp/vsb-vector/*.svg | sort -u | wc -l
```

If the count is over the target, or the SVG is bloated with near-duplicate
tints, posterize the raster and trace again. The retrace costs one cent.

```bash
magick /tmp/vsb-vector/raster.png -colors 5 +dither /tmp/vsb-vector/raster-5.png
URL=$(vsb upload /tmp/vsb-vector/raster-5.png --json | jq -r '.url')
vsb run vector/recraft-vectorize --image "$URL" --json
```

`magick` is ImageMagick. If it is missing, tighten the raster prompt
("exactly N colours, no anti-aliasing, hard edges") and rerun step 1.

## Deliver

- Output is `.svg`. Download template: `--download "./out/{request_id}.svg"`.
- `--download` only if the user opted into local saves. Otherwise hand
  back the share page `https://visualsandbox.com/share/<job_id>/`
  (vsb critical rule 5).
- The SVG has real paths. It opens in Illustrator, Figma, and Inkscape,
  and it drops into Cricut, Glowforge, and LightBurn.
- An animated SVG from route E is still one `.svg`. It loops in a
  browser and in Figma, but a cutter reads only the shapes.

## Cost estimation

```bash
vsb pricing vector/quiver-arrow-2 --json
vsb pricing vector/quiver-arrow-2-telos --json
vsb pricing vector/quiver-arrow-1.1 --json
vsb pricing vector/recraft-vectorize --json
vsb pricing image/nano-banana-2 --json
```

The Arrow 2 family quotes a ceiling, not a fixed price. The bill lands at
what the model actually drew, and never above the quote.

Route B is usually cheaper than route A for a detailed picture: one raster
plus one trace costs less than one dense Quiver SVG. For a plain icon,
route A on Arrow 2 is the cheaper of the two.

## Common gotchas

- **Recraft's field is `image`, a single string.** Quiver's is `images`,
  a list. Run `vsb schema vector/<slug> --json` before you guess.
- **`svg` plus a prompt edits. `svg` alone animates.** Pass a prompt when
  you meant to animate and you get an edit back. Force it with
  `--operation animate`, `edit`, `generate` or `vectorize`.
- **Only the Arrow 2 family has an `svg` field.** Arrow 1.1 and Recraft
  cannot edit and cannot animate. Check the slug before you promise it.
- **The slug has the category in it.** `vector/recraft-vectorize`, not
  `image-enhance/recraft-vectorize`.
- **Assume text comes out as paths, not `<text>`.** These models draw
  letters as shapes. Open the file before you promise editable type.
- **Gradients become colour bands.** Ask for solid fills in the raster
  prompt, or accept the bands.
- **Photos trace into thousands of paths.** Route C on a photo gives a
  stylized trace, not a clean logo. Regenerate as a flat raster instead.

Referenced files: 1 — file references are metadata, not package contents.

vsb-video29.5 KB

View saved version →

---
name: vsb-video
renamed_from: video
description: >
  Generate or edit video with Visual Sandbox. Trigger when the user wants
  text-to-video, image-to-video, or motion-control video via the vsb CLI.
  Always uses async + status polling — video runs take 30s–3min. Verify slugs
  with `vsb models --modality video --json`. Read it before any video in which
  people speak: it picks the speech and lip-sync path, and the price of each.
license: Proprietary. See LICENSE for the terms.
---

# Video generation with vsb

> **On the MCP surface?** If the `visual-sandbox` MCP tools are in your
> tool list, use `generate` and `get_job` instead of the shell commands
> below. The model choice and the prompt craft on this page still apply.
> See `vsb` → Two ways to run.

Video runs are slow (30s–3min). **Always use `--async`**, then poll with `vsb status`. Don't block a sync `vsb run` on a 2-minute job — the CLI will sit there and re-poll, blowing your terminal session.

Agents: don't block the conversation on a running job either — start it, keep talking, check later. Put the wait loop in a **background shell** (Claude Code: `run_in_background: true`), so the harness notifies you when the job lands. `vsb status` never waits on its own. Rules in `vsb` → Background generations.

## Rules for every paid video run

- **One request is one paid attempt.** A complaint, "fix it", a failed job or
  `retryable: true` is not a yes for another run. Ask before the next take.
- **One keyframe image for each shot first.** Make it from the anchors (the
  character, the theme, the product photo), at the video's shape, and get a
  yes on it before any video. Say how many images that is. Skip it when the
  person gives the first frame or asks for text to video.
- **A timeout is not a failure.** A `queued` or `processing` job still runs.
  Check the job you know before any new run, and claim no result before the
  job is `completed`.
- **Review mode (MCP).** A run of 10 cents or more can answer
  `awaiting_approval`. In ChatGPT, call `show_media`: the card shows the
  price and the Generate button. Elsewhere, give the `share_url`. Never press
  Generate for the person, and never request the run again while it waits.
- **A person in the video** must have agreed to it. A cloned voice needs the
  speaker's consent.

## The async + poll pattern

```bash
# 1. Submit
JOB=$(vsb run video/veo-3.1-fast \
  --prompt "a tiger walking through tall grass at golden hour, camera tracks alongside" \
  --aspect_ratio 16:9 \
  --duration 8 \
  --async --json | jq -r '.job_id')

echo "Job: $JOB"

# 2. Wait in a BACKGROUND shell. vsb status never waits, so loop until the
#    job is terminal. The shell's exit is the "done" signal.
until vsb status "$JOB" --json 2>&1 | jq -e '.status | IN("completed","failed","cancelled","error")' > /dev/null; do sleep 10; done
vsb status "$JOB" --result --download "./out/{request_id}.{ext}" --json > "/tmp/vsb-$JOB.json" 2>&1

# 3. On the notification, read /tmp/vsb-$JOB.json. Non-blocking peek any time:
vsb status "$JOB" --json | jq -r '.status'

# 4. Look at it before you report it: nine frames, first to last, in one image.
#    Pass the downloaded file, or the output URL from the JSON.
vsb video frame ./out/"$JOB".mp4 --json   # → out/<job>-frames.jpg — read it
```

Read the contact sheet before you tell the user the clip is done. A completed
job can still hold a frozen subject, a warped face or the wrong action.
`--at end` gives the last frame at full size, which is also the start frame for
the next shot (vsb critical rule 15).

Status can be `queued | in_progress | completed | failed | cancelled`. Open the
finished clip for the user with `open <file>` (the default player, not Preview
— Preview is for images, vsb critical rule 14).

`--download` only if the user opted into local saves — otherwise fetch with
`--result --json` alone and hand back the share page
`https://visualsandbox.com/share/<job_id>/`, never a raw CDN URL
(vsb critical rule 5).

## Picking a model

Verify with `vsb models --modality video --json | jq '.models[] | {slug, category, owner}'`.

| Task | Default slug | Notes |
|------|--------------|-------|
| Cinematic text-to-video, top quality | `video/veo-3.1` | Google. ~60–180s wall time. Most expensive. Best motion + lighting. |
| Cinematic but cheaper / faster | `video/veo-3.1-fast` | Same model family, ~30–60s, ~half the cost. Default for most use. |
| Image-to-video with brush motion | `video/kling-v3-motion-control` | Kuaishou. Requires a seed image + a motion specification. Fine-grained control over what moves. |
| General-purpose, cheap | `video/seedance-2` | ByteDance. Solid quality, lower cost. The only Seedance with 1080p and 4K. See [`vsb-seedance`](../vsb-seedance/SKILL.md). |
| Same family, faster | `video/seedance-2-fast` | Lower quality but ~2× faster than `seedance-2`. See [`vsb-seedance`](../vsb-seedance/SKILL.md). |
| Cheapest Seedance, drafts and batches | `video/seedance-2.0-mini` | ByteDance. Same inputs as `seedance-2` at the lowest rate; 480p/720p only; one reference video + one reference audio. See [`vsb-seedance`](../vsb-seedance/SKILL.md). |
| Newest Seedance, many references | `video/seedance-2.5` | ByteDance. Up to 30 reference images, reference videos and audios as lists, native audio, `adaptive` aspect; 480p to 1080p, and `--draft true` for a cheap 480p take to finish at 1080p. Runtime in [`vsb-seedance`](../vsb-seedance/SKILL.md), prompt craft in [`vsb-seedance-prompting`](../vsb-seedance-prompting/SKILL.md). |
| Everything in one endpoint, multi-shot, native audio | `video/kling-v3-omni-video` | Kuaishou. Text-to-video, start and end frames, up to 7 reference images, reference-video edit or style transfer, lip-synced audio, and up to 6 shots in one clip. 3–15s, `standard` 720p / `pro` 1080p / `4k`. Slow, about 3 minutes. |
| A portrait performs an audio file | `video/kling-avatar-v2` | Kuaishou. Audio-driven talking head. Feed one portrait plus one audio file and the face lip-syncs the whole clip. No prompt-driven motion. Output length matches the audio. `std` to iterate, `pro` for 1080p. |
| Short clip with sound, cheap, any length 1–15s | `video/grok-imagine-video` | xAI. Text-to-video and image-to-video on one slug. Native audio always on, no toggle. 480p/720p at one flat rate per second. ~50s for a 5s 720p clip. |
| Animate a still and keep its exact look | `video/grok-imagine-video-1.5` | xAI, preview. Image-to-video only, `--image` is required. Holds the detail and lighting of the source frame. Faster than the base model (~33s for 5s 720p) and costs more per second. |
| Cheapest in catalog, iterate fast | `video/p-video` | Pruna AI. Built-in `draft` toggle drops cost ~4× (~$0.005/s at 720p draft). Supports text, image, AND audio conditioning. Looser safety filter than Seedance. See [`vsb-p-video`](../vsb-p-video/SKILL.md). |
| Storyboard a clip from keyframes, or continue one, with audio | `video/flux-3` | Black Forest Labs. Prompt alone, 1 image (first frame), 2 images (first and last), or 3-10 as an ordered storyboard; or `start_video` to continue a clip. Images and `start_video` are mutually exclusive, and a continuation caps at 15s. 5-20s, 720p/1080p, audio on by default, `draft` for a cheap 720p preview. Cuts between shots unless you ask for one continuous take. |
| Reference-led shot with dialogue, a set voice, or 2K | `video/minimax-h3` | MiniMax. Generates stereo dialogue, foley and score in the same pass as the picture, and takes a reference audio clip to set the voice. 9 images, 3 videos, 3 audio clips, 4-15s, 768p or 2K. The soundtrack is never optional, so name each speaker and say what must stay quiet. |
| One long unbroken shot, up to 30 seconds, silent | `video/wan-3` | Alibaba. Ties the longest single clip in the catalog and costs little per second, but it writes no audio at all. 480p/720p/1080p, negative prompt, prompt expansion on by default. |
| Fast clip with sound, or any 4K output | `video/ltx-2.5-fast` | Lightricks. Synced audio in one pass, 720p to 4K, first and last frame, seconds to render. Two length limits stack: 2K and 4K stop at 10s, and 48/50 fps stops at 10s at any resolution. 16:9 and 9:16 only. |
| Quality Pruna tier, with speech and 1080p | `video/p-video-2` | Pruna AI. Successor to `p-video`: 1-20s, 720p/1080p at 24/48 fps, first and last frame, native speech and lip-sync from dialogue written in the prompt, audio-conditioned motion, `draft` halves the rate. An audio track or Auto duration sets the length, so the price settles after the run. |
| Best Pruna picture, silent | `video/p-video-2-pro` | Pruna AI. Text and image to video, 5-15s, 480p/768p, first and last frame, `mode` speed or quality, `prompt_upsampler` off/turbo/max. No audio at all and no 1080p; finish a delivery on `video/video-upscale`. |
| Edit a clip you already have, with a prompt | `video/p-video-edit` | Pruna AI. Source MP4/MOV up to 15s, output the same length. Up to 4 reference images bind the edit to a product, a face or a look. `draft` is the cheaper preview. Keeps the source audio. One principal change per prompt. |
| Swap who is on camera, keep the shot | `video/p-video-replace` | Pruna AI. Source clip plus 1-3 front-facing photos. Scene, camera, lighting and performance survive, only the person changes. 720p/1080p, `turbo` for speed, source up to 30s. Name who to change in `instruction_prompt` when two people are in frame. |
| Move a still with a clip's motion and audio | `video/p-video-animate` | Pruna AI. One `image` plus one driving `video`. The image sets the look, the video sets the motion, timing and audio, so lip sync needs no second pass. 720p/1080p, `turbo`, source up to 30s. Cheaper than `kling-v3-motion-control`, which adds an orientation control. |
| A photo speaks a typed script | `video/p-video-avatar` | Pruna AI. The only avatar model that writes the speech itself: type the words and pick one of 30 voices across 10 languages, or attach audio to override both. One photo in, MP4 out at 720p or 1080p. The cheapest talking head here. Length follows the speech; keep clips under 3 minutes or the face drifts. |
| Change what a person on camera says | `video/lipsync-2-pro` | Sync. Redraws only the mouth and keeps your footage, so it starts from video, never a still. The person must already talk in the clip. Makes no voice. Source up to 30s, `sync_mode` decides what happens when the two lengths differ. Slow by design: minutes for a clip of seconds. The final pass, not the draft pass. |
| Enlarge a short clip and rebuild its detail | `video/flux-video-upscale` | Black Forest Labs. Video in, video out; keeps the frame shape and the audio. `upscale_factor` 1.5-3, output capped near 4K, `creativity` 0 precise (faithful, for faces and products) or 1 creative (invents detail, tuned for generated footage). Source MP4 under 50 MB and 6s. Use `video/video-upscale` for longer or real footage and for frame-rate changes. |
| Enlarge a finished clip, or change its frame rate | `video/video-upscale` | Topaz Labs. Not a generator: video in, video out, no prompt. 720p / 1080p / 4K and 24 / 30 / 60 fps. Source MP4 or MOV under 200 MB, capped at 60s / 30s / 10s by target resolution. Use it to finish a 720p generation for delivery. |
| Rebuild lost detail in a soft, old or generated clip, fast | `video/starlight-fast-2` | Topaz Labs. Generative (diffusion) upscaler, no prompt. `resolution` 1080p or 4k. Source MP4, MOV or WEBM, up to 10s. Keeps the source frame rate, no fps control. Billed per output frame, so a 60fps source costs twice a 30fps one. Half the price per frame of `starlight-precise-2.6`. Sound in the output is not documented. |
| Rebuild faces, fabrics and fine text for final delivery | `video/starlight-precise-2.6` | Topaz Labs. The quality Starlight tier, no prompt. `resolution` 1080p or 4k, `sharpness` 1-5 (default 3; lower for faces, higher for text). Source MP4, MOV or WEBM, up to 5s. Keeps the source frame rate. Twice the price per frame of `starlight-fast-2`; test there first. |

## People who talk: pick the sound path before you spend

Read this section before any video in which people speak. The model you pick
decides who makes the voice, who moves the lips, and whether the clips have
sound at all. A wrong pick costs the whole batch.

### Which model does what

Prices are per second of output at 720p, from `vsb pricing` in October 2026.
Read them live before you quote.

| Slug | Photoreal faces | Speech it writes | Takes your voice track | $/s | Use it for |
|------|-----------------|------------------|------------------------|-----|------------|
| `video/veo-3.1` | Accepted, a child too, in a real run | Yes. `--generate_audio true`, the line in double quotes | No | 0.25 silent, 0.50 with audio | **Path A default.** Native speech and lips in one pass |
| `video/kling-v3-omni-video` | Accepted, a child too, in a real run | Yes. `--generate_audio true` (the default is `false`), the line in quotes | No | 0.21 silent, 0.28 with audio | Path A for up to 15 s, or several shots in one clip |
| `video/p-video-2` | Accepted | Yes, from quoted dialogue | Yes, `--audio`. The clip runs as long as the audio | 0.02 draft, 0.04 standard | **Path B default.** Cheapest, one voice across shots |
| `video/kling-avatar-v2` | Not tested | No | Yes, `--audio`, one portrait. Length = audio | 0.07 `std`, 0.14 `pro` | A talking head with little body motion |
| `video/lipsync-2-pro` | Accepted | No | Yes, `--audio`, on a clip you already have | 0.11 | New words on a clip where the person already talks |
| `video/seedance-2.5` | **Refused**, AI faces too. An AI-made face passes with `--ai_character true` | Yes, from quoted dialogue | Voice samples in `reference_audios`, see Path C | 0.29 | Shots with no photoreal face, or an AI character |

### Path A: native speech, one voice per clip

Run `video/veo-3.1` with `--generate_audio true`. Write the line in the prompt
in double quotes and name who says it. The model makes the voice, the lip
movement and the room sound in one run. Pick this path when one voice per clip
is fine: one speaker, an ad, a short scene.

- Each clip invents its own voice. The same character can sound different in
  the next shot. Describe the voice the same way in every prompt (age, accent,
  pitch, pace) to keep it close. It still drifts.
- Veo cannot take an ElevenLabs voice or any other audio file.
- A clip is 4, 6 or 8 seconds. `--reference_images` work only at 16:9 and 8
  seconds, and Veo ignores `--last_frame_image` when they are set.
- `video/veo-3.1-fast` is the same path at $0.19/s with audio. It takes no
  reference images, and its face check was not tested.

### Path B: one voice in every shot

Make each line with `audio/elevenlabs-tts` and one designed voice per
character ([`vsb-audio`](../vsb-audio/SKILL.md)). Then give the line to a
model that takes audio:

1. **`video/p-video-2 --image <still> --audio <line>` (Recommended).** It is the
   cheapest path, and the voice is the same in every shot. The clip runs as
   long as the audio, so `--duration` has no effect. Pad the line with silence
   to the shot length you want. Say in the prompt that the person speaks, and
   leave sound words out of the prompt. The mouth moves less than with native
   speech.
2. **A clip in which the person already talks, then `video/lipsync-2-pro`.**
   Make the clip with Path A, or prompt the speech motion ("she talks to him,
   her mouth moves"). Lipsync redraws the mouth to follow the ElevenLabs line,
   and the output carries that line. Pad the line to the clip length and pass
   `--sync_mode cut_off`. Pass `--active_speaker true` when two faces are in
   frame.

ElevenLabs Voice Design refuses a voice that reads as a child. Path B has no
child voice, so tell the user before you plan a child who speaks.

### What Lipsync 2 Pro is, and what it is not

Lipsync 2 Pro changes the words of a clip in which a person already talks. Use
it to dub, to fix a line, or to swap a voice for one that stays the same
across shots. It makes no voice. It does not make a still face or a closed
mouth talk: Sync's docs say a clip with no speaking motion gets no lip
movement. A silent clip of a person who does not talk is the wrong input.

### Seedance 2.5 and photoreal people

- ByteDance's real-face detector refuses every input with a photoreal face,
  AI-generated faces included. The error is `provider_safety`, "The input or
  output was flagged as sensitive". A still with no face passes.
- `image` (the first frame) cannot go with `reference_images`,
  `reference_videos` or `reference_audios`. The error is E006.
- `reference_audios` drives lip-sync, but it needs at least one reference
  image or video. That reference carries the face, so the face filter refuses
  it too.
- `--ai_character true` lets an AI-made face through and keeps it the same
  across shots. Ask the user first, and never set it for a real person.
  Recipe in [`vsb-seedance`](../vsb-seedance/SKILL.md#one-realistic-ai-character-across-shots).
- Seedance speaks a line from `reference_audios` again in its own voice, so
  the voice drifts from clip to clip. To keep one voice, run each clip
  through `audio/elevenlabs-voice-changer` and put that sound back with
  `tool/video-add-audio --mode replace`. Recipe in
  [`vsb-seedance`](../vsb-seedance/SKILL.md#a-talking-ai-presenter-in-one-voice).
- Use Seedance 2.5 for shots with no photoreal face. A stylized character may
  pass; test one clip first.

### Path C: Seedance 2.5 with voice samples, for cartoon characters

Use this path for a series of shots with stylized characters who talk. The
voices stay the same in every shot, and Seedance moves the mouths and makes
the sound effects in the same run. A cartoon bird passed the face filter on
2026-10-05.

1. Make one voice sample for each speaker, once for the whole series: a
   designed ElevenLabs voice ([`vsb-audio`](../vsb-audio/SKILL.md)), or the
   real voice of a product. Put several kinds of lines in it: a question, an
   order, a laugh.
2. Cut the samples. **All reference audio of one run must be 15 seconds or
   less in total.** Two samples of 32 s and 18 s failed with "The model
   rejected these inputs". Samples of 8.8 s and 5.3 s passed. Cut at a pause.
3. Upload the samples once, and pass the same URLs in every shot:
   `--reference_audios '["<speaker 1>","<speaker 2>"]'`.
4. Pass the storyboard frame as a reference image, not as `--image`. A
   reference audio cannot go with a first frame (E006). Write "Image 1 is
   the opening shot: start on this composition". The clip starts close to
   the frame, not on it. Add the character sheet as Image 2.
5. Write each line in the prompt, and name its voice:
   `The bird says, in the voice of Audio 1: "Make it blue." Its beak opens
   and closes in sync with its words.` For a voice from off screen, say that
   no character moves its mouth to it.
6. End the prompt with: `Audio 1 and Audio 2 are only voice samples: copy the
   voices, not their words. No other speech. No music.` Add the music later
   in the edit, over all the shots.

Do not join the exact lines of one shot into the reference audio. Each shot
then gets a different audio, and Seedance changes the voice from shot to shot.

### Before you spend

1. **Say how the sound gets made, before the first run.** Name the path. When
   the clips are silent (`--generate_audio false`), say so, and say where the
   voices, effects and music come from. The user sees silent clips on the
   canvas and must know why.
2. **Run one pilot of the whole chain.** Make one shot with picture, voice,
   lips and mix. Show it to the user and get a yes before the batch.
3. **Say the full price of a batch before you run it** when it is more than
   about $10. Count every clip, every voice line and the likely retakes.
4. **Check the sound of every result.** A clip can come back with no audio
   stream. No output from this command means no audio:

   ```bash
   ffprobe -v error -select_streams a -show_entries stream=codec_name,duration -of csv=p=0 clip.mp4
   ```

5. **Mix with `vsb video timeline`** (`vsb-timeline`):
   shots on the base layer, voices, effects and music on the audio layer.

## Veo 3.1 (text-to-video, image-to-video)

```bash
# Text-to-video
JOB=$(vsb run video/veo-3.1-fast \
  --prompt "a hummingbird hovering near a red flower in slow motion, sunlight filtering through leaves" \
  --aspect_ratio 16:9 \
  --duration 8 \
  --async --json | jq -r '.job_id')

# Image-to-video — pass the still image to seed the first frame (verify the exact
# field name with `vsb schema video/<slug>`; currently `image` for veo + kling).
# IMPORTANT: always pass --aspect_ratio matching the source image. Most video
# models default to 16:9 and will silently crop/letterbox a portrait seed.
URL=$(vsb upload ./still.jpg --json | jq -r '.url')
JOB=$(vsb run video/veo-3.1-fast \
  --prompt "the woman turns her head and smiles" \
  --image "$URL" \
  --aspect_ratio 9:16 \
  --duration 8 \
  --async --json | jq -r '.job_id')
```

### Matching source aspect

When the seed comes from a sandbox node, read its aspect off the live canvas
instead of guessing — `vsb sandbox selection --json | jq -r '.nodes[0].generation.aspect_ratio'`
gives you `"9:16"`, `"16:9"`, `"1:1"`, etc. Pass that string straight into
`--aspect_ratio`. For an uploaded local file, use `sips -g pixelWidth -g pixelHeight ./still.jpg`
(macOS) or `identify -format "%wx%h" ./still.jpg` (ImageMagick) and pick the
nearest ratio in the model's `enum` (`vsb schema video/<slug> --json | jq '.inputs.aspect_ratio.enum'`).

Veo prompts work best when they describe **camera + subject + motion**. "Static
shot of a tiger" is a hint to keep the camera still; "tracking shot following
the tiger" implies movement.

## Grok Imagine Video and 1.5 (audio always on)

```bash
# Text-to-video, base model only.
JOB=$(vsb run video/grok-imagine-video \
  --prompt "a surfer drops into a heavy wave at dawn, camera tracks alongside, spray and board chatter" \
  --aspect_ratio 16:9 \
  --resolution 720p \
  --duration 5 \
  --async --json | jq -r '.job_id')

# Image-to-video. 1.5 requires --image; the base model treats it as optional.
IMG=$(vsb upload ./frame.jpg --json | jq -r '.url')
JOB=$(vsb run video/grok-imagine-video-1.5 \
  --image "$IMG" \
  --prompt "slow push in, she turns to the camera and smiles, rain on the window" \
  --aspect_ratio auto \
  --duration 5 \
  --async --json | jq -r '.job_id')
```

Both models share one schema: `prompt`, `aspect_ratio` (`auto`, `16:9`, `4:3`,
`1:1`, `9:16`, `3:4`, `3:2`, `2:3`), `resolution` (`480p` or `720p`),
`duration` (1–15s, default 5), and `image`. Rate per second is flat across
resolutions, so 720p is the default rather than an upgrade.

- **Audio is always generated and there is no toggle.** Write the sound design
  into the prompt. Naming the sounds ("cars passing, a skateboard rolling on
  pavement") beats a category word ("city noise").
- **`--image` is required on 1.5** and optional on the base model, where it
  sets the first frame. A prompt-only run belongs on `video/grok-imagine-video`.
- **`auto` aspect** follows the attached image, or falls back to 16:9 when
  there is none. It is the safe setting for image-to-video.
- **Billing follows the delivered length**, not the requested one, so a clip
  that comes back short costs less than the estimate.
- No reference-image list, no last-frame pin, and no video editing on either
  slug. Chain clips instead: feed the last frame of one in as the `--image` of
  the next.

## Kling motion control (image → video with directed motion)

```bash
URL=$(vsb upload ./portrait.jpg --json | jq -r '.url')
JOB=$(vsb run video/kling-v3-motion-control \
  --image "$URL" \
  --prompt "the wind blows her hair to the right" \
  --duration 5 \
  --async --json | jq -r '.job_id')
```

Run `vsb schema video/kling-v3-motion-control --json` first — it has
specific motion-control fields beyond `prompt`.

## Kling Avatar 2.0 (portrait + audio → talking head)

```bash
IMG=$(vsb upload ./host.jpg --json | jq -r '.url')
AUD=$(vsb upload ./line.mp3 --json | jq -r '.url')

JOB=$(vsb run video/kling-avatar-v2 \
  --image "$IMG" \
  --audio "$AUD" \
  --mode std \
  --async --json | jq -r '.job_id')
```

The audio drives the clip, so there is no `duration` and no `aspect_ratio`.
The output runs as long as the audio. The provider enforces the limits at
upload: the portrait is JPG or PNG under 10 MB, at least 300 px per side, with
an aspect ratio between 1:2.5 and 2.5:1; the audio is MP3, WAV, M4A, or AAC
under 5 MB. `--prompt` is optional and only nudges emotion or a small camera
move; leave it empty for a neutral read. `std` and `pro` share one engine, so
iterate on `std` and re-render the winner on `pro` with the same inputs.

Billing reserves credits for the audio's full duration up front, then refunds
the unused part when the clip finishes.

## Kling 3.0 Omni (references, reference video, multi-shot)

```bash
JOB=$(vsb run video/kling-v3-omni-video \
  --prompt "the woman in the first image walks into the cafe from the second image and orders" \
  --reference_images "[\"$URL1\",\"$URL2\"]" \
  --duration 10 \
  --mode pro \
  --generate_audio true \
  --aspect_ratio 16:9 \
  --async --json | jq -r '.job_id')
```

- Bind references by position in the prompt. Write "the first image", "the
  second image". Up to 7 references, or 4 when a reference video is attached.
- `--reference_video` has two modes. `--video_reference_type base` rewrites
  the clip in place and keeps its motion and timing. `feature` lifts the camera
  move and the look onto a new prompt-driven scene.
- Native audio turns off when a reference video is attached, because the
  reference supplies the track. `4k` does not accept a reference video.
- `--multi_prompt` scripts up to 6 shots in one clip, each with its own prompt
  and duration. Read `vsb schema video/kling-v3-omni-video --json` for its
  exact shape before you write one.
- `--generate_audio true` costs more per second than audio off. Iterate on
  `standard`, lock the take on `pro`, then re-render on `4k` unchanged.

## Topaz Video Upscale (finish a clip, no prompt)

```bash
SRC=$(vsb upload ./clip.mp4 --json | jq -r '.url')

JOB=$(vsb run video/video-upscale \
  --video "$SRC" \
  --resolution 1080p \
  --fps 30 \
  --async --json | jq -r '.job_id')
```

It takes no prompt, like the two Starlight slugs. The source clip is the whole
input, so there is no `--prompt`, no `--aspect_ratio` and no `--duration`, and
the output keeps the shape of the source.

- `--resolution` is `720p`, `1080p` (default) or `4k`. `--fps` is `24`, `30`
  (default) or `60`, so the same run can also retime a choppy clip.
- **The source has hard caps and they differ per resolution**: 60 seconds at
  720p, 30 at 1080p, 10 at 4K. The file must be MP4 or MOV under 200 MB. Cut a
  longer piece into parts, upscale each, and join them in an editor.
- Billed per second of output. The rate rises with resolution and 60fps costs
  twice 30fps, so run `vsb pricing video/video-upscale --json` before a 4K job.
- Expect a wait: a 12 second source took about 43 seconds at 720p/30fps and
  about 173 seconds at 1080p/60fps. 4K is roughly four times 1080p.
- It enlarges and retimes. It does not restyle and it adds no content, so a
  soft source comes back soft, only bigger.
- **The common chain**: generate the shot on the model with the best motion,
  then finish it here. A still image goes to `image-enhance/upscale` instead,
  never here.
- **Starlight rebuilds the detail instead.** `video/starlight-fast-2` and
  `video/starlight-precise-2.6` take the same `--video` and `--resolution`
  (`1080p` or `4k` only, no `--fps`). Precise also takes `--sharpness 1-5`.
  The caps are 10s on Fast 2 and 5s on Precise 2.6. Both bill per output frame
  at the source frame rate, so a 60fps source costs twice a 30fps one.

## Cost estimation (do this BEFORE running)

```bash
vsb pricing video/veo-3.1-fast --json
# {
#   "slug": "video/veo-3.1-fast",
#   "currency": "USD",
#   "unit": "second",
#   "user_cost_estimate": 0.19,
#   ...
# }
```

Most video models are billed per *second of output*. An 8-second clip on
`veo-3.1-fast` with audio at $0.19/s = $1.52. On `veo-3.1` with audio it is
$0.50/s, so $4.00. Always show the user the estimated cost before confirming.

## Cancelling

```bash
vsb status "$JOB" --cancel --json
```

Returns 1 if the job already finished. Clean exit code = clean cancel.

## Dialogue text rules (any model that speaks)

Applies to every prompt that contains spoken lines — Veo dialogue, P-Video
narration, Kling Avatar scripts, and any future speech model.

- **Never put an em dash (—) or en dash (–) in dialogue text.** Speech
  synthesis vocalizes dashes unpredictably — a dramatic pause at best,
  hallucinated words at worst (a real run turned `"Visual Sandbox —"` into
  audio that transcribes back as "Visual Sandbox 1008"). Use a comma, a
  period, or an ellipsis `...` for the same beat.
- Punctuation the models handle reliably: `, . ? ! ...`
- Keep the rule for the *dialogue string only* — dashes in the visual
  description parts of the prompt are fine.

## Common gotchas

- **Status can stay at `queued` for 30+ seconds** before flipping to `in_progress`. That's the provider queue, not your CLI hanging.
- **`progress` can be `null` even mid-run** — not all providers report it.
- **Some video models reject aspect ratios you'd expect to work.** Check `vsb schema video/<slug> --json | jq '.inputs.aspect_ratio.enum'` to see the allowed list.
- **Default aspect is 16:9 on veo + seedance.** Image-to-video runs that omit `--aspect_ratio` silently crop or letterbox a 9:16 / 1:1 seed. Always pass the aspect that matches the source image (see "Matching source aspect" above).
- **Result `urls` may be a single-element array** — extract index 0 if you need a single URL.
- **Don't poll faster than every 3–5 seconds.** The async runner backoffs internally; tight client loops just waste API quota.

Referenced files: 1 — file references are metadata, not package contents.

vsb-video-to-reels4.31 KB

View saved version →

---
name: vsb-video-to-reels
description: >
  Cut a long video into short vertical clips with the Visual Sandbox MCP
  tools: find the best passages in a podcast, an interview, a webinar or a
  talk, cut and reframe them to 9:16 in one render, and burn word-timed
  captions from the source's own transcript. Trigger when the person asks to
  "turn this video into reels", "make shorts from my podcast", "find the best
  clips", "clip this interview", "repurpose this video for TikTok", sends a
  YouTube, TikTok, Instagram or X link, or attaches a long video and asks for
  short ones. Transcribes, proposes the passages, and cuts only the ones the
  person picks. Uses the MCP tools only.
license: Proprietary. See LICENSE for the terms.
---

# Long video to reels

The transcript finds the passages, the person picks them, and one render cuts
them. You pick the moments; `cut_and_reframe_video` only cuts what it is
given. Use the MCP tools only.

## 1. Get the video

- **A public page** (YouTube, TikTok, Instagram, X): keep the link for step 2,
  and call `download_video` with it for step 4. A site can refuse the server;
  then ask the person to attach the file. Use only video the person has the
  right to use.
- **A file in the chat** goes to `upload_media` as `file`. Use the `url` it
  returns.

The source can be up to 2 hours long.

## 2. Transcribe it

- **A public page:** call `transcribe_link` with the link. A page with
  captions, such as most YouTube videos, costs nothing. A page without
  captions ends the run with no charge. Then say that Scribe charges for the
  real length, and on a yes call `transcribe_link` again with
  `use_captions: false`.
- **A file:** run `audio/scribe` with the file URL. Call `estimate_cost` with
  the length first, and say the price.

`get_job` returns the transcript as timed lines, `[12.3-18.0] The sentence.`
Keep the job id: the captions in step 4 read the words from it.

## 3. Propose the passages

A passage with a hook, the context and a payoff beats a quotable line with no
end. Default to one passage of 30 to 60 seconds; offer up to five.

- It opens with a strong line in its first 3 seconds: a claim, a question or
  a surprise.
- It ends at the end of a thought, not in the middle of a sentence.
- It stands alone. A viewer who did not see the rest understands it.
- A passage can join two or three parts of the source, in order or not, when
  the joined parts still say what the speaker meant.

Cut at a pause. A pause of 400 ms or more is a clean cut; take care between
150 and 400 ms; below 150 ms, keep more context. **Never join parts so that a
speaker seems to say something they did not say.**

Show a list: the start and end seconds of each part, the length, and the
first line. Wait for the person to pick.

## 4. Cut, reframe and caption in one run

Call `cut_and_reframe_video` once for each reel:

- `video`: the source URL from step 1.
- `segments`: the parts in the order the reel plays them, each with `start`
  and `end` in source seconds. Up to 40 parts and 5 minutes in all, each at
  least 0.15 s.
- `framing` for each part: `center_crop` for one person in the middle,
  `pad` when two people sit far apart, or `crop` with `crop_x`, `crop_y`,
  `crop_width` and `crop_height` in source pixels to follow one speaker.
- `aspect_ratio`: `9:16` by default.
- `captions`: one of the six looks of `add_captions`, with `transcript` set
  to the job id of step 2: `highlight` (large capitals, the spoken word lit
  up), `hormozi`, `beast`, `classic`, `box` or `plain`. Each look has its own
  place and number of words. Use `none` when the source already has captions
  burned in.

The cut is free. Captions from the source transcript need no second
transcription. Send the same `sandbox_name` on every run, such as
"Podcast ep 12 reels".

To change the caption look, run `cut_and_reframe_video` again with the same
parts and another look. The cut is free, and the old captions do not stay on
the new reel. The looks are in [`vsb-edit-video`](../vsb-edit-video/SKILL.md).

## 5. Hand it over

Call `show_media` with the job ids, and give the `sandbox_url`. Look at the
frames before you report a reel.

## Rules

- Cut nothing before the person picks the passages.
- One reel is one run. A complaint about one reel is not a yes for new runs
  of the others.
- A failed cut costs nothing. Read the error, fix the input, and run again.

Referenced files: 1 — file references are metadata, not package contents.

Release notes

A guided first run: your photos, product, logo or website become saved anchors, then one preview frame, a first 5-second video and three voice takes. New skills: motion graphics from HTML, brand themes read from your website, swap a person into a video, and feedback. Reels now cut several parts of a long video or a video link in one run. Films keep their script and shots between chats. A larger run waits for your press of Generate in the card.

Declared in the saved package. Remote tools may change independently.

Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package license
SEE LICENSE IN LICENSE
Package author
Visual Sandbox
Keywords
See keywords
Getting started skill
./skills/vsb-setup/SKILL.md
Commerce declaration
Does not support commerceThis does not establish whether access is free or paid.
Review scenarios
5 positive · 3 negativeScenario counts, not test results.

Declared capabilities

  • Generate videos from text or images
  • Animate photos
  • Short films with a recurring character
  • UGC-style video ads
  • Talking avatars from a photo
  • Voice cloning
  • Long videos cut into short clips
  • Join and edit video clips
  • Word-timed captions
  • Voiceovers and text to speech
  • Generate images from text
  • Edit images with a reference photo
  • Product photo shoots
  • Ad creatives and ad variations
  • Reusable characters
  • Music and sound effects
  • SVG logos and icons
  • 3D models
  • Web pages and cards in editable layers
  • Asks before a larger run

Package observed Nov 5, 2026.

Technical details
First seen
Nov 5, 2026 · 00:00 UTC
Last seen
Nov 5, 2026 · 00:00 UTC
Collection status
Collected

plugin_asdk_app_6ad7a6e23400819198e9cc963ee1da61

Download plugin data (JSON)