@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
@@ -0,0 +1,518 @@
1
+ # Editing and compositing actions
2
+
3
+ The everyday editing actions in VidScript: layering, cutting, sound, titles,
4
+ color, and delivery. Each section shows one working spelling. Every example
5
+ compiles as written; add an `output` line to render it.
6
+
7
+ These are VidScript features. The TypeScript SDK covers clips, text, audio,
8
+ filters, and transitions, but not masks, blends, crops, ducking, or the
9
+ delivery keys below. For those, write the scene in VidScript; a VidScript file
10
+ can still `import` an SDK scene and `use` it (see `vidscript-guide.md`).
11
+
12
+ ## Contents
13
+
14
+ - Layering and compositing: picture-in-picture, blend modes, stacks, masks and
15
+ mattes, crop and shape, moving a group, split screens, hiding a layer
16
+ - Cutting and timing: trim, speed, reverse, freeze, ramps, loops, back-to-back
17
+ cuts, frame-accurate times, transitions with handles, image sequences
18
+ - Sound: a clip's own sound, volume and fades, J- and L-cuts, ducking, source
19
+ offset, pan, volume automation, loudness
20
+ - Text and titles: per-word reveals, easing, rotation, captions
21
+ - Color and finishing: per-clip grades, LUTs, CDL, color spaces, motion blur
22
+ - Reuse and delivery: functions, timelines, modules, output settings, timecode
23
+ - What VidScript can't do yet
24
+
25
+ ## Layering and compositing
26
+
27
+ Text always draws over video. Between two video layers, say which one goes on
28
+ top with `Overlay`, `Composite`, or `blend:`; two plain clips placed in the
29
+ same window have no defined order.
30
+
31
+ ### Picture-in-picture
32
+
33
+ `Overlay` puts one picture over a clip at a position (`x`, `y`, `opacity`).
34
+ The overlaid side can be a clip, `text(…)`, `solid(…)`, or a function call:
35
+
36
+ ```vidscript
37
+ input main = "interview.mp4"
38
+ input cam = "screen.mp4"
39
+ [0s .. 6s] = main.Overlay(cam.Resize(360, 640), x: 680, y: 60, opacity: 0.9)
40
+ [6s .. 9s] = main.Overlay(text("LIVE", size: 48), x: 40, y: 40)
41
+ ```
42
+
43
+ For rounded corners or any other placement property, place the inset as its
44
+ own layer with `blend: "normal"`, which puts it over what is below. Placement
45
+ properties follow any value, including a method chain; `video cam` and a bare
46
+ `cam` are the same placement:
47
+
48
+ ```vidscript
49
+ input main = "interview.mp4"
50
+ input cam = "screen.mp4"
51
+ [0s .. 6s] = main
52
+ [0s .. 6s] = cam.Trim(10s, 16s), width: 360, height: 640, x: 680, y: 60, radius: 24, blend: "normal"
53
+ ```
54
+
55
+ ### Blend modes
56
+
57
+ `blend:` blends a layer with whatever is below it in the same window. Modes:
58
+ `normal`, `multiply`, `screen`, `overlay`, `difference`, `add`, `subtract`,
59
+ `darken`, `lighten`.
60
+
61
+ ```vidscript
62
+ input footage = "city.mp4"
63
+ input flare = "light-leak.mp4"
64
+ [0s .. 5s] = footage
65
+ [0s .. 5s] = flare, blend: "screen", opacity: 0.7
66
+ [0s .. 5s] = solid("#ff6a00", opacity: 0.25), blend: "multiply"
67
+ ```
68
+
69
+ A blend with nothing below it is a compile error; place the base layer first.
70
+
71
+ ### Stacking composites
72
+
73
+ `Composite` returns a value, so layers stack:
74
+
75
+ ```vidscript
76
+ input base = "city.mp4"
77
+ input grain = "film-grain.mp4"
78
+ input leak = "light-leak.mp4"
79
+ let graded = base.Composite(grain, mode: "overlay", opacity: 0.4)
80
+ [0s .. 5s] = graded.Composite(leak, mode: "screen")
81
+ ```
82
+
83
+ Both sides must overlap in time.
84
+
85
+ ### Masks and mattes
86
+
87
+ `Mask(matte, mode:)` draws a value through a matte. The matte can be any
88
+ picture: a `solid` shape, `text(…)`, a function, or another clip (a track
89
+ matte). Modes: `"alpha"` (default), `"luma"`, `"inverted"`.
90
+
91
+ ```vidscript
92
+ input portrait = "portrait.mp4"
93
+ input matte = "matte.mp4"
94
+ [0s .. 4s] = portrait.Mask(solid("#fff", x: 140, y: 560, width: 800, height: 800, radius: 400))
95
+ [4s .. 8s] = portrait.Mask(text("SALE", size: 400))
96
+ [8s .. 12s] = portrait.Mask(matte, mode: "luma")
97
+ ```
98
+
99
+ - The matte is in output pixels, the same space as the placement: put the
100
+ shape where the clip appears on screen. The matte's color doesn't matter in
101
+ `"alpha"` mode.
102
+ - Place the masked value (`[…] = a.Mask(…)`). `a.Mask(…)` on its own line after
103
+ `a` is placed is a compile error.
104
+ - A masked clip can't be an `Overlay` or `Composite` side. Place it as its own
105
+ layer instead, with `blend: "normal"` to put it over another video:
106
+ `[0s .. 4s] = portrait.Mask(…), blend: "normal"`.
107
+
108
+ ### Crop, resize, rounded corners, corner pin, rotation
109
+
110
+ ```vidscript
111
+ input wide = "landscape.mp4"
112
+ input screen = "app-demo.mp4"
113
+ [0s .. 4s] = wide.Crop(420, 0, 1080, 1920)
114
+ [4s .. 8s] = screen, width: 900, height: 900, x: 90, y: 510, radius: 48, rotation: 4
115
+ [8s .. 12s] = screen, corners: [[120, 400], [960, 460], [940, 1500], [140, 1440]]
116
+ ```
117
+
118
+ - `Crop(x, y, width, height)` takes a rectangle in source pixels from the
119
+ top-left; the rectangle fills the clip's display size, so `Resize` to the
120
+ same aspect to avoid stretching. A second `Crop` is relative to the first.
121
+ - `radius:` rounds the corners, in pixels.
122
+ - `corners:` pins the picture to four output points, clockwise from top-left.
123
+ - `rotation:` is in degrees. Positive values turn video counter-clockwise
124
+ (text turns the other way: see the note under Text and titles).
125
+
126
+ ### Move several layers as one
127
+
128
+ A function that returns `video` draws as one item. Its placement takes
129
+ `animate:`, `blend:`, `opacity:`, and the transform properties, so this is the
130
+ camera move or the animated lower third. Times inside the body count from where
131
+ the function is placed. Inputs declared at the top of the script aren't visible
132
+ inside a function; pass clips in as parameters (`fn card(clip: video) -> video`).
133
+ On a function value, `Crop`, `Filter`, `Mask`, `Opacity`, and `Resize` work;
134
+ `Trim`, `Speed`, and `Loop` are errors.
135
+
136
+ ```vidscript
137
+ input footage = "city.mp4"
138
+ fn lowerThird(title: string) -> video {
139
+ [0s .. 3s] = solid("#111111", width: 800, height: 160, x: 140, y: 1500, radius: 24)
140
+ [0s .. 3s] = text title, size: 56, area: "lower-third", color: "#ffffff"
141
+ }
142
+ [0s .. 3s] = footage
143
+ [0s .. 3s] = lowerThird("Day 1 · Goa"), animate: { type: "slideX", from: { x: -900 }, to: { x: 0 }, easing: "easeOutCubic" }
144
+ ```
145
+
146
+ ### Split screens
147
+
148
+ Grid areas work for video as well as text. `rows` and `cols` take `%` and `fr`
149
+ tracks; `areas` has one string per row:
150
+
151
+ ```vidscript
152
+ input before = "before.mp4"
153
+ input after = "after.mp4"
154
+ grid rows: "1fr", cols: "1fr 1fr", areas: ["left right"]
155
+ [0s .. 5s] = video before, area: "left"
156
+ [0s .. 5s] = video after, area: "right"
157
+ [0s .. 5s] = text "Before", area: "left"
158
+ [0s .. 5s] = text "After", area: "right"
159
+ ```
160
+
161
+ The grid applies to the whole script; `area:` is opt-in per placement, so
162
+ later full-frame placements are unaffected. A video in an area is sized to the
163
+ cell and stretched to fit, so crop it to the cell's shape first: a 1920x1080
164
+ clip in a 960x1080 half is `before.Crop(480, 0, 960, 1080)`. Text in an area is
165
+ centered in the cell; `area:` also works inside functions.
166
+
167
+ A transition can't start from a layout of several clips. To dissolve from a
168
+ split screen into one shot, run the layout past the cut and fade the shot in
169
+ over it:
170
+
171
+ ```vidscript
172
+ import motion from "@scenerok/basic-animations"
173
+ input before = "before.mp4"
174
+ input after = "after.mp4"
175
+ grid rows: "1fr", cols: "1fr 1fr", areas: ["left right"]
176
+ [0s .. 5.5s] = before.Trim(0s, 5.5s).Crop(480, 0, 960, 1080), area: "left"
177
+ [0s .. 5.5s] = after.Trim(0s, 5.5s).Crop(480, 0, 960, 1080), area: "right"
178
+ [5s .. 10s] = after.Trim(5s, 10s), blend: "normal", animate: motion.fadeIn(0.5s)
179
+ ```
180
+
181
+ ### Hide a layer without deleting it
182
+
183
+ ```vidscript
184
+ input footage = "city.mp4"
185
+ input draft = "draft-overlay.mp4"
186
+ [0s .. 5s] = footage
187
+ [0s .. 5s] = video draft, enabled: false
188
+ ```
189
+
190
+ ## Cutting and timing
191
+
192
+ A placed clip lasts its own length: the trimmed range divided by the speed,
193
+ times the loop count. Make the window that long. A longer window does not hold
194
+ the last frame (see Hold a frame below). `Speed(0)` is the exception: it holds
195
+ for the whole window.
196
+
197
+ ### Trim, speed, reverse, freeze, loop
198
+
199
+ Methods return a new value; chain them:
200
+
201
+ ```vidscript
202
+ input clip = "surf.mp4"
203
+ [0s .. 4s] = clip.Trim(2s, 6s)
204
+ [4s .. 6s] = clip.Trim(10s, 14s).Speed(2)
205
+ [6s .. 8s] = clip.Trim(2s, 4s).Speed(-1)
206
+ [8s .. 9s] = clip.Trim(5s, 6s).Speed(0)
207
+ [9s .. 15s] = clip.Trim(0s, 2s).Loop(3)
208
+ ```
209
+
210
+ - `Trim(in, out)` plays exactly that source range. A second `Trim` is relative
211
+ to the first: `clip.Trim(10s, 20s).Trim(2s, 4s)` plays 12–14 s.
212
+ - `Speed(n)`: above 1 is faster, below 1 slower, negative plays backwards,
213
+ `0` holds the in-point frame.
214
+ - `Loop(n)` repeats the range `n` times.
215
+ - Each placement is its own value: trimming the same input in several
216
+ placements cuts several independent pieces.
217
+
218
+ ### Hold a frame
219
+
220
+ `Speed(0)` holds the first frame of the range for the whole window. To end on
221
+ a freeze of the last frame, hold a range one frame long (1/fps seconds; 0.04 s
222
+ at 25 fps) at the end:
223
+
224
+ ```vidscript
225
+ input clip = "surf.mp4"
226
+ [0s .. 4s] = clip.Trim(16s, 18s).Speed(0.5)
227
+ [4s .. 6s] = clip.Trim(17.96s, 18s).Speed(0)
228
+ ```
229
+
230
+ ### Speed ramps
231
+
232
+ Animate `speed` across the placement:
233
+
234
+ ```vidscript
235
+ input clip = "surf.mp4"
236
+ [0s .. 3s] = clip, animate: { type: "speed", from: { speed: 0.4 }, to: { speed: 2 }, easing: "easeInOutCubic" }
237
+ ```
238
+
239
+ ### Back-to-back cuts, gaps, and markers
240
+
241
+ `[- 2s]` starts where the last visual block ended. `gap()` leaves an empty
242
+ stretch; `marker("name")` records a named point (not drawn):
243
+
244
+ ```vidscript
245
+ input a = "shot-a.mp4"
246
+ input b = "shot-b.mp4"
247
+ [- 2s] = a
248
+ [- 0.5s] = gap()
249
+ [- 2s] = b
250
+ [2s .. 2s] = marker("beat drop")
251
+ ```
252
+
253
+ ### Frame-accurate times
254
+
255
+ `48 frame` is 48 frames at the output frame rate (30 when `output` sets no
256
+ `fps:`; a computed `fps:` is an error):
257
+
258
+ ```vidscript
259
+ [0s .. 48 frame] = text "Two seconds at 24 fps"
260
+ output to "video.mp4", resolution: "1920x1080", fps: 24
261
+ ```
262
+
263
+ ### Transitions and handles
264
+
265
+ Transitions come from `@scenerok/transitions` (full list in
266
+ `transitions-catalog.md`). By default the window sits between two clips. To
267
+ dissolve across a straight cut instead, add handles: `in_offset` reads that
268
+ much of the incoming clip before its in-point, `out_offset` that much of the
269
+ outgoing clip past its out-point. The clips need that much spare source.
270
+
271
+ A transition joins plain clips only. It is a compile error when either side is
272
+ masked, cropped, motion-blurred, or a placed function; transition between the
273
+ plain clips and apply those edits away from the cut.
274
+
275
+ ```vidscript
276
+ import tr from "@scenerok/transitions"
277
+ input a = "shot-a.mp4"
278
+ input b = "shot-b.mp4"
279
+ [0s .. 4s] = a.Trim(2s, 6s)
280
+ [4s .. 8s] = b.Trim(2s, 6s)
281
+ [3.8s .. 4.2s] = tr.dissolve(in_offset: 0.2s, out_offset: 0.2s)
282
+ ```
283
+
284
+ ### Image sequences
285
+
286
+ A numbered image sequence is one input. `count:` is the number of frames;
287
+ `rate:` defaults to the output fps; `first:` is the first file number
288
+ (default 0):
289
+
290
+ ```vidscript
291
+ input frames = "renders/shot.%04d.png", rate: 12, first: 1, count: 24
292
+ [0s .. 2s] = frames
293
+ ```
294
+
295
+ `Trim`, `Speed`, and `Loop` work on it. For cloud renders the pattern must be
296
+ an `https` URL pattern.
297
+
298
+ ## Sound
299
+
300
+ Every sound is an `audio` line with its own window, so J- and L-cuts need no
301
+ special syntax: start or end the sound before or after the picture cut.
302
+
303
+ ### A clip's own sound, volume, and fades
304
+
305
+ Video inputs are silent until you place their sound:
306
+
307
+ ```vidscript
308
+ input a = "talk-a.mp4"
309
+ input b = "talk-b.mp4"
310
+ [0s .. 4s] = a
311
+ [4s .. 8s] = b
312
+ [0s .. 4s] = audio a, fade_in: 0.3s
313
+ [3.5s .. 8s] = audio b.Trim(0s, 4.5s), volume: 0.9, fade_out: 1s
314
+ ```
315
+
316
+ The second `audio` line is a J-cut: `b`'s sound starts half a second before its
317
+ picture.
318
+
319
+ ### Duck music under a voice
320
+
321
+ `duck_under:` lowers a bed to about a third of its volume, with short ramps,
322
+ for the whole window in which the named track plays (it follows the track's
323
+ time window, not speech). To duck only while someone speaks, place their
324
+ sound in separate windows, one per line of speech. Name an `input`, or a `let`
325
+ binding of generated speech:
326
+
327
+ ```vidscript
328
+ import xai from "@scenerok/xai"
329
+ input bed = "music.mp3"
330
+ let vo = xai.tts("Here is what changed this week.", voice: "rex")
331
+ [0s .. 10s] = audio bed, volume: 0.6, duck_under: vo, fade_out: 1.5s
332
+ [1s .. 1s + vo.duration] = audio vo
333
+ ```
334
+
335
+ ### Start partway into a file, pan, and automate volume
336
+
337
+ ```vidscript
338
+ input song = "song.mp3"
339
+ input sfx = "whoosh.wav"
340
+ [0s .. 8s] = audio song, trim_start: 30, animate: { type: "volume", from: { volume: 0 }, to: { volume: 0.8 }, start: 0, end: 3 }
341
+ [2s .. 3s] = audio sfx, pan: -0.6
342
+ ```
343
+
344
+ - `trim_start:` (seconds) or `song.Trim(30s, 38s)` starts the file 30 s in.
345
+ - `pan:` runs from -1 (left) to 1 (right).
346
+ - `animate: { type: "volume", … }` changes the level over time.
347
+
348
+ ### Even loudness
349
+
350
+ ```vidscript
351
+ input voice = "raw-voiceover.wav"
352
+ [0s .. 10s] = audio voice, loudness: "ebu-r128"
353
+ ```
354
+
355
+ Generated speech and music are already normalized to broadcast loudness;
356
+ use `loudness: "ebu-r128"` for your own recordings. It is the only value.
357
+
358
+ ## Text and titles
359
+
360
+ ```vidscript
361
+ [0s .. 2.5s] = text "Built for speed", area: "title", stagger: "word", animate: { type: "slideY", from: { y: 40, opacity: 0 }, to: { y: 0, opacity: 1 }, easing: "spring" }
362
+ [2.5s .. 5s] = text "NEW", size: 120, rotation: -8, anchor: "top-left", x: "8%", y: "12%"
363
+ [0s .. 8s] = captions("talk.srt", size: 40, area: "caption")
364
+ ```
365
+
366
+ - `style:` is `title` (64 px), `subtitle` (54), `caption` (36), or `default` (48).
367
+ - Text without `x`, `y`, or `area:` is centered on the frame.
368
+ - `stagger: "word"` or `"char"` reveals the text in pieces over the animation.
369
+ - Easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`,
370
+ `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeOutBack`, `spring`.
371
+ - `rotation:` turns text around its `anchor:`; a positive value turns static
372
+ text clockwise, the opposite of video. Check the preview when direction
373
+ matters.
374
+ - `captions("file.srt", …)` makes one text layer per cue from a SubRip file;
375
+ the other arguments style every cue.
376
+
377
+ For catalog text effects (word cascades, counters, kinetic type), see
378
+ `animations-picker.md`.
379
+
380
+ ## Color and finishing
381
+
382
+ `filter` without `target:` grades everything in its window. To grade one
383
+ clip, add `target:` or use the `Filter` method; inside a function it grades
384
+ only that function's picture.
385
+
386
+ ```vidscript
387
+ input a = "shot-a.mp4"
388
+ input b = "shot-b.mp4"
389
+ [0s .. 4s] = a
390
+ [0s .. 4s] = filter "sepia", intensity: 0.6, target: a
391
+ [4s .. 8s] = b.Filter("saturation", amount: 1.2)
392
+ [4s .. 8s] = filter "vignette", intensity: 0.3
393
+ ```
394
+
395
+ Filters that change each pixel on its own can target one clip: `sepia`,
396
+ `monochrome`, `contrast`, `saturation`, `brightness`, `vignette`, `premultiply`, `lut`, `cdl`.
397
+ Filters that sample neighbouring pixels (`blur`, `glitch`, `chromatic`, and the
398
+ VFX in `shaders-filters.md`) can't take `target:`. To blur only part of the
399
+ picture, put the clip and the filter in a function; the filter stays inside it:
400
+
401
+ ```vidscript
402
+ input speaker = "speaker.mp4"
403
+ fn blurred(clip: video) -> video {
404
+ [0s .. 6s] = clip
405
+ [0s .. 6s] = filter "blur", radius: 8
406
+ }
407
+ [0s .. 6s] = blurred(speaker.Trim(0s, 6s))
408
+ [0s .. 6s] = text "Sharp on top", area: "title"
409
+ ```
410
+
411
+ Each filter reads its own parameter names; any other name is ignored without
412
+ an error, so use these:
413
+
414
+ | Filter | Parameter (default) |
415
+ | --- | --- |
416
+ | `sepia`, `monochrome` | `intensity`, 0–1 |
417
+ | `vignette`, `glitch` | `intensity` (0.5) |
418
+ | `contrast`, `saturation` | `amount`, a multiplier (contrast 1.2, saturation 1.5; 1 is unchanged) |
419
+ | `brightness` | `amount`, added to each channel (0.1; 0 is unchanged) |
420
+ | `blur` | `radius` in pixels (5) |
421
+ | `chromatic` | `offset` (0.02) |
422
+ | `lut` | `file` |
423
+ | `cdl` | `slope`, `offset`, `power`, `saturation` |
424
+ | `premultiply` | none: multiplies color by alpha, for footage with straight alpha |
425
+
426
+ ### LUTs and CDL
427
+
428
+ ```vidscript
429
+ input a = "log-footage.mp4"
430
+ [0s .. 4s] = a
431
+ [0s .. 4s] = filter "lut", file: "look.cube", target: a
432
+ [0s .. 4s] = filter "cdl", slope: "1.1 1 0.9", offset: "0 0 0", power: "1 1 1", saturation: 1
433
+ ```
434
+
435
+ - `filter "lut"` applies a 3D `.cube` file next to the script (512 KB at
436
+ most, up to 33 points per axis). Use the `filter` line, not `Filter("lut")`.
437
+ - `filter "cdl"` takes ASC CDL `slope`, `offset`, `power` (three numbers each)
438
+ and `saturation`.
439
+
440
+ ### Color spaces and motion blur
441
+
442
+ ```vidscript
443
+ input a = "acescg-plate.mp4"
444
+ input logo = "logo.png"
445
+ [0s .. 3s] = video a, input_colorspace: "ACEScg", output_colorspace: "sRGB"
446
+ [0s .. 3s] = video logo, width: 300, height: 300, x: 390, y: 800, animate: { type: "slideX", from: { x: -400 }, to: { x: 390 }, easing: "easeOutCubic" }, motion_blur: { shutter: 180, samples: 8 }
447
+ ```
448
+
449
+ - Color spaces: `sRGB`, `Rec.709` (or `rec709`), `Display P3`, `ACEScg`,
450
+ `Linear`, `Gamma 2.2` (or `gamma22`).
451
+ - `motion_blur: true` (or `{ shutter, samples }`) blurs an item's own
452
+ `animate:` movement. Each sample is another render of the frame, so use
453
+ it on fast moves only. It errors on text, solids, captions, and clips inside
454
+ a transition.
455
+
456
+ ## Reuse and delivery
457
+
458
+ ### Functions, timelines, and modules
459
+
460
+ ```vidscript
461
+ input product = "product.png"
462
+ export fn card(title: string, accent: string = "#ff5500") -> video {
463
+ [0s .. 2s] = solid(accent, opacity: 0.85)
464
+ [0s .. 2s] = text title, area: "title", color: "#ffffff"
465
+ }
466
+ export timeline intro(clip: video) {
467
+ [- 2s] = clip
468
+ }
469
+ [0s .. 2s] = card("Spring drop")
470
+ [2s .. 4s] = card(title: "Free shipping", accent: "#0f766e")
471
+ use intro at 4s .. 4s with { clip: product }
472
+ ```
473
+
474
+ - Arguments are checked at the call: a missing, unknown, or wrong-kind
475
+ argument is a compile error that names it.
476
+ - `-> video` draws as one item; a `timeline` body's items join the caller.
477
+ - `import brand from "./brand.vid"` loads another file's exports
478
+ (`brand.card("Hi")`).
479
+
480
+ ### Output settings and timecode
481
+
482
+ ```vidscript
483
+ input a = "master.mp4"
484
+ [0s .. 10s] = a
485
+ output to "master.mp4", resolution: "1920x1080", fps: 25, codec: "h264", bitrate: "8M", start_timecode: "01:00:00:00", burn_in: "timecode"
486
+ ```
487
+
488
+ `start_timecode` is `HH:MM:SS:FF` and its frame field must be below `fps`.
489
+ `burn_in: "timecode"` draws the running timecode on the picture.
490
+
491
+ ## What VidScript can't do yet
492
+
493
+ Don't try to work around these. Tell the user when a request needs one.
494
+
495
+ | Request | Today |
496
+ | --- | --- |
497
+ | Two aspect ratios from one script | One `output` per script; a second `output` line replaces the first. Write one script per format. |
498
+ | Transparent video (alpha) | `alpha: true` is a compile error; renders are H.264 MP4. |
499
+ | Multicam angle switching | No syntax. Cut between the angle clips with time blocks. |
500
+ | Mixed frame rates | A timeline has one frame rate, the output `fps`. |
501
+ | `repeat n { … }` and named tracks | Not in the language. Use a function or timeline and `use` it per repeat. |
502
+ | Formats other than H.264 MP4 | `format:` and `codec:` are accepted, but every render is H.264 MP4 today. |
503
+ | Rectangle and ellipse primitives | Use `solid(…)` with `width`, `height`, and `radius:` (a circle when the solid is square and `radius` is half its width), or `corners:` for a four-point shape. |
504
+
505
+ ## Compiles, but does nothing
506
+
507
+ These pass validation and are silently ignored. Avoid them:
508
+
509
+ - A filter parameter the filter doesn't have (`filter "saturation", value: 1.2`); use the table above.
510
+ - An easing that isn't in the list (`easing: "easeOutElastic"` runs linear).
511
+ - `animate: { keyframes: [...] }`: the points are dropped. For a path with
512
+ more than two points, list two-point segments with absolute `start` / `end`:
513
+
514
+ ```vidscript
515
+ [0s .. 2s] = text "Path", animate: [{ type: "slideX", from: { x: 0 }, to: { x: 300 }, start: 0, end: 1 }, { type: "slideX", from: { x: 300 }, to: { x: 120 }, start: 1, end: 2 }]
516
+ ```
517
+
518
+ - `Overlay(…, radius: …)`: use a placement with `blend: "normal"` (Picture-in-picture above).
@@ -11,33 +11,33 @@ SceneRok skills are shared across several runtimes. **Detect where you are runni
11
11
  | ChatGPT Actions, OAuth to SceneRok, `openaiFileIdRefs` | **ChatGPT GPT** | `validateVidscript` → `renderVideo`; import uploads first |
12
12
  | Gemini Gem or Grok Skill; may lack HTTP tools | **Consumer chat (limited)** | Compose VidScript; user renders elsewhere if no API |
13
13
  | `scenerok-mcp` tools (`scenerok_validate_vidscript`, `scenerok_render_video`, …) or remote `https://mcp.scenerok.com/mcp` | **MCP host** | MCP tools instead of raw CLI |
14
- | Reading bundled `SKILL.md` only | **Router-only** | Fetch remote docs from index (next section) |
14
+ | Only `SKILL.md` text available, no `references/` files | **Router-only** | Fetch references from the base URL below |
15
15
 
16
16
  When unsure, ask one clarifying question: *"Are we in the SceneRok editor, your terminal, or a chat app like ChatGPT?"*
17
17
 
18
- ## Remote doc loading (router-only / desktop)
18
+ ## Loading references
19
19
 
20
- 1. Fetch `https://scenerok.com/.well-known/agent-skills/index.json`
21
- 2. **Always load** `vidscript-strict.md` and `intent-router.md`
22
- 3. Load `environments.md` (this file) on first turn
23
- 4. Conditionally load catalogs per `intent-router.md`
24
- 5. Offline: `scenerok skills sync` then read local skill directory
25
- 6. Freshness: `scenerok skills check` · update: `scenerok skills update`
26
-
27
- Base URL: `https://scenerok.com/.well-known/agent-skills/{filename}`
20
+ - **Installed skill** (`scenerok skills install <platform>`, or the copy inside the `@scenerok/cli` or `@scenerok/sdk` npm package): read `references/<file>` next to `SKILL.md`.
21
+ - **Router-only:** fetch `https://scenerok.com/.well-known/agent-skills/<file>`; the list is at `https://scenerok.com/.well-known/agent-skills/index.json`.
22
+ - **Freshness:** `scenerok skills check` · refresh: `scenerok skills update`
28
23
 
29
24
  ## Capability matrix
30
25
 
31
- | Capability | Web editor | CLI / MCP | ChatGPT Action | Gemini / Grok (no API) |
32
- | --- | --- | --- | --- | --- |
33
- | Edit VidScript in place | `patch_vidscript` | Write `.vid` / `.ts` file | Return script to user | Return script |
34
- | Validate | Editor API (VidScript) | `scenerok validate` (`.vid` or `.ts`) | `validateVidscript` (+ `language` for SDK) | Explain only |
35
- | Render | Editor UI / API (VidScript) | `scenerok render` (`.vid` or `.ts`) | `renderVideo` + poll | Instruct user |
36
- | Shell / ffprobe / sips | No | Yes | No | No |
37
- | Browse product URL | If browser tool | If browser tool | Limited | Limited |
38
- | User asset library | Yes (logged in) | Via URLs / uploads | Via `importOpenAIConversationFiles` | Ask for public URLs |
39
- | Ask brand questions | Yes (chat) | Yes (terminal) | Yes (chat) | Yes (chat) |
40
- | OAuth / API tokens | Session cookie | `scenerok auth login` | OAuth per user | Manual token fallback |
26
+ | Capability | Web editor | CLI | MCP | ChatGPT Action | Gemini / Grok (no API) |
27
+ | --- | --- | --- | --- | --- | --- |
28
+ | Edit VidScript in place | `patch_vidscript` | Write `.vid` / `.ts` file | Pass the script as a string | Return script to user | Return script |
29
+ | Validate | Editor API (VidScript) | `scenerok validate` (`.vid` or `.ts`) | `scenerok_validate_vidscript` (`language: "typescript"` for SDK) | `validateVidscript` (+ `language` for SDK) | Explain only |
30
+ | Deep-validate with generated assets | No | `--generate-assets --report-json` | No | No | No |
31
+ | Render | Editor UI / API (VidScript) | `scenerok render` (`.vid` or `.ts`) | `scenerok_render_video` + `scenerok_check_render_status` | `renderVideo` + poll | Instruct user |
32
+ | Check the rendered MP4 | No | `scenerok lint-video` | No | No | No |
33
+ | Local files (`input "./x.mp4"`, `file: "look.cube"`, `import "./brand.vid"`) | No; library URLs | Yes | No; public URLs only | No; imported files | No |
34
+ | Shell / ffprobe / sips | No | Yes | Only if the host has a shell | No | No |
35
+ | Browse product URL | If browser tool | If browser tool | If browser tool | Limited | Limited |
36
+ | User asset library | Yes (logged in) | Via URLs / uploads | Via URLs | Via `importOpenAIConversationFiles` | Ask for public URLs |
37
+ | Ask brand questions | Yes (chat) | Yes (terminal) | Yes | Yes (chat) | Yes (chat) |
38
+ | OAuth / API tokens | Session cookie | `scenerok auth login` | Configured in the MCP server | OAuth per user | Manual token fallback |
39
+
40
+ Where deep validation or the MP4 check isn't available, validate, render, and review the result with the user instead.
41
41
 
42
42
  ## Environment-specific rules
43
43
 
@@ -49,10 +49,9 @@ Base URL: `https://scenerok.com/.well-known/agent-skills/{filename}`
49
49
 
50
50
  ### Desktop agent (CLI / Cursor / Claude Code / Codex)
51
51
  - Run `scenerok validate` before every render (`.vid` or `.ts`).
52
- - Prefer `@scenerok/sdk` for agent-authored complex scenes; VidScript for short human edits.
52
+ - Write VidScript by default and for every editing action; use `@scenerok/sdk` only for loops or computed timing.
53
53
  - Probe local assets with `loadVideo` / media stdlib, or `file` / `ffprobe` / `sips` / `identify` before placement.
54
- - Install router: `scenerok skills install <platform> [--sync]`
55
- - Full offline bundle: `scenerok skills sync`
54
+ - Install the skill: `scenerok skills install <platform>` (claude, codex, cursor, opencode, aider)
56
55
 
57
56
  ### ChatGPT GPT
58
57
  - Load `consumer-chat-apps.md` and `chatgpt-gpt-instructions.md` for OAuth and file import.
@@ -69,5 +68,5 @@ Base URL: `https://scenerok.com/.well-known/agent-skills/{filename}`
69
68
  | --- | --- | --- |
70
69
  | `scenerok render ...` | Use editor render | Use `renderVideo` action |
71
70
  | `scenerok auth login` | N/A (session) | OAuth instead |
72
- | Fetch remote skill URLs | Already inlined locally | Fetch if network tool exists |
71
+ | Fetch reference URLs | Already inlined locally | Fetch if a network tool exists |
73
72
  | Local file paths in VidScript | Use library HTTPS URLs | Use imported asset URLs |
@@ -1,8 +1,6 @@
1
1
  # SceneRok Intent Router
2
2
 
3
- Load after **`vidscript-strict.md`** and **`environments.md`** when the user wants a video.
4
-
5
- **Remote base:** `https://scenerok.com/.well-known/agent-skills/`
3
+ Pick the workflow for a video request, then read only the references it lists.
6
4
 
7
5
  ## Step 0 — Detect environment
8
6
 
@@ -23,6 +21,7 @@ Defaults unless the user says otherwise:
23
21
  | Product URL + CTA + lifestyle b-roll | Product promo |
24
22
  | Tweet / Reddit / Spotify / follow card | Social proof card |
25
23
  | Multiple clips + cross-fades / whips | Scene montage |
24
+ | User's own footage to cut, trim, speed up, layer, mask, grade, or mix | Edit existing footage |
26
25
  | Voiceover explainer | Narrated explainer |
27
26
  | PR / code walk / terminal | Code demo |
28
27
 
@@ -33,6 +32,7 @@ Defaults unless the user says otherwise:
33
32
  | **Product promo** | `ai-models.md`, `text-styles.md`, `music.md`, `workflow.md`, `brand-setup.md` (+ `sdk-guide.md` if TS) | `xai.imagine` / `await xai.genVideo(...)`, `motion.popIn` / `fadeIn`, `eleven.music` |
34
33
  | **Motion graphic** | `animations-picker.md` (+ `animations-catalog.md` if needed) | `animations.*` (VidScript HyperFrames; prefer VidScript for catalog blocks) |
35
34
  | **Social proof card** | `animations-picker.md` | `animations.x_post`, `reddit_post`, … (VidScript) |
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
38
  | **Code demo / PR** | `animations-picker.md` (code rows) | `code_typing`, `code_diff` (VidScript) |
@@ -44,7 +44,7 @@ Defaults unless the user says otherwise:
44
44
  ## Staged routing
45
45
 
46
46
  1. **Classify** — workflow from table above
47
- 2. **Choose authoring** — short/human → VidScript; complex/agent (loops, computed timing, multi-clip arithmetic) → `@scenerok/sdk` (`sdk-guide.md`)
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`, …)
50
50
  5. **Brand** — if promo and missing colors/logo, one message per `brand-setup.md`
@@ -57,21 +57,11 @@ Defaults unless the user says otherwise:
57
57
 
58
58
  - `animations.<fn>(...)` over hand-authored HTML when a block fits
59
59
  - Text/headline treatments: try `animations.text_*` (text-anim family) before `motion.*` primitives or custom HTML
60
- - `tr.<fn>()` for scene transitions; `filter "…"` for single-clip VFX
60
+ - `tr.<fn>()` for scene transitions; `filter "…"` for grades and VFX
61
61
  - No matching block → `text` + `animate: motion.<primitive>`, or `animations.custom_html(...)` to author your own HTML/CSS + GSAP / Three.js / any seekable JS animation lib (full author contract + minimal template in `animations-catalog.md` → "Custom HTML")
62
62
  - Unverified blocks (`verified: no`) ship demo content — prefer verified families
63
63
  - Complex procedural timelines → TypeScript `@scenerok/sdk` (`sdk-guide.md`) instead of exploding VidScript
64
64
 
65
- ## Conditional loading
66
-
67
- - Skip `animations-catalog.md` unless you need full param tables
68
- - Skip `transitions-catalog.md` unless multiple clips or explicit transition ask
69
- - Always `vidscript-strict.md` before VidScript composition
70
- - Load `sdk-guide.md` before TypeScript SDK composition (scheduling, `t.grid`, AI plugin imports, `async main`)
71
- - Load `media-stdlib.md` when probing durations, grabbing frames, `loadVideo`, or layout-fitting assets
72
- - Load `ai-models.md` / `tts.md` / `music.md` for generative media in **either** language (examples cover both)
73
- - `vidscript-guide.md` only for grammar questions
74
-
75
65
  ## Reference
76
66
 
77
67
  - Grammar: `vidscript-guide.md` or https://scenerok.com/.well-known/agent-skills/vidscript-guide.md
@@ -35,10 +35,10 @@ import media from "@scenerok/media"
35
35
  input hero = "./hero.mp4"
36
36
  let d = media.length(hero)
37
37
  let loudness = media.loudness(hero)
38
- let thumb = media.grabFrame(hero, d / 2)
38
+ let thumb = media.grabFrame(hero, d / 2) # data URL of the middle frame, to look at
39
39
 
40
40
  [0s .. d] = video hero
41
- [0s .. 2s] = video thumb, width: 400
41
+ [d .. d + 2s] = hero.Trim(d / 2, d / 2 + 1s).Speed(0) # hold the middle frame for 2 s
42
42
  ```
43
43
 
44
44
  `media.length` returns a time value usable in arithmetic and `[start .. end]` ranges.
@@ -63,4 +63,3 @@ let thumb = media.grabFrame(hero, d / 2)
63
63
  | `fitContain` / `fitCover` | yes | `media.fitContain` | `{ width, height, x, y }` |
64
64
  | `parseTime` / `timecode` / `beats` / `clampTime` | yes | yes | time helpers |
65
65
 
66
- Implementation: `src/lib/media/stdlib/`. VidScript plugin: `src/plugins/media/`.