@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.
Files changed (32) 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 +150 -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 +560 -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/scenerok/references/sdk-guide.md +301 -0
  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 +250 -67
  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/sdk-guide.md +0 -201
  31. package/skills/remote/vidscript-sample.md +0 -28
  32. package/skills/shared/SKILL.md +0 -157
@@ -0,0 +1,560 @@
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
+ Timecode literals are `HH:MM:SS:FF@rate`, non-drop-frame (`;` is an error),
264
+ FF below the rate, hours 0-23. Without `@rate` they use the literal output fps:
265
+
266
+ ```vidscript
267
+ [00:00:01:12@24 .. 00:00:03:00@24] = text "1.5 s to 3 s"
268
+ output to "video.mp4", resolution: "1920x1080", fps: 24
269
+ ```
270
+
271
+ NTSC `fps:` values (23.976, 29.97, 47.952, 59.94, 119.88) are encoded at the
272
+ exact `24000/1001` style rate, and block start/end times snap to whole frames
273
+ of the output (`[0.1s .. 0.35s]` at 30 fps is frames 3 to 11).
274
+
275
+ ### Transitions and handles
276
+
277
+ Transitions come from `@scenerok/transitions` (full list in
278
+ `transitions-catalog.md`). By default the window sits between two clips. To
279
+ dissolve across a straight cut instead, add handles: `in_offset` reads that
280
+ much of the incoming clip before its in-point, `out_offset` that much of the
281
+ outgoing clip past its out-point. The clips need that much spare source.
282
+
283
+ A transition joins plain clips only. It is a compile error when either side is
284
+ masked, cropped, motion-blurred, or a placed function; transition between the
285
+ plain clips and apply those edits away from the cut.
286
+
287
+ ```vidscript
288
+ import tr from "@scenerok/transitions"
289
+ input a = "shot-a.mp4"
290
+ input b = "shot-b.mp4"
291
+ [0s .. 4s] = a.Trim(2s, 6s)
292
+ [4s .. 8s] = b.Trim(2s, 6s)
293
+ [3.8s .. 4.2s] = tr.dissolve(in_offset: 0.2s, out_offset: 0.2s)
294
+ ```
295
+
296
+ ### Image sequences
297
+
298
+ A numbered image sequence is one input. `count:` is the number of frames;
299
+ `rate:` defaults to the output fps; `first:` is the first file number
300
+ (default 0):
301
+
302
+ ```vidscript
303
+ input frames = "renders/shot.%04d.png", rate: 12, first: 1, count: 24
304
+ [0s .. 2s] = frames
305
+ ```
306
+
307
+ `Trim`, `Speed`, and `Loop` work on it. For cloud renders the pattern must be
308
+ an `https` URL pattern.
309
+
310
+ ### Mixed frame rates
311
+
312
+ `rate:` on a plain video input says what frame rate the footage runs at, for a
313
+ timeline at another rate. Each output frame shows the source frame its time falls
314
+ in, so 23.976 footage in a 30 fps timeline repeats frames in a 2:3-like cadence
315
+ and never skips one:
316
+
317
+ ```vidscript
318
+ input clip = "clip.mp4", rate: 23.976
319
+ [0s .. 4s] = clip.Trim(1s, 5s)
320
+ output to "out.mp4", resolution: "1920x1080", fps: 30
321
+ ```
322
+
323
+ Write it only when the footage rate differs from `fps`. It is an error on audio
324
+ or a still image; `first:` and `count:` stay sequence-only. A probed rate is never
325
+ applied for you: if the probe differs from the declared rate by more than 0.1%
326
+ the compile warns and the declared rate wins. Audio is not conformed.
327
+
328
+ ## Sound
329
+
330
+ Every sound is an `audio` line with its own window, so J- and L-cuts need no
331
+ special syntax: start or end the sound before or after the picture cut.
332
+
333
+ ### A clip's own sound, volume, and fades
334
+
335
+ Video inputs are silent until you place their sound:
336
+
337
+ ```vidscript
338
+ input a = "talk-a.mp4"
339
+ input b = "talk-b.mp4"
340
+ [0s .. 4s] = a
341
+ [4s .. 8s] = b
342
+ [0s .. 4s] = audio a, fade_in: 0.3s
343
+ [3.5s .. 8s] = audio b.Trim(0s, 4.5s), volume: 0.9, fade_out: 1s
344
+ ```
345
+
346
+ The second `audio` line is a J-cut: `b`'s sound starts half a second before its
347
+ picture.
348
+
349
+ ### Duck music under a voice
350
+
351
+ `duck_under:` lowers a bed to about a third of its volume, with short ramps,
352
+ for the whole window in which the named track plays (it follows the track's
353
+ time window, not speech). To duck only while someone speaks, place their
354
+ sound in separate windows, one per line of speech. Name an `input`, or a `let`
355
+ binding of generated speech:
356
+
357
+ ```vidscript
358
+ import xai from "@scenerok/xai"
359
+ input bed = "music.mp3"
360
+ let vo = xai.tts("Here is what changed this week.", voice: "rex")
361
+ [0s .. 10s] = audio bed, volume: 0.6, duck_under: vo, fade_out: 1.5s
362
+ [1s .. 1s + vo.duration] = audio vo
363
+ ```
364
+
365
+ ### Start partway into a file, pan, and automate volume
366
+
367
+ ```vidscript
368
+ input song = "song.mp3"
369
+ input sfx = "whoosh.wav"
370
+ [0s .. 8s] = audio song, trim_start: 30, animate: { type: "volume", from: { volume: 0 }, to: { volume: 0.8 }, start: 0, end: 3 }
371
+ [2s .. 3s] = audio sfx, pan: -0.6
372
+ ```
373
+
374
+ - `trim_start:` (seconds) or `song.Trim(30s, 38s)` starts the file 30 s in.
375
+ - `pan:` runs from -1 (left) to 1 (right).
376
+ - `animate: { type: "volume", … }` changes the level over time.
377
+
378
+ ### Even loudness
379
+
380
+ ```vidscript
381
+ input voice = "raw-voiceover.wav"
382
+ [0s .. 10s] = audio voice, loudness: "ebu-r128"
383
+ ```
384
+
385
+ Generated speech and music are already normalized to broadcast loudness;
386
+ use `loudness: "ebu-r128"` for your own recordings. It is the only value.
387
+
388
+ ## Text and titles
389
+
390
+ ```vidscript
391
+ [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" }
392
+ [2.5s .. 5s] = text "NEW", size: 120, rotation: -8, anchor: "top-left", x: "8%", y: "12%"
393
+ [0s .. 8s] = captions("talk.srt", size: 40, area: "caption")
394
+ ```
395
+
396
+ - `style:` is `title` (64 px), `subtitle` (54), `caption` (36), or `default` (48).
397
+ - Text without `x`, `y`, or `area:` is centered on the frame.
398
+ - `stagger: "word"` or `"char"` reveals the text in pieces over the animation.
399
+ - Easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`,
400
+ `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeOutBack`, `spring`.
401
+ - `rotation:` turns text around its `anchor:`; a positive value turns static
402
+ text clockwise, the opposite of video. Check the preview when direction
403
+ matters.
404
+ - `captions("file.srt", …)` makes one text layer per cue from a SubRip file;
405
+ the other arguments style every cue.
406
+
407
+ For catalog text effects (word cascades, counters, kinetic type), see
408
+ `animations-picker.md`.
409
+
410
+ ## Color and finishing
411
+
412
+ `filter` without `target:` grades everything in its window. To grade one
413
+ clip, add `target:` or use the `Filter` method; inside a function it grades
414
+ only that function's picture.
415
+
416
+ ```vidscript
417
+ input a = "shot-a.mp4"
418
+ input b = "shot-b.mp4"
419
+ [0s .. 4s] = a
420
+ [0s .. 4s] = filter "sepia", intensity: 0.6, target: a
421
+ [4s .. 8s] = b.Filter("saturation", amount: 1.2)
422
+ [4s .. 8s] = filter "vignette", intensity: 0.3
423
+ ```
424
+
425
+ Filters that change each pixel on its own can target one clip: `sepia`,
426
+ `monochrome`, `contrast`, `saturation`, `brightness`, `vignette`, `premultiply`, `lut`, `cdl`.
427
+ Filters that sample neighbouring pixels (`blur`, `glitch`, `chromatic`, and the
428
+ VFX in `shaders-filters.md`) can't take `target:`. To blur only part of the
429
+ picture, put the clip and the filter in a function; the filter stays inside it:
430
+
431
+ ```vidscript
432
+ input speaker = "speaker.mp4"
433
+ fn blurred(clip: video) -> video {
434
+ [0s .. 6s] = clip
435
+ [0s .. 6s] = filter "blur", radius: 8
436
+ }
437
+ [0s .. 6s] = blurred(speaker.Trim(0s, 6s))
438
+ [0s .. 6s] = text "Sharp on top", area: "title"
439
+ ```
440
+
441
+ Each filter reads its own parameter names; any other name is ignored without
442
+ an error, so use these:
443
+
444
+ | Filter | Parameter (default) |
445
+ | --- | --- |
446
+ | `sepia`, `monochrome` | `intensity`, 0–1 |
447
+ | `vignette`, `glitch` | `intensity` (0.5) |
448
+ | `contrast`, `saturation` | `amount`, a multiplier (contrast 1.2, saturation 1.5; 1 is unchanged) |
449
+ | `brightness` | `amount`, added to each channel (0.1; 0 is unchanged) |
450
+ | `blur` | `radius` in pixels (5) |
451
+ | `chromatic` | `offset` (0.02) |
452
+ | `lut` | `file` |
453
+ | `cdl` | `slope`, `offset`, `power`, `saturation` |
454
+ | `premultiply` | none: multiplies color by alpha, for footage with straight alpha |
455
+
456
+ ### LUTs and CDL
457
+
458
+ ```vidscript
459
+ input a = "log-footage.mp4"
460
+ [0s .. 4s] = a
461
+ [0s .. 4s] = filter "lut", file: "look.cube", target: a
462
+ [0s .. 4s] = filter "cdl", slope: "1.1 1 0.9", offset: "0 0 0", power: "1 1 1", saturation: 1
463
+ ```
464
+
465
+ - `filter "lut"` applies a 3D `.cube` file next to the script (512 KB at
466
+ most, up to 33 points per axis). Use the `filter` line, not `Filter("lut")`.
467
+ - `filter "cdl"` takes ASC CDL `slope`, `offset`, `power` (three numbers each)
468
+ and `saturation`.
469
+
470
+ ### Color spaces and motion blur
471
+
472
+ ```vidscript
473
+ input a = "acescg-plate.mp4"
474
+ input logo = "logo.png"
475
+ [0s .. 3s] = video a, input_colorspace: "ACEScg", output_colorspace: "sRGB"
476
+ [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 }
477
+ ```
478
+
479
+ - Color spaces: `sRGB`, `Rec.709` (or `rec709`), `Display P3`, `ACEScg`,
480
+ `Linear`, `Gamma 2.2` (or `gamma22`).
481
+ - `motion_blur: true` (or `{ shutter, samples }`) blurs an item's own
482
+ `animate:` movement. Each sample is another render of the frame, so use
483
+ it on fast moves only. It errors on text, solids, captions, and clips inside
484
+ a transition.
485
+
486
+ ## Reuse and delivery
487
+
488
+ ### Functions, timelines, and modules
489
+
490
+ ```vidscript
491
+ input product = "product.png"
492
+ export fn card(title: string, accent: string = "#ff5500") -> video {
493
+ [0s .. 2s] = solid(accent, opacity: 0.85)
494
+ [0s .. 2s] = text title, area: "title", color: "#ffffff"
495
+ }
496
+ export timeline intro(clip: video) {
497
+ [- 2s] = clip
498
+ }
499
+ [0s .. 2s] = card("Spring drop")
500
+ [2s .. 4s] = card(title: "Free shipping", accent: "#0f766e")
501
+ use intro at 4s .. 4s with { clip: product }
502
+ ```
503
+
504
+ - Arguments are checked at the call: a missing, unknown, or wrong-kind
505
+ argument is a compile error that names it.
506
+ - `-> video` draws as one item; a `timeline` body's items join the caller.
507
+ - `import brand from "./brand.vid"` loads another file's exports
508
+ (`brand.card("Hi")`).
509
+
510
+ ### Output settings and timecode
511
+
512
+ ```vidscript
513
+ input a = "master.mp4"
514
+ [0s .. 10s] = a
515
+ output to "master.mp4", resolution: "1920x1080", fps: 25, codec: "h264", bitrate: "8M", start_timecode: "01:00:00:00", burn_in: "timecode"
516
+ ```
517
+
518
+ `start_timecode` is `HH:MM:SS:FF` and its frame field must be below `fps`.
519
+ `burn_in: "timecode"` draws the running timecode on the picture.
520
+
521
+ ### Offline media
522
+
523
+ ```vidscript
524
+ input a = "shot-a.mp4"
525
+ input b = "shot-b.mp4" # not delivered yet
526
+ [0s .. 4s] = a
527
+ [4s .. 8s] = b
528
+ output to "draft.mp4", resolution: "1920x1080", fps: 25, allow_offline: true
529
+ ```
530
+
531
+ With `allow_offline: true`, an input whose file is missing at compile time becomes an offline clip (OTIO `MissingReference`): it keeps its place and source path, lasts the block or `Trim` length, and draws as a flat `#2b2b2b` slate labelled `MEDIA OFFLINE` and the input name. `Resize`, `x`/`y`, `opacity` and `animate:` still apply. Its sound is dropped with a warning, and a transition at its seam is an error. Without the option a missing local file is a compile error. Media that disappears only at render time still fails the render.
532
+
533
+ ## What VidScript can't do yet
534
+
535
+ Don't try to work around these. Tell the user when a request needs one.
536
+
537
+ | Request | Today |
538
+ | --- | --- |
539
+ | Two aspect ratios from one script | Two `output` statements compile to two canvases, but only the local headless renderer renders both (one file per output). The render queue (web app, CLI, API) rejects a script with more than one `output` before billing, so write one script per format for a hosted render. |
540
+ | Transparent video (alpha) | `alpha: true` is a compile error; renders are H.264 MP4. |
541
+ | Multicam angle switching | No syntax. Cut between the angle clips with time blocks. |
542
+ | Mixed frame rates | A timeline has one frame rate, the output `fps`. |
543
+ | `repeat n { … }` and named tracks | Not in the language. Use a function or timeline and `use` it per repeat. |
544
+ | Formats other than H.264 MP4 | `format:` and `codec:` are accepted, but every render is H.264 MP4 today. |
545
+ | 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. |
546
+
547
+ ## Compiles, but does nothing
548
+
549
+ These pass validation and are silently ignored. Avoid them:
550
+
551
+ - A filter parameter the filter doesn't have (`filter "saturation", value: 1.2`); use the table above.
552
+ - An easing that isn't in the list (`easing: "easeOutElastic"` runs linear).
553
+ - `animate: { keyframes: [...] }`: the points are dropped. For a path with
554
+ more than two points, list two-point segments with absolute `start` / `end`:
555
+
556
+ ```vidscript
557
+ [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 }]
558
+ ```
559
+
560
+ - `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 |