@scenerok/cli 2.2.0 → 2.4.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 +6 -6
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +31 -25
- package/dist/utils/skillsManifest.d.ts +13 -3
- package/dist/utils/skillsManifest.d.ts.map +1 -1
- package/dist/utils/skillsManifest.js +35 -12
- package/examples/system/rokmilk-chocolate-promo.vid +1 -1
- package/examples/system/voice-visual-promo.vid +1 -1
- package/package.json +3 -3
- package/skills/scenerok/SKILL.md +150 -0
- package/skills/{remote → scenerok/references}/ai-models.md +1 -2
- package/skills/{remote → scenerok/references}/animations-catalog.md +139 -2
- package/skills/{remote → scenerok/references}/animations-picker.md +12 -1
- package/skills/{remote → scenerok/references}/brand-setup.md +3 -1
- package/skills/{remote → scenerok/references}/catalog-index.json +163 -19
- package/skills/scenerok/references/editing-actions.md +560 -0
- package/skills/{remote → scenerok/references}/environments.md +23 -24
- package/skills/{remote → scenerok/references}/intent-router.md +5 -15
- package/skills/{remote → scenerok/references}/media-stdlib.md +2 -3
- package/skills/{remote → scenerok/references}/music.md +2 -21
- package/skills/scenerok/references/sdk-guide.md +301 -0
- package/skills/{remote → scenerok/references}/shaders-filters.md +9 -6
- package/skills/{remote → scenerok/references}/text-styles.md +15 -35
- package/skills/{remote → scenerok/references}/transitions-catalog.md +2 -0
- package/skills/{remote → scenerok/references}/tts.md +4 -22
- package/skills/{remote → scenerok/references}/vidscript-guide.md +250 -67
- package/skills/scenerok/references/vidscript-sample.md +44 -0
- package/skills/{remote → scenerok/references}/vidscript-strict.md +41 -26
- package/skills/{remote → scenerok/references}/workflow.md +6 -7
- package/skills/remote/sdk-guide.md +0 -201
- package/skills/remote/vidscript-sample.md +0 -28
- package/skills/shared/SKILL.md +0 -157
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
VidScript is a declarative DSL for composing short-form videos. Write a script, render an MP4.
|
|
4
4
|
|
|
5
|
+
This is the grammar reference. For task-by-task editing (layering, masks, cuts, speed, sound mixing, grading, delivery), use `editing-actions.md`; for the rules agents most often break, `vidscript-strict.md`.
|
|
6
|
+
|
|
7
|
+
Contents: Agent quick start · Program structure · Statements (input, output, let, import) · Time blocks · Including TypeScript scenes · Instructions (clips, text, grid areas, video methods, compositing, filters, motion blur, shaders, audio, plugin calls) · Reuse: functions and modules · Animations and effects · Expressions · Template placeholders · CLI commands · Best practices
|
|
8
|
+
|
|
5
9
|
## Agent quick start (read before composing)
|
|
6
10
|
|
|
7
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.*`.
|
|
8
12
|
2. **Validate early** — `scenerok validate file.vid` (errors show as `Line N:C — message`).
|
|
9
13
|
3. **Music package syntax** — `import eleven from "@elevenlabs/music"`, then call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`.
|
|
10
|
-
4. **Copy working examples** —
|
|
14
|
+
4. **Copy working examples** — `vidscript-sample.md`, and the `.vid` files in the `@scenerok/cli` package's `examples/system/` (e.g. `minimal-text-reel.vid`, `ecom-product-launch.vid`, `rokmilk-chocolate-promo.vid`).
|
|
11
15
|
5. **Use URL assets selectively** — if the user gives a website or product URL and browser screenshot tools are available, you can capture above-fold screenshots, product images, app screenshots, and logos for VidScript `input` assets. Do not treat screenshots as mandatory or as the only visual source.
|
|
12
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`.
|
|
13
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.
|
|
@@ -59,6 +63,15 @@ input logo = "./assets/logo.png"
|
|
|
59
63
|
|
|
60
64
|
Supports HTTP(S) URLs, `/uploads/` paths, and local paths for video/image assets.
|
|
61
65
|
|
|
66
|
+
A numbered image sequence is one input: the pattern names the frames, and `count:` says how many (a local compile can find it by probing). Optional `rate:` (frames per second, default the output fps) and `first:` (number of the first file, default 0):
|
|
67
|
+
|
|
68
|
+
```vidscript
|
|
69
|
+
input frames = "shots/shot.%04d.png", rate: 12, first: 1, count: 24
|
|
70
|
+
[0s .. 2s] = frames
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Place it by name like a video; `Trim`, `Speed`, and `Loop` work on it. `first:` and `count:` on a plain file are errors. `rate:` on a plain video file declares the footage's frame rate for a timeline at another rate (`input clip = "clip.mp4", rate: 23.976` in a 30 fps output; each output frame shows the source frame its time falls in). It is an error on audio or a still, never auto-applied from a probe, and a probed rate more than 0.1% off the declared one warns. On the render workers the pattern must be an `https` URL pattern (or a path on the worker).
|
|
74
|
+
|
|
62
75
|
For ads based on a supplied URL, browser tools can capture real website visuals and save product images or logos when useful. Use screenshots sparingly: favor above-fold, cropped, or focused captures that remain legible at video size. Avoid full-page or long scrolling screenshots as backgrounds because their text will usually become unreadable.
|
|
63
76
|
|
|
64
77
|
Website captures are optional grounding assets, not a ban on generated video. A strong ad may combine product/logo/app screenshots with generated lifestyle clips, motion backgrounds, transitions, and VidScript text primitives. Use generative clips when they improve storytelling, show context, or make the ad feel more polished.
|
|
@@ -75,6 +88,8 @@ identify asset.webp
|
|
|
75
88
|
Use real videos as motion footage and still images as static clips. The `video` primitive accepts both; the renderer detects image vs video:
|
|
76
89
|
|
|
77
90
|
```vidscript
|
|
91
|
+
import motion from "@scenerok/basic-animations"
|
|
92
|
+
|
|
78
93
|
input screenshot = "assets/screenshots/homepage.png"
|
|
79
94
|
[0s .. 4.5s] = video screenshot, width: 1080, height: 1920, x: 0, y: 0, animate: motion.fadeIn(0.35s)
|
|
80
95
|
```
|
|
@@ -99,14 +114,20 @@ output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
|
99
114
|
output to "reel.mp4", resolution: "720x1280", format: "mp4", codec: "h264", bitrate: "5M", background: "#0D0D0D"
|
|
100
115
|
```
|
|
101
116
|
|
|
117
|
+
Several `output` statements make several canvases (`compileVidscript` returns one IR each in `irs`; `ir` is the first). Each output is the whole script lowered against its own `resolution`, `fps`, and `background`, so percent positions and `grid` areas resolve per canvas. Every output needs its own path (two with the same path is a compile error). Where it renders: the headless renderer (`renderVidscriptOutputsToMP4`, local scripts) writes one file per output. Where it errors: the render queue (the web app, the CLI, and the API) delivers one artifact per render, so a script with more than one `output` is rejected with `N outputs are rendered … only one output per render is supported by the render queue yet` before anything is billed. Use one `output` for a hosted render.
|
|
118
|
+
|
|
102
119
|
| Option | Type | Default | Description |
|
|
103
120
|
|--------|------|---------|-------------|
|
|
104
121
|
| `resolution` | string | `"1080x1920"` | Width×Height or single number |
|
|
105
122
|
| `fps` | number | `30` | Frames per second |
|
|
106
|
-
| `format` | string | `"mp4"` | Container format |
|
|
107
|
-
| `codec` | string | `"h264"` | Video codec |
|
|
123
|
+
| `format` | string | `"mp4"` | Container format. Every render is MP4 today |
|
|
124
|
+
| `codec` | string | `"h264"` | Video codec. Every render is H.264 today |
|
|
108
125
|
| `bitrate` | string | `"5M"` | Encoding bitrate |
|
|
109
126
|
| `background` | string | `"#000000"` | Background color hex |
|
|
127
|
+
| `start_timecode` | string | none | Timecode of the first frame, `"HH:MM:SS:FF"` (non-drop-frame; the frame field must be below `fps`). Written to the mp4's timecode track |
|
|
128
|
+
| `burn_in` | string | none | `"timecode"` draws the running timecode (from `start_timecode`, else `00:00:00:00`) onto the picture |
|
|
129
|
+
| `allow_offline` | boolean | `false` | `true` renders an input whose file is missing at compile time (media probing on) as a "MEDIA OFFLINE" slate: a flat `#2b2b2b` frame labelled with the input name, over the clip's block or `Trim` length. `Resize`, `x`/`y`, `opacity` and `animate:` still apply; its sound is dropped with a warning; a transition at its seam is an error. Without it a missing local file is a compile error. Media that goes missing only at render time still fails the render |
|
|
130
|
+
| `alpha` | boolean | `false` | Needs `format: "mov", codec: "prores"` or `format: "webm", codec: "vp9"`. No render path delivers alpha yet (renders are H.264 mp4), so `alpha: true` is a compile error for now |
|
|
110
131
|
|
|
111
132
|
### Variable Assignment
|
|
112
133
|
|
|
@@ -139,9 +160,10 @@ Time blocks are the core of VidScript. They define when instructions execute on
|
|
|
139
160
|
### Dynamic Playhead (recommended)
|
|
140
161
|
|
|
141
162
|
```vidscript
|
|
163
|
+
input hero = "hero.mp4"
|
|
142
164
|
[-] = hero # auto-append: starts at visual cursor
|
|
143
|
-
[- 3s] = text "Title", size: 72 # auto-start, last 3 seconds
|
|
144
|
-
[- 2.5s] = filter "
|
|
165
|
+
[- 3s] = text "Title", size: 72 # auto-start, last 3 seconds
|
|
166
|
+
[- 2.5s] = filter "vignette", intensity: 0.4
|
|
145
167
|
```
|
|
146
168
|
|
|
147
169
|
VidScript keeps separate auto-playhead cursors for visual and audio content. Visual blocks (`video`, `text`, filters, shaders, visual plugin calls, and bare visual inputs) advance the visual cursor. Audio blocks (`audio`, `xai.tts`, `eleven.music`, and other audio plugin calls) advance the audio cursor. `[- duration]` advances the relevant cursor by the explicit duration.
|
|
@@ -163,19 +185,20 @@ The generated video above starts at the current visual cursor, usually `0s`, eve
|
|
|
163
185
|
### Explicit Range
|
|
164
186
|
|
|
165
187
|
```vidscript
|
|
188
|
+
input hero = "hero.mp4"
|
|
166
189
|
[0s .. 5s] = hero # absolute range
|
|
167
190
|
[0.5s .. 3s] = text "Hello" # explicit times
|
|
168
|
-
[prev + 1s .. prev + 4s] = filter "
|
|
191
|
+
[prev + 1s .. prev + 4s] = filter "vignette" # expression-based
|
|
169
192
|
```
|
|
170
193
|
|
|
171
194
|
Use `..` (not `-`) as the range separator to avoid ambiguity with subtraction.
|
|
172
195
|
|
|
173
196
|
### Time Formats
|
|
174
197
|
|
|
175
|
-
```
|
|
198
|
+
```text
|
|
176
199
|
5s # 5 seconds
|
|
177
200
|
300ms # 300 milliseconds
|
|
178
|
-
frame
|
|
201
|
+
90 frame # 90 frames at the output fps (default 30): 3 seconds
|
|
179
202
|
0:30 # 30 seconds
|
|
180
203
|
1:30:00 # 1 hour 30 minutes
|
|
181
204
|
```
|
|
@@ -186,7 +209,7 @@ frame 90 # 90 frames at 30fps = 3 seconds
|
|
|
186
209
|
|
|
187
210
|
```vidscript
|
|
188
211
|
[- 2s] = text "Hello" # plays from cursor to cursor+2s
|
|
189
|
-
[prev + 1s .. prev + 3s] =
|
|
212
|
+
[prev + 1s .. prev + 3s] = text "World" # 1s after "Hello" ends, for 2s
|
|
190
213
|
```
|
|
191
214
|
|
|
192
215
|
### Symbolic Durations (`asset.duration`)
|
|
@@ -208,40 +231,74 @@ let vo2 = xai.tts("Let's dive in", voice: "eve")
|
|
|
208
231
|
- Exported timelines expose `.duration` too: `use scene1 at 0s .. 0s with {}` then `[scene1.duration .. scene1.duration + 4] = ...`. A `use` also advances the playhead past its content, so a following `[-]` block never overlaps it.
|
|
209
232
|
- This replaces guessing: `[0s .. 5s] = audio xai.tts(...)` clips or overlaps when the real audio is longer. For simple back-to-back narration, consecutive `[-] = audio ...` blocks also never overlap.
|
|
210
233
|
|
|
211
|
-
##
|
|
234
|
+
## Import TypeScript modules
|
|
212
235
|
|
|
213
|
-
|
|
236
|
+
`import kit from "./kit.ts"` (or `.js`) runs the module with `@scenerok/sdk` and binds its exports as a namespace, like a `.vid` module. Each export is a VidScript value of the matching kind:
|
|
214
237
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
238
|
+
| TypeScript export | In VidScript |
|
|
239
|
+
| --- | --- |
|
|
240
|
+
| `video(…)`, `loadVideo(…)`, `imageSequence(…)` | a video value: `[0s .. 5s] = kit.hero`, `kit.hero.Trim(1s, 3s)` |
|
|
241
|
+
| `image(…)` | an image value |
|
|
242
|
+
| `text(…)` | a text value |
|
|
243
|
+
| `audio(…)`, `loadAudio(…)` | an audio value: `[0s .. 10s] = audio kit.bed` |
|
|
244
|
+
| `solid(…)`, `gap()`, `marker(…)` | the builtin values `solid()`, `gap()`, `marker()` return |
|
|
245
|
+
| `timeline()` | a timeline value: flat when placed bare, one group with properties or methods |
|
|
246
|
+
| a number, string, array, or plain object | that data: `color: kit.accent` |
|
|
247
|
+
| a function returning any of the above | a function: `kit.lowerThird("Konkan", color: "#f50")` |
|
|
248
|
+
| `export default` scene (`main()` returning `t.toIR()`) | `use kit at 0s .. 0s with {}`; `kit.duration` is its length |
|
|
218
249
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
250
|
+
```ts
|
|
251
|
+
// kit.ts
|
|
252
|
+
import { timeline, video, image, text, solid, fadeIn } from '@scenerok/sdk'
|
|
253
|
+
|
|
254
|
+
export const accent = '#ff5500'
|
|
255
|
+
export const hero = video('beach.mp4', { duration: 12 }).trim(2, 8)
|
|
256
|
+
export const logo = image('logo.png', { width: 200, height: 200, x: 40, y: 40 })
|
|
257
|
+
export const title = text('Konkan Day 1', { size: 72, area: 'title', animate: fadeIn(0.4) })
|
|
258
|
+
|
|
259
|
+
export function lowerThird(name: string, opts: { color?: string } = {}) {
|
|
260
|
+
const t = timeline()
|
|
261
|
+
t[0][3] = solid(opts.color ?? accent, { height: 160, y: 880 })
|
|
262
|
+
t[0][3] = text(name, { size: 48, y: '88%' })
|
|
263
|
+
return t
|
|
264
|
+
}
|
|
265
|
+
```
|
|
222
266
|
|
|
223
|
-
|
|
267
|
+
```vidscript fragment
|
|
268
|
+
import kit from "./kit.ts"
|
|
269
|
+
|
|
270
|
+
[0s .. 6s] = kit.hero
|
|
271
|
+
[0s .. 6s] = kit.logo, opacity: 0.9
|
|
272
|
+
[1s .. 4s] = kit.title
|
|
273
|
+
[3s .. 6s] = kit.lowerThird("Nidheesh", color: kit.accent), blend: "screen"
|
|
274
|
+
use kit.lowerThird at 6s .. 9s with { color: "#00aa88" }
|
|
275
|
+
|
|
276
|
+
output to "reel.mp4", resolution: "1920x1080"
|
|
224
277
|
```
|
|
225
278
|
|
|
226
279
|
Rules:
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
280
|
+
- A `timeline()` with no size takes this script's output size and fps, so it lays out on the same canvas.
|
|
281
|
+
- Exported values keep their own properties; placement properties override them (`kit.logo, opacity: 0.9`).
|
|
282
|
+
- Place an audio export as `audio kit.bed`. In a `[-]` block a bare `kit.bed` starts at the picture playhead, because the block's channel is decided before the module runs.
|
|
283
|
+
- Function calls pass positional arguments in order and named arguments as one trailing options object: `kit.lowerThird("A", color: "#f50")` calls `lowerThird("A", { color: "#f50" })`; `use kit.f at … with { … }` calls `f({ … })`. Arguments are data (strings, numbers, booleans, lists, objects); VidScript clips and timelines cannot be passed into TypeScript.
|
|
284
|
+
- An exported promise is awaited (`export const clip = loadVideo("hero.mp4")`). Top-level `await` is not supported.
|
|
285
|
+
- Filters, shaders, and transitions are not exportable values; use them inside an exported timeline, or as VidScript statements.
|
|
286
|
+
- Relative paths only (`./` / `../`). The module is one file: the CLI uploads it with the `.vid`, so it cannot import other local files.
|
|
231
287
|
|
|
232
288
|
## Instructions (inside time blocks)
|
|
233
289
|
|
|
234
|
-
|
|
290
|
+
Each line places one thing. Several lines can share a window:
|
|
235
291
|
|
|
236
292
|
```vidscript
|
|
237
|
-
|
|
238
|
-
hero.Trim(
|
|
239
|
-
|
|
293
|
+
input hero = "hero.mp4"
|
|
294
|
+
[0s .. 5s] = hero.Trim(0s, 7.5s).Speed(1.5)
|
|
295
|
+
[0s .. 5s] = text "Hello", area: "title"
|
|
240
296
|
```
|
|
241
297
|
|
|
242
298
|
### Clip Reference
|
|
243
299
|
|
|
244
300
|
```vidscript
|
|
301
|
+
input hero = "hero.mp4"
|
|
245
302
|
[-] = hero # just play the input clip
|
|
246
303
|
```
|
|
247
304
|
|
|
@@ -268,7 +325,7 @@ hero.Speed(factor: 1.5)
|
|
|
268
325
|
| `align` | string | `left`, `center`, `right`. Default: `center` |
|
|
269
326
|
| `line_height` | number | Line height multiplier for multi-line text. Default: 1.2 |
|
|
270
327
|
| `letter_spacing` | number | Extra letter spacing in pixels. Default: 0 |
|
|
271
|
-
| `rotation` | number | Rotation in degrees. Positive
|
|
328
|
+
| `rotation` | number | Rotation in degrees. Positive turns text clockwise (video turns the other way). Default: 0 |
|
|
272
329
|
| `opacity` | number | Opacity 0–1. Default: 1 |
|
|
273
330
|
| `stroke` | string | Outline color |
|
|
274
331
|
| `stroke_width` | number | Outline thickness |
|
|
@@ -276,7 +333,8 @@ hero.Speed(factor: 1.5)
|
|
|
276
333
|
| `shadow_blur` | number | Shadow blur radius |
|
|
277
334
|
| `shadow_offset_x` | number | Shadow horizontal offset |
|
|
278
335
|
| `shadow_offset_y` | number | Shadow vertical offset |
|
|
279
|
-
| `
|
|
336
|
+
| `anchor` | string | Point the text rotates and positions around, e.g. `"top-left"` |
|
|
337
|
+
| `stagger` | string | `"word"` or `"char"`: reveal the text piece by piece over its `animate:` |
|
|
280
338
|
|
|
281
339
|
Params are optional and comma-separated. Params can be on the same line as the content.
|
|
282
340
|
|
|
@@ -311,21 +369,58 @@ t[0][s(3)] = text('Left column', { area: 'left' })
|
|
|
311
369
|
|
|
312
370
|
### Video Methods
|
|
313
371
|
|
|
314
|
-
|
|
315
|
-
hero.
|
|
316
|
-
|
|
317
|
-
hero.Speed(factor: 1.5) # playback rate
|
|
318
|
-
hero.Loop(count: 3) # loop count
|
|
319
|
-
hero.Opacity(value: 0.5, duration: 2s) # opacity with duration
|
|
320
|
-
```
|
|
372
|
+
Methods are operations on values. Capitalised names are methods; lowercase names are
|
|
373
|
+
properties (`hero.duration`). A method returns **a new value** — chain them, or bind
|
|
374
|
+
the result with `let`:
|
|
321
375
|
|
|
322
|
-
|
|
376
|
+
```vidscript
|
|
377
|
+
input beach = "beach.mp4"
|
|
378
|
+
let hero = beach.Trim(2s, 8s).Speed(1.5)
|
|
379
|
+
[0s .. 4s] = hero, opacity: 0.9
|
|
380
|
+
[4s .. 8s] = beach.Trim(0s, 2s).Loop(2).Resize(720, 1280)
|
|
381
|
+
[8s .. 10s] = beach.Opacity(0.5).Filter("sepia", intensity: 0.8)
|
|
382
|
+
[10s .. 12s] = beach.Crop(0, 0, 640, 360)
|
|
383
|
+
let framed = beach.Mask(solid("#fff", radius: 120, width: 800, height: 500), mode: "luma")
|
|
384
|
+
[12s .. 14s] = framed
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
| Method | Arguments | Result |
|
|
388
|
+
|--------|-----------|--------|
|
|
389
|
+
| `Trim(start, end)` | source times | Plays that source range |
|
|
390
|
+
| `Speed(factor)` | number | Playback rate; negative reverses, `0` holds a frame |
|
|
391
|
+
| `Loop(count)` | number | Repeats the range `count` times |
|
|
392
|
+
| `Resize(width, height)` | pixels | Display size |
|
|
393
|
+
| `Opacity(value)` | 0–1 | Opacity |
|
|
394
|
+
| `Filter(name, …params)` | filter name | A grade on this value only |
|
|
395
|
+
| `Crop(x, y, width, height)` | source pixels from the top-left | The rectangle, filling the display size |
|
|
396
|
+
| `Mask(matte, mode?)` | any picture; `"alpha"`, `"luma"`, `"inverted"` | Drawn through the matte |
|
|
397
|
+
| `Overlay(other, x?, y?, opacity?)` | a picture value | `other` on top at a position |
|
|
398
|
+
| `Composite(other, mode?, x?, y?, opacity?)` | a picture value, a blend mode | `other` blended on top |
|
|
399
|
+
|
|
400
|
+
Arguments can be positional or named (`hero.Trim(start: 0s, end: 5s)`).
|
|
401
|
+
|
|
402
|
+
A second `Trim` is relative to the first: `a.Trim(10s, 20s).Trim(2s, 4s)` plays 12–14 s. A second `Crop` is relative to the first the same way.
|
|
403
|
+
`Crop` keeps the display size, so the rectangle is scaled to fill it; `Resize` to the rectangle's aspect avoids stretching.
|
|
404
|
+
The `Mask` matte is any picture value — a `solid` with `radius:` or `corners:` for a shape, `text(…)`, a function, or another clip (a track matte) — drawn in the same frame at the same local time.
|
|
405
|
+
Place a masked value (`[0s .. 4s] = a.Mask(…)`); a `Mask` line after the clip is placed, an unknown mode, a masked clip as a
|
|
406
|
+
`Composite` side, and a cropped clip inside a transition are compile errors.
|
|
407
|
+
`audio x` places a sound — an audio file, or a video's own sound (`audio a.Trim(1s, 3s)`);
|
|
408
|
+
`video` on an audio file is an error.
|
|
409
|
+
|
|
410
|
+
A placed value lasts its own length (range ÷ |speed| × loops), so make its window that long.
|
|
411
|
+
Each placement is its own value: `[0s .. 4s] = a.Trim(0s, 4s)` and `[4s .. 6s] = a.Trim(10s, 12s)` are two independent cuts.
|
|
412
|
+
|
|
413
|
+
A method on its own line right after `[…] = hero` (the legacy spelling) still edits the clip that time block placed. Outside a time block it rebinds the name:
|
|
414
|
+
`bg.Loop(count: 3)` makes `bg` the looped clip for later placements.
|
|
323
415
|
|
|
324
416
|
### Compositing
|
|
325
417
|
|
|
326
418
|
```vidscript
|
|
327
|
-
hero
|
|
328
|
-
|
|
419
|
+
input hero = "hero.mp4"
|
|
420
|
+
input logo = "logo.png"
|
|
421
|
+
input texture = "light-leak.mp4"
|
|
422
|
+
[0s .. 4s] = hero.Overlay(logo, x: 50, y: 100, opacity: 0.8)
|
|
423
|
+
[4s .. 8s] = hero.Composite(texture, x: 0, y: 0, opacity: 0.5, mode: "screen")
|
|
329
424
|
```
|
|
330
425
|
|
|
331
426
|
| Param | Type | Description |
|
|
@@ -333,16 +428,36 @@ hero.Composite(overlay, x: 0, y: 0, opacity: 0.5, mode: "screen")
|
|
|
333
428
|
| `x` | number | Horizontal offset |
|
|
334
429
|
| `y` | number | Vertical offset |
|
|
335
430
|
| `opacity` | number | 0–1 transparency |
|
|
336
|
-
| `mode` | string | Blend mode for Composite |
|
|
431
|
+
| `mode` | string | Blend mode for Composite: `normal`, `multiply`, `screen`, `overlay`, `difference`, `add`, `subtract`, `darken`, `lighten` |
|
|
432
|
+
|
|
433
|
+
The other side can be a clip name, or a value: a function's picture, a `solid(…)`, a
|
|
434
|
+
`text(…)`, or a trimmed clip. A clip joins as a composite layer; anything else draws as a
|
|
435
|
+
group blended with `mode` (`x`, `y`, and `opacity` place that group). A composite is itself
|
|
436
|
+
a target, so layers stack: `let ab = a.Composite(b, mode: "screen")` then
|
|
437
|
+
`[0s .. 4s] = ab.Composite(card("x"), mode: "multiply")`. Both sides must overlap in time.
|
|
337
438
|
|
|
338
439
|
### Filters
|
|
339
440
|
|
|
340
441
|
```vidscript
|
|
341
|
-
|
|
442
|
+
input hero = "hero.mp4"
|
|
443
|
+
[0s .. 5s] = hero
|
|
444
|
+
[0s .. 2s] = filter "chromatic", offset: 0.01
|
|
342
445
|
[0s .. 5s] = filter "vignette", intensity: 0.3
|
|
446
|
+
[0s .. 5s] = filter "contrast", amount: 1.2, target: hero
|
|
343
447
|
```
|
|
344
448
|
|
|
345
|
-
Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`.
|
|
449
|
+
Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`, plus `lut` and `cdl` (below and in `editing-actions.md`) and the VFX in `shaders-filters.md`. A `filter` line grades everything in its window; `target: clip` grades one clip.
|
|
450
|
+
|
|
451
|
+
**LUT.** `filter "lut", file: "look.cube"` applies a 3D `.cube` look to the frame, to one clip with `target:`, or to a function body's picture. The compiler reads the file (relative to the `.vid`, 512 KB at most, up to 33 points per axis) and embeds the table, so render workers need nothing else. The lookup runs on sRGB-encoded values with 8-bit trilinear interpolation. `Filter("lut")` as a method is an error; use the statement.
|
|
452
|
+
|
|
453
|
+
### Motion blur
|
|
454
|
+
|
|
455
|
+
`motion_blur: true` or `motion_blur: { shutter: 180, samples: 8 }` on a video/image placement or a placed function averages `samples` sub-frames across the shutter angle (above 0, up to 360; samples 2 to 64) of that item's own `animate:` motion. Each sample is another render of the frame, so a frame with a blurred item costs `samples` times as much. It does not re-sample the video's own frames, does not blur a group's moving children (set it on them), and is an error on text, solids, captions, audio, and a clip at a transition.
|
|
456
|
+
|
|
457
|
+
```vidscript
|
|
458
|
+
input hero = "hero.mp4"
|
|
459
|
+
[0s .. 2s] = hero, animate: { type: "slideX", from: { x: 0 }, to: { x: 900 } }, motion_blur: { shutter: 360, samples: 16 }
|
|
460
|
+
```
|
|
346
461
|
|
|
347
462
|
### Shaders
|
|
348
463
|
|
|
@@ -359,7 +474,8 @@ Fragment must sample `tDiffuse`, write `gl_FragColor`, and declare uniforms matc
|
|
|
359
474
|
### Audio
|
|
360
475
|
|
|
361
476
|
```vidscript
|
|
362
|
-
|
|
477
|
+
input music = "music.mp3"
|
|
478
|
+
[0s .. 15s] = audio music, volume: 0.5, fade_in: 1s, fade_out: 2s
|
|
363
479
|
```
|
|
364
480
|
|
|
365
481
|
Audio sources can be MP3 inputs, audio plugin results, or the embedded audio stream from an input video. Video inputs are visually silent unless you explicitly add `audio clip`. Plugin-generated audio (TTS, music, and probed native audio on generated video clips) is EBU R128 loudness-normalized during render to about -16 LUFS; `volume` is a relative gain applied after that. Ordinary imported audio/video is not auto-normalized.
|
|
@@ -369,6 +485,13 @@ Audio sources can be MP3 inputs, audio plugin results, or the embedded audio str
|
|
|
369
485
|
| `volume` | number | Relative gain (0–1+). For generated audio, applied after loudness normalization. |
|
|
370
486
|
| `fade_in` | number | Fade-in duration (seconds) |
|
|
371
487
|
| `fade_out` | number | Fade-out duration (seconds) |
|
|
488
|
+
| `trim_start` | number | Start this many seconds into the file |
|
|
489
|
+
| `pan` | number | -1 (left) to 1 (right) |
|
|
490
|
+
| `duck_under` | name | Lower this track while the named track plays |
|
|
491
|
+
| `loudness` | string | `"ebu-r128"` normalizes an imported file |
|
|
492
|
+
| `animate` | object | `{ type: "volume", from: { volume: 0 }, to: { volume: 1 } }` automates the level |
|
|
493
|
+
|
|
494
|
+
Mixing recipes (J- and L-cuts, ducking, automation): `editing-actions.md` → Sound.
|
|
372
495
|
|
|
373
496
|
### Plugin Calls
|
|
374
497
|
|
|
@@ -433,7 +556,7 @@ Use `aspect_ratio: "9:16"` for vertical output, `"16:9"` for wide, `"1:1"` for s
|
|
|
433
556
|
|
|
434
557
|
**Invalid (will not validate):**
|
|
435
558
|
|
|
436
|
-
```vidscript
|
|
559
|
+
```vidscript invalid
|
|
437
560
|
[-] = video xai.imagine("...") # missing xAI import
|
|
438
561
|
[0s .. 5s] = video cf.video("...") # missing Cloudflare import
|
|
439
562
|
[0s .. 15s] = audio eleven.generateMusic("...") # missing `import eleven from "@elevenlabs/music"`
|
|
@@ -441,20 +564,92 @@ Use `aspect_ratio: "9:16"` for vertical output, `"16:9"` for wide, `"1:1"` for s
|
|
|
441
564
|
[0s .. 15s] = audio eleven.music("..."), volume: 0.5 # trailing params after direct plugin call
|
|
442
565
|
```
|
|
443
566
|
|
|
444
|
-
##
|
|
567
|
+
## Reuse: values, functions, and modules
|
|
568
|
+
|
|
569
|
+
Anything that can be placed is a value: an input, a plugin result, `text(…)` / `audio(…)` /
|
|
570
|
+
`solid(…)`, a method chain, or a function call. A time block places one.
|
|
571
|
+
|
|
572
|
+
### Functions
|
|
573
|
+
|
|
574
|
+
```vidscript
|
|
575
|
+
export fn lowerThird(title: string, color: string = "#ff5500") -> video {
|
|
576
|
+
[- 2s] = text title, color: color, size: 48
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
[0s .. 2s] = lowerThird("Konkan Day 1")
|
|
580
|
+
[2s .. 4s] = lowerThird(title: "Day 2", color: "#00ff00")
|
|
581
|
+
|
|
582
|
+
# one-liners: fn … = expression
|
|
583
|
+
fn label(s: string) -> text = text(s, size: 24)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
| Form | Meaning |
|
|
587
|
+
|------|---------|
|
|
588
|
+
| `fn name(params) -> kind { … }` | Block body: places items when called and placed |
|
|
589
|
+
| `fn name(params) -> kind = expr` | Expression body: returns that value |
|
|
590
|
+
| `export fn …` | Exported (callable through an imported namespace) |
|
|
591
|
+
| `export timeline name(…) { … }` | Same as `export fn name(…) -> timeline { … }` |
|
|
592
|
+
|
|
593
|
+
Return kinds: `video`, `image`, `text`, `audio`, `timeline`. `-> video` is a closed unit
|
|
594
|
+
(place it with properties — `[0s .. 4s] = card("A"), x: 40, opacity: 0.5, blend: "screen"` — or
|
|
595
|
+
chain `.Opacity()`, `.Resize()`, `.Filter()`, `.Crop()`, `.Mask()` and it draws as one item);
|
|
596
|
+
`-> timeline` is open, its items join the caller. A `filter` or `shader` without `target:`
|
|
597
|
+
inside a function or timeline body grades that unit's picture, not the whole frame.
|
|
598
|
+
|
|
599
|
+
Arguments are checked at the call: a wrong kind, an unknown or missing parameter, too
|
|
600
|
+
many arguments, or a value of the wrong kind is a compile error naming the parameter.
|
|
601
|
+
Optional parameters take `name: kind = default`.
|
|
445
602
|
|
|
446
|
-
|
|
603
|
+
`use card at 0s .. 2s with { title: "A" }` is exactly `[0s .. 2s] = card(title: "A")` —
|
|
604
|
+
the same IR.
|
|
447
605
|
|
|
448
|
-
|
|
606
|
+
### Modules
|
|
607
|
+
|
|
608
|
+
```vidscript
|
|
609
|
+
import brand from "./brand.vid" # another .vid file's exports
|
|
610
|
+
|
|
611
|
+
[0s .. 2s] = brand.lowerThird("Hello")
|
|
612
|
+
[0s .. 2s] = text "x", color: brand.accent
|
|
613
|
+
use brand.card at 2s .. 4s with { title: "B" }
|
|
614
|
+
```
|
|
449
615
|
|
|
450
|
-
|
|
451
|
-
|
|
616
|
+
`brand.vid` exports with `export fn lowerThird(title: string) -> video { … }`,
|
|
617
|
+
`export timeline card(title: string) { … }`, and `export const accent = "#ff5500"`.
|
|
618
|
+
|
|
619
|
+
### Builtin values
|
|
620
|
+
|
|
621
|
+
```vidscript
|
|
622
|
+
[0s .. 2s] = solid("#1a2b3c", opacity: 0.8) # flat-colour rectangle
|
|
623
|
+
[2s .. 3s] = gap() # transparent gap (advances the cursor)
|
|
624
|
+
[4s .. 4s] = marker("act 2") # named instant (timeline data, not rendered)
|
|
625
|
+
[0s .. 8s] = captions("talk.srt", size: 32) # one text layer per SRT cue
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Any visual value takes the same placement properties: `x`, `y`, `width`, `height`,
|
|
629
|
+
`opacity`, `rotation`, `radius`, `corners`, `area`, `blend`, `animate`, `enabled`.
|
|
630
|
+
`blend: "screen"` on a clip lowers to a composite layer over whatever is below the placement;
|
|
631
|
+
on text, a solid, or a function it draws as a group with that blend. A blend needs something
|
|
632
|
+
below it in the same window, or it is a compile error. More than 4 nested groups, or more
|
|
633
|
+
than 16 alive at once, is a compile warning (each group renders into its own GPU target).
|
|
634
|
+
`volume:` on a generated clip with its own sound sets that sound's level.
|
|
635
|
+
|
|
636
|
+
### Names must resolve
|
|
637
|
+
|
|
638
|
+
An unquoted word is only allowed where a value is an enumeration (`align: center`,
|
|
639
|
+
`position: top`, `style: title`, `mode: screen`). Everywhere else an unresolved name is
|
|
640
|
+
`Unknown variable 'titel'`. Quote strings; pass real variables.
|
|
641
|
+
|
|
642
|
+
## Animations and effects
|
|
643
|
+
|
|
644
|
+
`animate:` moves any text or picture over time, with `motion.*` calls or object descriptors. Filters are separate `filter` lines (above), and can be animated too.
|
|
452
645
|
|
|
453
646
|
### animate: parameter
|
|
454
647
|
|
|
455
648
|
```vidscript
|
|
456
649
|
import motion from "@scenerok/basic-animations"
|
|
457
650
|
|
|
651
|
+
input hero = "hero.mp4"
|
|
652
|
+
input app_screen = "app-screen.png"
|
|
458
653
|
[0s .. 3s] = text "Hello", x: "50%", y: "50%", animate: motion.fadeIn(0.8s)
|
|
459
654
|
|
|
460
655
|
[-] = video hero, animate: [motion.fadeIn(0.5s), motion.slideY(-40, 0, 1.2s)]
|
|
@@ -473,17 +668,19 @@ import motion from "@scenerok/basic-animations"
|
|
|
473
668
|
}
|
|
474
669
|
```
|
|
475
670
|
|
|
476
|
-
|
|
671
|
+
Easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeOutBack`, `spring`. A list of objects with absolute `start` / `end` chains segments on one property (a path with more than two points); `keyframes:` is not supported and is silently dropped.
|
|
477
672
|
|
|
478
|
-
###
|
|
673
|
+
### Animated filters
|
|
479
674
|
|
|
480
675
|
```vidscript
|
|
676
|
+
import motion from "@scenerok/basic-animations"
|
|
677
|
+
|
|
481
678
|
[0s .. 5s] = filter "vignette", intensity: 0.4
|
|
482
679
|
|
|
483
680
|
[0s .. 5s] = filter "glitch", intensity: 0.5, animate: motion.fadeIn(1s)
|
|
484
681
|
```
|
|
485
682
|
|
|
486
|
-
|
|
683
|
+
Filters are optional finishing tools; use them only when they improve the composition. Parameter names per filter: `editing-actions.md` → Color and finishing.
|
|
487
684
|
|
|
488
685
|
### Animation functions (`@scenerok/basic-animations`)
|
|
489
686
|
|
|
@@ -500,24 +697,12 @@ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignett
|
|
|
500
697
|
| `motion.float(duration?, amp?)` | Gentle vertical bob |
|
|
501
698
|
| `motion.typewriter(duration?)` | Character reveal |
|
|
502
699
|
|
|
503
|
-
### Render architecture (contributors)
|
|
504
|
-
|
|
505
|
-
Implemented registry-based rendering. See `plan-v1/35-render-subsystem-architecture.md`.
|
|
506
|
-
|
|
507
|
-
| Registry | Path | Add via |
|
|
508
|
-
|----------|------|---------|
|
|
509
|
-
| Motion | `src/lib/motion/registry.ts` | `registerEasing`, `registerMotionBehavior` |
|
|
510
|
-
| Surface | `src/render/surfaces/registry.ts` | `registerSurfaceRenderer` |
|
|
511
|
-
| Effect | `src/render/effects/registry.ts` | `registerEffect` or `src/shaders/library.ts` |
|
|
512
|
-
| Engine | `src/render/engine/index.ts` | `createRenderEngine` backends |
|
|
513
|
-
|
|
514
|
-
For SceneRok platform engineering, load the **`scenerok-development`** skill (`.agents/skills/scenerok-development/`).
|
|
515
|
-
|
|
516
700
|
## Expressions
|
|
517
701
|
|
|
518
702
|
Used in time specs, `let` values, and function arguments:
|
|
519
703
|
|
|
520
704
|
```vidscript
|
|
705
|
+
let title_text = "Launch"
|
|
521
706
|
let x = 5s # Time literals
|
|
522
707
|
let y = x * 2 # Arithmetic
|
|
523
708
|
let prompt = "Scene: " + title_text # String concatenation
|
|
@@ -533,7 +718,7 @@ Operators: `+`, `-`, `*`, `/` with standard precedence.
|
|
|
533
718
|
[- 3s] = text "{{headline | Default Text}}", color: "{{color | #FFFFFF}}"
|
|
534
719
|
```
|
|
535
720
|
|
|
536
|
-
|
|
721
|
+
Templates fill each `{{name | default}}` before the script compiles; outside a template the default is used.
|
|
537
722
|
|
|
538
723
|
## CLI Commands
|
|
539
724
|
|
|
@@ -556,8 +741,6 @@ scenerok status <render-id>
|
|
|
556
741
|
# Install agent skills
|
|
557
742
|
scenerok skills install <platform>
|
|
558
743
|
|
|
559
|
-
# Set API secrets for plugins
|
|
560
|
-
scenerok secrets set ELEVENLABS_API_KEY=your-key
|
|
561
744
|
```
|
|
562
745
|
|
|
563
746
|
## Best Practices
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# VidScript sample: a 13-second product promo
|
|
2
|
+
|
|
3
|
+
A complete script to copy and adapt: two generated shots joined by a dissolve,
|
|
4
|
+
titles in named areas, a voiceover, and a music bed that ducks under it.
|
|
5
|
+
Validate it before rendering.
|
|
6
|
+
|
|
7
|
+
```vidscript
|
|
8
|
+
import xai from "@scenerok/xai"
|
|
9
|
+
import eleven from "@elevenlabs/music"
|
|
10
|
+
import motion from "@scenerok/basic-animations"
|
|
11
|
+
import tr from "@scenerok/transitions"
|
|
12
|
+
|
|
13
|
+
# Copy lives in variables so it is easy to change.
|
|
14
|
+
let product = "RokMilk"
|
|
15
|
+
let hook = "Chocolate milkshake, ready in seconds"
|
|
16
|
+
let cta = "Find it at your nearest grocery store"
|
|
17
|
+
|
|
18
|
+
# Generated media is bound with let, so its real length is known.
|
|
19
|
+
# Prompts never ask for text; all copy is added with `text` below.
|
|
20
|
+
let kitchen = xai.imagine("Chocolate milk carton on a sunlit kitchen counter, cinematic lighting, shallow depth of field, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 6)
|
|
21
|
+
let pour = xai.imagine("Slow pour of chocolate milkshake into a tall glass, macro, warm light, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 6)
|
|
22
|
+
let vo = xai.tts("Meet RokMilk. Chocolate milkshake, ready in seconds. Find it at your nearest grocery store.", voice: "eve")
|
|
23
|
+
let bed = eleven.music("Warm, upbeat acoustic bed for a food ad", duration: 13, instrumental: true)
|
|
24
|
+
|
|
25
|
+
# Picture: each [-] starts where the previous visual block ended.
|
|
26
|
+
[-] = kitchen
|
|
27
|
+
[- 0.6s] = tr.dissolve()
|
|
28
|
+
[-] = pour
|
|
29
|
+
|
|
30
|
+
# Titles go in named areas, never at guessed coordinates.
|
|
31
|
+
[0s .. 3s] = text product, style: title, area: "title", animate: motion.popIn(0.6s)
|
|
32
|
+
[3s .. 7s] = text hook, area: "lower-third", animate: motion.riseIn(0.6s)
|
|
33
|
+
[9s .. 12.6s] = text cta, area: "caption", animate: motion.fadeIn(0.5s)
|
|
34
|
+
|
|
35
|
+
# Sound: the voiceover runs for its measured length; the bed ducks under it.
|
|
36
|
+
[0s .. vo.duration] = audio vo
|
|
37
|
+
[0s .. 13s] = audio bed, volume: 0.6, duck_under: vo, fade_out: 1.5s
|
|
38
|
+
|
|
39
|
+
output to "rokmilk-promo.mp4", resolution: "1080x1920", fps: 30
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To cut your own footage instead of generating it, replace the `let … =
|
|
43
|
+
xai.imagine(…)` lines with `input` declarations and see `editing-actions.md`.
|
|
44
|
+
The `@scenerok/cli` package ships more scripts in `examples/system/`.
|