@scenerok/cli 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/plugin.d.ts +89 -0
- package/dist/commands/plugin.d.ts.map +1 -0
- package/dist/commands/plugin.js +651 -0
- package/dist/index.js +7 -0
- package/dist/lib/api.d.ts +150 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +34 -0
- package/dist/lib/plugin-checks.bundle.mjs +5206 -0
- package/dist/lib/plugin-checks.d.ts +97 -0
- package/dist/lib/plugin-checks.d.ts.map +1 -0
- package/dist/lib/plugin-checks.js +215 -0
- package/dist/lib/plugin-scaffold.d.ts +29 -0
- package/dist/lib/plugin-scaffold.d.ts.map +1 -0
- package/dist/lib/plugin-scaffold.js +194 -0
- package/package.json +7 -5
- package/skills/scenerok/SKILL.md +22 -2
- package/skills/scenerok/references/catalog-index.json +1 -1
- package/skills/scenerok/references/editing-actions.md +43 -1
- package/skills/scenerok/references/sdk-guide.md +149 -49
- package/skills/scenerok/references/vidscript-guide.md +49 -13
- package/skills/scenerok/references/vidscript-strict.md +1 -1
|
@@ -260,6 +260,18 @@ input b = "shot-b.mp4"
|
|
|
260
260
|
output to "video.mp4", resolution: "1920x1080", fps: 24
|
|
261
261
|
```
|
|
262
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
|
+
|
|
263
275
|
### Transitions and handles
|
|
264
276
|
|
|
265
277
|
Transitions come from `@scenerok/transitions` (full list in
|
|
@@ -295,6 +307,24 @@ input frames = "renders/shot.%04d.png", rate: 12, first: 1, count: 24
|
|
|
295
307
|
`Trim`, `Speed`, and `Loop` work on it. For cloud renders the pattern must be
|
|
296
308
|
an `https` URL pattern.
|
|
297
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
|
+
|
|
298
328
|
## Sound
|
|
299
329
|
|
|
300
330
|
Every sound is an `audio` line with its own window, so J- and L-cuts need no
|
|
@@ -488,13 +518,25 @@ output to "master.mp4", resolution: "1920x1080", fps: 25, codec: "h264", bitrate
|
|
|
488
518
|
`start_timecode` is `HH:MM:SS:FF` and its frame field must be below `fps`.
|
|
489
519
|
`burn_in: "timecode"` draws the running timecode on the picture.
|
|
490
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
|
+
|
|
491
533
|
## What VidScript can't do yet
|
|
492
534
|
|
|
493
535
|
Don't try to work around these. Tell the user when a request needs one.
|
|
494
536
|
|
|
495
537
|
| Request | Today |
|
|
496
538
|
| --- | --- |
|
|
497
|
-
| Two aspect ratios from one script |
|
|
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. |
|
|
498
540
|
| Transparent video (alpha) | `alpha: true` is a compile error; renders are H.264 MP4. |
|
|
499
541
|
| Multicam angle switching | No syntax. Cut between the angle clips with time blocks. |
|
|
500
542
|
| Mixed frame rates | A timeline has one frame rate, the output `fps`. |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use **TypeScript + `@scenerok/sdk`** when the scene needs loops, branches, arithmetic timing, or data-driven composition. Keep **VidScript** for short human-authored promos and templates.
|
|
4
4
|
|
|
5
|
-
Both paths emit the same `IRTimeline
|
|
5
|
+
Both paths emit the same `IRTimeline`, and the SDK follows VidScript's rules: a value placed with the same properties lowers to the same IR. Preview and render (Pass 2) are unchanged.
|
|
6
6
|
|
|
7
7
|
## When to choose which
|
|
8
8
|
|
|
@@ -10,6 +10,7 @@ Both paths emit the same `IRTimeline`. Preview and render (Pass 2) are unchanged
|
|
|
10
10
|
|--------|----------|
|
|
11
11
|
| Short promo, human edit, template placeholders | VidScript (`.vid`) |
|
|
12
12
|
| Loop over clips, computed `start`/`end`, agent-authored complex timeline | TypeScript + `@scenerok/sdk` (`.ts`) |
|
|
13
|
+
| Mostly an edit, with one data-driven part | VidScript that imports a `.ts` module ([below](#use-typescript-from-vidscript)) |
|
|
13
14
|
|
|
14
15
|
## Schedule syntax
|
|
15
16
|
|
|
@@ -35,53 +36,120 @@ export default async function main() {
|
|
|
35
36
|
|------|---------|
|
|
36
37
|
| `t[start][end] = value` | Preferred dual-index schedule |
|
|
37
38
|
| `t.range(start, end, value)` | Explicit call (no Proxy) |
|
|
38
|
-
| `t['-'] = value` / `t.push(value)` | Auto-append at playhead |
|
|
39
|
+
| `t['-'] = value` / `t.push(value, duration?)` | Auto-append at the playhead |
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
Times are numbers (seconds), `s(3)` / `ms(300)`, `t.frames(48)`, or strings: `'3s'`, `'300ms'`, `'1:30'`, `'48f'` (frames of the timeline's fps), `'01:00:00:00'` (timecode at the timeline's fps, or `'01:00:00:00@24'`). Full JS arithmetic works. Like VidScript, every window snaps to the timeline's frame grid; `fps: 23.976` and the other NTSC rates are exact (`ir.timeBase`).
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
A video or audio value whose length is known ends when it stops playing, even inside a longer window. Without a known length it fills the window.
|
|
43
44
|
|
|
44
|
-
Use `loadVideo(path)` / `loadAudio(path)` to probe
|
|
45
|
+
Use `loadVideo(path)` / `loadAudio(path)` to probe the length through the media stdlib, or pass `duration` when you know it:
|
|
45
46
|
|
|
46
47
|
```ts
|
|
47
48
|
const clip = await loadVideo('./hero.mp4')
|
|
48
49
|
t[0][clip.duration] = clip
|
|
50
|
+
t.push(video('./b.mp4', { duration: 4 }))
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
## Values
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
| Factory | VidScript | Notes |
|
|
56
|
+
|---------|-----------|-------|
|
|
57
|
+
| `video(src, opts)` / `loadVideo` | `input a = "a.mp4"` | `rate:` interprets the footage at a frame rate |
|
|
58
|
+
| `imageSequence('shot.%04d.png', { count, rate?, first? })` | `input frames = "shot.%04d.png", count: …` | Plays `count / rate` seconds |
|
|
59
|
+
| `image(src, opts)` | an image input | Fills its window; 3 s when pushed |
|
|
60
|
+
| `text(content, opts)` | `text "…"` | `stagger: 'word' \| 'char'` for `typewriter()` |
|
|
61
|
+
| `audio(src, opts)` / `loadAudio` | `audio x` | `trimStart`, `pan`, `duckUnder`, `loudness: 'ebu-r128'`, `animate: volumeRamp(…)` |
|
|
62
|
+
| `solid(color, opts)` | `solid("#101820")` | Full frame unless sized; with `radius`/`corners` it is a shape matte |
|
|
63
|
+
| `gap()` | `gap()` | `t.push(gap(), 2)` moves the playhead 2 s |
|
|
64
|
+
| `marker(name, metadata?)` | `marker("act 2")` | `t[4][4] = marker('act 2')`; timeline data, never drawn |
|
|
65
|
+
| `filter(name, params)` / `shader(glsl, params)` | `filter "glow"` | Grades the frame (inside a nested timeline: only that timeline) |
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
Values are immutable. Methods return a copy, so one clip can be cut many ways:
|
|
56
68
|
|
|
57
69
|
```ts
|
|
58
|
-
|
|
70
|
+
const beach = video('./beach.mp4', { duration: 60 })
|
|
71
|
+
t.push(beach.trim(10, 20).trim(2, 4)) // source 12–14 s: a second trim is relative to the first
|
|
72
|
+
t.push(beach.trim(30, 36).speed(1.5).resize(960, 540))
|
|
73
|
+
t.push(beach.trim(40, 44).crop(0, 0, 1280, 720).filter('sepia', { intensity: 0.6 }))
|
|
74
|
+
```
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
| Method | On | Meaning |
|
|
77
|
+
|--------|----|---------|
|
|
78
|
+
| `trim(start, end?)` | video, image, audio | Keep a source range |
|
|
79
|
+
| `speed(x)` / `loop(n)` | video | Retiming; the value's `duration` follows |
|
|
80
|
+
| `resize(w, h)` / `opacity(x)` | video, image, timeline | Drawn size, opacity |
|
|
81
|
+
| `filter(name, params)` | video, image, timeline | Grade only this value (pointwise colour filters on clips) |
|
|
82
|
+
| `crop(x, y, w, h)` | video, image, timeline | Source pixels from the top-left; a second crop is relative |
|
|
83
|
+
| `mask(matte, { mode })` | video, image, timeline | `alpha` (default), `luma`, `inverted`; the matte is any picture |
|
|
84
|
+
| `animate(…)` / `with({ … })` | all pictures | Add animations / merge options |
|
|
85
|
+
|
|
86
|
+
## Placement properties
|
|
87
|
+
|
|
88
|
+
Every picture takes the same options as a VidScript placement:
|
|
89
|
+
|
|
90
|
+
| Option | Meaning |
|
|
91
|
+
|--------|---------|
|
|
92
|
+
| `x`, `y`, `width`, `height` | Top-left and drawn size in output pixels |
|
|
93
|
+
| `area` | A grid area; explicit `x`/`y`/`width`/`height` win |
|
|
94
|
+
| `opacity`, `rotation` | Constant opacity and rotation (degrees) over the window |
|
|
95
|
+
| `radius`, `corners` | Rounded corners; four `[x, y]` corner pins (TL → TR → BR → BL) |
|
|
96
|
+
| `blend` | `multiply`, `screen`, `overlay`, `difference`, `add`, `subtract`, `darken`, `lighten` over what is already placed in the window |
|
|
97
|
+
| `motionBlur` | `true` or `{ shutter, samples }`: blurs the value's own `animate` motion |
|
|
98
|
+
| `enabled` | `false` skips the placement |
|
|
99
|
+
| `animate` | One animation or a list |
|
|
62
100
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
```ts
|
|
102
|
+
t[0][6] = video('./city.mp4')
|
|
103
|
+
t[0][6] = video('./flare.mp4', { blend: 'screen', opacity: 0.7 })
|
|
104
|
+
t[1][4] = image('./logo.png', { width: 240, height: 240, x: 60, y: 60, radius: 120 })
|
|
105
|
+
t[0][6] = video('./portrait.mp4').mask(solid('#fff', { width: 800, height: 800, x: 560, y: 140, radius: 400 }))
|
|
106
|
+
```
|
|
68
107
|
|
|
69
|
-
|
|
70
|
-
t[0][s(3)] = text('Left column', { size: 40, area: 'left' })
|
|
108
|
+
A blend needs something below it in the window; nothing below is an error, as in VidScript.
|
|
71
109
|
|
|
72
|
-
|
|
110
|
+
## Animations
|
|
111
|
+
|
|
112
|
+
`fadeIn`, `fadeOut`, `slideY`, `slideX`, `scale`, `popIn`, `riseIn`, `swingIn`, `glitchIn`, `float`, `typewriter`, `rotate` — the same set and defaults as `@scenerok/basic-animations`. Any other type: `animation('kenBurns', { scale: 1 }, { scale: 1.1 }, 0)` (a duration of 0 spans the window). Gain automation on audio: `volumeRamp(0, 1, '2s')`.
|
|
113
|
+
|
|
114
|
+
## Nested timelines and groups
|
|
115
|
+
|
|
116
|
+
A timeline is a value. Placed bare, its items join the parent shifted to the window start — like VidScript `use`. A nested timeline without its own size takes its parent's canvas.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
function card(title: string) {
|
|
120
|
+
const c = timeline({ name: 'card' })
|
|
121
|
+
c[0][3] = solid('#101820', { height: 200, y: 880, opacity: 0.85 })
|
|
122
|
+
c[0][3] = text(title, { size: 48, y: '88%' })
|
|
123
|
+
return c
|
|
73
124
|
}
|
|
125
|
+
|
|
126
|
+
t[2][5] = card('Konkan Day 1') // flat
|
|
127
|
+
t[6][9] = card('Day 2').opacity(0.8).place({ x: 40, y: -120 }) // one group
|
|
128
|
+
t[9][12] = card('Day 3').filter('grayscale') // the grade covers only the card
|
|
74
129
|
```
|
|
75
130
|
|
|
76
|
-
|
|
131
|
+
`place(opts)`, `opacity`, `resize`, `filter`, `crop`, and `mask` draw the timeline as one `IRGroup`, as does a frame `filter` inside it. A group lasts for its window; a longer body is cut off. Transitions cannot be inside a group.
|
|
77
132
|
|
|
78
|
-
|
|
133
|
+
## Grid areas
|
|
79
134
|
|
|
80
|
-
Same
|
|
135
|
+
Same CSS-grid regions as VidScript. Declare a grid on the timeline, then place with `area:` (text centres in the cell; videos, images, and solids fill it):
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
139
|
+
t.grid({
|
|
140
|
+
rows: '20% 1fr 30%',
|
|
141
|
+
cols: '1fr 1fr',
|
|
142
|
+
areas: ['title title', 'left right', 'caption caption'],
|
|
143
|
+
})
|
|
144
|
+
t[0][s(3)] = text('BIG SALE', { size: 72, area: 'title' })
|
|
145
|
+
t[0][s(3)] = image('./shoe.png', { area: 'left' })
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Without `t.grid(...)`, `area: 'title' | 'stage' | 'lower-third' | 'caption'` uses the built-in social-video preset (portrait vs landscape).
|
|
81
149
|
|
|
82
150
|
## Transitions
|
|
83
151
|
|
|
84
|
-
Same `@scenerok/transitions` package as VidScript. Schedule a transition window between abutting clips; `toIR()` binds `fromId` / `toId
|
|
152
|
+
Same `@scenerok/transitions` package as VidScript. Schedule a transition window between abutting clips; `toIR()` binds `fromId` / `toId`:
|
|
85
153
|
|
|
86
154
|
```ts
|
|
87
155
|
import { timeline, video } from '@scenerok/sdk'
|
|
@@ -89,28 +157,67 @@ import tr from '@scenerok/transitions'
|
|
|
89
157
|
|
|
90
158
|
export default function main() {
|
|
91
159
|
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
92
|
-
|
|
93
160
|
t[0][5] = video('./a.mp4', { duration: 5 })
|
|
94
|
-
t[5][5.6] = tr.dissolve()
|
|
161
|
+
t[5][5.6] = tr.dissolve({ inOffset: 0.3, outOffset: 0.3 })
|
|
95
162
|
t[5.6][10] = video('./b.mp4', { duration: 4.4 })
|
|
96
|
-
|
|
97
|
-
// Or auto-append with catalog default (0.6s):
|
|
98
|
-
// t.push(tr.push({ direction: 'right' }))
|
|
99
|
-
|
|
100
163
|
return t.toIR()
|
|
101
164
|
}
|
|
102
165
|
```
|
|
103
166
|
|
|
104
|
-
Common options: `direction`, `ease`, `strength`, `mode` (`roll` | `freeze`). Full catalog: `transitions-catalog.md`.
|
|
167
|
+
Common options: `direction`, `ease`, `strength`, `mode` (`roll` | `freeze`), and the handles `inOffset` / `outOffset` (source seconds read past each cut). Full catalog: `transitions-catalog.md`.
|
|
168
|
+
|
|
169
|
+
## Output
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
timeline({
|
|
173
|
+
width: 1920, height: 1080, fps: 23.976,
|
|
174
|
+
output: { path: 'reel.mp4', startTimecode: '01:00:00:00', burnIn: 'timecode' },
|
|
175
|
+
})
|
|
176
|
+
```
|
|
105
177
|
|
|
106
178
|
## Entry contract
|
|
107
179
|
|
|
108
|
-
Export `default async function main()` (or sync `main`) that returns either
|
|
180
|
+
Export `default async function main()` (or sync `main`) that returns either `t.toIR()`, the timeline, or an `IRTimeline` object. `async main` lets you `await loadVideo(...)`, `await getVideoLength(...)`, or `await grabFrame(...)`.
|
|
181
|
+
|
|
182
|
+
## Use TypeScript from VidScript
|
|
183
|
+
|
|
184
|
+
A `.vid` can import a TypeScript module and place what it exports. Export values instead of (or beside) a default scene:
|
|
109
185
|
|
|
110
|
-
|
|
111
|
-
|
|
186
|
+
```ts
|
|
187
|
+
// kit.ts
|
|
188
|
+
import { timeline, video, text, audio, solid } from '@scenerok/sdk'
|
|
189
|
+
|
|
190
|
+
export const accent = '#ff5500'
|
|
191
|
+
export const hero = video('beach.mp4', { duration: 12 }).trim(2, 8)
|
|
192
|
+
export const title = text('Konkan Day 1', { size: 72, area: 'title' })
|
|
193
|
+
export const bed = audio('bed.mp3', { duration: 30, volume: 0.4 })
|
|
194
|
+
|
|
195
|
+
export function lowerThird(name: string, opts: { color?: string } = {}) {
|
|
196
|
+
const t = timeline()
|
|
197
|
+
t[0][3] = solid(opts.color ?? accent, { height: 160, y: 880 })
|
|
198
|
+
t[0][3] = text(name, { size: 48, y: '88%' })
|
|
199
|
+
return t
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```vidscript fragment
|
|
204
|
+
import kit from "./kit.ts"
|
|
112
205
|
|
|
113
|
-
|
|
206
|
+
[0s .. 6s] = kit.hero
|
|
207
|
+
[1s .. 4s] = kit.title
|
|
208
|
+
[0s .. 10s] = audio kit.bed
|
|
209
|
+
[3s .. 6s] = kit.lowerThird("Nidheesh", color: kit.accent)
|
|
210
|
+
|
|
211
|
+
output to "reel.mp4", resolution: "1920x1080"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- `timeline()` without a size takes the importing script's output size and fps.
|
|
215
|
+
- Named arguments reach a function as one trailing options object; `use kit.lowerThird at 6s .. 9s with { color: "#0a8" }` calls `lowerThird({ color: "#0a8" })`.
|
|
216
|
+
- A default-exported scene still works: `use kit at 0s .. 0s with {}`, and `kit.duration` is its length.
|
|
217
|
+
- Filters, shaders, and transitions are not exportable values; use them inside an exported timeline.
|
|
218
|
+
- The module is one file (the CLI uploads it next to the `.vid`); top-level `await` is not supported, but an exported promise is awaited.
|
|
219
|
+
|
|
220
|
+
See `vidscript-guide.md` → Import TypeScript modules.
|
|
114
221
|
|
|
115
222
|
## Validate / render
|
|
116
223
|
|
|
@@ -119,16 +226,6 @@ scenerok validate scene.ts
|
|
|
119
226
|
scenerok render scene.ts --watch
|
|
120
227
|
```
|
|
121
228
|
|
|
122
|
-
You can also **include** an SDK scene from VidScript:
|
|
123
|
-
|
|
124
|
-
```vidscript fragment
|
|
125
|
-
import intro from "./intro.ts"
|
|
126
|
-
use intro at 0s .. 0s with {}
|
|
127
|
-
output to "reel.mp4"
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
See `vidscript-guide.md` → Include TypeScript / JavaScript scenes.
|
|
131
|
-
|
|
132
229
|
MCP:
|
|
133
230
|
|
|
134
231
|
- `scenerok_validate_vidscript` with `language: "typescript"`
|
|
@@ -137,7 +234,7 @@ MCP:
|
|
|
137
234
|
|
|
138
235
|
## AI plugins (xAI / Cloudflare / ElevenLabs)
|
|
139
236
|
|
|
140
|
-
Same packages as VidScript. Import them in TypeScript; the SceneRok compile host runs the real plugins and returns SDK `video` / `audio`
|
|
237
|
+
Same packages as VidScript. Import them in TypeScript; the SceneRok compile host runs the real plugins and returns SDK `video` / `audio` values (a generated clip with sound brings its sound along; set its level with `volume`):
|
|
141
238
|
|
|
142
239
|
```ts
|
|
143
240
|
import { timeline } from '@scenerok/sdk'
|
|
@@ -159,7 +256,7 @@ export default async function main() {
|
|
|
159
256
|
t[0][clip.duration] = clip
|
|
160
257
|
t[clip.duration][clip.duration + still.duration] = still
|
|
161
258
|
t.push(vo)
|
|
162
|
-
t.push(bed)
|
|
259
|
+
t.push(bed.with({ volume: 0.3, duckUnder: vo }))
|
|
163
260
|
return t.toIR()
|
|
164
261
|
}
|
|
165
262
|
```
|
|
@@ -172,10 +269,13 @@ export default async function main() {
|
|
|
172
269
|
|
|
173
270
|
Requires the same env secrets as VidScript (`XAI_API_KEY`, `ELEVENLABS_API_KEY`, Cloudflare AI Gateway credentials). Generation is billed on render the same way as VidScript plugins.
|
|
174
271
|
|
|
175
|
-
##
|
|
272
|
+
## VidScript only
|
|
273
|
+
|
|
274
|
+
- `filter "lut", file: "look.cube"` and `captions("talk.srt")` read files at compile time.
|
|
275
|
+
- Several `output` statements (two canvases) and `allow_offline: true`.
|
|
276
|
+
- `--generate-assets` (SDK scripts generate assets during `scenerok render` when plugins are called).
|
|
176
277
|
|
|
177
|
-
|
|
178
|
-
- `--generate-assets` remains VidScript-only (SDK scripts generate assets during `scenerok render` when plugins are called)
|
|
278
|
+
Import the TypeScript part into a `.vid` when a scene needs these.
|
|
179
279
|
|
|
180
280
|
## Media stdlib
|
|
181
281
|
|
|
@@ -70,7 +70,7 @@ input frames = "shots/shot.%04d.png", rate: 12, first: 1, count: 24
|
|
|
70
70
|
[0s .. 2s] = frames
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
Place it by name like a video; `Trim`, `Speed`, and `Loop` work on it. `
|
|
73
|
+
Place it by name like a video; `Trim`, `Speed`, and `Loop` work on it. `first:` and `count:` on a plain file are errors. `rate:` on a plain video file declares the footage's frame rate for a timeline at another rate (`input clip = "clip.mp4", rate: 23.976` in a 30 fps output; each output frame shows the source frame its time falls in). It is an error on audio or a still, never auto-applied from a probe, and a probed rate more than 0.1% off the declared one warns. On the render workers the pattern must be an `https` URL pattern (or a path on the worker).
|
|
74
74
|
|
|
75
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.
|
|
76
76
|
|
|
@@ -114,6 +114,8 @@ output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
|
114
114
|
output to "reel.mp4", resolution: "720x1280", format: "mp4", codec: "h264", bitrate: "5M", background: "#0D0D0D"
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
Several `output` statements make several canvases (`compileVidscript` returns one IR each in `irs`; `ir` is the first). Each output is the whole script lowered against its own `resolution`, `fps`, and `background`, so percent positions and `grid` areas resolve per canvas. Every output needs its own path (two with the same path is a compile error). Where it renders: the headless renderer (`renderVidscriptOutputsToMP4`, local scripts) writes one file per output. Where it errors: the render queue (the web app, the CLI, and the API) delivers one artifact per render, so a script with more than one `output` is rejected with `N outputs are rendered … only one output per render is supported by the render queue yet` before anything is billed. Use one `output` for a hosted render.
|
|
118
|
+
|
|
117
119
|
| Option | Type | Default | Description |
|
|
118
120
|
|--------|------|---------|-------------|
|
|
119
121
|
| `resolution` | string | `"1080x1920"` | Width×Height or single number |
|
|
@@ -124,6 +126,7 @@ output to "reel.mp4", resolution: "720x1280", format: "mp4", codec: "h264", bitr
|
|
|
124
126
|
| `background` | string | `"#000000"` | Background color hex |
|
|
125
127
|
| `start_timecode` | string | none | Timecode of the first frame, `"HH:MM:SS:FF"` (non-drop-frame; the frame field must be below `fps`). Written to the mp4's timecode track |
|
|
126
128
|
| `burn_in` | string | none | `"timecode"` draws the running timecode (from `start_timecode`, else `00:00:00:00`) onto the picture |
|
|
129
|
+
| `allow_offline` | boolean | `false` | `true` renders an input whose file is missing at compile time (media probing on) as a "MEDIA OFFLINE" slate: a flat `#2b2b2b` frame labelled with the input name, over the clip's block or `Trim` length. `Resize`, `x`/`y`, `opacity` and `animate:` still apply; its sound is dropped with a warning; a transition at its seam is an error. Without it a missing local file is a compile error. Media that goes missing only at render time still fails the render |
|
|
127
130
|
| `alpha` | boolean | `false` | Needs `format: "mov", codec: "prores"` or `format: "webm", codec: "vp9"`. No render path delivers alpha yet (renders are H.264 mp4), so `alpha: true` is a compile error for now |
|
|
128
131
|
|
|
129
132
|
### Variable Assignment
|
|
@@ -228,26 +231,59 @@ let vo2 = xai.tts("Let's dive in", voice: "eve")
|
|
|
228
231
|
- Exported timelines expose `.duration` too: `use scene1 at 0s .. 0s with {}` then `[scene1.duration .. scene1.duration + 4] = ...`. A `use` also advances the playhead past its content, so a following `[-]` block never overlaps it.
|
|
229
232
|
- This replaces guessing: `[0s .. 5s] = audio xai.tts(...)` clips or overlaps when the real audio is longer. For simple back-to-back narration, consecutive `[-] = audio ...` blocks also never overlap.
|
|
230
233
|
|
|
231
|
-
##
|
|
234
|
+
## Import TypeScript modules
|
|
235
|
+
|
|
236
|
+
`import kit from "./kit.ts"` (or `.js`) runs the module with `@scenerok/sdk` and binds its exports as a namespace, like a `.vid` module. Each export is a VidScript value of the matching kind:
|
|
237
|
+
|
|
238
|
+
| TypeScript export | In VidScript |
|
|
239
|
+
| --- | --- |
|
|
240
|
+
| `video(…)`, `loadVideo(…)`, `imageSequence(…)` | a video value: `[0s .. 5s] = kit.hero`, `kit.hero.Trim(1s, 3s)` |
|
|
241
|
+
| `image(…)` | an image value |
|
|
242
|
+
| `text(…)` | a text value |
|
|
243
|
+
| `audio(…)`, `loadAudio(…)` | an audio value: `[0s .. 10s] = audio kit.bed` |
|
|
244
|
+
| `solid(…)`, `gap()`, `marker(…)` | the builtin values `solid()`, `gap()`, `marker()` return |
|
|
245
|
+
| `timeline()` | a timeline value: flat when placed bare, one group with properties or methods |
|
|
246
|
+
| a number, string, array, or plain object | that data: `color: kit.accent` |
|
|
247
|
+
| a function returning any of the above | a function: `kit.lowerThird("Konkan", color: "#f50")` |
|
|
248
|
+
| `export default` scene (`main()` returning `t.toIR()`) | `use kit at 0s .. 0s with {}`; `kit.duration` is its length |
|
|
232
249
|
|
|
233
|
-
|
|
250
|
+
```ts
|
|
251
|
+
// kit.ts
|
|
252
|
+
import { timeline, video, image, text, solid, fadeIn } from '@scenerok/sdk'
|
|
253
|
+
|
|
254
|
+
export const accent = '#ff5500'
|
|
255
|
+
export const hero = video('beach.mp4', { duration: 12 }).trim(2, 8)
|
|
256
|
+
export const logo = image('logo.png', { width: 200, height: 200, x: 40, y: 40 })
|
|
257
|
+
export const title = text('Konkan Day 1', { size: 72, area: 'title', animate: fadeIn(0.4) })
|
|
258
|
+
|
|
259
|
+
export function lowerThird(name: string, opts: { color?: string } = {}) {
|
|
260
|
+
const t = timeline()
|
|
261
|
+
t[0][3] = solid(opts.color ?? accent, { height: 160, y: 880 })
|
|
262
|
+
t[0][3] = text(name, { size: 48, y: '88%' })
|
|
263
|
+
return t
|
|
264
|
+
}
|
|
265
|
+
```
|
|
234
266
|
|
|
235
267
|
```vidscript fragment
|
|
236
|
-
import
|
|
237
|
-
import bump from "./bump.js"
|
|
268
|
+
import kit from "./kit.ts"
|
|
238
269
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
[
|
|
270
|
+
[0s .. 6s] = kit.hero
|
|
271
|
+
[0s .. 6s] = kit.logo, opacity: 0.9
|
|
272
|
+
[1s .. 4s] = kit.title
|
|
273
|
+
[3s .. 6s] = kit.lowerThird("Nidheesh", color: kit.accent), blend: "screen"
|
|
274
|
+
use kit.lowerThird at 6s .. 9s with { color: "#00aa88" }
|
|
242
275
|
|
|
243
|
-
output to "reel.mp4", resolution: "
|
|
276
|
+
output to "reel.mp4", resolution: "1920x1080"
|
|
244
277
|
```
|
|
245
278
|
|
|
246
279
|
Rules:
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
280
|
+
- A `timeline()` with no size takes this script's output size and fps, so it lays out on the same canvas.
|
|
281
|
+
- Exported values keep their own properties; placement properties override them (`kit.logo, opacity: 0.9`).
|
|
282
|
+
- Place an audio export as `audio kit.bed`. In a `[-]` block a bare `kit.bed` starts at the picture playhead, because the block's channel is decided before the module runs.
|
|
283
|
+
- Function calls pass positional arguments in order and named arguments as one trailing options object: `kit.lowerThird("A", color: "#f50")` calls `lowerThird("A", { color: "#f50" })`; `use kit.f at … with { … }` calls `f({ … })`. Arguments are data (strings, numbers, booleans, lists, objects); VidScript clips and timelines cannot be passed into TypeScript.
|
|
284
|
+
- An exported promise is awaited (`export const clip = loadVideo("hero.mp4")`). Top-level `await` is not supported.
|
|
285
|
+
- Filters, shaders, and transitions are not exportable values; use them inside an exported timeline, or as VidScript statements.
|
|
286
|
+
- Relative paths only (`./` / `../`). The module is one file: the CLI uploads it with the `.vid`, so it cannot import other local files.
|
|
251
287
|
|
|
252
288
|
## Instructions (inside time blocks)
|
|
253
289
|
|
|
@@ -237,7 +237,7 @@ input logo = "logo.png"
|
|
|
237
237
|
- A placed value lasts its own length (trimmed range ÷ speed × loops). Make the window that long.
|
|
238
238
|
- Put one video over another with `Overlay`, `Composite`, or `blend:`. Two plain clips in the same window have no defined order.
|
|
239
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).
|
|
240
|
+
- Frame counts are written `48 frame` and use the output fps (30 if `output` has none). Timecodes are `01:00:02:12@24` (`@rate` optional; non-drop-frame only). Block times snap to the output frame grid; NTSC fps (23.976, 29.97) encodes at the exact rational rate.
|
|
241
241
|
|
|
242
242
|
All editing actions (masks, speed ramps, ducking, LUTs, timecode): `editing-actions.md`.
|
|
243
243
|
|