# VidScript Language Reference (v2)

**For AI Agents** — Complete grammar and syntax reference for composing videos with VidScript.

## Overview

VidScript is a declarative DSL for video composition. You write a script, SceneRok renders an MP4.

**Core philosophy:**
- **Declarative** — Describe what the video contains, not how to render it frame-by-frame
- **Playhead-driven** — Dynamic `[-]` timeblocks auto-advance separate audio and visual cursors
- **Expression-powered** — Arithmetic, function calls, and property access throughout
- **Plugin-extensible** — Animations, effects, and new features via plugins with minimal grammar changes

---

## Program Structure

A VidScript program is a sequence of statements separated by newlines:

```vidscript
input hero = "https://cdn.example.com/hero.mp4"

[0s .. 5s] = hero

text "Hello World", font: "Inter Bold", size: 72, color: "#FF5733"

output to "video.mp4", resolution: "1080x1920", fps: 30
```

---

## Statements

### Input Declaration

```vidscript
input hero = "https://cdn.example.com/hero.mp4"
input logo = "./assets/logo.png"
```

Supports HTTP(S) URLs, `/uploads/` paths, and local paths.

### Output Declaration

```vidscript
output to "video.mp4", resolution: "1080x1920", fps: 30
output to "reel.mp4", resolution: "720x1280", format: "mp4", codec: "h264", bitrate: "5M", background: "#0D0D0D"
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `resolution` | string | `"1080x1920"` | Width×Height or single number |
| `fps` | number | `30` | Frames per second |
| `format` | string | `"mp4"` | Container format |
| `codec` | string | `"h264"` | Video codec |
| `bitrate` | string | `"5M"` | Encoding bitrate |
| `background` | string | `"#000000"` | Background color hex |

### Variable Assignment

```vidscript
let brandColor = "#6366F1"
let fadeTime = 0.5s
let titleSize = 72
let clipName = "hero"
```

Variables hold strings, numbers, time literals, and object expressions. Evaluated at compile time.

### Import / Export (Module System)

```vidscript
# Export a constant
export const BRAND_COLOR = "#FF5733"

# Export a timeline
export timeline intro(clip: string, title_text: string) {
  input hero = clip
  [0s .. 5s] = hero
  text title_text, font: "Inter Bold", size: 64
}

# Import from another file
import { intro } from "./templates/intro"

# Use the imported timeline
intro(clip: "hero.mp4", title_text: "Welcome")

# Import shaders
import shader "noise" from "https://cdn.example.com/shaders/noise.glsl"
```

---

## Time Blocks

Time blocks define when things happen:

```vidscript
# Absolute range
[0s .. 5s] = hero

# Auto-advance (starts at the current cursor for that channel)
[-] = logo

# With explicit duration
[- 2s] = text "Quick message"

# Using prev keyword
[prev + 1s .. prev + 4s] = hero
```

`[-]` timing is channel-aware. Audio blocks advance the audio playhead, while visual blocks advance the visual playhead. A music bed or TTS sequence does not delay the next `[-] = video ...` block:

```vidscript
import xai from "@scenerok/xai"
import eleven from "@elevenlabs/music"

input product = "https://cdn.example.com/product.png"
let bed = eleven.music("Warm music bed", duration: 15, instrumental: true)
[0s .. 15s] = audio bed, volume: 0.35
[-] = video xai.imageToVideo(product, "Slow premium camera move", aspect_ratio: "9:16", duration: 6)
```

The video starts at the visual playhead, usually `0s`, even though the audio playhead reaches `15s`. Mixed blocks containing both audio and visual instructions advance both playheads.

### Time Units

```vidscript
5s           # 5 seconds
300ms        # 300 milliseconds
frame 90     # 90 frames at 30fps = 3 seconds
0:30         # 30 seconds
1:30:00      # 1 hour 30 minutes
```

### The `prev` Keyword

`prev` refers to the current channel-aware playhead position for the block being compiled. In a visual block it follows the visual cursor; in an audio block it follows the audio cursor.

```vidscript
[- 2s] = text "Hello"                              # plays from cursor to cursor+2s
[prev + 1s .. prev + 3s] = filter "glow"           # 1s after text ends, for 2s
```

---

## Instructions (Inside Time Blocks)

Multiple instructions in the same time block go on separate lines:

```vidscript
[0s .. 5s] = hero
hero.Trim(start: 0s, end: 5s)
hero.Speed(factor: 1.5)
```

### Clip Reference

```vidscript
[0s .. 5s] = hero
[2s .. 4s] = logo
[5s .. 8s] = video hero
```

A bare identifier referencing an input by name. Displays the input for the duration of the time block.

### Text Overlays

```vidscript
text "Hello World", font: "Inter Bold", size: 72, color: "#FF5733", x: 50%, y: 50%
```

**Text Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `content` | string | Text content (first positional arg) |
| `font` | string | Font family (e.g., "Inter Bold") |
| `size` | number \| string | Font size in output canvas pixels. `size: 72` is shorthand for `size: "72px"`. |
| `color` | string | Text color (hex) |
| `stroke` | string | Stroke color |
| `stroke_width` | number | Stroke width |
| `x` | number/string | Horizontal position (pixels or "50%") |
| `y` | number/string | Vertical position (pixels or "50%") |
| `position` | string | Preset position: "center", "top", "bottom", "left", "right" |
| `opacity` | number | Opacity (0-1) |
| `rotation` | number | Rotation in degrees |
| `align` | string | Text alignment: "left", "center", "right" |
| `line_height` | number | Line height multiplier |
| `letter_spacing` | number | Letter spacing in pixels |
| `shadow_color` | string | Shadow color |
| `shadow_blur` | number | Shadow blur radius |
| `shadow_offset_x` | number | Shadow horizontal offset |
| `shadow_offset_y` | number | Shadow vertical offset |
| `animate` | object/array | Animation descriptor(s) — see Animations section |
| `effects` | object/array | Effect descriptor(s) — see Effects section |

### Video Clip with Parameters

```vidscript
video hero, animate: motion.fadeIn(0.5s), opacity: 0.8
```

New in v2: Video clips can take parameters just like text overlays.

### Video Operations

```vidscript
hero.Trim(start: 0s, end: 5s)
hero.Resize(width: 1080, height: 1920)
hero.Speed(factor: 1.5)
hero.Loop(count: 3)
hero.Opacity(value: 0.5, duration: 2s)
```

### Overlay / Composite

```vidscript
# Overlay one clip on top of another
hero.Overlay(logo, x: 50, y: 50, opacity: 0.8)

# Composite with blending mode
hero.Composite(logo, x: 0, y: 0, opacity: 1, mode: "screen")
```

### Filters (Builtin Effects)

```vidscript
filter "monochrome", intensity: 0.8
effect "vignette", strength: 0.5
filter "blur", radius: 3
filter "chromatic", amount: 0.1
```

**Available builtin filters:** `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`

### Shaders (Custom GLSL)

```vidscript
shader "noise", intensity: 0.5, speed: 1.0
```

Requires importing the shader first:
```vidscript
import shader "noise" from "./shaders/noise.glsl"
```

### Audio

```vidscript
audio "background_music", source: "./music.mp3", volume: 0.5, fade_in: 1s, fade_out: 2s
```

Note: Audio is parsed but not yet rendered in the current pipeline (ffmpeg uses `-an`).

### Plugin Calls (explicit import form)

```vidscript
import xai from "@scenerok/xai"
import motion from "@scenerok/basic-animations"

[-] = video xai.imagine("Cinematic product shot", aspect_ratio: "9:16")
[-] = audio xai.tts("Welcome", voice: "eve")
[-] = beatDetect.next()
```

**Always import first** — no more global magic names. The single obvious form is `import name from "@scope/pkg"`.

Any function call in the instruction position after an import is treated as a plugin invocation. Plugins run at compile time in isolated-vm sandboxes.

---

## Animations, Effects & Plugins (v2 Extensibility)

VidScript v2 uses a **minimal-grammar, plugin-first** architecture.

### `animate:` Parameter

Attach animations to **any** text or video surface:

```vidscript
text "Hello", animate: motion.fadeIn(0.8s)

video hero, animate: [motion.fadeIn(0.5s), motion.slideY(-40, 0, 1.2s)]

text "Title", animate: {
  type: "fade",
  from: { opacity: 0 },
  to: { opacity: 1 },
  start: 0,
  end: 1.5
}
```

### `effects:` Parameter

Attach post-processing effects with animated parameters:

```vidscript
text "Dramatic", effects: [grayscale(intensity: 0.8)]

video hero, effects: [{
  name: "glitch",
  strength: animate: { type: "fade", from: { strength: 0 }, to: { strength: 1 } }
}]
```

### Available Animation Functions (`basic-animations` plugin)

| Function | Signature | Description |
|----------|-----------|-------------|
| `fadeIn` | `motion.fadeIn(duration?)` | Opacity 0 → 1 |
| `fadeOut` | `motion.fadeOut(duration?)` | Opacity 1 → 0 |
| `slideX` | `motion.slideX(from, to, duration?)` | Horizontal slide |
| `slideY` | `motion.slideY(from, to, duration?)` | Vertical slide |
| `popIn` | `motion.popIn(duration?)` | Scale + fade entrance with overshoot |
| `riseIn` | `motion.riseIn(duration?, distance?)` | Upward entrance with fade |
| `swingIn` | `motion.swingIn(duration?)` | Rotating slide/fade entrance |
| `glitchIn` | `motion.glitchIn(duration?)` | Jitter + flash entrance |
| `float` | `motion.float(duration?, amplitude?)` | Gentle vertical bob |
| `typewriter` | `motion.typewriter(duration?)` | Reveals text character-by-character |

More plugins will be added for advanced text animations, pixel effects, 3D transforms, etc.

**Architecture details:** `plan-v1/29-surface-animation-effect-plugin-architecture.md`

---

## Values & Types

### String
```vidscript
"Hello World"
'https://example.com/video.mp4'
"Line 1\nLine 2"
"Package labeled \"AER-01\""
"Prompt: " + title_text
```

### Number
```vidscript
30
1080
0.5
2.5
```

### Boolean
```vidscript
true
false
```

### Identifier
```vidscript
hero
BRAND_COLOR
myTimeline
```

Cannot be reserved keywords: `output`, `input`, `export`, `import`, `use`, `let`, `fn`, `true`, `false`, `end`.

### Qualified Reference
```vidscript
hero.duration
myTimeline.clips
```

### Array
```vidscript
[1, 2, 3]
["a", "b", "c"]
```

### Object
```vidscript
{ name: "test", value: 42 }
```

### Time Literal
```vidscript
5s
300ms
frame 90
```

### Function Call
```vidscript
imagine("prompt", aspect_ratio: "9:16")
myTimeline(clip: "hero.mp4")
```

---

## Expressions

Used in time specs, `let` values, and function arguments:

```vidscript
let x = 5s                    # Time literals
let y = x * 2                 # Arithmetic
let prompt = "Scene: " + title_text  # String concatenation
let isReady = true && false   # Boolean logic
let color = invert("#FFFFFF") # Function calls
let size = hero.width / 2     # Property access
let arr = [1, 2, 3]           # Array literals
let obj = { a: 1, b: 2 }      # Object literals
```

---

## Template Placeholders

Templates support placeholders for dynamic values:

```vidscript
input hero = "{{heroClip | default: https://cdn.example.com/hero.mp4}}"

text "{{titleText | default: Hello World}}", font: "Inter Bold", size: 72
```

Placeholders use the syntax: `{{placeholderName | default: defaultValue}}`

---

## CLI Commands

```bash
scenerok auth login          # Browser OAuth, saves token
scenerok validate <file>     # Validate VidScript syntax
scenerok project upload <file> --assets ./assets  # Upload project + local assets
scenerok project upload <file> --render --watch --download ./renders  # Upload/render/download
scenerok project render <project-id> --watch --download ./renders  # Render uploaded project
scenerok render <file> --project-id <id> --watch  # Submit job and attach to project
scenerok status <id>         # Check render status
scenerok status <id> --download ./renders  # Download completed render
scenerok cache pull          # Download generated asset cache locally
scenerok skills install <platform>  # Install agent skills
```

---

## Full Example

```vidscript
# Inputs
input hero = "https://cdn.example.com/hero.mp4"
input logo = "https://cdn.example.com/logo.png"

# Variables
let brandColor = "#6366F1"
let fadeTime = 0.5s
let titleSize = 64

# Timeline
[0s .. 5s] = hero
hero.Trim(start: 0s, end: 5s)

[- 2s] = text "Welcome", font: "Inter Bold", size: titleSize, color: brandColor, animate: motion.fadeIn(fadeTime)

[- 3s] = logo
logo.Resize(width: 200, height: 200)

[prev + 1s .. prev + 4s] = hero
hero.Overlay(logo, x: 50, y: 50, opacity: 0.8)

[- 2s] = text "Thanks for watching!", font: "Inter", size: 48, animate: motion.slideY(30, 0, 1s)

# Effects
[0s .. 10s] = filter "vignette", strength: 0.3

# Output
output to "video.mp4", resolution: "1080x1920", fps: 30, background: "#000000"
```

---

## Important Constraints

- **Text params must be on the SAME line as the `text` instruction.** Multi-line params are not supported by the grammar.
- Animations and effects are data-driven in the IR and consumed by both the browser preview engine and the final render.
- All plugin calls run at compile time in isolated-vm sandboxes with controlled network/storage access.

---

**Docs:** https://scenerok.com/docs/vidscript
**Plan:** plan-v1/29-surface-animation-effect-plugin-architecture.md
