@scenerok/cli 2.2.0 → 2.3.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.
Files changed (31) hide show
  1. package/README.md +6 -6
  2. package/dist/commands/skills.d.ts.map +1 -1
  3. package/dist/commands/skills.js +31 -25
  4. package/dist/utils/skillsManifest.d.ts +13 -3
  5. package/dist/utils/skillsManifest.d.ts.map +1 -1
  6. package/dist/utils/skillsManifest.js +35 -12
  7. package/examples/system/rokmilk-chocolate-promo.vid +1 -1
  8. package/examples/system/voice-visual-promo.vid +1 -1
  9. package/package.json +3 -3
  10. package/skills/scenerok/SKILL.md +147 -0
  11. package/skills/{remote → scenerok/references}/ai-models.md +1 -2
  12. package/skills/{remote → scenerok/references}/animations-catalog.md +139 -2
  13. package/skills/{remote → scenerok/references}/animations-picker.md +12 -1
  14. package/skills/{remote → scenerok/references}/brand-setup.md +3 -1
  15. package/skills/{remote → scenerok/references}/catalog-index.json +163 -19
  16. package/skills/scenerok/references/editing-actions.md +518 -0
  17. package/skills/{remote → scenerok/references}/environments.md +23 -24
  18. package/skills/{remote → scenerok/references}/intent-router.md +5 -15
  19. package/skills/{remote → scenerok/references}/media-stdlib.md +2 -3
  20. package/skills/{remote → scenerok/references}/music.md +2 -21
  21. package/skills/{remote → scenerok/references}/sdk-guide.md +2 -2
  22. package/skills/{remote → scenerok/references}/shaders-filters.md +9 -6
  23. package/skills/{remote → scenerok/references}/text-styles.md +15 -35
  24. package/skills/{remote → scenerok/references}/transitions-catalog.md +2 -0
  25. package/skills/{remote → scenerok/references}/tts.md +4 -22
  26. package/skills/{remote → scenerok/references}/vidscript-guide.md +202 -55
  27. package/skills/scenerok/references/vidscript-sample.md +44 -0
  28. package/skills/{remote → scenerok/references}/vidscript-strict.md +41 -26
  29. package/skills/{remote → scenerok/references}/workflow.md +6 -7
  30. package/skills/remote/vidscript-sample.md +0 -28
  31. 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** — see `examples/system/` in this skill folder (e.g. `minimal-text-reel.vid`, `ecom-product-launch.vid`, `rokmilk-chocolate-promo.vid`).
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. `rate:`, `first:`, `count:` on a plain file are errors. 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
  ```
@@ -103,10 +118,13 @@ output to "reel.mp4", resolution: "720x1280", format: "mp4", codec: "h264", bitr
103
118
  |--------|------|---------|-------------|
104
119
  | `resolution` | string | `"1080x1920"` | Width×Height or single number |
105
120
  | `fps` | number | `30` | Frames per second |
106
- | `format` | string | `"mp4"` | Container format |
107
- | `codec` | string | `"h264"` | Video codec |
121
+ | `format` | string | `"mp4"` | Container format. Every render is MP4 today |
122
+ | `codec` | string | `"h264"` | Video codec. Every render is H.264 today |
108
123
  | `bitrate` | string | `"5M"` | Encoding bitrate |
109
124
  | `background` | string | `"#000000"` | Background color hex |
125
+ | `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 |
126
+ | `burn_in` | string | none | `"timecode"` draws the running timecode (from `start_timecode`, else `00:00:00:00`) onto the picture |
127
+ | `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
128
 
111
129
  ### Variable Assignment
112
130
 
@@ -139,9 +157,10 @@ Time blocks are the core of VidScript. They define when instructions execute on
139
157
  ### Dynamic Playhead (recommended)
140
158
 
141
159
  ```vidscript
160
+ input hero = "hero.mp4"
142
161
  [-] = hero # auto-append: starts at visual cursor
143
- [- 3s] = text "Title", size: 72 # auto-start, last 3 seconds
144
- [- 2.5s] = filter "glow", intensity: 0.8
162
+ [- 3s] = text "Title", size: 72 # auto-start, last 3 seconds
163
+ [- 2.5s] = filter "vignette", intensity: 0.4
145
164
  ```
146
165
 
147
166
  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 +182,20 @@ The generated video above starts at the current visual cursor, usually `0s`, eve
163
182
  ### Explicit Range
164
183
 
165
184
  ```vidscript
185
+ input hero = "hero.mp4"
166
186
  [0s .. 5s] = hero # absolute range
167
187
  [0.5s .. 3s] = text "Hello" # explicit times
168
- [prev + 1s .. prev + 4s] = filter "glow" # expression-based
188
+ [prev + 1s .. prev + 4s] = filter "vignette" # expression-based
169
189
  ```
170
190
 
171
191
  Use `..` (not `-`) as the range separator to avoid ambiguity with subtraction.
172
192
 
173
193
  ### Time Formats
174
194
 
175
- ```vidscript
195
+ ```text
176
196
  5s # 5 seconds
177
197
  300ms # 300 milliseconds
178
- frame 90 # 90 frames at 30fps = 3 seconds
198
+ 90 frame # 90 frames at the output fps (default 30): 3 seconds
179
199
  0:30 # 30 seconds
180
200
  1:30:00 # 1 hour 30 minutes
181
201
  ```
@@ -186,7 +206,7 @@ frame 90 # 90 frames at 30fps = 3 seconds
186
206
 
187
207
  ```vidscript
188
208
  [- 2s] = text "Hello" # plays from cursor to cursor+2s
189
- [prev + 1s .. prev + 3s] = filter "glow" # 1s after text ends, for 2s
209
+ [prev + 1s .. prev + 3s] = text "World" # 1s after "Hello" ends, for 2s
190
210
  ```
191
211
 
192
212
  ### Symbolic Durations (`asset.duration`)
@@ -212,7 +232,7 @@ let vo2 = xai.tts("Let's dive in", voice: "eve")
212
232
 
213
233
  Import a `@scenerok/sdk` scene (`.ts` / `.js`) into VidScript with the same default-import form as plugins, then `use` it like a timeline:
214
234
 
215
- ```vidscript
235
+ ```vidscript fragment
216
236
  import intro from "./intro.ts"
217
237
  import bump from "./bump.js"
218
238
 
@@ -231,17 +251,18 @@ Rules:
231
251
 
232
252
  ## Instructions (inside time blocks)
233
253
 
234
- Multiple instructions in the same time block go on separate lines:
254
+ Each line places one thing. Several lines can share a window:
235
255
 
236
256
  ```vidscript
237
- [0s .. 5s] = hero
238
- hero.Trim(start: 0s, end: 5s)
239
- hero.Speed(factor: 1.5)
257
+ input hero = "hero.mp4"
258
+ [0s .. 5s] = hero.Trim(0s, 7.5s).Speed(1.5)
259
+ [0s .. 5s] = text "Hello", area: "title"
240
260
  ```
241
261
 
242
262
  ### Clip Reference
243
263
 
244
264
  ```vidscript
265
+ input hero = "hero.mp4"
245
266
  [-] = hero # just play the input clip
246
267
  ```
247
268
 
@@ -268,7 +289,7 @@ hero.Speed(factor: 1.5)
268
289
  | `align` | string | `left`, `center`, `right`. Default: `center` |
269
290
  | `line_height` | number | Line height multiplier for multi-line text. Default: 1.2 |
270
291
  | `letter_spacing` | number | Extra letter spacing in pixels. Default: 0 |
271
- | `rotation` | number | Rotation in degrees. Positive = clockwise. Default: 0 |
292
+ | `rotation` | number | Rotation in degrees. Positive turns text clockwise (video turns the other way). Default: 0 |
272
293
  | `opacity` | number | Opacity 0–1. Default: 1 |
273
294
  | `stroke` | string | Outline color |
274
295
  | `stroke_width` | number | Outline thickness |
@@ -276,7 +297,8 @@ hero.Speed(factor: 1.5)
276
297
  | `shadow_blur` | number | Shadow blur radius |
277
298
  | `shadow_offset_x` | number | Shadow horizontal offset |
278
299
  | `shadow_offset_y` | number | Shadow vertical offset |
279
- | `animation` | string | Parsed but not yet rendered |
300
+ | `anchor` | string | Point the text rotates and positions around, e.g. `"top-left"` |
301
+ | `stagger` | string | `"word"` or `"char"`: reveal the text piece by piece over its `animate:` |
280
302
 
281
303
  Params are optional and comma-separated. Params can be on the same line as the content.
282
304
 
@@ -311,21 +333,58 @@ t[0][s(3)] = text('Left column', { area: 'left' })
311
333
 
312
334
  ### Video Methods
313
335
 
314
- ```vidscript
315
- hero.Trim(start: 0s, end: 5s) # trim clip (positional: hero.Trim(0s, 5s))
316
- hero.Resize(width: 720, height: 1280) # resize (positional: hero.Resize(720, 1280))
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
- ```
336
+ Methods are operations on values. Capitalised names are methods; lowercase names are
337
+ properties (`hero.duration`). A method returns **a new value** — chain them, or bind
338
+ the result with `let`:
321
339
 
322
- Arguments can be positional or named using `:`.
340
+ ```vidscript
341
+ input beach = "beach.mp4"
342
+ let hero = beach.Trim(2s, 8s).Speed(1.5)
343
+ [0s .. 4s] = hero, opacity: 0.9
344
+ [4s .. 8s] = beach.Trim(0s, 2s).Loop(2).Resize(720, 1280)
345
+ [8s .. 10s] = beach.Opacity(0.5).Filter("sepia", intensity: 0.8)
346
+ [10s .. 12s] = beach.Crop(0, 0, 640, 360)
347
+ let framed = beach.Mask(solid("#fff", radius: 120, width: 800, height: 500), mode: "luma")
348
+ [12s .. 14s] = framed
349
+ ```
350
+
351
+ | Method | Arguments | Result |
352
+ |--------|-----------|--------|
353
+ | `Trim(start, end)` | source times | Plays that source range |
354
+ | `Speed(factor)` | number | Playback rate; negative reverses, `0` holds a frame |
355
+ | `Loop(count)` | number | Repeats the range `count` times |
356
+ | `Resize(width, height)` | pixels | Display size |
357
+ | `Opacity(value)` | 0–1 | Opacity |
358
+ | `Filter(name, …params)` | filter name | A grade on this value only |
359
+ | `Crop(x, y, width, height)` | source pixels from the top-left | The rectangle, filling the display size |
360
+ | `Mask(matte, mode?)` | any picture; `"alpha"`, `"luma"`, `"inverted"` | Drawn through the matte |
361
+ | `Overlay(other, x?, y?, opacity?)` | a picture value | `other` on top at a position |
362
+ | `Composite(other, mode?, x?, y?, opacity?)` | a picture value, a blend mode | `other` blended on top |
363
+
364
+ Arguments can be positional or named (`hero.Trim(start: 0s, end: 5s)`).
365
+
366
+ 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.
367
+ `Crop` keeps the display size, so the rectangle is scaled to fill it; `Resize` to the rectangle's aspect avoids stretching.
368
+ 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.
369
+ Place a masked value (`[0s .. 4s] = a.Mask(…)`); a `Mask` line after the clip is placed, an unknown mode, a masked clip as a
370
+ `Composite` side, and a cropped clip inside a transition are compile errors.
371
+ `audio x` places a sound — an audio file, or a video's own sound (`audio a.Trim(1s, 3s)`);
372
+ `video` on an audio file is an error.
373
+
374
+ A placed value lasts its own length (range ÷ |speed| × loops), so make its window that long.
375
+ Each placement is its own value: `[0s .. 4s] = a.Trim(0s, 4s)` and `[4s .. 6s] = a.Trim(10s, 12s)` are two independent cuts.
376
+
377
+ 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:
378
+ `bg.Loop(count: 3)` makes `bg` the looped clip for later placements.
323
379
 
324
380
  ### Compositing
325
381
 
326
382
  ```vidscript
327
- hero.Overlay(logo, x: 50, y: 100, opacity: 0.8)
328
- hero.Composite(overlay, x: 0, y: 0, opacity: 0.5, mode: "screen")
383
+ input hero = "hero.mp4"
384
+ input logo = "logo.png"
385
+ input texture = "light-leak.mp4"
386
+ [0s .. 4s] = hero.Overlay(logo, x: 50, y: 100, opacity: 0.8)
387
+ [4s .. 8s] = hero.Composite(texture, x: 0, y: 0, opacity: 0.5, mode: "screen")
329
388
  ```
330
389
 
331
390
  | Param | Type | Description |
@@ -333,16 +392,36 @@ hero.Composite(overlay, x: 0, y: 0, opacity: 0.5, mode: "screen")
333
392
  | `x` | number | Horizontal offset |
334
393
  | `y` | number | Vertical offset |
335
394
  | `opacity` | number | 0–1 transparency |
336
- | `mode` | string | Blend mode for Composite |
395
+ | `mode` | string | Blend mode for Composite: `normal`, `multiply`, `screen`, `overlay`, `difference`, `add`, `subtract`, `darken`, `lighten` |
396
+
397
+ The other side can be a clip name, or a value: a function's picture, a `solid(…)`, a
398
+ `text(…)`, or a trimmed clip. A clip joins as a composite layer; anything else draws as a
399
+ group blended with `mode` (`x`, `y`, and `opacity` place that group). A composite is itself
400
+ a target, so layers stack: `let ab = a.Composite(b, mode: "screen")` then
401
+ `[0s .. 4s] = ab.Composite(card("x"), mode: "multiply")`. Both sides must overlap in time.
337
402
 
338
403
  ### Filters
339
404
 
340
405
  ```vidscript
341
- [- 2s] = filter "glow", intensity: 0.8
406
+ input hero = "hero.mp4"
407
+ [0s .. 5s] = hero
408
+ [0s .. 2s] = filter "chromatic", offset: 0.01
342
409
  [0s .. 5s] = filter "vignette", intensity: 0.3
410
+ [0s .. 5s] = filter "contrast", amount: 1.2, target: hero
343
411
  ```
344
412
 
345
- Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`.
413
+ 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.
414
+
415
+ **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.
416
+
417
+ ### Motion blur
418
+
419
+ `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.
420
+
421
+ ```vidscript
422
+ input hero = "hero.mp4"
423
+ [0s .. 2s] = hero, animate: { type: "slideX", from: { x: 0 }, to: { x: 900 } }, motion_blur: { shutter: 360, samples: 16 }
424
+ ```
346
425
 
347
426
  ### Shaders
348
427
 
@@ -359,7 +438,8 @@ Fragment must sample `tDiffuse`, write `gl_FragColor`, and declare uniforms matc
359
438
  ### Audio
360
439
 
361
440
  ```vidscript
362
- audio music, volume: 0.5, fade_in: 1s, fade_out: 2s
441
+ input music = "music.mp3"
442
+ [0s .. 15s] = audio music, volume: 0.5, fade_in: 1s, fade_out: 2s
363
443
  ```
364
444
 
365
445
  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 +449,13 @@ Audio sources can be MP3 inputs, audio plugin results, or the embedded audio str
369
449
  | `volume` | number | Relative gain (0–1+). For generated audio, applied after loudness normalization. |
370
450
  | `fade_in` | number | Fade-in duration (seconds) |
371
451
  | `fade_out` | number | Fade-out duration (seconds) |
452
+ | `trim_start` | number | Start this many seconds into the file |
453
+ | `pan` | number | -1 (left) to 1 (right) |
454
+ | `duck_under` | name | Lower this track while the named track plays |
455
+ | `loudness` | string | `"ebu-r128"` normalizes an imported file |
456
+ | `animate` | object | `{ type: "volume", from: { volume: 0 }, to: { volume: 1 } }` automates the level |
457
+
458
+ Mixing recipes (J- and L-cuts, ducking, automation): `editing-actions.md` → Sound.
372
459
 
373
460
  ### Plugin Calls
374
461
 
@@ -433,7 +520,7 @@ Use `aspect_ratio: "9:16"` for vertical output, `"16:9"` for wide, `"1:1"` for s
433
520
 
434
521
  **Invalid (will not validate):**
435
522
 
436
- ```vidscript
523
+ ```vidscript invalid
437
524
  [-] = video xai.imagine("...") # missing xAI import
438
525
  [0s .. 5s] = video cf.video("...") # missing Cloudflare import
439
526
  [0s .. 15s] = audio eleven.generateMusic("...") # missing `import eleven from "@elevenlabs/music"`
@@ -441,20 +528,92 @@ Use `aspect_ratio: "9:16"` for vertical output, `"16:9"` for wide, `"1:1"` for s
441
528
  [0s .. 15s] = audio eleven.music("..."), volume: 0.5 # trailing params after direct plugin call
442
529
  ```
443
530
 
444
- ## Animations, Effects & Rendering (v2)
531
+ ## Reuse: values, functions, and modules
532
+
533
+ Anything that can be placed is a value: an input, a plugin result, `text(…)` / `audio(…)` /
534
+ `solid(…)`, a method chain, or a function call. A time block places one.
535
+
536
+ ### Functions
537
+
538
+ ```vidscript
539
+ export fn lowerThird(title: string, color: string = "#ff5500") -> video {
540
+ [- 2s] = text title, color: color, size: 48
541
+ }
542
+
543
+ [0s .. 2s] = lowerThird("Konkan Day 1")
544
+ [2s .. 4s] = lowerThird(title: "Day 2", color: "#00ff00")
545
+
546
+ # one-liners: fn … = expression
547
+ fn label(s: string) -> text = text(s, size: 24)
548
+ ```
549
+
550
+ | Form | Meaning |
551
+ |------|---------|
552
+ | `fn name(params) -> kind { … }` | Block body: places items when called and placed |
553
+ | `fn name(params) -> kind = expr` | Expression body: returns that value |
554
+ | `export fn …` | Exported (callable through an imported namespace) |
555
+ | `export timeline name(…) { … }` | Same as `export fn name(…) -> timeline { … }` |
556
+
557
+ Return kinds: `video`, `image`, `text`, `audio`, `timeline`. `-> video` is a closed unit
558
+ (place it with properties — `[0s .. 4s] = card("A"), x: 40, opacity: 0.5, blend: "screen"` — or
559
+ chain `.Opacity()`, `.Resize()`, `.Filter()`, `.Crop()`, `.Mask()` and it draws as one item);
560
+ `-> timeline` is open, its items join the caller. A `filter` or `shader` without `target:`
561
+ inside a function or timeline body grades that unit's picture, not the whole frame.
562
+
563
+ Arguments are checked at the call: a wrong kind, an unknown or missing parameter, too
564
+ many arguments, or a value of the wrong kind is a compile error naming the parameter.
565
+ Optional parameters take `name: kind = default`.
566
+
567
+ `use card at 0s .. 2s with { title: "A" }` is exactly `[0s .. 2s] = card(title: "A")` —
568
+ the same IR.
569
+
570
+ ### Modules
571
+
572
+ ```vidscript
573
+ import brand from "./brand.vid" # another .vid file's exports
574
+
575
+ [0s .. 2s] = brand.lowerThird("Hello")
576
+ [0s .. 2s] = text "x", color: brand.accent
577
+ use brand.card at 2s .. 4s with { title: "B" }
578
+ ```
579
+
580
+ `brand.vid` exports with `export fn lowerThird(title: string) -> video { … }`,
581
+ `export timeline card(title: string) { … }`, and `export const accent = "#ff5500"`.
582
+
583
+ ### Builtin values
584
+
585
+ ```vidscript
586
+ [0s .. 2s] = solid("#1a2b3c", opacity: 0.8) # flat-colour rectangle
587
+ [2s .. 3s] = gap() # transparent gap (advances the cursor)
588
+ [4s .. 4s] = marker("act 2") # named instant (timeline data, not rendered)
589
+ [0s .. 8s] = captions("talk.srt", size: 32) # one text layer per SRT cue
590
+ ```
591
+
592
+ Any visual value takes the same placement properties: `x`, `y`, `width`, `height`,
593
+ `opacity`, `rotation`, `radius`, `corners`, `area`, `blend`, `animate`, `enabled`.
594
+ `blend: "screen"` on a clip lowers to a composite layer over whatever is below the placement;
595
+ on text, a solid, or a function it draws as a group with that blend. A blend needs something
596
+ below it in the same window, or it is a compile error. More than 4 nested groups, or more
597
+ than 16 alive at once, is a compile warning (each group renders into its own GPU target).
598
+ `volume:` on a generated clip with its own sound sets that sound's level.
445
599
 
446
- VidScript v2 uses a **minimal-grammar, plugin-first** architecture. The compiler lowers VidScript into `IRTimeline` — the single contract shared by browser preview and final render.
600
+ ### Names must resolve
447
601
 
448
- User-facing extension uses two parameters on any text or video surface:
602
+ An unquoted word is only allowed where a value is an enumeration (`align: center`,
603
+ `position: top`, `style: title`, `mode: screen`). Everywhere else an unresolved name is
604
+ `Unknown variable 'titel'`. Quote strings; pass real variables.
449
605
 
450
- - `animate:` — motion over time (plugin calls or object descriptors)
451
- - `effects:` — post-processing filters with optional animated params
606
+ ## Animations and effects
607
+
608
+ `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
609
 
453
610
  ### animate: parameter
454
611
 
455
612
  ```vidscript
456
613
  import motion from "@scenerok/basic-animations"
457
614
 
615
+ input hero = "hero.mp4"
616
+ input app_screen = "app-screen.png"
458
617
  [0s .. 3s] = text "Hello", x: "50%", y: "50%", animate: motion.fadeIn(0.8s)
459
618
 
460
619
  [-] = video hero, animate: [motion.fadeIn(0.5s), motion.slideY(-40, 0, 1.2s)]
@@ -473,17 +632,19 @@ import motion from "@scenerok/basic-animations"
473
632
  }
474
633
  ```
475
634
 
476
- Compiler lowers `animate:` into `IRMotionTrack[]` (property-path keyframes). Legacy `IRAnimation` descriptors are adapted at runtime for backward compatibility.
635
+ 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
636
 
478
- ### effects: parameter
637
+ ### Animated filters
479
638
 
480
639
  ```vidscript
640
+ import motion from "@scenerok/basic-animations"
641
+
481
642
  [0s .. 5s] = filter "vignette", intensity: 0.4
482
643
 
483
644
  [0s .. 5s] = filter "glitch", intensity: 0.5, animate: motion.fadeIn(1s)
484
645
  ```
485
646
 
486
- Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`. Filters are optional finishing tools; use them only when they improve the composition.
647
+ Filters are optional finishing tools; use them only when they improve the composition. Parameter names per filter: `editing-actions.md` → Color and finishing.
487
648
 
488
649
  ### Animation functions (`@scenerok/basic-animations`)
489
650
 
@@ -500,24 +661,12 @@ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignett
500
661
  | `motion.float(duration?, amp?)` | Gentle vertical bob |
501
662
  | `motion.typewriter(duration?)` | Character reveal |
502
663
 
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
664
  ## Expressions
517
665
 
518
666
  Used in time specs, `let` values, and function arguments:
519
667
 
520
668
  ```vidscript
669
+ let title_text = "Launch"
521
670
  let x = 5s # Time literals
522
671
  let y = x * 2 # Arithmetic
523
672
  let prompt = "Scene: " + title_text # String concatenation
@@ -533,7 +682,7 @@ Operators: `+`, `-`, `*`, `/` with standard precedence.
533
682
  [- 3s] = text "{{headline | Default Text}}", color: "{{color | #FFFFFF}}"
534
683
  ```
535
684
 
536
- Placeholders are filled before parsing via `fillPlaceholders()`.
685
+ Templates fill each `{{name | default}}` before the script compiles; outside a template the default is used.
537
686
 
538
687
  ## CLI Commands
539
688
 
@@ -556,8 +705,6 @@ scenerok status <render-id>
556
705
  # Install agent skills
557
706
  scenerok skills install <platform>
558
707
 
559
- # Set API secrets for plugins
560
- scenerok secrets set ELEVENLABS_API_KEY=your-key
561
708
  ```
562
709
 
563
710
  ## 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/`.
@@ -1,6 +1,6 @@
1
1
  # VidScript v2 — Strict Agent Reference
2
2
 
3
- Follow these rules exactly. When in doubt, fetch `vidscript-sample.md` from https://scenerok.com/.well-known/agent-skills/ or run `scenerok skills sync` for local copies.
3
+ Follow these rules exactly. For a complete working script, see `vidscript-sample.md`; for cuts, layering, masks, audio mixing, and grading, see `editing-actions.md`.
4
4
 
5
5
  ## Rule 1: Package imports are mandatory
6
6
 
@@ -17,11 +17,7 @@ Without the owning import, calls like `xai.imagine`, `cf.video`, `xai.tts`, `ele
17
17
 
18
18
  ## Rule 2: Use URL assets selectively
19
19
 
20
- When the user gives a product, company, ecommerce, landing page, or app URL and you have browser/screenshot capability, you can visit the page and capture useful visual evidence. Favor above-fold screenshots, product views, app screenshots, pricing/offer cards, testimonials, and detectable logos or brand marks.
21
-
22
- Do not use full-page or very long website screenshots as video backgrounds; their text is usually illegible at output size. Crop or frame screenshots to the specific visual proof point, and use them sparingly.
23
-
24
- Website screenshots are optional source material, not a requirement to build the whole video from screenshots. You may combine them with still generation, text-to-video, image-to-video, reference-to-video, or extension plugins for lifestyle scenes, motion backgrounds, transitions, or product context. Do not invent claims; use website copy or ask the user.
20
+ A product or landing-page URL is optional source material. Capture focused, legible pieces (above-the-fold views, product shots, logos), never full-page screenshots as backgrounds, and don't invent claims. The full URL workflow is in `workflow.md`.
25
21
 
26
22
  ## Rule 2.5: Probe real asset type and dimensions before placement
27
23
 
@@ -37,6 +33,8 @@ identify asset.webp
37
33
  Use real video files for motion footage and still image inputs for screenshots, logos, product photos, and brand marks. The `video` primitive accepts both videos and images; image inputs render as static clips for the block duration.
38
34
 
39
35
  ```vidscript
36
+ import motion from "@scenerok/basic-animations"
37
+
40
38
  input screenshot = "assets/screenshots/homepage.png"
41
39
  [0s .. 4s] = video screenshot, width: 1080, height: 1920, x: 0, y: 0, animate: motion.fadeIn(0.35s)
42
40
  ```
@@ -53,6 +51,7 @@ Add entry motion to real assets, not only text. When a static visual needs a cle
53
51
  ```vidscript
54
52
  import motion from "@scenerok/basic-animations"
55
53
 
54
+ input product = "assets/product.png"
56
55
  [0s .. 3.4s] = video product, width: 760, height: 615, x: 160, y: 300, animate: [motion.riseIn(0.6s, 70), motion.float(2.4s, 8)]
57
56
  [3.4s .. 4s] = video product, width: 760, height: 615, x: 160, y: 300, animate: motion.fadeOut(0.6s)
58
57
  ```
@@ -64,8 +63,10 @@ AI-generated images and videos must not contain readable text. In every prompt f
64
63
  Good prompt pattern:
65
64
 
66
65
  ```vidscript
66
+ import xai from "@scenerok/xai"
67
+
67
68
  [-] = video xai.imagine("Clean cinematic product background, premium lighting, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 5)
68
- [0.5s .. 3s] = text "Launch offer", style: title, color: "#FFFFFF", x: "50%", y: "30%", stroke: "#000000", stroke_width: 3
69
+ [0.5s .. 3s] = text "Launch offer", style: title, color: "#FFFFFF", area: "title", stroke: "#000000", stroke_width: 3
69
70
  ```
70
71
 
71
72
  Invalid: asking the generator for signs, labels, packaging text, app UI text, title cards, lower thirds, prices, CTA buttons, or any readable copy.
@@ -101,13 +102,9 @@ Do not overuse one plugin function. Choose deliberately:
101
102
  | Voiceover | `xai.tts`, `xai.textToSpeech` |
102
103
  | Music bed | `eleven.music`, `eleven.generateMusic`, `eleven.composeMusic` |
103
104
 
104
- Use `imageToVideo` for one extracted screenshot/product/logo image. Use `referenceToVideo` for 1-7 reference images when extracted objects should guide the generated shot. xAI `referenceToVideo` requires a prompt and duration must be <= 10s.
105
+ Use `imageToVideo` for one extracted screenshot/product/logo image and `referenceToVideo` for 1–7 reference images (xAI: prompt required, duration ≤ 10s). Cloudflare calls take an explicit `model:` and `aspect_ratio:`; models, signatures, and durations are in `ai-models.md` and the plugin table in `vidscript-guide.md`.
105
106
 
106
- For Cloudflare calls, always prefer explicit `model:` and use `aspect_ratio: "9:16"` for vertical, `"16:9"` for wide, `"1:1"` for square, or model-supported values such as `"4:3"`, `"3:4"`, and `"21:9"`. `cf.imageToVideo(image, prompt, model: ..., aspect_ratio: ..., duration: ...)` accepts `aspect_ratio`; for Runway Gen-4.5, SceneRok translates `"9:16"` to Cloudflare's required `ratio: "720:1280"`. Useful models include `pixverse/v6`, `vidu/q3-turbo`, `runwayml/gen-4.5`, `minimax/hailuo-2.3`, `bytedance/seedance-2.0`, `google/veo-3.1`, `google/veo-3.1-fast`, `google/veo-3-fast`, `black-forest-labs/flux-2-pro-preview`, and `openai/gpt-image-2`. Veo models accept `duration: 4`, `duration: 4s`, or `duration: "4s"` — all forms snap to the supported 4s/6s/8s durations.
107
-
108
- xAI TTS voices are only `eve`, `ara`, `rex`, `sal`, and `leo`.
109
-
110
- For ElevenLabs music, import `@elevenlabs/music` and use `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`. Do not call `eleven.tts(...)`; ElevenLabs TTS is not registered in VidScript today. Ads, promos, tutorials, and reels should normally include either `xai.tts` voiceover, an ElevenLabs music bed, or both unless the user asks for silent output.
107
+ xAI TTS voices are only `eve`, `ara`, `rex`, `sal`, and `leo`. ElevenLabs is music only (`eleven.music`, `eleven.generateMusic`, `eleven.composeMusic`); there is no `eleven.tts`. Ads, promos, tutorials, and reels should normally have a voiceover, a music bed, or both unless the user asks for silence.
111
108
 
112
109
  ## Rule 4.5: `[-]` uses separate audio and visual playheads
113
110
 
@@ -129,16 +126,20 @@ Use explicit ranges when you need exact synchronization, overlaps, or cuts. Do n
129
126
 
130
127
  ## Rule 4.6: Never guess generated-asset durations
131
128
 
132
- Invalid — the real TTS length is unknown at write time, so a guessed window clips or overlaps:
129
+ Avoid — the real TTS length is unknown at write time, so a guessed window clips or overlaps:
130
+
131
+ ```vidscript avoid
132
+ import xai from "@scenerok/xai"
133
133
 
134
- ```vidscript
135
134
  [0s .. 3s] = audio xai.tts("A sentence that may well run past three seconds", voice: "eve")
136
135
  [3s .. 6s] = audio xai.tts("This one starts while the first is still playing", voice: "eve")
137
136
  ```
138
137
 
139
- Valid — assign with `let` and reference the measured `duration` (arithmetic allowed), or use `[-]` auto blocks:
138
+ Do this — assign with `let` and reference the measured `duration` (arithmetic allowed), or use `[-]` auto blocks:
140
139
 
141
140
  ```vidscript
141
+ import xai from "@scenerok/xai"
142
+
142
143
  let vo1 = xai.tts("First line", voice: "eve")
143
144
  let vo2 = xai.tts("Second line", voice: "eve")
144
145
  [0 .. vo1.duration] = audio vo1
@@ -207,10 +208,12 @@ let bed = eleven.music("Soft premium ad music", duration: 15, instrumental: true
207
208
 
208
209
  ```vidscript
209
210
  [0s .. 15s] = filter "vignette", intensity: 0.3
210
- [0s .. 15s] = filter "saturation", value: 1.12
211
+ [0s .. 15s] = filter "saturation", amount: 1.12
211
212
  ```
212
213
 
213
- Filters are optional finishing tools, not required ingredients. Use them only when they improve the visual result. `saturation` uses `value`, not `intensity`.
214
+ Filters are optional finishing tools, not required ingredients. Use them only when they improve the visual result. 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
+
216
+ A `filter` line grades everything in its window. To grade one clip, add `target: clip` or write `clip.Filter("sepia", intensity: 0.6)`.
214
217
 
215
218
  ## Rule 10: Always end with output
216
219
 
@@ -220,6 +223,24 @@ output to "video.mp4", resolution: "1080x1920", fps: 30
220
223
 
221
224
  Do **not** use legacy `config { }` blocks — they are not part of VidScript v2.
222
225
 
226
+ ## Rule 11: Edits are values — place the result
227
+
228
+ `Trim`, `Speed`, `Loop`, `Crop`, `Resize`, `Mask`, `Filter`, `Overlay`, and `Composite` return a new value. Place the result in one line:
229
+
230
+ ```vidscript
231
+ input hero = "hero.mp4"
232
+ input logo = "logo.png"
233
+ [0s .. 4s] = hero.Trim(2s, 6s).Crop(420, 0, 1080, 1920)
234
+ [4s .. 8s] = hero.Trim(6s, 10s).Overlay(logo.Resize(200, 200), x: 840, y: 60)
235
+ ```
236
+
237
+ - A placed value lasts its own length (trimmed range ÷ speed × loops). Make the window that long.
238
+ - Put one video over another with `Overlay`, `Composite`, or `blend:`. Two plain clips in the same window have no defined order.
239
+ - `Mask` on its own line after the clip is placed is a compile error.
240
+ - Frame counts are written `48 frame` and use the output fps (30 if `output` has none).
241
+
242
+ All editing actions (masks, speed ramps, ducking, LUTs, timecode): `editing-actions.md`.
243
+
223
244
  ## Animations (after import)
224
245
 
225
246
  ```vidscript
@@ -240,12 +261,6 @@ Each error line includes `Line N:C — message` when the server returns location
240
261
 
241
262
  ## Bundled examples
242
263
 
243
- | File | Use case |
244
- |------|----------|
245
- | `minimal-text-reel.vid` | Text-only, no API keys |
246
- | `product-launch.vid` | User video input + text + vignette |
247
- | `ecom-product-launch.vid` | xAI product visuals + CTA |
248
- | `3-tips-fitness.vid` | Multiple `[-]` blocks + xAI per segment |
249
- | `rokmilk-chocolate-promo.vid` | 15s generated-video promo pattern |
264
+ The `@scenerok/cli` npm package ships working scripts in `examples/system/`: `minimal-text-reel.vid` (text only), `product-launch.vid` (your own video + text), `ecom-product-launch.vid` (generated product visuals + CTA), `3-tips-fitness.vid` (several `[-]` blocks), `rokmilk-chocolate-promo.vid` (15 s generated promo).
250
265
 
251
- Full grammar: `vidscript-guide.md` or https://scenerok.com/.well-known/agent-skills/vidscript-guide.md
266
+ Full grammar: `vidscript-guide.md`.