@scenerok/sdk 0.26.0 → 0.27.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/package.json +1 -1
- package/skills/scenerok/SKILL.md +73 -14
- package/skills/scenerok/references/animations-catalog.md +9 -0
- package/skills/scenerok/references/animations-picker.md +3 -0
- package/skills/scenerok/references/catalog-index.json +1 -1
- package/skills/scenerok/references/editing-actions.md +4 -2
- package/skills/scenerok/references/intent-router.md +3 -3
- package/skills/scenerok/references/look-and-feel.md +139 -0
- package/skills/scenerok/references/plugins.md +4 -1
- package/skills/scenerok/references/recipes.md +221 -0
- package/skills/scenerok/references/shaders-filters.md +1 -1
- package/skills/scenerok/references/text-styles.md +5 -0
- package/skills/scenerok/references/vidscript-guide.md +3 -3
- package/skills/scenerok/references/vidscript-strict.md +3 -3
- package/skills/scenerok/references/workflow.md +17 -21
package/package.json
CHANGED
package/skills/scenerok/SKILL.md
CHANGED
|
@@ -35,14 +35,15 @@ Copy this checklist into your notes and work through it:
|
|
|
35
35
|
|
|
36
36
|
```text
|
|
37
37
|
- [ ] 1. Identify the environment (web editor, CLI, ChatGPT, MCP, chat without tools)
|
|
38
|
-
- [ ] 2.
|
|
39
|
-
- [ ] 3.
|
|
40
|
-
-
|
|
41
|
-
- [ ]
|
|
42
|
-
- [ ]
|
|
38
|
+
- [ ] 2. Pick the closest recipe in recipes.md and read look-and-feel.md
|
|
39
|
+
- [ ] 3. Gather pictures: page images, screenshots, logo, footage, or generated
|
|
40
|
+
stills (look-and-feel.md → Sourcing imagery). Probe each one; crop with ffmpeg
|
|
41
|
+
- [ ] 4. Decide the look: palette, two fonts, type sizes, beats. Write them as `let`s
|
|
42
|
+
- [ ] 5. Write the script from the recipe; read other references only as needed
|
|
43
|
+
- [ ] 6. Validate. Fix every error and aesthetic warning; validate again until clean
|
|
43
44
|
- [ ] 7. Uses TTS or generated media: deep-validate and fix the report (CLI only)
|
|
44
|
-
- [ ] 8. Render
|
|
45
|
-
- [ ] 9.
|
|
45
|
+
- [ ] 8. Render, pull one frame per beat, look at them, run the freeze check
|
|
46
|
+
- [ ] 9. Fix what the frames show and render again. Done = the quality gate passes
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
With the CLI:
|
|
@@ -56,9 +57,13 @@ scenerok lint-video <render-id> --cost medium --json # billed
|
|
|
56
57
|
|
|
57
58
|
`scenerok render script.vid --validate` renders and checks the MP4 in one step.
|
|
58
59
|
|
|
59
|
-
With marketplace plugins
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
With marketplace plugins: search, inspect, dry-run, then run a pinned ref
|
|
61
|
+
(full workflow in [plugins.md](references/plugins.md)). A plugin's output must
|
|
62
|
+
pass the same quality gate. When a plugin's template is text over a flat
|
|
63
|
+
colour, draft it (`scenerok plugin draft … --out draft.vid`), add the
|
|
64
|
+
pictures and motion from [recipes.md](references/recipes.md), then
|
|
65
|
+
`scenerok plugin render-draft <id> --vidscript draft.vid`. Today that covers
|
|
66
|
+
the first-party `listing-promo` and `release-video` templates.
|
|
62
67
|
|
|
63
68
|
```bash
|
|
64
69
|
scenerok plugin search "product promo"
|
|
@@ -96,6 +101,43 @@ ChatGPT, and MCP have their own validate and render calls but no deep
|
|
|
96
101
|
validation or MP4 check, and can't read local files:
|
|
97
102
|
[environments.md](references/environments.md).
|
|
98
103
|
|
|
104
|
+
## Quality gate
|
|
105
|
+
|
|
106
|
+
`validate` tells you the script is valid, not that the video is good. A video
|
|
107
|
+
is done only when all of these hold. Text over a flat colour fails, however
|
|
108
|
+
clean the script.
|
|
109
|
+
|
|
110
|
+
1. **A picture under every beat.** Each beat shows a real asset (product
|
|
111
|
+
photo, screenshot, logo, footage, screen recording) or a generated image or
|
|
112
|
+
video. No beat is type on a plain colour.
|
|
113
|
+
2. **Nothing stands still.** Every picture and text line has `animate:` with an
|
|
114
|
+
entrance; anything that leaves before the end also has an exit. Stills get a
|
|
115
|
+
slow push; logos float. After rendering, this prints nothing (no hold of 3 s
|
|
116
|
+
or longer; a `freeze_start` with no `freeze_end` runs to the end):
|
|
117
|
+
`ffmpeg -hide_banner -i out.mp4 -vf freezedetect=n=0.003:d=3 -map 0:v -f null - 2>&1 | grep freeze_start`
|
|
118
|
+
3. **A deliberate look.** The script opens with `let` values for its palette
|
|
119
|
+
(bg, ink, muted, one accent) and its two fonts. Sizes follow the type scale
|
|
120
|
+
in [look-and-feel.md](references/look-and-feel.md): at least 44 px on a
|
|
121
|
+
1920 px tall canvas.
|
|
122
|
+
4. **One grade.** `filter "cdl"` and/or `vignette` (or a LUT from an HTTPS
|
|
123
|
+
URL) spans the whole video.
|
|
124
|
+
5. **Sound.** A music bed, a voiceover, or the footage's own audio, unless the
|
|
125
|
+
user asked for silence.
|
|
126
|
+
6. **Validate clean.** Fix every error and every aesthetic warning, or write
|
|
127
|
+
down why a warning doesn't apply (each recipe names the warnings it accepts
|
|
128
|
+
and why).
|
|
129
|
+
7. **Looked at.** Render, pull one frame per beat, open the frames, and fix what
|
|
130
|
+
you see: wrapped or colliding text, black boxes where a picture should be,
|
|
131
|
+
a fallback font, empty bands. Then render again. Validate's text sizes are
|
|
132
|
+
estimates, so only the frames show the real result.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
for t in 1.5 5 9 13; do ffmpeg -loglevel error -y -ss $t -i out.mp4 -frames:v 1 frame-$t.png; done
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Without a shell (web editor, ChatGPT, MCP), check 1-6 against the script and
|
|
139
|
+
ask the user to watch the preview before you call it done.
|
|
140
|
+
|
|
99
141
|
## Rules that prevent most failures
|
|
100
142
|
|
|
101
143
|
1. **Import before calling.** `import xai from "@scenerok/xai"`, then
|
|
@@ -114,6 +156,11 @@ validation or MP4 check, and can't read local files:
|
|
|
114
156
|
6. **Use only documented syntax.** Don't invent functions or parameters. A
|
|
115
157
|
validation error names the problem; fix that line rather than rewriting the
|
|
116
158
|
script.
|
|
159
|
+
7. **`font:` is a Google Fonts family name.** `"Inter Bold"` is not a family
|
|
160
|
+
and falls back. For heavy type use a heavy family such as
|
|
161
|
+
`"Archivo Black"`.
|
|
162
|
+
8. **Crop stills with ffmpeg, not `.Crop()`.** Prepare images before you place
|
|
163
|
+
them; a cropped remote still rendered as nothing in testing.
|
|
117
164
|
|
|
118
165
|
## References
|
|
119
166
|
|
|
@@ -121,6 +168,7 @@ Read the ones the task needs. Paths are relative to this file.
|
|
|
121
168
|
|
|
122
169
|
| Task | Read |
|
|
123
170
|
| --- | --- |
|
|
171
|
+
| Any video | [recipes.md](references/recipes.md) and [look-and-feel.md](references/look-and-feel.md) (always): complete product promo, release notes and talking-head scripts; palette, type scale, safe areas, motion, sourcing pictures |
|
|
124
172
|
| Any VidScript | [vidscript-strict.md](references/vidscript-strict.md) (always) |
|
|
125
173
|
| Editing and compositing: trims, speed, reverse, freeze, picture-in-picture, blend modes, masks, crops, split screens, audio mixing and ducking, grading and LUTs, captions, timecode | [editing-actions.md](references/editing-actions.md) |
|
|
126
174
|
| Full grammar | [vidscript-guide.md](references/vidscript-guide.md) |
|
|
@@ -148,14 +196,25 @@ chat apps are there too: `consumer-chat-apps.md`,
|
|
|
148
196
|
`chatgpt-gpt-instructions.md`, `gemini-gem-instructions.md`, `grok-skill.md`.
|
|
149
197
|
Previews of every catalog item: https://scenerok.com/catalog.
|
|
150
198
|
|
|
151
|
-
##
|
|
199
|
+
## One beat, done properly
|
|
200
|
+
|
|
201
|
+
The smallest unit that passes the gate: a real picture with a push, copy that
|
|
202
|
+
enters and leaves, a grade, and sound. A video is several of these; the full
|
|
203
|
+
scripts are in [recipes.md](references/recipes.md).
|
|
152
204
|
|
|
153
205
|
```vidscript
|
|
154
206
|
import motion from "@scenerok/basic-animations"
|
|
155
207
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
208
|
+
let ink = "#F4F6FA"
|
|
209
|
+
let display = "Archivo Black"
|
|
210
|
+
|
|
211
|
+
input shot = "https://cdn.example.com/product-lifestyle.jpg" # 1080x1920, probed
|
|
212
|
+
input bed = "https://cdn.example.com/music.mp3"
|
|
213
|
+
|
|
214
|
+
[0s .. 4s] = shot, width: 1080, height: 1920, x: 0, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.08 }, easing: "linear" }
|
|
215
|
+
[0.2s .. 4s] = text "Cleans while you're out", font: display, size: 96, color: ink, x: "50%", y: "28%", max_width: "86%", align: center, shadow_color: "#000000", shadow_blur: 24, animate: [motion.riseIn(0.5s, 60), motion.fadeOut(0.3s)]
|
|
216
|
+
[0s .. 4s] = filter "vignette", intensity: 0.3
|
|
217
|
+
[0s .. 4s] = audio bed, volume: 0.5, fade_out: 0.5s
|
|
159
218
|
output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
160
219
|
```
|
|
161
220
|
|
|
@@ -98,6 +98,15 @@ overlays (composite over video/scenes), honor every param, and scale to any outp
|
|
|
98
98
|
Prefer these over hand-authoring when a headline/caption/stat treatment fits; see the
|
|
99
99
|
`text-anim` rows in `animations-picker.md` for intent mapping.
|
|
100
100
|
|
|
101
|
+
## Code blocks in vertical video
|
|
102
|
+
|
|
103
|
+
At a 9:16 output the 12 VS Code theme `code_snippet_*` blocks and `code_diff` switch to a
|
|
104
|
+
native 1080x1920 layout: no desktop chrome, ~40px type that wraps instead of shrinking, a
|
|
105
|
+
line-by-line reveal with a moving focus bar and caret (a +/− count and a focus ring for
|
|
106
|
+
`code_diff`), a camera that scrolls long code, and a lift-and-fade exit timed to the
|
|
107
|
+
placement. Same params, same theme palettes; 16:9 is unchanged. It still reads best with
|
|
108
|
+
3–12 short lines: put the one change that matters on screen, not the whole file.
|
|
109
|
+
|
|
101
110
|
## Custom HTML (bring your own animation)
|
|
102
111
|
|
|
103
112
|
`animations.custom_html(...)` renders **your own HTML/CSS/JS** through the same HyperFrames
|
|
@@ -47,10 +47,13 @@ Param resolution: **named > positional > default**.
|
|
|
47
47
|
| Block / family | Issue | Fix |
|
|
48
48
|
| --- | --- | --- |
|
|
49
49
|
| `code_diff` | Line indices may be 0-based in adapter | Match sample in `animations-catalog.md` |
|
|
50
|
+
| Code blocks in 9:16 | Only the VS Code theme `code_snippet_*` blocks and `code_diff` have a portrait layout; `code_typing`, `code_morph`, the terminal blocks, etc. are scaled down from 16:9 and read small | Prefer `code_snippet_<theme>` / `code_diff` for vertical video; pass 3–12 short lines, not a file |
|
|
50
51
|
| Unverified blocks | `scenerok validate` warns `verified:false` | Accept demo render or switch to verified family |
|
|
51
52
|
| Maps / liquid-glass / app-showcase | Ignore user params | Use for demo aesthetic only, or hand-author |
|
|
52
53
|
| Adapter blocks | No inline audio/text siblings | Put `audio`, `text`, `filter` on separate time blocks |
|
|
53
54
|
| `logo_outro` | Renders upstream demo's Figma-style mark when no logo param given | Pass `brand: "scenerok"` or `logoUrl: "<image url>"` |
|
|
55
|
+
| `code_*` and most blocks in 9:16 | Designed at 1920x1080 and scaled to fit: 56 % size with empty bands above and below (lower thirds have native 9:16 layouts) | Use them in 16:9; in 9:16 build a code card from `text` lines (`recipes.md` → Release notes) |
|
|
56
|
+
| `text_counter_stat` | `suffix` defaults to `"+"`, so 12 reads "12+" | Pass `suffix: ""` unless the number really is a floor |
|
|
54
57
|
|
|
55
58
|
## Two-phase compose recipe
|
|
56
59
|
|
|
@@ -462,8 +462,10 @@ input a = "log-footage.mp4"
|
|
|
462
462
|
[0s .. 4s] = filter "cdl", slope: "1.1 1 0.9", offset: "0 0 0", power: "1 1 1", saturation: 1
|
|
463
463
|
```
|
|
464
464
|
|
|
465
|
-
- `filter "lut"` applies a 3D `.cube` file
|
|
466
|
-
|
|
465
|
+
- `filter "lut"` applies a 3D `.cube` file (512 KB at most, up to 33 points
|
|
466
|
+
per axis). Use the `filter` line, not `Filter("lut")`. A hosted compile (CLI
|
|
467
|
+
render, web, MCP) can't read a local path: give an HTTPS URL or a `data:`
|
|
468
|
+
URI, or use `cdl`.
|
|
467
469
|
- `filter "cdl"` takes ASC CDL `slope`, `offset`, `power` (three numbers each)
|
|
468
470
|
and `saturation`.
|
|
469
471
|
|
|
@@ -29,13 +29,13 @@ Defaults unless the user says otherwise:
|
|
|
29
29
|
|
|
30
30
|
| Workflow | Load these slugs | Primary APIs (VidScript · SDK) |
|
|
31
31
|
| --- | --- | --- |
|
|
32
|
-
| **Product promo** | `
|
|
32
|
+
| **Product promo** | `recipes.md`, `look-and-feel.md`, `brand-setup.md` (+ `ai-models.md` when the page has no usable pictures, `sdk-guide.md` if TS) | page images as `input` with a scale push, `motion.riseIn` / `popIn`, `solid` CTA button, `filter "cdl"`, `eleven.music` |
|
|
33
33
|
| **Motion graphic** | `animations-picker.md` (+ `animations-catalog.md` if needed) | `animations.*` (VidScript HyperFrames; prefer VidScript for catalog blocks) |
|
|
34
34
|
| **Social proof card** | `animations-picker.md` | `animations.x_post`, `reddit_post`, … (VidScript) |
|
|
35
35
|
| **Edit existing footage** | `editing-actions.md` (+ `media-stdlib.md` to probe the clips, `transitions-catalog.md` for cuts) | `Trim` / `Speed` / `Loop` / `Crop` / `Mask` / `Overlay` / `Composite`, `blend:`, `duck_under:`, `filter "lut"` (VidScript only) |
|
|
36
36
|
| **Scene montage** | `transitions-catalog.md`, `ai-models.md` (+ `sdk-guide.md` if TS loops) | `tr.dissolve` · SDK: `import tr from '@scenerok/transitions'` + `t[start][end] = tr.dissolve()` |
|
|
37
37
|
| **Narrated explainer** | `tts.md`, `text-styles.md`, `music.md`, `workflow.md` (+ `sdk-guide.md` if TS) | `xai.tts` · `await xai.tts(...)` + `t.push(vo)` |
|
|
38
|
-
| **Code demo / PR** | `animations-picker.md` (code rows) | `code_typing`, `code_diff` (VidScript) |
|
|
38
|
+
| **Code demo / PR / release notes** | `recipes.md` (release notes), `animations-picker.md` (code rows) | 16:9: `code_typing`, `code_diff`; 9:16: a code card of `text` lines with `motion.typewriter` (VidScript) |
|
|
39
39
|
| **Text animation / headline / stat** | `animations-picker.md` (text-anim rows) | `animations.text_*` (VidScript) · SDK kinetic: `text` + `fadeIn` / `popIn` |
|
|
40
40
|
| **Kinetic title only** | `text-styles.md` (+ `sdk-guide.md` if TS) | `text` + `motion.*` · SDK: `text(..., { area })` + `.animate(fadeIn(...))` |
|
|
41
41
|
| **Custom-authored animation (no block fits)** | `animations-catalog.md` → "Custom HTML" | `animations.custom_html` (VidScript) |
|
|
@@ -43,7 +43,7 @@ Defaults unless the user says otherwise:
|
|
|
43
43
|
|
|
44
44
|
## Staged routing
|
|
45
45
|
|
|
46
|
-
1. **Classify** — workflow from table above
|
|
46
|
+
1. **Classify** — workflow from table above, then the matching script in `recipes.md`
|
|
47
47
|
2. **Choose authoring** — VidScript by default and for every editing action (masks, blends, crops, ducking, LUTs, timecode); `@scenerok/sdk` (`sdk-guide.md`) only for loops or computed timing (it has none of the editing actions)
|
|
48
48
|
3. **Load catalogs** — only what that workflow needs (conditional loading)
|
|
49
49
|
4. **Name block or API** — VidScript: pick `animations.<fn>` from `animations-picker.md`. SDK: factories (`video` / `loadVideo`, `text`, `fadeIn`) + AI imports (`@scenerok/xai`, …)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Look and feel
|
|
2
|
+
|
|
3
|
+
Decide the look before you write a block. Declare it as `let` values at the top
|
|
4
|
+
of the script and use only those values below. `recipes.md` shows the whole
|
|
5
|
+
system in use.
|
|
6
|
+
|
|
7
|
+
## Palette
|
|
8
|
+
|
|
9
|
+
Four colours, declared once:
|
|
10
|
+
|
|
11
|
+
| Name | Use | Default (dark) |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `bg` | Canvas and plates behind cards | `#0A0C10` |
|
|
14
|
+
| `ink` | Headlines and body copy | `#F4F6FA` |
|
|
15
|
+
| `muted` | Secondary lines: ratings, URLs, captions | `#A3ADBD` |
|
|
16
|
+
| `accent` | One brand colour: kicker, price, bullets, CTA button, highlight bar | sampled from the brand |
|
|
17
|
+
|
|
18
|
+
Sample the accent from the brand's own pixels (a button or logo on the page)
|
|
19
|
+
or from `brand-setup.md`. Don't invent one. Use at most three non-neutral text
|
|
20
|
+
colours (`validate` warns above that).
|
|
21
|
+
|
|
22
|
+
## Type
|
|
23
|
+
|
|
24
|
+
Use two families: a display face and a body face. `font:` is one Google
|
|
25
|
+
Fonts family, loaded at weight 400. A weight in the name (`"Inter Bold"`,
|
|
26
|
+
`"Inter Black"`) is not a family: the render falls back to a default face.
|
|
27
|
+
For heavy type, use a family that is heavy at 400.
|
|
28
|
+
|
|
29
|
+
Rendered and checked: `Archivo Black` (display), `Inter` (body),
|
|
30
|
+
`JetBrains Mono` (code; turns `=>` into a ligature). Other single-weight
|
|
31
|
+
display families: `Anton`, `Bebas Neue`.
|
|
32
|
+
|
|
33
|
+
Sizes in output pixels (`size:`). `validate` flags text under 2.2 % of the
|
|
34
|
+
canvas height:
|
|
35
|
+
|
|
36
|
+
| Role | 1080x1920 (9:16) | 1920x1080 (16:9) |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Hero / price | 120-150 | 120-140 |
|
|
39
|
+
| Headline | 84-104 | 72-88 |
|
|
40
|
+
| Bullet / caption card | 64-72 | 48-56 |
|
|
41
|
+
| Body / kicker / URL (the floor) | 44-48 | 32-36 |
|
|
42
|
+
|
|
43
|
+
Kickers: body face, accent colour, `letter_spacing: 6`-`10`, all caps.
|
|
44
|
+
Validate's text geometry is an estimate. A display face can run 15-20 % wider
|
|
45
|
+
than it predicts, so check the rendered frame before trusting a line to fit.
|
|
46
|
+
|
|
47
|
+
## Layout and safe areas
|
|
48
|
+
|
|
49
|
+
- Text `x`/`y` place the text's centre (`anchor: "left"` places its
|
|
50
|
+
left-centre). Prefer `area:` when a named region fits. Use explicit pixels
|
|
51
|
+
for designed layouts like the recipes.
|
|
52
|
+
- 9:16: keep copy between y 220 and y 1500 and x 72 to 1008. Feed UI covers
|
|
53
|
+
the top ~8 %, the bottom ~20 % and a right-hand icon rail.
|
|
54
|
+
- 16:9: keep copy inside a 96 px margin. Put the picture on one side and the
|
|
55
|
+
copy on the other.
|
|
56
|
+
- Margins: 48 px at the edge of a card; cards use `radius: 32`-`48`.
|
|
57
|
+
- Show at most 3 text overlays at once, except the lines of a code card.
|
|
58
|
+
|
|
59
|
+
## Pictures
|
|
60
|
+
|
|
61
|
+
Every beat has a picture under its type. In order of preference:
|
|
62
|
+
|
|
63
|
+
1. **Real**: product photos, logos, app screenshots, footage, screen recordings.
|
|
64
|
+
2. **Generated**: `xai.image`, `xai.imageToVideo` from a real photo, `xai.imagine`
|
|
65
|
+
(`ai-models.md`; prompts forbid text).
|
|
66
|
+
3. **Made**: a brand-colour glow or gradient rendered with ffmpeg (recipes →
|
|
67
|
+
Release notes), as a background behind real pictures, never as the only picture.
|
|
68
|
+
|
|
69
|
+
Placing stills in a frame of a different shape:
|
|
70
|
+
|
|
71
|
+
- **Card**: the still at its own aspect, `radius:` and `blend: "normal"`, on
|
|
72
|
+
a plain or blurred plate (a product on white in a 900 px card).
|
|
73
|
+
- **Band + blurred fill**: a wide crop as a full-width band, over a blurred,
|
|
74
|
+
darkened 9:16 copy of the same shot. This works for small or landscape stills.
|
|
75
|
+
- **Cover**: oversize the clip to fill, e.g. 16:9 into 9:16 at
|
|
76
|
+
`width: 3413, height: 1920, x: -1166`. This keeps transitions legal, which
|
|
77
|
+
`Crop` doesn't.
|
|
78
|
+
|
|
79
|
+
Prepare stills with ffmpeg before you place them. Crop out baked-in
|
|
80
|
+
captions, make the blurred fill, and probe the result:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ffmpeg -i in.jpg -vf "crop=W:H:X:Y,scale=1080:-2" band.jpg
|
|
84
|
+
ffmpeg -i in.jpg -vf "crop=W:H:X:Y,scale=1080:1920,boxblur=40:2,eq=brightness=-0.18" fill.jpg
|
|
85
|
+
sips -g pixelWidth -g pixelHeight band.jpg
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use `.Crop()` for video footage. A `.Crop()` on a remote still rendered as
|
|
89
|
+
nothing in testing, so crop stills with ffmpeg.
|
|
90
|
+
|
|
91
|
+
## Sourcing imagery
|
|
92
|
+
|
|
93
|
+
Collect pictures before you write copy:
|
|
94
|
+
|
|
95
|
+
| Source | How |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| Product page | Image URLs from `og:image`, the JSON-LD `Product.image`, Shopify `/products/<handle>.json` → `images`, Amazon `#landingImage` `data-old-hires` and the gallery `m.media-amazon.com/images/I/…` URLs |
|
|
98
|
+
| Page screenshot | Browser tool at 1440x900 (desktop) and 390x844 (mobile). Crop to the hero, the price card, or the feature you name; never use a full page as a background |
|
|
99
|
+
| Logo | The site's header SVG/PNG, `apple-touch-icon`, or the GitHub org avatar (`https://avatars.githubusercontent.com/u/<id>`) |
|
|
100
|
+
| App or site in motion | A screen recording (QuickTime, `ffmpeg -f avfoundation`, a browser recorder) as an `input` clip |
|
|
101
|
+
| Nothing usable | `xai.image` / `xai.imageToVideo` (`ai-models.md`) |
|
|
102
|
+
|
|
103
|
+
Local `input` files upload with `scenerok render`. Probe every file
|
|
104
|
+
(`sips`, `ffprobe`) and size placements from the measured aspect.
|
|
105
|
+
|
|
106
|
+
## Files the server must read
|
|
107
|
+
|
|
108
|
+
`filter "lut", file:` and `captions("…srt")` are read by the compiler. On a
|
|
109
|
+
hosted compile (CLI, web, MCP) a local path fails with `local asset not
|
|
110
|
+
found`. Pass an HTTPS URL or a `data:` URI, or grade with `filter "cdl"`,
|
|
111
|
+
which needs no file.
|
|
112
|
+
|
|
113
|
+
## Motion
|
|
114
|
+
|
|
115
|
+
| Phase | Use | Timing |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| Entrance | `motion.riseIn(0.5s, 40-80)` for copy and cards, `motion.popIn(0.4-0.6s)` for prices, buttons, logos, `motion.typewriter` for code, `motion.fadeIn(0.4s)` for secondary lines | Stagger items 0.3-0.6 s apart |
|
|
118
|
+
| Hold | Stills: a push `{ type: "scale", from: { scale: 1 }, to: { scale: 1.08 }, easing: "linear" }`. Logos and products: `motion.float(3s, 10-14)` | A static hold lasts 3 s at most |
|
|
119
|
+
| Exit | `motion.fadeOut(0.3s)` on everything that leaves before the end | The last 0.3 s of the block |
|
|
120
|
+
|
|
121
|
+
A push on a soft gradient doesn't count as motion: give it a still, footage,
|
|
122
|
+
or a floating element to move. Fast moves can take `motion_blur:`
|
|
123
|
+
(`editing-actions.md`).
|
|
124
|
+
|
|
125
|
+
## Pacing
|
|
126
|
+
|
|
127
|
+
- First visible picture and words by 0.3 s; the hook reads in under 1 s.
|
|
128
|
+
- Beats last 2.5-4.5 s each. A 15 s promo is 4 beats; a 30 s video is 6-8.
|
|
129
|
+
- One idea per beat: the hook, the product, three features, the price, and the call to action.
|
|
130
|
+
- Close on the call to action for at least 2.5 s.
|
|
131
|
+
|
|
132
|
+
## What premium means here
|
|
133
|
+
|
|
134
|
+
- A real picture fills or anchors every beat; no beat is text on a flat colour.
|
|
135
|
+
- One accent, used sparingly; everything else is ink and muted on dark (or the reverse).
|
|
136
|
+
- Big type with tight copy: 3-6 words a line, never a paragraph.
|
|
137
|
+
- Every element arrives, holds with a little motion, and leaves.
|
|
138
|
+
- One grade ties it together: `filter "cdl"` warmth or coolness plus a `vignette` of about 0.3.
|
|
139
|
+
- Sound under all of it: a music bed, a voiceover, or both.
|
|
@@ -7,7 +7,10 @@ cheaper than hand-composing. **Never assume which plugins exist** — discover
|
|
|
7
7
|
them at run time; the catalog grows and changes.
|
|
8
8
|
|
|
9
9
|
Plugins are the reuse-first path: check `intent-router.md` for the workflow
|
|
10
|
-
match
|
|
10
|
+
match. A plugin's video must still pass the quality gate in `SKILL.md`. Draft
|
|
11
|
+
first, read the filled script, and if a beat is text over a flat colour, add
|
|
12
|
+
the pictures and motion from `recipes.md` before `render-draft`. Today that
|
|
13
|
+
applies to the first-party `listing-promo` and `release-video` templates.
|
|
11
14
|
|
|
12
15
|
## Workflow (MCP)
|
|
13
16
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
Three complete scripts that pass the quality gate in `SKILL.md`. Each uses
|
|
4
|
+
the look system in `look-and-feel.md`. Copy the one that fits, then swap in
|
|
5
|
+
your own assets, copy, and palette. Keep the structure: a picture under every
|
|
6
|
+
beat, motion on everything, and a grade and sound over the whole video.
|
|
7
|
+
|
|
8
|
+
## Product promo (9:16, 15 s)
|
|
9
|
+
|
|
10
|
+
Four beats: hook, hero, features, then price and CTA. The images are the
|
|
11
|
+
product page's own (Amazon `landingImage`, `og:image`, or JSON-LD `image`;
|
|
12
|
+
see `look-and-feel.md` → Sourcing imagery). Prepare them before you write the
|
|
13
|
+
script. Amazon stills are 500 px squares with baked-in captions, so crop to a
|
|
14
|
+
clean region and make a blurred 9:16 backdrop from the same shot:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
ffmpeg -i room.jpg -vf "crop=500:185:0:95,scale=1080:-2" room-band.jpg
|
|
18
|
+
ffmpeg -i room.jpg -vf "crop=104:185:140:95,scale=1080:1920,boxblur=40:2,eq=brightness=-0.18" room-bg.jpg
|
|
19
|
+
sips -g pixelWidth -g pixelHeight room-band.jpg # 1080x398: place at 984x363
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```vidscript
|
|
23
|
+
import motion from "@scenerok/basic-animations"
|
|
24
|
+
import eleven from "@elevenlabs/music"
|
|
25
|
+
|
|
26
|
+
# Look: one palette, two fonts, one accent.
|
|
27
|
+
let bg = "#0A0C10"
|
|
28
|
+
let ink = "#F4F6FA"
|
|
29
|
+
let muted = "#A3ADBD"
|
|
30
|
+
let accent = "#3D8BFF"
|
|
31
|
+
let display = "Archivo Black"
|
|
32
|
+
let body = "Inter"
|
|
33
|
+
|
|
34
|
+
# Assets: the product page's own images, cropped and blurred with ffmpeg first.
|
|
35
|
+
input hero = "hero.jpg" # 483x472, product on white
|
|
36
|
+
input roomBand = "room-band.jpg" # 1080x398, crop of a lifestyle shot
|
|
37
|
+
input roomBg = "room-bg.jpg" # 1080x1920, same shot, blurred + darkened
|
|
38
|
+
input closeBand = "closeup-band.jpg" # 1080x368
|
|
39
|
+
input closeBg = "closeup-bg.jpg" # 1080x1920
|
|
40
|
+
|
|
41
|
+
# Slow push-in for every still, so no frame is ever frozen.
|
|
42
|
+
let push = { type: "scale", from: { scale: 1 }, to: { scale: 1.08 }, easing: "linear" }
|
|
43
|
+
|
|
44
|
+
# 1. Hook (0-3 s): lifestyle shot; the headline lands in the first second.
|
|
45
|
+
[0s .. 3s] = roomBg, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
46
|
+
[0s .. 3s] = roomBand, width: 984, height: 363, x: 48, y: 820, radius: 36, blend: "normal", animate: [motion.riseIn(0.5s, 80), push, motion.fadeOut(0.3s)]
|
|
47
|
+
[0.2s .. 3s] = text "Vacuums. Mops.\nEmpties itself.", font: display, size: 104, color: ink, line_height: 1.05, x: "50%", y: "27%", max_width: "86%", align: center, shadow_color: "#000000", shadow_blur: 24, animate: [motion.riseIn(0.6s, 50), motion.fadeOut(0.3s)]
|
|
48
|
+
|
|
49
|
+
# 2. Hero (3-7 s): the product on its own white card.
|
|
50
|
+
[3s .. 7s] = solid(bg)
|
|
51
|
+
[3s .. 7s] = hero, width: 900, height: 880, x: 90, y: 300, radius: 48, blend: "normal", animate: [motion.fadeIn(0.4s), push, motion.fadeOut(0.3s)]
|
|
52
|
+
[3.3s .. 7s] = text "ECOVACS", font: body, size: 44, color: accent, letter_spacing: 10, x: "50%", y: "64.5%", align: center, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
53
|
+
[3.5s .. 7s] = text "DEEBOT T30e OMNI", font: display, size: 72, color: ink, x: "50%", y: "71%", max_width: "88%", align: center, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
54
|
+
|
|
55
|
+
# 3. Features (7-11 s): a close-up band, then one bullet every 0.6 s.
|
|
56
|
+
[7s .. 11s] = closeBg, width: 1080, height: 1920, x: 0, y: 0, animate: push
|
|
57
|
+
[7s .. 11s] = closeBand, width: 984, height: 335, x: 48, y: 300, radius: 36, blend: "normal", animate: [motion.fadeIn(0.4s), push, motion.fadeOut(0.3s)]
|
|
58
|
+
[7.4s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 868, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
59
|
+
[7.4s .. 11s] = text "25,000 Pa suction", font: display, size: 64, color: ink, x: 150, y: 880, anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
60
|
+
[8.0s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 1028, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
61
|
+
[8.0s .. 11s] = text "ZeroTangle 3.0 brush", font: display, size: 64, color: ink, x: 150, y: 1040, anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
62
|
+
[8.6s .. 11s] = solid(accent, width: 24, height: 24, x: 96, y: 1188, radius: 12), animate: [motion.popIn(0.4s), motion.fadeOut(0.3s)]
|
|
63
|
+
[8.6s .. 11s] = text "Self-emptying dock", font: display, size: 64, color: ink, x: 150, y: 1200, max_width: "80%", anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
64
|
+
|
|
65
|
+
# 4. Price and CTA (11-15 s). The button is a solid with a radius under the label.
|
|
66
|
+
[11s .. 15s] = solid(bg)
|
|
67
|
+
[11s .. 15s] = hero, width: 560, height: 548, x: 260, y: 260, radius: 40, blend: "normal", animate: [motion.popIn(0.5s), motion.float(3s, 10)]
|
|
68
|
+
[11.3s .. 15s] = text "₹44,999", font: display, size: 150, color: ink, x: "50%", y: "50%", align: center, animate: motion.popIn(0.5s)
|
|
69
|
+
[11.6s .. 15s] = text "★ 4.4 · 166 ratings", font: body, size: 48, color: muted, x: "50%", y: "58.5%", align: center, animate: motion.fadeIn(0.4s)
|
|
70
|
+
[12s .. 15s] = solid(accent, width: 660, height: 132, x: 210, y: 1210, radius: 66), animate: motion.popIn(0.5s)
|
|
71
|
+
[12s .. 15s] = text "Shop on Amazon", font: display, size: 56, color: "#FFFFFF", x: "50%", y: 1276, align: center, animate: motion.fadeIn(0.4s)
|
|
72
|
+
|
|
73
|
+
# Grade: warm the whole frame, pull the edges down.
|
|
74
|
+
[0s .. 15s] = filter "cdl", slope: "1.04 1.0 0.96", offset: "0 0 0", power: "1 1 1", saturation: 1.08
|
|
75
|
+
[0s .. 15s] = filter "vignette", intensity: 0.3
|
|
76
|
+
|
|
77
|
+
# Sound: a bed under everything.
|
|
78
|
+
let bed = eleven.music("Bright, modern electronic product-ad bed, confident pulse, clean ending", duration: 15, instrumental: true)
|
|
79
|
+
[0s .. 15s] = audio bed, volume: 0.5, fade_out: 1s
|
|
80
|
+
|
|
81
|
+
output to "promo.mp4", resolution: "1080x1920", fps: 30, background: "#0A0C10"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`validate` warns `too-many-animations` at the bullets: the three bullets
|
|
85
|
+
leave together on one 0.3 s fade, and the linter counts each exit as motion.
|
|
86
|
+
Accept that warning here and fix any other.
|
|
87
|
+
|
|
88
|
+
Variations: replace the hook band with
|
|
89
|
+
`xai.imageToVideo(hero, "slow orbit around the product, …", aspect_ratio: "9:16", duration: 3)`
|
|
90
|
+
for a moving hero shot (`ai-models.md`). For 16:9, use the 16:9 type scale
|
|
91
|
+
and put the product on the right and the copy on the left.
|
|
92
|
+
|
|
93
|
+
## Release notes / changelog (9:16, 18 s)
|
|
94
|
+
|
|
95
|
+
Four beats: version hook, the one new feature as typed code with a
|
|
96
|
+
highlight, a stat, and an outro. Facts come from the release body
|
|
97
|
+
(`gh api repos/<owner>/<repo>/releases/latest --jq .body`); count the
|
|
98
|
+
`fix`/`feat` lines rather than estimating. The logo is the project's own
|
|
99
|
+
(`https://avatars.githubusercontent.com/u/<id>`, or the README image). The
|
|
100
|
+
background is a brand glow made with ffmpeg:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ffmpeg -f lavfi -i "color=c=black:s=1080x1920,format=rgb24" -frames:v 1 \
|
|
104
|
+
-vf "geq=r='11+90*exp(-((X-760)^2+(Y-520)^2)/180000)':g='11+30*exp(-((X-760)^2+(Y-520)^2)/180000)':b='15+4*exp(-((X-760)^2+(Y-520)^2)/180000)'" glow.png
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```vidscript
|
|
108
|
+
import motion from "@scenerok/basic-animations"
|
|
109
|
+
import animations from "@scenerok/animations"
|
|
110
|
+
import eleven from "@elevenlabs/music"
|
|
111
|
+
|
|
112
|
+
let ink = "#F5F5F7"
|
|
113
|
+
let muted = "#9CA3AF"
|
|
114
|
+
let accent = "#FF5B11"
|
|
115
|
+
let display = "Archivo Black"
|
|
116
|
+
let mono = "JetBrains Mono"
|
|
117
|
+
|
|
118
|
+
input glow = "glow.png" # 1080x1920, made with ffmpeg: brand-colour glow on near-black
|
|
119
|
+
input flame = "flame.png" # 460x460 RGBA, the project's logo
|
|
120
|
+
|
|
121
|
+
# Ambient layer: the background drifts for the whole video.
|
|
122
|
+
[0s .. 18s] = glow, width: 1080, height: 1920, x: 0, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.15 }, easing: "linear" }
|
|
123
|
+
|
|
124
|
+
# 1. Hook (0-3.5 s)
|
|
125
|
+
[0s .. 3.5s] = flame, width: 460, height: 460, x: 310, y: 340, blend: "normal", animate: [motion.popIn(0.6s), motion.float(2.9s, 14), motion.fadeOut(0.3s)]
|
|
126
|
+
[0.3s .. 3.5s] = text "RELEASE NOTES", font: mono, size: 44, color: accent, letter_spacing: 8, x: "50%", y: "50%", align: center, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
127
|
+
[0.5s .. 3.5s] = text "hono v4.13.14", font: display, size: 120, color: ink, x: "50%", y: "57%", max_width: "88%", align: center, animate: [motion.riseIn(0.6s, 60), motion.fadeOut(0.3s)]
|
|
128
|
+
|
|
129
|
+
# 2. The new feature as code (3.5-10 s): headline, card, then the code types itself.
|
|
130
|
+
[3.5s .. 10s] = text "NEW IN BEARER-AUTH", font: mono, size: 44, color: accent, letter_spacing: 6, x: 96, y: 300, anchor: "left", align: left, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
131
|
+
[3.6s .. 10s] = text "Error messages can\nreturn a Response", font: display, size: 84, color: ink, line_height: 1.1, x: 96, y: 440, anchor: "left", align: left, animate: [motion.riseIn(0.5s, 40), motion.fadeOut(0.3s)]
|
|
132
|
+
[4s .. 10s] = solid("#16161D", width: 960, height: 700, x: 60, y: 620, radius: 32), animate: [motion.riseIn(0.5s, 60), motion.fadeOut(0.3s)]
|
|
133
|
+
# One text layer per code line: a tall multi-line block can clip. Indent with x, not spaces.
|
|
134
|
+
[4.4s .. 10s] = text "bearerAuth({ token,", font: mono, size: 44, color: ink, x: 108, y: 760, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
135
|
+
[4.9s .. 10s] = text "invalidToken: {", font: mono, size: 44, color: ink, x: 164, y: 836, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
136
|
+
[5.4s .. 10s] = text "message: (c) =>", font: mono, size: 44, color: accent, x: 220, y: 912, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
137
|
+
[5.9s .. 10s] = text "c.json(problem, 401),", font: mono, size: 44, color: accent, x: 276, y: 988, anchor: "left", align: left, animate: [motion.typewriter(0.5s), motion.fadeOut(0.3s)]
|
|
138
|
+
[6.4s .. 10s] = text "}})", font: mono, size: 44, color: ink, x: 108, y: 1064, anchor: "left", align: left, animate: [motion.typewriter(0.3s), motion.fadeOut(0.3s)]
|
|
139
|
+
# Highlight beat: a bar sweeps under the changed lines once the code is typed.
|
|
140
|
+
[7.2s .. 10s] = solid(accent, width: 900, height: 158, x: 90, y: 872, radius: 16, opacity: 0.16), animate: [{ type: "slideX", from: { x: -1000 }, to: { x: 0 }, start: 0, end: 0.5, easing: "easeOutCubic" }, motion.fadeOut(0.3s)]
|
|
141
|
+
[7.4s .. 10s] = text "Any content type, e.g. problem+json", font: mono, size: 44, color: muted, x: 108, y: 1220, anchor: "left", align: left, animate: [motion.fadeIn(0.4s), motion.fadeOut(0.3s)]
|
|
142
|
+
|
|
143
|
+
# 3. Highlight (10-14 s): one number, then three rows. suffix: "" because the block defaults to "+".
|
|
144
|
+
[10s .. 14s] = animations.text_counter_stat("12", "BUG FIXES", suffix: "", accent: accent)
|
|
145
|
+
[11s .. 14s] = text "serve-static: GET and HEAD only", font: mono, size: 44, color: ink, x: "50%", y: "70%", align: center, animate: [motion.riseIn(0.4s, 30), motion.fadeOut(0.3s)]
|
|
146
|
+
[11.4s .. 14s] = text "css: safer url() minifying", font: mono, size: 44, color: ink, x: "50%", y: "74.5%", align: center, animate: [motion.riseIn(0.4s, 30), motion.fadeOut(0.3s)]
|
|
147
|
+
[11.8s .. 14s] = text "trie-router: keep first param", font: mono, size: 44, color: ink, x: "50%", y: "79%", align: center, animate: [motion.riseIn(0.4s, 30), motion.fadeOut(0.3s)]
|
|
148
|
+
|
|
149
|
+
# 4. Outro (14-18 s)
|
|
150
|
+
[14s .. 18s] = flame, width: 320, height: 320, x: 380, y: 520, blend: "normal", animate: [motion.popIn(0.5s), motion.float(3s, 12)]
|
|
151
|
+
[14.3s .. 18s] = solid("#1C1C24", width: 860, height: 120, x: 110, y: 950, radius: 60), animate: motion.popIn(0.5s)
|
|
152
|
+
[14.3s .. 18s] = text "npm i hono@4.13.14", font: mono, size: 48, color: ink, x: "50%", y: 1010, align: center, animate: motion.fadeIn(0.4s)
|
|
153
|
+
[14.8s .. 18s] = text "github.com/honojs/hono", font: mono, size: 44, color: muted, x: "50%", y: 1150, align: center, animate: motion.fadeIn(0.4s)
|
|
154
|
+
|
|
155
|
+
# Grade
|
|
156
|
+
[0s .. 18s] = filter "cdl", slope: "1.03 1.0 0.97", offset: "0 0 0", power: "1 1 1", saturation: 1.05
|
|
157
|
+
[0s .. 18s] = filter "vignette", intensity: 0.35
|
|
158
|
+
|
|
159
|
+
let bed = eleven.music("Minimal driving synth pulse for a developer product update, clean and modern", duration: 18, instrumental: true)
|
|
160
|
+
[0s .. 18s] = audio bed, volume: 0.5, fade_out: 1.5s
|
|
161
|
+
|
|
162
|
+
output to "release.mp4", resolution: "1080x1920", fps: 30, background: "#0B0B0F"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`validate` warns `crowded-text-overlays` and `too-many-animations` on the
|
|
166
|
+
code card, because each code line counts as an overlay. Accept those two
|
|
167
|
+
there; fix any other warning. The `code_*` catalog blocks are 1920x1080
|
|
168
|
+
designs: use them in 16:9, where they fill the frame. In 9:16 they shrink to
|
|
169
|
+
56 % with empty bands above and below.
|
|
170
|
+
|
|
171
|
+
## Talking head with captions (9:16, 14 s)
|
|
172
|
+
|
|
173
|
+
A 16:9 interview cut for a vertical feed: fill the frame by oversizing the
|
|
174
|
+
clip (no `Crop`, so transitions stay legal), punch in on the key line, cut
|
|
175
|
+
away to b-roll with a whip pan, and burn in short, timed caption cards.
|
|
176
|
+
|
|
177
|
+
```vidscript
|
|
178
|
+
import motion from "@scenerok/basic-animations"
|
|
179
|
+
import animations from "@scenerok/animations"
|
|
180
|
+
import tr from "@scenerok/transitions"
|
|
181
|
+
|
|
182
|
+
let ink = "#FFFFFF"
|
|
183
|
+
let accent = "#FFD43B"
|
|
184
|
+
let display = "Archivo Black"
|
|
185
|
+
|
|
186
|
+
input talk = "https://cdn.example.com/interview-16x9.mp4" # 1920x1080, probed
|
|
187
|
+
input broll = "https://cdn.example.com/broll-16x9.mp4" # 1920x1080, probed
|
|
188
|
+
input bed = "https://cdn.example.com/music.mp3"
|
|
189
|
+
|
|
190
|
+
# Fill 9:16 from 16:9: scale to 1920 tall (3413 wide) and centre it, x = (1080 - 3413) / 2.
|
|
191
|
+
# Cut 1: wide, with a slow push. Cut 2: punch in on the key line. Cut 3: b-roll. Cut 4: back to the speaker.
|
|
192
|
+
[0s .. 4s] = talk.Trim(12s, 16s), width: 3413, height: 1920, x: -1166, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.05 }, easing: "linear" }
|
|
193
|
+
[4s .. 7s] = talk.Trim(16s, 19s), width: 3413, height: 1920, x: -1166, y: 0, animate: { type: "scale", from: { scale: 1.18 }, to: { scale: 1.22 }, easing: "linear" }
|
|
194
|
+
[6.8s .. 7.2s] = tr.whip_pan()
|
|
195
|
+
[7.2s .. 10s] = broll.Trim(0s, 2.8s), width: 3413, height: 1920, x: -1166, y: 0, animate: { type: "scale", from: { scale: 1.1 }, to: { scale: 1 }, easing: "linear" }
|
|
196
|
+
[9.8s .. 10.2s] = tr.dissolve()
|
|
197
|
+
[10.2s .. 14s] = talk.Trim(22.2s, 26s), width: 3413, height: 1920, x: -1166, y: 0, animate: { type: "scale", from: { scale: 1 }, to: { scale: 1.05 }, easing: "linear" }
|
|
198
|
+
|
|
199
|
+
# Name card from the catalog (it has a native 9:16 layout).
|
|
200
|
+
[0.6s .. 4s] = animations.lt_kicker_name("FOUNDER", "Asha Rao", accent: accent)
|
|
201
|
+
|
|
202
|
+
# Captions: 2-4 words per card, timed to the transcript, the key words in the accent colour.
|
|
203
|
+
[0.2s .. 1.4s] = text "We shipped it", font: display, size: 72, color: ink, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
204
|
+
[1.4s .. 2.8s] = text "in nine days", font: display, size: 72, color: accent, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
205
|
+
[4s .. 5.6s] = text "and it paid", font: display, size: 72, color: ink, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
206
|
+
[5.6s .. 7s] = text "for itself", font: display, size: 72, color: accent, stroke: "#000000", stroke_width: 6, x: "50%", y: "70%", max_width: "84%", align: center, animate: motion.popIn(0.25s)
|
|
207
|
+
|
|
208
|
+
# Sound: the speaker's own audio, with music ducked under it.
|
|
209
|
+
[0s .. 7s] = audio talk.Trim(12s, 19s)
|
|
210
|
+
[10.2s .. 14s] = audio talk.Trim(22.2s, 26s)
|
|
211
|
+
[0s .. 14s] = audio bed, volume: 0.5, duck_under: talk, fade_out: 1s
|
|
212
|
+
|
|
213
|
+
[0s .. 14s] = filter "cdl", slope: "1.02 1.0 0.98", offset: "0 0 0", power: "1 1 1", saturation: 1.05
|
|
214
|
+
|
|
215
|
+
output to "talk.mp4", resolution: "1080x1920", fps: 30
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
With a transcript in an SRT file, `captions("talk.srt", font: display, size: 72, area: "caption")`
|
|
219
|
+
replaces the caption lines (`editing-actions.md` → Text and titles). On a
|
|
220
|
+
hosted render the file must be an HTTPS URL; see `look-and-feel.md` → Files the
|
|
221
|
+
server must read.
|
|
@@ -21,7 +21,7 @@ A `filter` line grades everything on screen during its window. Add `target: clip
|
|
|
21
21
|
| `saturation` | Color intensity |
|
|
22
22
|
| `brightness` | Exposure lift/drop |
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Give every finished video one grade across its whole length: `cdl` for warmth or coolness plus `vignette` around 0.3 (see `look-and-feel.md`). Effect filters (`glitch`, `chromatic`, `blur`) are optional accents; don't stack them by default.
|
|
25
25
|
|
|
26
26
|
## VFX filters
|
|
27
27
|
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
`import motion from "@scenerok/basic-animations"` — use with `animate:` on `text` blocks.
|
|
4
4
|
|
|
5
|
+
Fonts: `font:` is one Google Fonts family at weight 400. Names with a weight such as
|
|
6
|
+
`"Inter Black"` or `"Inter Bold"` (in some catalog examples below) are not families and fall
|
|
7
|
+
back to a default face in renders; use a heavy family such as `"Archivo Black"` instead.
|
|
8
|
+
Type scale, palette and motion timing: `look-and-feel.md`.
|
|
9
|
+
|
|
5
10
|
## Motion primitives
|
|
6
11
|
|
|
7
12
|
| Primitive | Suits | Phase |
|
|
@@ -11,8 +11,8 @@ Contents: Agent quick start · Program structure · Statements (input, output, l
|
|
|
11
11
|
1. **Import every package function** — use `import xai from "@scenerok/xai"`, `import cf from "@scenerok/cloudflare"`, `import eleven from "@elevenlabs/music"`, and `import motion from "@scenerok/basic-animations"` before calling `xai.*`, `cf.*`, `eleven.*`, or `motion.*`.
|
|
12
12
|
2. **Validate early** — `scenerok validate file.vid` (errors show as `Line N:C — message`).
|
|
13
13
|
3. **Music package syntax** — `import eleven from "@elevenlabs/music"`, then call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`.
|
|
14
|
-
4. **
|
|
15
|
-
5. **
|
|
14
|
+
4. **Start from a recipe** — `recipes.md` (product promo, release notes, talking head) and the look system in `look-and-feel.md`. A finished video passes the quality gate in `SKILL.md`: a picture under every beat, motion on everything, one grade, sound.
|
|
15
|
+
5. **A URL means its pictures go in the video** — when the user gives a website or product URL, use the page's own product images, logo and focused screenshots as `input` assets (`look-and-feel.md` → Sourcing imagery). Don't use a full-page screenshot as a background.
|
|
16
16
|
6. **Probe asset type and dimensions** — before placing real assets, identify image vs video with `file`; inspect width, height, and duration with `ffprobe`, `sips`, or `identify`; then scale from the measured aspect ratio and set explicit `width`, `height`, `x`, and `y`.
|
|
17
17
|
7. **Use still images directly** — `input` supports videos and images. Place still screenshots, logos, and product photos with `video image_input`; the renderer treats images as static clips for the block duration.
|
|
18
18
|
8. **Use the full media plugin toolkit** — choose still generation, text-to-video, image-to-video, reference-to-video, video extension, TTS, and music deliberately. Do not default to only `xai.imagine`.
|
|
@@ -681,7 +681,7 @@ import motion from "@scenerok/basic-animations"
|
|
|
681
681
|
[0s .. 5s] = filter "glitch", intensity: 0.5, animate: motion.fadeIn(1s)
|
|
682
682
|
```
|
|
683
683
|
|
|
684
|
-
|
|
684
|
+
Grade the whole video once (`cdl`, `vignette`, or a `lut`); effect filters are optional accents. Parameter names per filter: `editing-actions.md` → Color and finishing.
|
|
685
685
|
|
|
686
686
|
### Animation functions (`@scenerok/basic-animations`)
|
|
687
687
|
|
|
@@ -15,9 +15,9 @@ Without the owning import, calls like `xai.imagine`, `cf.video`, `xai.tts`, `ele
|
|
|
15
15
|
|
|
16
16
|
**Never** call package functions without importing the package alias first.
|
|
17
17
|
|
|
18
|
-
## Rule 2:
|
|
18
|
+
## Rule 2: A URL means its pictures go in the video
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
When the user gives a product or landing-page URL, the video uses that page's own pictures: product shots, logos, focused screenshots. Reading only the title and price is not enough. Take focused, legible pieces, never a full-page screenshot as a background, and don't invent claims. How to collect and prepare them: `look-and-feel.md` → Sourcing imagery.
|
|
21
21
|
|
|
22
22
|
## Rule 2.5: Probe real asset type and dimensions before placement
|
|
23
23
|
|
|
@@ -211,7 +211,7 @@ let bed = eleven.music("Soft premium ad music", duration: 15, instrumental: true
|
|
|
211
211
|
[0s .. 15s] = filter "saturation", amount: 1.12
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
Every finished video carries one grade over the whole timeline (`cdl` and/or `vignette`, or a `lut` from an HTTPS URL): see the quality gate in `SKILL.md`. Effect filters (`glitch`, `chromatic`, `blur`) stay optional. Each filter has its own parameter name (`saturation` takes `amount`, `blur` takes `radius`); a wrong name is ignored without an error. The table is in `editing-actions.md` → Color and finishing.
|
|
215
215
|
|
|
216
216
|
A `filter` line grades everything in its window. To grade one clip, add `target: clip` or write `clip.Filter("sepia", intensity: 0.6)`.
|
|
217
217
|
|
|
@@ -2,35 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## Pipeline
|
|
4
4
|
|
|
5
|
-
1. **Understand the goal** — promo, testimonial, meme, code demo, etc.
|
|
5
|
+
1. **Understand the goal** — promo, testimonial, meme, code demo, etc. Pick the closest script in `recipes.md`
|
|
6
6
|
2. **Choose authoring** — VidScript (`.vid`) by default and for every editing action; TypeScript `@scenerok/sdk` (`.ts`, `sdk-guide.md`) only for loops or computed timing
|
|
7
|
-
3. **Plan the structure** —
|
|
8
|
-
4. **Gather
|
|
9
|
-
5. **Probe
|
|
10
|
-
6. **
|
|
7
|
+
3. **Plan the structure** — beats of 2.5–4.5 s, each with a picture, copy, and motion (`look-and-feel.md`)
|
|
8
|
+
4. **Gather pictures** — page images, focused screenshots, logo, footage, screen recordings; generated stills or clips when nothing real exists. A video with no picture is not done
|
|
9
|
+
5. **Probe and prepare** — `file` / `ffprobe` / `sips` / `identify` (or `loadVideo` / media stdlib); crop and blur stills with ffmpeg; never guess sizes
|
|
10
|
+
6. **Decide the look** — palette, two fonts, and type sizes as `let` values (`look-and-feel.md`)
|
|
11
11
|
7. **Choose plugin modality** — still / t2v / i2v / ref2v / extend / TTS / music (`ai-models.md`, `tts.md`, `music.md`)
|
|
12
12
|
8. **Compose** — measured dimensions, `area:` / `t.grid` for text, symbolic durations (`vo.duration` / returned node `.duration`), entry/exit beats
|
|
13
13
|
9. **Validate** — `scenerok validate script.vid` or `scenerok validate scene.ts`
|
|
14
14
|
10. **Deep-validate (VidScript)** — for scripts with TTS/generated media: `scenerok validate script.vid --generate-assets --report-json` (billed; assets cache for the render). SDK plugins generate during `scenerok render` instead. Fix `offCanvas`/`wasShrunk` text, `collisions`, and unintended `audio.overlaps`, then re-validate.
|
|
15
15
|
11. **Render** — `scenerok render script.vid --watch` or `scenerok render scene.ts --watch`
|
|
16
|
-
12. **
|
|
17
|
-
13. **Deliver** — share download URL
|
|
16
|
+
12. **Look at the render** — pull one frame per beat with ffmpeg and open them; run the freeze check (both commands are in the quality gate in `SKILL.md`). Fix what you see and render again. `scenerok lint-video <render-id> --cost medium --json` (billed) adds an LLM review of frame grabs
|
|
17
|
+
13. **Deliver** — share the download URL once the quality gate passes
|
|
18
18
|
|
|
19
19
|
## Website URL asset workflow
|
|
20
20
|
|
|
21
|
-
When the user
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
6. Use stills as timeline inputs or feed into `xai.imageToVideo` / `xai.referenceToVideo` (VidScript or SDK).
|
|
29
|
-
7. Combine real assets with generated clips, text, and music.
|
|
30
|
-
8. Scale from measured aspect ratio; keep primary visuals in ~80–88% frame width for 9:16.
|
|
31
|
-
9. Animate assets: `motion.popIn` / SDK `popIn`, `fadeIn`, `slideY`.
|
|
32
|
-
10. Generative prompts must require **no text, logos, watermarks, or readable UI** in AI output — add copy with `text`.
|
|
33
|
-
11. Do not invent product claims — extract from site or ask the user.
|
|
21
|
+
When the user gives a product, company, landing-page or store URL, the page's
|
|
22
|
+
own pictures go in the video. Reading only the title, price and bullets is not
|
|
23
|
+
enough. Collect the image URLs and focused screenshots, probe them, crop out
|
|
24
|
+
baked-in captions, and place them as cards, bands over a blurred fill, or
|
|
25
|
+
full-frame covers: `look-and-feel.md` → Pictures and Sourcing imagery. A worked
|
|
26
|
+
example is the product promo in `recipes.md`. Don't invent product claims;
|
|
27
|
+
extract them from the page or ask.
|
|
34
28
|
|
|
35
29
|
## Best practices
|
|
36
30
|
|
|
@@ -62,7 +56,9 @@ If render fails: check `scenerok status <id>`, fix missing assets or syntax, re-
|
|
|
62
56
|
|
|
63
57
|
## Sample video types
|
|
64
58
|
|
|
65
|
-
- **Product promo** — hero
|
|
59
|
+
- **Product promo** — hero image, features, price + CTA (`recipes.md`)
|
|
60
|
+
- **Release notes** — logo, typed code, stat, outro (`recipes.md`)
|
|
61
|
+
- **Talking head** — speaker cut, punch-ins, b-roll, captions (`recipes.md`)
|
|
66
62
|
- **Social hook** — fast cuts, bold text
|
|
67
63
|
- **Testimonial** — speaker clip + quote overlay
|
|
68
64
|
- **Meme remix** — reaction clip + punchlines
|