@shotstack/cli 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/shotstack.js +42 -42
- package/package.json +1 -1
- package/skills/shotstack/SKILL.md +15 -10
- package/skills/shotstack/references/asset-library.md +2 -0
- package/skills/shotstack/references/beat-sync.md +78 -0
- package/skills/shotstack/references/generation.md +3 -1
- package/skills/shotstack/references/html5-effects.md +109 -0
- package/skills/shotstack/references/html5-snippets.md +20 -19
- package/skills/shotstack/references/html5.md +53 -12
- package/skills/shotstack/references/motion.md +60 -96
- package/skills/shotstack/references/onboarding.md +2 -2
- package/skills/shotstack/shared/agent-core.md +75 -27
|
@@ -1,119 +1,98 @@
|
|
|
1
|
-
# Motion
|
|
1
|
+
# Motion on Shotstack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
What can move in an edit, on which layer, and how much control each layer gives you. Timing, easing, travel and rhythm are design choices for the piece; this file covers the mechanics.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
There are two kinds of motion. **Built-in motion** (clip transitions, effects and keyframes, and `rich-text` animation) takes fixed options from the schema and needs care when combined on an existing asset. **`html5`** takes any seekable animation.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Time
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **The timeline is in seconds.** Clip `start`/`length`, keyframe `start`/`length` and GSAP `duration` are all seconds. Convert frame-based specs with the output frame rate (`output.fps`): at 25 fps one frame is 0.04 s.
|
|
10
|
+
- **An `html5` clip captures its animation for the clip's `length`,** measured from the clip's start. Animation that runs past the end is cut; animation that finishes early holds its last state.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| `instant` | 0.2 | micro shifts, near-imperceptible feedback |
|
|
14
|
-
| `fast` | 0.33 | **exits**, small moves |
|
|
15
|
-
| `base` | 0.6 | **the default entrance** — ~80% of reveals |
|
|
16
|
-
| `slow` | 0.8 | large entrances, hero moves |
|
|
17
|
-
| `slower` | 1.0 | full-scene transitions |
|
|
18
|
-
| `hold` | 1.5 | minimum settled hold before a clip ends |
|
|
19
|
-
|
|
20
|
-
Most entrances are `base` (0.6 s). Exits are deliberately faster than entrances — get out of the way faster than you came in.
|
|
12
|
+
## What each layer can move
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
| Where | Entrance / settle | Exit |
|
|
14
|
+
| Layer | What moves | Control |
|
|
25
15
|
|---|---|---|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
29
|
-
`
|
|
30
|
-
|
|
31
|
-
**Overshoot is opt-in, not default.** A gentle `back.out(1.4)` (GSAP) is reserved for **one** "hero" element per scene where a pop is intended — a logo sting, a headline punch. Calm is the default; energetic is fine when it *earns* it. Never sprinkle bounce across every element.
|
|
32
|
-
|
|
33
|
-
### Stagger — one value
|
|
34
|
-
|
|
35
|
-
`0.13 s` (≈ 4 frames at 30 fps) between siblings in any cascade — words, list items, grouped reveals. One value everywhere; present but never frantic. In GSAP: `stagger: 0.13`. In CSS: `animation-delay` steps of `0.13s` (`0s, 0.13s, 0.26s, …`).
|
|
16
|
+
| Clip `transition` | the whole clip, in and out | `fade` `reveal` `wipe*` `slide*` `carousel*` `shuffle*` `zoom`, each with `Slow`/`Fast` variants. Timing and easing are engine-fixed. |
|
|
17
|
+
| Clip `effect` | a Ken Burns drift on an image or video | `zoomIn` `zoomOut` `slideLeft` `slideRight` `slideUp` `slideDown`, each with `Slow`/`Fast` |
|
|
18
|
+
| Clip keyframes | `scale`, `opacity`, `offset.x`/`.y`, `transform.rotate.angle`, `transform.skew.x`/`.y`, an asset's `volume`, a video's `speed` | Tween arrays with your own `start` and `length`; `interpolation` and `easing` from fixed lists (below) |
|
|
19
|
+
| `rich-text` `animation` | the text's entrance | `preset` + `duration` + `style` + `direction`; easing is engine-fixed |
|
|
20
|
+
| `html5` | anything inside the clip | any seekable GSAP, anime.js, Lottie or CSS animation, any easing, per-element timing. Rules in [`html5.md`](html5.md). |
|
|
36
21
|
|
|
37
|
-
###
|
|
22
|
+
### rich-text animation
|
|
38
23
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
| Preset | Example |
|
|
25
|
+
|---|---|
|
|
26
|
+
| fade in | `{ "preset": "fadeIn", "duration": 0.6 }` |
|
|
27
|
+
| rise + fade | `{ "preset": "ascend", "duration": 0.6, "direction": "up" }` |
|
|
28
|
+
| slide in | `{ "preset": "slideIn", "duration": 0.6, "direction": "up" }` |
|
|
29
|
+
| word-by-word cascade | `{ "preset": "shift", "duration": 0.6, "style": "word", "direction": "up" }` |
|
|
30
|
+
| typewriter | `{ "preset": "typewriter", "duration": 0.8, "style": "character" }` |
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
Schema constraints (`@shotstack/schemas` → `RichTextAnimation`): `preset` ∈ `fadeIn · slideIn · typewriter · ascend · shift · movingLetters`; `duration` 0.1–30 s; **`style` (`word`/`character`) applies only to `typewriter` and `shift`** (ignored on the others); `direction` is **required** for `slideIn`, `ascend`, `shift`, `movingLetters`. Rich-text `animation` is **entrance-only**: exits use the clip `transition.out`. For control over easing, per-character timing or exits, use `html5`.
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
### Clip keyframes
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
| 2 — **`rich-text` `animation`** | medium (`preset` + `duration` + `style` + `direction`) | set `duration` from the scale; `style:"word"` gives the staggered cascade |
|
|
51
|
-
| 3 — **`html5` GSAP / CSS** | full (any property, any easing) | the full vocabulary below — durations, the house ease, `0.13` stagger |
|
|
36
|
+
- `interpolation` is `linear` (the default), `bezier` or `constant`. `easing` takes effect with `bezier`.
|
|
37
|
+
- `easing` is `ease`, `easeIn`, `easeOut`, `easeInOut`, or `easeIn` / `easeOut` / `easeInOut` followed by `Quad`, `Cubic`, `Quart`, `Quint`, `Sine`, `Expo`, `Circ` or `Back` (for example `easeOutCubic`, `easeInOutBack`).
|
|
38
|
+
- To match an `html5` clip's GSAP ease: `easeOutQuad` ≈ `power1.out`, `easeOutCubic` ≈ `power2.out`, `easeOutQuart` ≈ `power3.out`.
|
|
52
39
|
|
|
53
|
-
###
|
|
40
|
+
### Combining built-in motion on an existing asset
|
|
54
41
|
|
|
55
|
-
`
|
|
42
|
+
- Keyframes move the whole rendered clip; a `rich-text` preset animates the text inside it. A slide preset plus an `offset` keyframe moves the text twice, so animate each property one way per clip.
|
|
43
|
+
- Keyframe times are relative to the clip's `start`, and a tween that runs past the clip's `length` is cut off.
|
|
44
|
+
- Footage, images and logos are scaled by `fit` first, and keyframe `scale` multiplies that, so a large scale on a low-resolution source shows its pixels.
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
|---|---|
|
|
59
|
-
| fade in | `{ "preset": "fadeIn", "duration": 0.6 }` |
|
|
60
|
-
| rise + fade (the workhorse) | `{ "preset": "ascend", "duration": 0.6, "direction": "up" }` |
|
|
61
|
-
| slide in | `{ "preset": "slideIn", "duration": 0.6, "direction": "up" }` |
|
|
62
|
-
| word-by-word cascade | `{ "preset": "shift", "duration": 0.6, "style": "word", "direction": "up" }` |
|
|
63
|
-
| typewriter | `{ "preset": "typewriter", "duration": 0.8, "style": "character" }` |
|
|
46
|
+
## Easing available in html5
|
|
64
47
|
|
|
65
|
-
|
|
48
|
+
- **GSAP:** `none`, `power1`–`power4`, `sine`, `circ`, `expo`, `back(overshoot)`, `elastic(amplitude, period)`, `bounce` and `steps(n)`, each as `.in`, `.out` or `.inOut`.
|
|
49
|
+
- **CSS:** any `cubic-bezier()`, `steps()` or `linear(…)` point list, on `@keyframes`, transitions and `Element.animate()`.
|
|
66
50
|
|
|
67
|
-
##
|
|
51
|
+
## Recipes
|
|
68
52
|
|
|
69
|
-
|
|
53
|
+
Syntax starting points, deliberately different in feel. Build each scene on one top-level timeline and place tweens with GSAP's position parameter (`0`, `'<'`, `'-=0.2'`, labels) rather than `delay`.
|
|
70
54
|
|
|
71
55
|
```js
|
|
72
|
-
|
|
73
|
-
gsap.from('.el', { opacity: 0, y: 12, duration: 0.6, ease: 'power3.out' });
|
|
56
|
+
const tl = gsap.timeline();
|
|
74
57
|
|
|
75
|
-
//
|
|
76
|
-
|
|
58
|
+
// settle: rise and fade into place
|
|
59
|
+
tl.from('#title', { opacity: 0, y: 24, duration: 0.6, ease: 'power3.out' }, 0);
|
|
77
60
|
|
|
78
|
-
//
|
|
79
|
-
|
|
61
|
+
// blur reveal: opacity, blur and rise off one tween
|
|
62
|
+
tl.from('#sub', { opacity: 0, y: 16, filter: 'blur(10px)', duration: 0.6, ease: 'power3.out' }, 0.3);
|
|
80
63
|
|
|
81
|
-
//
|
|
82
|
-
|
|
64
|
+
// pop: overshoot on arrival
|
|
65
|
+
tl.from('#badge', { opacity: 0, scale: 0.6, duration: 0.5, ease: 'back.out(2.2)' }, 0.8);
|
|
83
66
|
|
|
84
|
-
//
|
|
85
|
-
|
|
67
|
+
// slam: big travel, hard snap
|
|
68
|
+
tl.from('#hit', { opacity: 0, x: -600, skewX: -18, duration: 0.45, ease: 'expo.out' }, 1.2);
|
|
86
69
|
|
|
87
|
-
//
|
|
88
|
-
|
|
70
|
+
// character cascade: wrap each character in a <span class="c">
|
|
71
|
+
tl.from('#word .c', { opacity: 0, yPercent: 110, rotation: 12, duration: 0.5, ease: 'power4.out', stagger: 0.03 }, 1.6);
|
|
89
72
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
```
|
|
73
|
+
// elastic settle
|
|
74
|
+
tl.from('#price', { scale: 0, duration: 1.1, ease: 'elastic.out(1, 0.45)' }, 2.0);
|
|
93
75
|
|
|
94
|
-
|
|
76
|
+
// exit, overlapping the next beat
|
|
77
|
+
tl.to('#title, #sub', { opacity: 0, y: -12, duration: 0.33, ease: 'power2.in' }, '-=0.1');
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
tl.from('.el', { opacity: 0, y: 12, duration: 0.6, ease: 'power3.out' }) // in (base)
|
|
99
|
-
.to({}, { duration: 1.5 }) // hold
|
|
100
|
-
.to('.el', { opacity: 0, y: 8, duration: 0.33, ease: 'power2.in' }); // out (fast)
|
|
79
|
+
// ambient loop: a drift that runs for the rest of the clip
|
|
80
|
+
tl.to('#glow', { opacity: 0.4, duration: 1.6, ease: 'sine.inOut', repeat: -1, yoyo: true }, 0);
|
|
101
81
|
```
|
|
102
82
|
|
|
103
83
|
### CSS equivalents (no JS)
|
|
104
84
|
|
|
105
85
|
```css
|
|
106
|
-
/* house ease entrance */
|
|
107
86
|
.el { animation: rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; }
|
|
108
|
-
@keyframes rise { from { opacity: 0; transform: translateY(
|
|
87
|
+
@keyframes rise { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
|
|
109
88
|
|
|
110
|
-
/* staggered siblings
|
|
111
|
-
.w:nth-child(1){animation-delay:0s}.w:nth-child(2){animation-delay:.
|
|
89
|
+
/* staggered siblings */
|
|
90
|
+
.w:nth-child(1){animation-delay:0s}.w:nth-child(2){animation-delay:.08s}.w:nth-child(3){animation-delay:.16s}
|
|
112
91
|
```
|
|
113
92
|
|
|
114
|
-
## Brand kit
|
|
93
|
+
## Brand kit through merge fields
|
|
115
94
|
|
|
116
|
-
|
|
95
|
+
`merge` find/replace runs over every string in the edit, including `html5` `html`, `css` and `js`. Declare the brand once as top-level fields and reference them in every clip, and one edit re-skins the whole video:
|
|
117
96
|
|
|
118
97
|
```json
|
|
119
98
|
"merge": [
|
|
@@ -129,23 +108,8 @@ Don't hardcode hex and font per snippet. Declare the brand as top-level `merge[]
|
|
|
129
108
|
.cta { background: {{accent}}; }
|
|
130
109
|
```
|
|
131
110
|
|
|
132
|
-
`merge` find/replace runs over the whole edit, including `html`/`css`/`js` strings, so `{{accent}}` resolves everywhere. Keep one accent and use it sparingly — a headline word, a number, a CTA, one glow — everything else neutral. **Colour is earned, never sprinkled.**
|
|
133
|
-
|
|
134
|
-
## Motion checklist
|
|
135
|
-
|
|
136
|
-
Before rendering anything animated:
|
|
137
|
-
|
|
138
|
-
1. **Durations from the scale?** (`0.6` in, `0.33` out — not `0.45`, `0.7`, `1.2`.)
|
|
139
|
-
2. **House ease, not linear?** `power3.out` / `cubic-bezier(0.16,1,0.3,1)` for tracked motion.
|
|
140
|
-
3. **Overshoot only on one hero element**, if at all.
|
|
141
|
-
4. **Stagger is `0.13`**, applied via `stagger` / `animation-delay`.
|
|
142
|
-
5. **Travel 12–24 px; scale ≥ 0.92.** Restraint over flash.
|
|
143
|
-
6. **One focal element per moment;** competing elements staggered.
|
|
144
|
-
7. **It holds before it cuts** (settled `hold`, not an instant exit).
|
|
145
|
-
8. **Brand colours via `merge[]`**, accent used sparingly.
|
|
146
|
-
|
|
147
111
|
## See also
|
|
148
112
|
|
|
149
|
-
- [`html5
|
|
150
|
-
- [`html5.md`](html5.md) —
|
|
151
|
-
- `shared/agent-core.md` → "Motion
|
|
113
|
+
- [`html5.md`](html5.md) — the html5 sandbox: seekable animation only, sizing, fonts, preloaded libraries
|
|
114
|
+
- [`html5-snippets.md`](html5-snippets.md) — ready-made clips to remix
|
|
115
|
+
- `shared/agent-core.md` → "Motion" — the compact summary (shared with the MCP server)
|
|
@@ -42,8 +42,8 @@ e-commerce app full of product images answers question 2).
|
|
|
42
42
|
(`references/asset-library.md`) and motion-graphic snippets
|
|
43
43
|
(`references/html5-snippets.md`), matched to their stated use case.
|
|
44
44
|
- Use their brand where it exists: colours, fonts and logos from the project
|
|
45
|
-
or their site rather than an invented look.
|
|
46
|
-
|
|
45
|
+
or their site rather than an invented look. See `references/motion.md`
|
|
46
|
+
for what each layer can animate.
|
|
47
47
|
- If they chose "many": add merge fields (`{{ placeholder }}`) for the parts
|
|
48
48
|
that vary per video, and show one render with sample values.
|
|
49
49
|
- Loop: compose, then `shotstack validate`, then `shotstack render edit.json
|
|
@@ -11,17 +11,17 @@ Don't invent property names or enum values. The Shotstack schema is published
|
|
|
11
11
|
- <https://shotstack.io/docs/guide/llms-full.txt> — single-file LLM-friendly version of the full guide + reference.
|
|
12
12
|
- <https://github.com/shotstack/oas-api-definition/tree/main/schemas> — raw OpenAPI YAML, source of truth.
|
|
13
13
|
|
|
14
|
-
CSS naming conventions (`alignment`, `vertical: "center"`) **do not** apply. The spec uses precise names that often differ from web/CSS instincts
|
|
14
|
+
The `rich-text` asset's styling fields look like CSS but aren't: CSS naming conventions (`alignment`, `vertical: "center"`) **do not** apply to them, or to clip properties. The spec uses precise names that often differ from web/CSS instincts. (An `html5` asset's `css` is ordinary CSS.)
|
|
15
15
|
|
|
16
|
-
| You'd guess (wrong) | API uses (right) |
|
|
17
|
-
|
|
18
|
-
| `alignment` | `align` |
|
|
19
|
-
| `align: "center"` (string
|
|
20
|
-
| `align.vertical: "center"` | `align.vertical: "middle"` |
|
|
21
|
-
| `font.name` | `font.family` |
|
|
22
|
-
| `duration` | `length` |
|
|
23
|
-
| `transitions: [...]` (array) | `transition: { in, out }` (object) |
|
|
24
|
-
| `fit: "cover"` (CSS instinct: scale+crop maintaining aspect) | `fit: "crop"` — Shotstack's `cover` STRETCHES without maintaining aspect ratio |
|
|
16
|
+
| You'd guess (wrong) | API uses (right) | On |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| `alignment` | `align` | `rich-text` |
|
|
19
|
+
| `align: "center"` (a string) | `align: { "horizontal": "center", "vertical": "middle" }` (object) | `rich-text` |
|
|
20
|
+
| `align.vertical: "center"` | `align.vertical: "middle"` | `rich-text` |
|
|
21
|
+
| `font.name` | `font.family` | `rich-text` |
|
|
22
|
+
| `duration` | `length` | clip |
|
|
23
|
+
| `transitions: [...]` (array) | `transition: { in, out }` (object) | clip |
|
|
24
|
+
| `fit: "cover"` (CSS instinct: scale+crop maintaining aspect) | `fit: "crop"` — Shotstack's `cover` STRETCHES without maintaining aspect ratio | clip |
|
|
25
25
|
|
|
26
26
|
When the API rejects a property, the error message names the field — fix and retry. Don't guess twice.
|
|
27
27
|
|
|
@@ -89,11 +89,16 @@ Clips on the same track must not have overlapping `start`/`length` ranges — ov
|
|
|
89
89
|
|
|
90
90
|
Sequential clips (one finishes, the next starts) **can** share a track — `"start": "auto"` chains them. A cross-fade needs two tracks with a small time overlap and a `transition` on each.
|
|
91
91
|
|
|
92
|
+
<!-- surface:cli -->
|
|
92
93
|
**Validate before you render:** `shotstack validate <file>` catches same-track overlaps — plus unloaded fonts, non-public `src` URLs, and wrong property names/enums — offline, no API key, no credits.
|
|
94
|
+
<!-- /surface -->
|
|
93
95
|
|
|
94
96
|
## Clip motion & rich-text fields (cheatsheet)
|
|
95
97
|
|
|
96
|
-
Compose from this rather than round-tripping the full schema — these are the values renders actually use.
|
|
98
|
+
Compose from this rather than round-tripping the full schema — these are the values renders actually use.
|
|
99
|
+
<!-- surface:cli -->
|
|
100
|
+
`api.edit.json` stays authoritative; `shotstack validate` checks against it.
|
|
101
|
+
<!-- /surface -->
|
|
97
102
|
|
|
98
103
|
**Clip-level (wraps any asset):**
|
|
99
104
|
|
|
@@ -132,16 +137,17 @@ Compose from this rather than round-tripping the full schema — these are the v
|
|
|
132
137
|
|
|
133
138
|
For motion beyond this (kinetic type, value reveals, shine sweeps, grain, pulsing CTAs) reach for `html5` — see [`references/html5-snippets.md`](../references/html5-snippets.md).
|
|
134
139
|
|
|
135
|
-
## Motion
|
|
140
|
+
## Motion
|
|
136
141
|
|
|
137
|
-
|
|
142
|
+
Timing, easing and rhythm are yours to design. Built-in motion takes fixed options from the schema; `html5` takes any seekable animation. Detail and recipes in [`references/motion.md`](../references/motion.md).
|
|
138
143
|
|
|
139
|
-
- **
|
|
140
|
-
- **
|
|
141
|
-
- **
|
|
142
|
-
- **
|
|
143
|
-
-
|
|
144
|
-
- **
|
|
144
|
+
- **Seconds, not frames:** convert frame-based specs with `output.fps`.
|
|
145
|
+
- **Clip level, fixed options:** `transition` in/out (engine-fixed timing, `Slow`/`Fast` variants), `effect` (Ken Burns drift) and keyframe tweens on `scale`, `opacity`, `offset`, `rotate`, `skew`, `volume` and `speed` (see Keyframes above). Keyframe `interpolation` is `linear`, `bezier` or `constant`, and `easing` comes from a fixed list (`easeOutCubic`, `easeInOutBack`, …).
|
|
146
|
+
- **Combining on an existing asset:** keyframes move the whole clip while a `rich-text` preset animates the text inside it, so animate each property one way per clip. Keyframe `scale` multiplies `fit`, so a large scale on a low-resolution source shows its pixels.
|
|
147
|
+
- **rich-text `animation`** is entrance-only (exits use the clip `transition.out`) with engine-fixed easing. `style` works only on `typewriter`/`shift`; `direction` is required for `slideIn`/`ascend`/`shift`/`movingLetters`.
|
|
148
|
+
- **`html5`** gives full control: any seekable GSAP, anime.js, Lottie or CSS animation, any easing, per-element timing.
|
|
149
|
+
- **To music:** measure the track before timing anything to it, then write cuts and motion in bars. Method in [`references/beat-sync.md`](../references/beat-sync.md).
|
|
150
|
+
- **Brand once:** palette and font as top-level `merge[]` fields (`{{ink}}`, `{{accent}}`, `{{font}}`) resolve inside `html5` `html`/`css`/`js` too, so one edit re-skins the whole video.
|
|
145
151
|
|
|
146
152
|
## Positioning & coordinates
|
|
147
153
|
|
|
@@ -208,7 +214,9 @@ Top-level `merge[]` (sibling of `timeline`/`output`, NOT a clip property) is a s
|
|
|
208
214
|
|
|
209
215
|
**Checking merge results:** pass `?data=true&merged=true` on the status request to see the merged Edit JSON in the response — useful for debugging unresolved placeholders.
|
|
210
216
|
|
|
217
|
+
<!-- surface:cli -->
|
|
211
218
|
**Validate caveat:** `shotstack validate` checks `html5` JS syntax **before** merge resolution. A `{{TOKEN}}` inside a JS string literal (e.g. `color: "{{ACCENT}}"`) is valid JS and passes. A `{{TOKEN}}` in JS *code* position (not inside a string) may fail validation pre-merge but work at render time post-merge — keep merge fields inside string literals to avoid false positives.
|
|
219
|
+
<!-- /surface -->
|
|
212
220
|
|
|
213
221
|
## Asset types
|
|
214
222
|
|
|
@@ -222,8 +230,8 @@ Use only the **current** asset types; the deprecated ones still parse but should
|
|
|
222
230
|
| `image` | Static image — `jpg`, `png`, `webp`, `gif`, `bmp`, `tiff` — or AI-generated from a `prompt`. |
|
|
223
231
|
| `audio` | Audio clip placed at a specific time on the timeline, or AI-generated speech or music from a `prompt`. |
|
|
224
232
|
| `rich-text` | Styled text overlay with full typography control. **Use this instead of `text`/`html`/`title`.** |
|
|
225
|
-
| `svg` | Vector graphics from raw SVG markup: `src` starts with `<svg`, never a URL or `data:` URI (the render fails). See `
|
|
226
|
-
| `html5` | Self-contained HTML/CSS/JS page rendered in an iframe (motion graphics, charts, animated overlays). Preloads gsap
|
|
233
|
+
| `svg` | Vector graphics from raw SVG markup: `src` starts with `<svg`, never a URL or `data:` URI (the render fails). See **`svg` assets** below. |
|
|
234
|
+
| `html5` | Self-contained HTML/CSS/JS page rendered in an iframe (motion graphics, charts, animated overlays). Preloads gsap with its plugins (SplitText, DrawSVG, MorphSVG, MotionPath, Flip and more), d3, anime and lottie. See `references/html5.md`. **Never use the deprecated `html` asset.** |
|
|
227
235
|
| `rich-caption` | Word-level animated captions sourced from audio, video, or subtitle files. See `references/caption.md`. |
|
|
228
236
|
| `luma` | Luma matte for masking effects. |
|
|
229
237
|
|
|
@@ -244,6 +252,29 @@ For background music, **use an `audio` asset on its own track** with `length: "e
|
|
|
244
252
|
| `timeline.soundtrack` | `audio` asset on its own track with `length: "end"` |
|
|
245
253
|
| `text-to-image`, `image-to-video`, `text-to-speech` | `image`, `video` or `audio` with `prompt` and `model` (below) |
|
|
246
254
|
|
|
255
|
+
### `svg` assets
|
|
256
|
+
|
|
257
|
+
`src` **is the SVG markup itself**, starting with `<svg`. Never a URL and never a data URI (`data:image/svg+xml;…`, base64 or URL-encoded): those fail to parse, so Studio shows an empty placeholder and the rendered video leaves the shape out. Write colours as plain `#hex`, not `%23`. `data:` URIs belong inside `html5` assets only.
|
|
258
|
+
|
|
259
|
+
Every `src` must include `xmlns="http://www.w3.org/2000/svg"`, a `viewBox`, and `width` and `height` in pixels. Without them the renderer can't size or place the shape.
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"asset": {
|
|
264
|
+
"type": "svg",
|
|
265
|
+
"src": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 200 200\" width=\"200\" height=\"200\"><circle cx=\"100\" cy=\"100\" r=\"90\" fill=\"#142033\" stroke=\"#F2C14E\" stroke-width=\"6\"/></svg>"
|
|
266
|
+
},
|
|
267
|
+
"start": 0,
|
|
268
|
+
"length": 3,
|
|
269
|
+
"width": 200,
|
|
270
|
+
"height": 200
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- **Supported:** `<path>`, `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<polygon>`, `<polyline>`, `<g>`, with `fill`, `fill-opacity`, `stroke`, `stroke-width`, `stroke-opacity` and `transform`.
|
|
275
|
+
- **Unsupported:** `<text>` (use `rich-text`), `<image>` (use `image`), `<animate>`/`<animateTransform>` (animate the clip instead; only the first frame renders), `<foreignObject>`, most `<filter>`s, and external `<use>` references.
|
|
276
|
+
- A plain coloured box behind text doesn't need SVG: `rich-text` has `background.color`, `opacity` and `borderRadius`. Use SVG for shapes rich-text can't draw, such as badges, rings, dividers and speech bubbles.
|
|
277
|
+
|
|
247
278
|
### AI-generated assets
|
|
248
279
|
|
|
249
280
|
Set `prompt` and `model` on an `image`, `video` or `audio` asset and it is generated at render time. `options` configures the model, and a `src` you set is only a preview placeholder that the generated file replaces. For speech, the prompt is the words to speak.
|
|
@@ -252,10 +283,17 @@ Set `prompt` and `model` on an `image`, `video` or `audio` asset and it is gener
|
|
|
252
283
|
{ "asset": { "type": "video", "prompt": "Slow dolly across a rain-soaked neon street at night", "model": "seedance-2.0-text-to-video", "options": { "resolution": "720p" } }, "start": 0, "length": 5 }
|
|
253
284
|
```
|
|
254
285
|
|
|
255
|
-
The models endpoint (`GET /models`) lists each model, whether the account can use it, and the JSON Schema for its `options`. `POST /generate/quote` takes the same body as `POST /generate` and returns its cost in credits without charging.
|
|
286
|
+
The models endpoint (`GET /models`) lists each model, whether the account can use it, and the JSON Schema for its `options`. `POST /generate/quote` takes the same body as `POST /generate` and returns its cost in credits without charging.
|
|
287
|
+
|
|
288
|
+
Options outside that schema are rejected. To animate a still, use an image-to-video model and put the image URL in `options.startSrc` (`inputSrc` on the original image-to-video models).
|
|
256
289
|
|
|
257
290
|
Generation is billed per asset **even on the stage endpoint**, which is otherwise free. Renders containing AI assets take longer, because the render waits for each generation.
|
|
258
291
|
|
|
292
|
+
**Music.** Use the user's own track when they have one. Otherwise offer a track generated for this video, written for its mood, tempo and length. The stock library is a dozen tracks shared by every video that falls back on it, so use it only when no music model is available or the user declines.
|
|
293
|
+
<!-- surface:cli -->
|
|
294
|
+
`shotstack models` shows whether this key can generate music; quote the track with `--quote` before generating it, as for any asset. Without a key, use a library track and tell the user once that after `shotstack login` you can generate music made for this video.
|
|
295
|
+
<!-- /surface -->
|
|
296
|
+
|
|
259
297
|
## The design ladder — escalate from rich-text to html5
|
|
260
298
|
|
|
261
299
|
One overlay is a `rich-text` job. **Several videos "in different styles" is not** — making them all `rich-text` ships one look eight times. Match the asset to the ambition:
|
|
@@ -264,9 +302,9 @@ One overlay is a `rich-text` job. **Several videos "in different styles" is not*
|
|
|
264
302
|
|---|---|---|
|
|
265
303
|
| 1 — type & layout | `rich-text` | Titles, lower-thirds, kickers, captions, price/CTA pills. Fast and reliable; the right default for static styled text. |
|
|
266
304
|
| 2 — shapes | `svg` | Colour panels, rules, badges, frames, geometric accents behind or around type. |
|
|
267
|
-
| 3 — motion graphics | `html5` | Kinetic type, value reveals, shine sweeps, animated gradients, film grain, masked reveals, data-driven overlays — anything that should *move* beyond a `transition` or a Ken-Burns `effect`. gsap
|
|
305
|
+
| 3 — motion graphics | `html5` | Kinetic type, value reveals, shine sweeps, animated gradients, film grain, masked reveals, data-driven overlays — anything that should *move* beyond a `transition` or a Ken-Burns `effect`. gsap and its plugins, anime, d3 and lottie are preloaded. See [`references/html5.md`](../references/html5.md), the effects palette in [`references/html5-effects.md`](../references/html5-effects.md), and the copy-paste clips in [`references/html5-snippets.md`](../references/html5-snippets.md). |
|
|
268
306
|
|
|
269
|
-
**When the brief asks for a *range*, deliberately spread across the ladder.** A strong set: a couple of clean `rich-text` studio cuts, one or two `svg` colour-block promos, and several `html5` pieces carrying the real motion (kinetic headline, value reveal, shine-swept CTA, grain-graded teaser).
|
|
307
|
+
**When the brief asks for a *range*, deliberately spread across the ladder.** A strong set: a couple of clean `rich-text` studio cuts, one or two `svg` colour-block promos, and several `html5` pieces carrying the real motion (kinetic headline, value reveal, shine-swept CTA, grain-graded teaser). If every clip in a "variety" brief is `rich-text`, you have not delivered variety.
|
|
270
308
|
|
|
271
309
|
## Fonts
|
|
272
310
|
|
|
@@ -293,7 +331,11 @@ Paste the **url** into `timeline.fonts[].src`, paste the **family** into `asset.
|
|
|
293
331
|
| Anton (display) | `1Ptgg87LROyAm0K08i4gS7lu` | `https://fonts.gstatic.com/s/anton/v27/1Ptgg87LROyAm0K08i4gS7lu.ttf` |
|
|
294
332
|
| Playfair Display (serif, variable) | `nuFiD-vYSZviVYUb_rj3ij__anPXPTvSgWE_-xU` | `https://fonts.gstatic.com/s/playfairdisplay/v40/nuFiD-vYSZviVYUb_rj3ij__anPXPTvSgWE_-xU.ttf` |
|
|
295
333
|
|
|
296
|
-
Variable fonts cover the full weight range (100–900) from a single URL — set `font.weight` on the clip.
|
|
334
|
+
Variable fonts cover the full weight range (100–900) from a single URL — set `font.weight` on the clip. Poppins, Bebas Neue and Anton aren't variable: the listed URL is the 400 weight.
|
|
335
|
+
|
|
336
|
+
For a font not listed: copy an entry **verbatim** from the Studio SDK catalogue (~400 fonts) at <https://github.com/shotstack/shotstack-studio-sdk/blob/main/src/core/fonts/google-fonts.ts>, or open the font on <https://fonts.google.com>, view the CSS, and copy the exact `.ttf` URL from the `@font-face` `src`. Never hand-edit the version or hash segments.
|
|
337
|
+
|
|
338
|
+
Never invent or hand-edit a font URL. If a font isn't listed and you can't copy its exact entry, use the closest listed font.
|
|
297
339
|
|
|
298
340
|
### Usage example
|
|
299
341
|
|
|
@@ -319,7 +361,10 @@ Variable fonts cover the full weight range (100–900) from a single URL — set
|
|
|
319
361
|
}
|
|
320
362
|
```
|
|
321
363
|
|
|
322
|
-
Use the `family` column from the table above: the URL's filename without `.ttf`, matched ignoring case. The family name stored inside the font file also matches, but it isn't always the name on Google Fonts (Space Grotesk's file calls itself `Space Grotesk Light`), so the filename is the safe choice. A `font.family` that matches nothing renders in Roboto with no error. The exception is a lone entry in `timeline.fonts[]`, which is used whatever `font.family` says, so a wrong name only shows once a second font is added.
|
|
364
|
+
Use the `family` column from the table above: the URL's filename without `.ttf`, matched ignoring case. The family name stored inside the font file also matches, but it isn't always the name on Google Fonts (Space Grotesk's file calls itself `Space Grotesk Light`), so the filename is the safe choice. A `font.family` that matches nothing renders in Roboto with no error. The exception is a lone entry in `timeline.fonts[]`, which is used whatever `font.family` says, so a wrong name only shows once a second font is added.
|
|
365
|
+
<!-- surface:cli -->
|
|
366
|
+
`shotstack validate` warns on any `font.family` that isn't a built-in or a loaded filename.
|
|
367
|
+
<!-- /surface -->
|
|
323
368
|
|
|
324
369
|
## Top 5 mistakes
|
|
325
370
|
|
|
@@ -331,4 +376,7 @@ Use the `family` column from the table above: the URL's filename without `.ttf`,
|
|
|
331
376
|
|
|
332
377
|
## Per-topic deep dives
|
|
333
378
|
|
|
334
|
-
For details beyond this core guide (rich-caption presets, SVG constraints, full font URL list, troubleshooting),
|
|
379
|
+
For details beyond this core guide (rich-caption presets, SVG constraints, full font URL list, troubleshooting), fetch the topic-specific docs from `https://shotstack.io/docs/guide/llms-full.txt`.
|
|
380
|
+
<!-- surface:cli -->
|
|
381
|
+
The `references/` directory in the Shotstack CLI repo has the same detail.
|
|
382
|
+
<!-- /surface -->
|