@scenerok/cli 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +31 -25
- package/dist/utils/skillsManifest.d.ts +13 -3
- package/dist/utils/skillsManifest.d.ts.map +1 -1
- package/dist/utils/skillsManifest.js +35 -12
- package/examples/system/rokmilk-chocolate-promo.vid +1 -1
- package/examples/system/voice-visual-promo.vid +1 -1
- package/package.json +3 -3
- package/skills/scenerok/SKILL.md +150 -0
- package/skills/{remote → scenerok/references}/ai-models.md +1 -2
- package/skills/{remote → scenerok/references}/animations-catalog.md +139 -2
- package/skills/{remote → scenerok/references}/animations-picker.md +12 -1
- package/skills/{remote → scenerok/references}/brand-setup.md +3 -1
- package/skills/{remote → scenerok/references}/catalog-index.json +163 -19
- package/skills/scenerok/references/editing-actions.md +560 -0
- package/skills/{remote → scenerok/references}/environments.md +23 -24
- package/skills/{remote → scenerok/references}/intent-router.md +5 -15
- package/skills/{remote → scenerok/references}/media-stdlib.md +2 -3
- package/skills/{remote → scenerok/references}/music.md +2 -21
- package/skills/scenerok/references/sdk-guide.md +301 -0
- package/skills/{remote → scenerok/references}/shaders-filters.md +9 -6
- package/skills/{remote → scenerok/references}/text-styles.md +15 -35
- package/skills/{remote → scenerok/references}/transitions-catalog.md +2 -0
- package/skills/{remote → scenerok/references}/tts.md +4 -22
- package/skills/{remote → scenerok/references}/vidscript-guide.md +250 -67
- package/skills/scenerok/references/vidscript-sample.md +44 -0
- package/skills/{remote → scenerok/references}/vidscript-strict.md +41 -26
- package/skills/{remote → scenerok/references}/workflow.md +6 -7
- package/skills/remote/sdk-guide.md +0 -201
- package/skills/remote/vidscript-sample.md +0 -28
- package/skills/shared/SKILL.md +0 -157
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# SceneRok Intent Router
|
|
2
2
|
|
|
3
|
-
|
|
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** —
|
|
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
|
|
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
|
-
[
|
|
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/`.
|
|
@@ -2,34 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
`import eleven from "@elevenlabs/music"`
|
|
4
4
|
|
|
5
|
-
### VidScript
|
|
6
|
-
|
|
7
5
|
```vidscript
|
|
8
6
|
import eleven from "@elevenlabs/music"
|
|
9
7
|
let bed = eleven.music("Warm premium launch bed", duration: 15, instrumental: true)
|
|
10
8
|
[0s .. 15s] = audio bed, volume: 0.35, fade_out: 2s
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { timeline } from '@scenerok/sdk'
|
|
17
|
-
import eleven from '@elevenlabs/music'
|
|
18
|
-
|
|
19
|
-
export default async function main() {
|
|
20
|
-
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
21
|
-
const bed = await eleven.music('Warm premium launch bed', {
|
|
22
|
-
duration: 15,
|
|
23
|
-
force_instrumental: true,
|
|
24
|
-
})
|
|
25
|
-
t.push(bed.with({ volume: 0.35, fadeOut: 2 }))
|
|
26
|
-
return t.toIR()
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Use a binding (`let bed` / `const bed`) when you need volume or fades. Do not call `eleven.tts` — use `xai.tts` for voiceover.
|
|
11
|
+
Use `let bed = eleven.music(...)` then `audio bed, volume: ...` for fades. Do not call `eleven.tts`.
|
|
31
12
|
|
|
32
|
-
## Examples
|
|
13
|
+
## Examples
|
|
33
14
|
|
|
34
15
|
**Drone underscore**
|
|
35
16
|
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# @scenerok/sdk — TypeScript authoring for complex scenes
|
|
2
|
+
|
|
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
|
+
|
|
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
|
+
|
|
7
|
+
## When to choose which
|
|
8
|
+
|
|
9
|
+
| Signal | Language |
|
|
10
|
+
|--------|----------|
|
|
11
|
+
| Short promo, human edit, template placeholders | VidScript (`.vid`) |
|
|
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)) |
|
|
14
|
+
|
|
15
|
+
## Schedule syntax
|
|
16
|
+
|
|
17
|
+
JavaScript has no `t[0, end]` dual key (comma operator collapses to one index). Use dual-index assignment:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { timeline, loadVideo, text, audio, s, fadeIn } from '@scenerok/sdk'
|
|
21
|
+
|
|
22
|
+
export default async function main() {
|
|
23
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
24
|
+
const scene1 = await loadVideo('./hero.mp4')
|
|
25
|
+
|
|
26
|
+
t[0][scene1.duration] = scene1.animate(fadeIn(s(0.4)))
|
|
27
|
+
t['0s']['3s'] = text('Hello', { size: 72 })
|
|
28
|
+
t[scene1.duration][scene1.duration + s(2)] = text('Next')
|
|
29
|
+
t.push(audio('./bed.mp3', { duration: 7, volume: 0.4 }))
|
|
30
|
+
|
|
31
|
+
return t.toIR()
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Form | Meaning |
|
|
36
|
+
|------|---------|
|
|
37
|
+
| `t[start][end] = value` | Preferred dual-index schedule |
|
|
38
|
+
| `t.range(start, end, value)` | Explicit call (no Proxy) |
|
|
39
|
+
| `t['-'] = value` / `t.push(value, duration?)` | Auto-append at the playhead |
|
|
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`).
|
|
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.
|
|
44
|
+
|
|
45
|
+
Use `loadVideo(path)` / `loadAudio(path)` to probe the length through the media stdlib, or pass `duration` when you know it:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const clip = await loadVideo('./hero.mp4')
|
|
49
|
+
t[0][clip.duration] = clip
|
|
50
|
+
t.push(video('./b.mp4', { duration: 4 }))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Values
|
|
54
|
+
|
|
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) |
|
|
66
|
+
|
|
67
|
+
Values are immutable. Methods return a copy, so one clip can be cut many ways:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
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
|
+
```
|
|
75
|
+
|
|
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 |
|
|
100
|
+
|
|
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
|
+
```
|
|
107
|
+
|
|
108
|
+
A blend needs something below it in the window; nothing below is an error, as in VidScript.
|
|
109
|
+
|
|
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
|
|
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
|
|
129
|
+
```
|
|
130
|
+
|
|
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.
|
|
132
|
+
|
|
133
|
+
## Grid areas
|
|
134
|
+
|
|
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).
|
|
149
|
+
|
|
150
|
+
## Transitions
|
|
151
|
+
|
|
152
|
+
Same `@scenerok/transitions` package as VidScript. Schedule a transition window between abutting clips; `toIR()` binds `fromId` / `toId`:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { timeline, video } from '@scenerok/sdk'
|
|
156
|
+
import tr from '@scenerok/transitions'
|
|
157
|
+
|
|
158
|
+
export default function main() {
|
|
159
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
160
|
+
t[0][5] = video('./a.mp4', { duration: 5 })
|
|
161
|
+
t[5][5.6] = tr.dissolve({ inOffset: 0.3, outOffset: 0.3 })
|
|
162
|
+
t[5.6][10] = video('./b.mp4', { duration: 4.4 })
|
|
163
|
+
return t.toIR()
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
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
|
+
```
|
|
177
|
+
|
|
178
|
+
## Entry contract
|
|
179
|
+
|
|
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:
|
|
185
|
+
|
|
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"
|
|
205
|
+
|
|
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.
|
|
221
|
+
|
|
222
|
+
## Validate / render
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
scenerok validate scene.ts
|
|
226
|
+
scenerok render scene.ts --watch
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
MCP:
|
|
230
|
+
|
|
231
|
+
- `scenerok_validate_vidscript` with `language: "typescript"`
|
|
232
|
+
- `scenerok_render_video` with `language: "typescript"`
|
|
233
|
+
- `scenerok_get_sdk_guide` for this document
|
|
234
|
+
|
|
235
|
+
## AI plugins (xAI / Cloudflare / ElevenLabs)
|
|
236
|
+
|
|
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`):
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { timeline } from '@scenerok/sdk'
|
|
241
|
+
import xai from '@scenerok/xai'
|
|
242
|
+
import cf from '@scenerok/cloudflare'
|
|
243
|
+
import eleven from '@elevenlabs/music'
|
|
244
|
+
|
|
245
|
+
export default async function main() {
|
|
246
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
247
|
+
const clip = await xai.genVideo('Cinematic drone over mountains', {
|
|
248
|
+
duration: 5,
|
|
249
|
+
aspect_ratio: '9:16',
|
|
250
|
+
resolution: '720p',
|
|
251
|
+
})
|
|
252
|
+
const still = await cf.image('Product on seamless backdrop', { aspect_ratio: '9:16' })
|
|
253
|
+
const vo = await xai.tts('Welcome to SceneRok', { voice: 'eve' })
|
|
254
|
+
const bed = await eleven.music('Warm lo-fi bed', { duration: 12 })
|
|
255
|
+
|
|
256
|
+
t[0][clip.duration] = clip
|
|
257
|
+
t[clip.duration][clip.duration + still.duration] = still
|
|
258
|
+
t.push(vo)
|
|
259
|
+
t.push(bed.with({ volume: 0.3, duckUnder: vo }))
|
|
260
|
+
return t.toIR()
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
| Package | Import | Key functions |
|
|
265
|
+
|---------|--------|---------------|
|
|
266
|
+
| `@scenerok/xai` | `import xai from '@scenerok/xai'` | `imagine`, `genVideo`, `image`, `imageToVideo`, `referenceToVideo`, `extendVideo`, `tts`, `listVoices` |
|
|
267
|
+
| `@scenerok/cloudflare` | `import cf from '@scenerok/cloudflare'` | `video` / `imagine`, `image`, `imageToVideo`, `extendVideo`, `listModels` |
|
|
268
|
+
| `@elevenlabs/music` | `import eleven from '@elevenlabs/music'` | `music`, `generateMusic`, `composeMusic` |
|
|
269
|
+
|
|
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.
|
|
271
|
+
|
|
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).
|
|
277
|
+
|
|
278
|
+
Import the TypeScript part into a `.vid` when a scene needs these.
|
|
279
|
+
|
|
280
|
+
## Media stdlib
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { loadVideo, getVideoLength, grabFrame, beats } from '@scenerok/sdk'
|
|
284
|
+
|
|
285
|
+
export default async function main() {
|
|
286
|
+
const clip = await loadVideo('./hero.mp4')
|
|
287
|
+
const d = await getVideoLength('./hero.mp4')
|
|
288
|
+
const thumb = await grabFrame('./hero.mp4', d / 2)
|
|
289
|
+
// ...
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Same functions are available in VidScript via `import media from "@scenerok/media"`.
|
|
294
|
+
|
|
295
|
+
See `media-stdlib.md` and `ai-models.md` / `tts.md` / `music.md` for prompt guidance.
|
|
296
|
+
|
|
297
|
+
## Related
|
|
298
|
+
|
|
299
|
+
- VidScript grammar: `vidscript-guide.md`
|
|
300
|
+
- Intent routing: `intent-router.md`
|
|
301
|
+
- Package README: `packages/scenerok-sdk/README.md`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Built-in filters
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A `filter` line grades everything on screen during its window. Add `target: clip` (or use `clip.Filter(…)`) to grade one clip; see `editing-actions.md` → Color and finishing for which filters can target a clip, plus LUTs and CDL.
|
|
6
6
|
|
|
7
7
|
```vidscript
|
|
8
8
|
[0s .. 5s] = filter "vignette", intensity: 0.4
|
|
@@ -23,11 +23,12 @@ Apply on a time block with the clip active:
|
|
|
23
23
|
|
|
24
24
|
Use filters as optional finishing tools — do not add by default.
|
|
25
25
|
|
|
26
|
-
## VFX filters
|
|
26
|
+
## VFX filters
|
|
27
27
|
|
|
28
|
-
Native VFX
|
|
28
|
+
Native VFX through the same `filter` keyword. They work on the whole frame in their window:
|
|
29
29
|
|
|
30
30
|
```vidscript
|
|
31
|
+
input clip = "clip.mp4"
|
|
31
32
|
[0s .. 5s] = video clip
|
|
32
33
|
[0s .. 2s] = filter "liquid_glass", intensity: 0.6
|
|
33
34
|
```
|
|
@@ -52,7 +53,7 @@ import shader glow from "./shaders/glow.glsl"
|
|
|
52
53
|
|
|
53
54
|
Also works with npm package paths under `node_modules`:
|
|
54
55
|
|
|
55
|
-
```vidscript
|
|
56
|
+
```vidscript fragment
|
|
56
57
|
import shader glow from "shader-pack/shaders/glow.glsl"
|
|
57
58
|
```
|
|
58
59
|
|
|
@@ -84,14 +85,16 @@ The compiler reads the file at compile time and embeds the GLSL into IR (`IRShad
|
|
|
84
85
|
|
|
85
86
|
## Scene transitions
|
|
86
87
|
|
|
87
|
-
Scene transitions use `@scenerok/transitions` — see `transitions-catalog.md`. They blend **two** clips at window boundaries; filters
|
|
88
|
+
Scene transitions use `@scenerok/transitions` — see `transitions-catalog.md`. They blend **two** clips at window boundaries; filters grade the picture.
|
|
88
89
|
|
|
89
90
|
```vidscript
|
|
90
91
|
import tr from "@scenerok/transitions"
|
|
91
92
|
|
|
93
|
+
input clip_a = "clip-a.mp4"
|
|
94
|
+
input clip_b = "clip-b.mp4"
|
|
92
95
|
[0s .. 5s] = video clip_a
|
|
93
96
|
[5s .. 5.6s] = tr.dissolve()
|
|
94
97
|
[5.6s .. 10s] = video clip_b
|
|
95
98
|
```
|
|
96
99
|
|
|
97
|
-
Do not confuse `tr.dissolve()` (two
|
|
100
|
+
Do not confuse `tr.dissolve()` (a blend between two clips) with `filter "vignette"` (a grade).
|
|
@@ -1,43 +1,23 @@
|
|
|
1
1
|
# Text Styles and Motion Primitives
|
|
2
2
|
|
|
3
|
-
`import motion from "@scenerok/basic-animations"` — use with `animate:` on
|
|
4
|
-
|
|
5
|
-
In **TypeScript SDK**, use the built-in motion helpers from `@scenerok/sdk` (`fadeIn`, `fadeOut`, `slideY`, `slideX`, `scale`, `popIn`) and place text with `area` / `t.grid` (see `sdk-guide.md`).
|
|
3
|
+
`import motion from "@scenerok/basic-animations"` — use with `animate:` on `text` blocks.
|
|
6
4
|
|
|
7
5
|
## Motion primitives
|
|
8
6
|
|
|
9
|
-
| Primitive | Suits | Phase |
|
|
10
|
-
| --- | --- | --- |
|
|
11
|
-
| `motion.fadeIn` | subtle entrance | entry |
|
|
12
|
-
| `motion.fadeOut` | ending / cut-to-black | exit |
|
|
13
|
-
| `motion.slideX` | horizontal reveal | entry |
|
|
14
|
-
| `motion.slideY` | vertical rise / fall | entry |
|
|
15
|
-
| `motion.popIn` | impact / hero reveal | entry |
|
|
16
|
-
| `motion.riseIn` | title build-up | entry |
|
|
17
|
-
| `motion.swingIn` | playful entrance | entry |
|
|
18
|
-
| `motion.glitchIn` | energetic entrance | entry |
|
|
19
|
-
| `motion.float` | idle ambient drift | idle |
|
|
20
|
-
| `motion.typewriter` | technical typing reveal | entry |
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
Prefer named regions over hand-picked `x`/`y`:
|
|
25
|
-
|
|
26
|
-
```vidscript
|
|
27
|
-
[0 .. 3] = text "BIG SALE", area: "title", animate: motion.popIn(0.8)
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import { timeline, text, s, popIn } from '@scenerok/sdk'
|
|
32
|
-
|
|
33
|
-
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
34
|
-
t.grid({ rows: '16% 1fr 18% 10%', areas: ['title', 'stage', 'lower-third', 'caption'] })
|
|
35
|
-
t[0][s(3)] = text('BIG SALE', { size: 72, area: 'title' }).animate(popIn(s(0.8)))
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Built-in areas (no grid declaration): `title`, `stage`, `lower-third`, `caption`.
|
|
39
|
-
|
|
40
|
-
## Examples (VidScript)
|
|
7
|
+
| Primitive | Suits | Phase |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `motion.fadeIn` | subtle entrance | entry |
|
|
10
|
+
| `motion.fadeOut` | ending / cut-to-black | exit |
|
|
11
|
+
| `motion.slideX` | horizontal reveal | entry |
|
|
12
|
+
| `motion.slideY` | vertical rise / fall | entry |
|
|
13
|
+
| `motion.popIn` | impact / hero reveal | entry |
|
|
14
|
+
| `motion.riseIn` | title build-up | entry |
|
|
15
|
+
| `motion.swingIn` | playful entrance | entry |
|
|
16
|
+
| `motion.glitchIn` | energetic entrance | entry |
|
|
17
|
+
| `motion.float` | idle ambient drift | idle |
|
|
18
|
+
| `motion.typewriter` | technical typing reveal | entry |
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
41
21
|
|
|
42
22
|
### Bold hero headline
|
|
43
23
|
|
|
@@ -9,6 +9,8 @@ A transition window `[Ts .. Te]` binds the clip active at **Ts** (from) and **Te
|
|
|
9
9
|
```vidscript
|
|
10
10
|
import tr from "@scenerok/transitions"
|
|
11
11
|
|
|
12
|
+
input clip_a = "clip-a.mp4"
|
|
13
|
+
input clip_b = "clip-b.mp4"
|
|
12
14
|
[0s .. 5s] = video clip_a
|
|
13
15
|
[5s .. 5.6s] = tr.dissolve()
|
|
14
16
|
[5.6s .. 10s] = video clip_b
|
|
@@ -1,38 +1,20 @@
|
|
|
1
1
|
# Text-to-Speech
|
|
2
2
|
|
|
3
|
-
`import xai from "@scenerok/xai"` — voiceover via `xai.tts(...)`.
|
|
3
|
+
`import xai from "@scenerok/xai"` — voiceover via `audio xai.tts(...)`.
|
|
4
4
|
|
|
5
5
|
Voice IDs: `eve` (upbeat), `ara` (warm), `rex` (business), `sal` (balanced), `leo` (authoritative).
|
|
6
|
-
Catalog samples may use `Marin`, `Hudson` — check `xai.listVoices()` for current IDs.
|
|
7
|
-
|
|
8
|
-
### VidScript
|
|
9
|
-
|
|
10
6
|
```vidscript
|
|
11
7
|
import xai from "@scenerok/xai"
|
|
12
8
|
let vo = xai.tts("Welcome to SceneRok", voice: "eve")
|
|
13
9
|
[0 .. vo.duration] = audio vo
|
|
14
10
|
```
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import { timeline, text, s } from '@scenerok/sdk'
|
|
20
|
-
import xai from '@scenerok/xai'
|
|
21
|
-
|
|
22
|
-
export default async function main() {
|
|
23
|
-
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
24
|
-
const vo = await xai.tts('Welcome to SceneRok', { voice: 'eve' })
|
|
25
|
-
t.push(vo)
|
|
26
|
-
t[0][vo.duration] = text('Welcome', { size: 64, area: 'title' })
|
|
27
|
-
return t.toIR()
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Never guess a TTS window.** You cannot know the audio length while writing the script. VidScript: assign with `let` and use `vo.duration`, or `[-] = audio xai.tts(...)`. SDK: `await xai.tts(...)` then use `vo.duration` / `t.push(vo)`. Guessed ranges like `[0s .. 3s] = audio xai.tts("long sentence...")` clip or collide when the real audio runs longer.
|
|
12
|
+
**Never guess a TTS window.** You cannot know the audio length while writing the script. Either assign with `let` and use `vo.duration` in time expressions (measured from the generated file), or use `[-] = audio xai.tts(...)` blocks, which auto-size and never overlap each other. Guessed ranges like `[0s .. 3s] = audio xai.tts("long sentence...")` clip or collide when the real audio runs longer.
|
|
32
13
|
|
|
33
14
|
Chain multiple lines with symbolic arithmetic:
|
|
34
15
|
|
|
35
16
|
```vidscript
|
|
17
|
+
import xai from "@scenerok/xai"
|
|
36
18
|
let vo1 = xai.tts("First line", voice: "eve")
|
|
37
19
|
let vo2 = xai.tts("Second line", voice: "eve")
|
|
38
20
|
[0 .. vo1.duration] = audio vo1
|
|
@@ -40,7 +22,7 @@ let vo2 = xai.tts("Second line", voice: "eve")
|
|
|
40
22
|
[0 .. vo1.duration] = text "First headline", area: "title" # visual spans the narration
|
|
41
23
|
```
|
|
42
24
|
|
|
43
|
-
## Examples
|
|
25
|
+
## Examples
|
|
44
26
|
|
|
45
27
|
Catalog samples below use fixed example windows; in real scripts prefer `vo.duration` or `[-]` blocks as shown above.
|
|
46
28
|
|