greenlight-motion 0.1.5 → 0.2.1
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 +9 -9
- package/dist/bundle-inputs.json +14 -0
- package/dist/greenlight-motion-script.mjs +3250 -274
- package/dist/greenlight-motion.js +703 -661
- package/dist/greenlight-motion.mjs +41156 -39509
- package/dist/script-inputs.json +8 -0
- package/package.json +2 -2
- package/schema/capabilities.json +1 -1
- package/skill/SKILL.md +43 -12
- package/skill/references/scene-script.md +48 -6
- package/skill/references/video-camera.md +106 -4
- package/skill/references/video-design.md +5 -0
- package/skill/references/video-effects.md +18 -0
- package/skill/references/video-paths.md +33 -2
- package/skill/references/video-projects.md +32 -3
- package/skill/references/video-text-presets.md +31 -1
- package/skill/references/video-text.md +36 -1
package/skill/SKILL.md
CHANGED
|
@@ -18,12 +18,19 @@ You never write UI or animation code. If something is not in the format, it is n
|
|
|
18
18
|
## Setup (once per machine)
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
npx greenlight-motion@0.1
|
|
21
|
+
npx greenlight-motion@0.2.1 doctor
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
It checks Node 18+, Chrome or Edge, WebGPU and H.264 encoding, and prints a fix for anything missing. Do not install
|
|
25
25
|
anything globally without asking the user. Always pin the version in every command you run.
|
|
26
26
|
|
|
27
|
+
## First, a direction
|
|
28
|
+
|
|
29
|
+
Before any JSON, decide in a few lines what the video LOOKS like: one visual idea from the subject's own world carried
|
|
30
|
+
through every scene, the palette, the type, and what each scene shows besides its words. Presets make a video move;
|
|
31
|
+
the direction makes it look designed. `skill/references/scene-script.md` opens with how. Then build to it, and judge
|
|
32
|
+
every contact sheet against it.
|
|
33
|
+
|
|
27
34
|
## The fast way: a scene script
|
|
28
35
|
|
|
29
36
|
For a new video, start with a **scene script** instead of a whole project: a short JSON file — scenes, their texts and
|
|
@@ -32,23 +39,47 @@ animation presets (the same converter the GreenLight Motion connector uses in Cl
|
|
|
32
39
|
keyframes rarely look as good. The format, the design rules and the catalogues: `skill/references/scene-script.md`.
|
|
33
40
|
|
|
34
41
|
```
|
|
35
|
-
npx greenlight-motion@0.1
|
|
42
|
+
npx greenlight-motion@0.2.1 script <folder> --from scenes.json # or write <folder>/script.json, then: script <folder>
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
Then `check` and `stills` (below), fix the SCRIPT and run `script` again — it rewrites `project.json`.
|
|
39
|
-
|
|
45
|
+
Then `check` and `stills` (below), fix the SCRIPT and run `script` again — it rewrites `project.json`. A scene's
|
|
46
|
+
`camera` can be a real 3D camera (the editor's camera moves and rigs, zooms on spots, depth of field, layers in depth with
|
|
47
|
+
`z`). For what a script can't say (3D models, particles, motion paths, masks, text animators by hand), go on from `project.json` with the
|
|
40
48
|
workflow below and stop running `script` (it refuses to overwrite a project edited since, unless `--force`).
|
|
41
49
|
|
|
50
|
+
## Beyond the script: the signature visual
|
|
51
|
+
|
|
52
|
+
The script gives structure; the picture that makes the video THIS product's usually needs the engine's full format.
|
|
53
|
+
Build it with `apply` on the project `script` wrote (the item ids are the script's element ids):
|
|
54
|
+
|
|
55
|
+
- **A material of its own**: a custom GLSL shader on a shape or the whole background (water, light through glass,
|
|
56
|
+
paper, heat): `shapeBackground` / `background` `{ "type": "shader", "shaderId": "custom", "shaderCode": "void
|
|
57
|
+
mainImage(out vec4 o, in vec2 fc) { … }" }`, Shadertoy-style, `iTime` from the timeline
|
|
58
|
+
(`references/video-backgrounds.md`, Custom GLSL shaders).
|
|
59
|
+
- **Layers that interact**: a track matte, so words dip under a surface or show only through a shape
|
|
60
|
+
(`"trackMatte": { "sourceId": "<item>", "mode": "alpha" | "alphaInverted" | "luma" | "lumaInverted" }` on the layer
|
|
61
|
+
that is cut; the source is hidden from the output; `references/video-shapes.md`). Clipping masks and Layer Mask
|
|
62
|
+
(`references/video-effects.md`) for reveals.
|
|
63
|
+
- **Your own drawing and motion**: SVG you write (`{ "type": "image", "svg": "…" }` in the script), shapes with
|
|
64
|
+
keyframes, motion paths (`references/video-paths.md`), particles (`references/video-particles.md`), 3D
|
|
65
|
+
(`references/video-3d.md`), Text Animators by hand (`references/video-text.md`).
|
|
66
|
+
- **Sound when nothing may be downloaded**: write it in code. A short Node script can write a 48 kHz WAV (a soft bed, a
|
|
67
|
+
click, a noise whoosh) into `assets/`, then use it as the script's `music` or as an audio clip. Keep the bed quiet
|
|
68
|
+
under the hits.
|
|
69
|
+
|
|
70
|
+
Keep the build repeatable: put your edits in a file you `apply` again after each `script` run (`script --force`, then
|
|
71
|
+
`apply`), so the script and the hand-made layers never drift apart.
|
|
72
|
+
|
|
42
73
|
## Workflow
|
|
43
74
|
|
|
44
|
-
1. `npx greenlight-motion@0.1
|
|
75
|
+
1. `npx greenlight-motion@0.2.1 init <folder>` — starts from a working example (title, subtitle, accent line, four
|
|
45
76
|
options). `--from file.json` starts from a project you have (for example one exported from GreenLight Dash).
|
|
46
77
|
2. Edit `<folder>/project.json` — for changes to an EXISTING project prefer `apply` (below): it checks every edit and
|
|
47
78
|
applies all or nothing. Put pictures, video, sound and fonts in `<folder>/assets/` and refer to them as
|
|
48
79
|
`"asset:<file>"` (e.g. `"url": "asset:product.png"`).
|
|
49
|
-
3. `npx greenlight-motion@0.1
|
|
80
|
+
3. `npx greenlight-motion@0.2.1 check <folder>` — fix everything it lists (missing files, template mistakes, blank
|
|
50
81
|
frames, page errors). `--json` for machine output.
|
|
51
|
-
4. `npx greenlight-motion@0.1
|
|
82
|
+
4. `npx greenlight-motion@0.2.1 stills <folder>` — renders frames and **a labelled contact sheet**. Open and look at
|
|
52
83
|
it before you go on: layout, readability, timing. Fix and repeat. `--at 0.5,1.2,3` for exact times.
|
|
53
84
|
5. Hand it over: tell the user to open `<folder>/index.html` (Chrome or Edge), or `pack` it into one file, or
|
|
54
85
|
`render` the MP4 yourself (`--fps 60 --motion-blur` for fast motion).
|
|
@@ -113,7 +144,7 @@ editor's own normalizer), so write only what you mean.
|
|
|
113
144
|
Full references (generated from the editor's own docs; ignore the `/api/` routes they mention):
|
|
114
145
|
`skill/references/` — video-design, scene-script, video-projects, video-text, video-text-kinetic, video-text-presets, video-shapes, video-keyframes,
|
|
115
146
|
video-camera, video-3d, video-effects, video-transitions, video-particles, video-paths, video-backgrounds, video-grid.
|
|
116
|
-
Machine-readable lists: `npx greenlight-motion@0.1
|
|
147
|
+
Machine-readable lists: `npx greenlight-motion@0.2.1 schema effects | transitions | easings | paths | expressions`.
|
|
117
148
|
|
|
118
149
|
## Template
|
|
119
150
|
|
|
@@ -160,7 +191,7 @@ an absolute moment; `align` `sync` (default) / `start` / `end`; `offset` seconds
|
|
|
160
191
|
|
|
161
192
|
The library is 77 hand-picked sounds in categories (whooshes and swooshes, transitions, clicks, pop-ups and
|
|
162
193
|
notifications, data blips, typing and writing, foley, bubbles, impacts and bass drops, camera, gears, voice clips).
|
|
163
|
-
`npx greenlight-motion@0.1
|
|
194
|
+
`npx greenlight-motion@0.2.1 schema sounds` lists every one with its `category`, `use` (what it is for), `duration`
|
|
164
195
|
and `sync`. Pick by `category` and `use`, then by name.
|
|
165
196
|
|
|
166
197
|
- **Where the files come from:** the GreenLight sound CDN. `build` downloads each sound a cue uses into
|
|
@@ -176,7 +207,7 @@ The page has **Notes**: the user marks what should change right on the frame (a
|
|
|
176
207
|
and writes a note for each mark. Every note carries the moment (seconds and frame), the mark in composition pixels
|
|
177
208
|
and the layers under it (item ids in project.json), so you change exactly that and nothing else.
|
|
178
209
|
|
|
179
|
-
- **They review on this computer:** run `npx greenlight-motion@0.1
|
|
210
|
+
- **They review on this computer:** run `npx greenlight-motion@0.2.1 review <folder>` and give them the URL it prints
|
|
180
211
|
(it keeps running; `--open` opens their browser, only when they ask). Their notes land in `<folder>/review/`: read
|
|
181
212
|
`review/NOTES.md`, look at the `at-<t>s.png` pictures (the marks are numbered), make the changes (`apply`), `build`
|
|
182
213
|
again and tell them to reload the page.
|
|
@@ -205,11 +236,11 @@ that fails (`plugin`); look at `stills` for the look. A package that carries its
|
|
|
205
236
|
|
|
206
237
|
The same project opens in GreenLight Dash (the desktop editor this engine comes from) for full editing: the page's
|
|
207
238
|
edit note offers **Open in GreenLight Dash** (it imports the folder as a new project on the open board; a page with
|
|
208
|
-
changes saves a `.glvideo`). `npx greenlight-motion@0.1
|
|
239
|
+
changes saves a `.glvideo`). `npx greenlight-motion@0.2.1 pack <folder> --glvideo` writes that file yourself: ONE
|
|
209
240
|
`.glvideo` (the project + its files) that opens in GreenLight Dash on a double-click, or dropped on its board. Hand it
|
|
210
241
|
over when the user wants to keep editing in the app. A
|
|
211
242
|
GreenLight Dash project comes the other way through the app's **Export for agents** — a ready folder like the one
|
|
212
|
-
`init` makes, with the options the author ticked. `npx greenlight-motion@0.1
|
|
243
|
+
`init` makes, with the options the author ticked. `npx greenlight-motion@0.2.1 init <folder> --from <its project.json>`
|
|
213
244
|
starts from one too. Never tell the user they must install it: the page plays, changes options and renders on its own.
|
|
214
245
|
|
|
215
246
|
## Files
|
|
@@ -13,6 +13,19 @@ works too (it has to stay reachable). SVG you write: `{ "type": "image", "svg":
|
|
|
13
13
|
|
|
14
14
|
## The scene script
|
|
15
15
|
|
|
16
|
+
**Before the script, a direction.** Presets make a video move; they don't give it an idea. Decide in a few lines first,
|
|
17
|
+
and keep them next to the script:
|
|
18
|
+
- **The idea**: ONE picture from the subject's own world (its object, place or action: a coffee shop's steam and cup rim,
|
|
19
|
+
a budgeting app's receipt printing line by line, a running app's track the scenes run along), carried through every scene.
|
|
20
|
+
- **The palette**: background, text, one accent, plus only the colours that carry meaning (good / bad, before / after).
|
|
21
|
+
Light or dark on purpose.
|
|
22
|
+
- **The type**: a big headline against a small support line; aligned left or centred on purpose, not by default.
|
|
23
|
+
- **Each scene**: what it SHOWS besides its words, built from shapes, lines, `svg` you write, keyframes and the camera.
|
|
24
|
+
The same line centred on a gradient in every scene reads as a template. Judge the stills against the direction, not
|
|
25
|
+
only for mistakes.
|
|
26
|
+
What a script can't say (a custom GLSL shader on a shape or the background, words dipping under a surface with a track
|
|
27
|
+
matte, masks, particles, 3D) goes on from `project.json`: SKILL.md, "Beyond the script".
|
|
28
|
+
|
|
16
29
|
```json
|
|
17
30
|
{
|
|
18
31
|
"title": "Bean There — opening day",
|
|
@@ -29,8 +42,10 @@ works too (it has to stay reachable). SVG you write: `{ "type": "image", "svg":
|
|
|
29
42
|
{ "type": "pill", "w": 560, "h": 116, "color": "accent", "start": 0.15 },
|
|
30
43
|
{ "type": "text", "text": "Opening day: 2 for 1", "role": "body", "color": "#1b120d", "start": 0.3, "enter": "pop-overshoot", "sound": "data-1" } ] },
|
|
31
44
|
{ "name": "Close", "duration": 2.4, "transition": "Gradient Glow Stack", "background": { "shader": "silk", "look": "sunset" },
|
|
45
|
+
"camera": { "move": "cm-quiet-push", "dof": { "focus": "street", "aperture": 50 } },
|
|
32
46
|
"elements": [
|
|
33
|
-
{ "type": "
|
|
47
|
+
{ "type": "circle", "w": 420, "color": "#fff3e0", "opacity": 0.3, "at": [330, -60], "z": -700 },
|
|
48
|
+
{ "id": "street", "type": "text", "text": "12 Elm Street", "role": "headline", "textAnimation": "word-swing" },
|
|
34
49
|
{ "type": "text", "text": "OPEN 7 – 7", "role": "label", "at": [0, 130], "start": 0.8, "letterSpacing": 6 } ] }
|
|
35
50
|
]
|
|
36
51
|
}
|
|
@@ -44,15 +59,36 @@ works too (it has to stay reachable). SVG you write: `{ "type": "image", "svg":
|
|
|
44
59
|
- **line**: from `at` to `to`, `width`, drawn on (`draw`: false to skip).
|
|
45
60
|
- **image · video**: `src` (a file in the folder's assets/ as `asset:<name>`, or an https link) or, for an image, `svg` (SVG markup you write); `w` or `h`
|
|
46
61
|
(px; the other follows the file's shape) or `fit` cover | contain
|
|
47
|
-
(default: a video covers the frame, a picture sits at 70 % of contain), `rotate` (deg)
|
|
62
|
+
(default: a video covers the frame, a picture sits at 70 % of contain), `rotate` (deg). A video also takes
|
|
48
63
|
`startAt` (s into the file) and `volume` (0–100, default muted). No entrance unless you set `enter`.
|
|
49
64
|
- **Keyframes on any element** — your own motion: `"keyframes": { "x": [[0, -400], [0.8, 0, "Smooth Glide"]], "scale": [[0, 0.9], [2, 1.05]] }`
|
|
50
65
|
— tracks x, y (px offsets from `at`), scale, rotate (deg), opacity (0–1); each key [seconds into the element, value, easing of the
|
|
51
66
|
segment after it — Smooth Glide, AE Smooth, Expo Out, Back Out, Elastic, Linear …]. They replace an entrance's keys on the same track.
|
|
52
67
|
- **Every element**: `start` / `end` (s within its scene; default: staggered 0.12 s, to the scene's end), `enter` / `exit` (keyframe presets, `direction` up | down | left | right
|
|
53
|
-
for directional ones), `effects`: [{ "id", "params"? }], `sound` (played at its start), `id`, `label` (its name in the page's options)
|
|
54
|
-
|
|
68
|
+
for directional ones), `effects`: [{ "id", "params"? }], `sound` (played at its start), `id`, `label` (its name in the page's options), `opacity` (0–1),
|
|
69
|
+
`z` (px of depth, + toward the viewer, default 0: a real camera's dolly, orbit and depth of field separate layers by it), `locked`: true
|
|
70
|
+
(the scene's camera doesn't move it: a logo, a caption). Keyframes also take a `z` track.
|
|
71
|
+
- **Scene**: `duration` (s), `transition` into it (default Cross Fade, 0.6 s; `transitionDuration`), `camera` (below; default push-in),
|
|
55
72
|
`background` (overrides style), `sounds`: [{ "sfx", "at" }]. Script-level `sound`: auto (default: a whoosh on each transition and on the first headline) | none.
|
|
73
|
+
**A transition overlaps the scenes on both sides**, so the video lasts the scenes' durations added up MINUS every
|
|
74
|
+
transition: five 3 s scenes with the default 0.6 s transitions run 15 − 4 × 0.6 = 12.6 s. To hit a length, add the
|
|
75
|
+
transitions back to the scenes (`script` prints the total).
|
|
76
|
+
- **camera** — a quick move of the whole scene: none | push-in | pull-out | drift-left | drift-right | drift-up. Or a REAL camera that films the scene's elements in 3D (the
|
|
77
|
+
Video Editor's own camera layer; the scene's backdrop stays put, so its edge never shows). One of:
|
|
78
|
+
- `{ "move": id }` — a camera move from the catalogue (or just `"camera": "cm-quiet-push"`), spread over the scene;
|
|
79
|
+
- `{ "rig": id }` or `{ "rig": { "preset"?: id, "type"?, …fields } }` — an orbit, crane, push-in, truck, spiral, handheld or follow move
|
|
80
|
+
(`"camera": "heroOrbit"` works too). A `follow` rig rides elements: `"targets": [element ids]`. Layers are flat: keep an orbit within about
|
|
81
|
+
±70° of the front (heroOrbit, slowReveal) or they turn edge-on and read mirrored;
|
|
82
|
+
- `{ "zooms": [{ "start", "end", "at"?, "zoom"? }], "feel"? }` — zoom in on spots and back out, like a screen recording's Smart Zoom: `at` as for
|
|
83
|
+
elements (the spot to frame, clamped so the view stays inside the frame), `zoom` 1.05–4 (default the feel's, about 1.45), each zoom at least
|
|
84
|
+
0.4 s and none overlapping; `feel` studio | calm | precise | lively | follow (the spring). Put a screen recording in as a video element (or the
|
|
85
|
+
scene's video background) and zoom on what it shows;
|
|
86
|
+
- `{ "keyframes": { zoom | x | y | roll | dolly | tiltX | tiltY | focusZ: [[seconds, value, easing?], …] } }` — your own move: zoom (×, 1 = none),
|
|
87
|
+
x / y (px the camera pans right / down), roll / tilt (deg), dolly (px toward the scene: it changes perspective, so layers at different `z` part).
|
|
88
|
+
With a `move` they replace its track; a rig drives x, y, roll, dolly and tilt itself and zooms drive zoom, x and y, so key only the others there.
|
|
89
|
+
- plus `"dof": { "focus": element id | "focusZ": px, "aperture"?: 40, "blur"?: 100 }` (or `true`) — depth of field: what sits at another `z` blurs;
|
|
90
|
+
the focus stays on its subject while the camera dollies or rides a rig. `focusZ` keys are a rack focus (measured from where the camera starts).
|
|
91
|
+
- `start` / `end` (s in the scene; default the whole scene; outside them the camera rests). Keys and zooms count seconds into the camera.
|
|
56
92
|
- **background**: a colour, a CSS gradient, { "shader": id, "look"? } — shaders: silk, glowblobs, waves, plasma, mesh, fire, spacetravel, glitters, blueplanet, auroraridge, emberpool, neonvignette, electricvignette, spectrumdrift, emberfacet, silkdrift, eclipseprism, glassreeds, staticfringe, ripplefold, nightaurora, pixelbeams, petalgrid, amberblinds, icehalo, windowhaze (looks: references/video-backgrounds.md) — or { "image": src } / { "video": src } (covers the frame; b-roll behind the scene).
|
|
57
93
|
- **music** (script level): `"music": { "src": file, "volume": 80, "fadeIn": 0.3, "fadeOut": 1.2, "startAt": 0 }` — one track under the video.
|
|
58
94
|
|
|
@@ -63,7 +99,7 @@ the first version and check every version against it.
|
|
|
63
99
|
|
|
64
100
|
## Catalogues
|
|
65
101
|
|
|
66
|
-
**Text animations** (`textAnimation`; ★ = preferred): ★ `glide-text` Glide Text · `glide-continuous` Glide Continuous · `glide-soft-stop` Glide Soft Stop · ★ `ember-type` Ember Type · `molten-edge` Molten Edge · `focus-type` Focus Type · `tumble-type` Tumble Type · `flap-down` Flap Down · `drop-swell` Drop Swell · `rain-type` Rain Type · `counter-sweep` Counter Sweep · `sunset-shrink` Sunset Shrink · `boomerang` Boomerang · ★ `swing-pop` Swing Pop · ★ `word-swing` Word Swing · ★ `letter-vortex` Letter Vortex · `center-burst` Center Burst · ★ `pendulum` Pendulum · `spin-pop` Spin Pop · `domino` Domino · `flip-pop` Flip Pop · `card-turn` Card Turn · `slam-zoom` Slam Zoom · `orbit` Orbit · ★ `fan-deal` Fan Deal · ★ `whip` Whip · `rubber-letters` Rubber Letters · `pop-letters` Pop Letters · `drop-swing` Drop Swing · `coin-spin` Coin Spin · `converge` Converge · `roll-in` Roll In · `door-swing` Door Swing · `pill-type` Pill Type · `card-type` Card Type · `word-bars` Word Bars · `line-stack` Word Stack · `marker-type` Marker Type · `card-erase` Card Erase · `colour-sweep` Colour Sweep · `glow-pass` Glow Pass · `wave-pass` Wave Pass · `pop-through` Pop Through · `shine` Shine · ★ `marker` Marker · ★ `outline-pass` Outline Pass · ★ `neon-flow` Neon Flow · `rainbow` Rainbow · `gradient-flow` Gradient Flow · `jitter` Jitter · `float` Float · `flicker` Flicker · `breathe` Breathing Wave · `shimmer` Shimmer · `decode` Decode · `binary` Binary Decode · `alphabet-roll` Alphabet Roll · ★ `glitch` Glitch · `classic-fade-up` Fade Up · `classic-fade-down` Fade Down · `classic-rise-mask` Rise (Mask) · `classic-blur-in` Blur In · `classic-slide-in` Slide In · `classic-side-mask` Side Mask · `classic-tilt-drop` Tilt Drop · `classic-flip-up` Flip Up · `classic-typewriter` Typewriter · `classic-wave-rise` Wave Rise · `classic-bounce-letters` Bounce Letters · `classic-materialize` Materialize · `classic-neon-runner` Neon Runner · `classic-ink-highlight` Ink Highlight · `classic-flash-type` Flash Type · `classic-karaoke-sweep` Karaoke Sweep · `classic-chase-loop` Chase Loop · `classic-sung-words` Sung Words · `classic-marker-lines` Marker Words · `classic-pill-grow` Pill Grow · `classic-lower-third` Lower Third · `classic-drop-bar` Drop Bar · `classic-outline-tag` Outline Tag · `classic-prompt-field` Prompt Field · `classic-random-pop` Random Pop · `classic-chaos-letters` Chaos Letters · `classic-random-rise` Random Rise · `classic-ransom-note` Ransom Note · `classic-editorial-typer` Editorial Typer
|
|
102
|
+
**Text animations** (`textAnimation`; ★ = preferred): ★ `glide-text` Glide Text · `glide-continuous` Glide Continuous · `smart-overshoot` Smart Overshoot · `glide-soft-stop` Glide Soft Stop · ★ `ember-type` Ember Type · `molten-edge` Molten Edge · `focus-type` Focus Type · `tumble-type` Tumble Type · `flap-down` Flap Down · `drop-swell` Drop Swell · `rain-type` Rain Type · `counter-sweep` Counter Sweep · `sunset-shrink` Sunset Shrink · `boomerang` Boomerang · ★ `swing-pop` Swing Pop · ★ `word-swing` Word Swing · ★ `letter-vortex` Letter Vortex · `center-burst` Center Burst · ★ `pendulum` Pendulum · `spin-pop` Spin Pop · `domino` Domino · `flip-pop` Flip Pop · `card-turn` Card Turn · `slam-zoom` Slam Zoom · `orbit` Orbit · ★ `fan-deal` Fan Deal · ★ `whip` Whip · `rubber-letters` Rubber Letters · `pop-letters` Pop Letters · `drop-swing` Drop Swing · `coin-spin` Coin Spin · `converge` Converge · `roll-in` Roll In · `door-swing` Door Swing · `pill-type` Pill Type · `card-type` Card Type · `word-bars` Word Bars · `line-stack` Word Stack · `marker-type` Marker Type · `card-erase` Card Erase · `colour-sweep` Colour Sweep · `glow-pass` Glow Pass · `wave-pass` Wave Pass · `pop-through` Pop Through · `shine` Shine · ★ `marker` Marker · ★ `outline-pass` Outline Pass · ★ `neon-flow` Neon Flow · `rainbow` Rainbow · `gradient-flow` Gradient Flow · `jitter` Jitter · `float` Float · `flicker` Flicker · `breathe` Breathing Wave · `shimmer` Shimmer · `decode` Decode · `binary` Binary Decode · `alphabet-roll` Alphabet Roll · ★ `glitch` Glitch · `classic-fade-up` Fade Up · `classic-fade-down` Fade Down · `classic-rise-mask` Rise (Mask) · `classic-blur-in` Blur In · `classic-slide-in` Slide In · `classic-side-mask` Side Mask · `classic-tilt-drop` Tilt Drop · `classic-flip-up` Flip Up · `classic-typewriter` Typewriter · `classic-wave-rise` Wave Rise · `classic-bounce-letters` Bounce Letters · `classic-materialize` Materialize · `classic-neon-runner` Neon Runner · `classic-ink-highlight` Ink Highlight · `classic-flash-type` Flash Type · `classic-karaoke-sweep` Karaoke Sweep · `classic-chase-loop` Chase Loop · `classic-sung-words` Sung Words · `classic-marker-lines` Marker Words · `classic-pill-grow` Pill Grow · `classic-lower-third` Lower Third · `classic-drop-bar` Drop Bar · `classic-outline-tag` Outline Tag · `classic-prompt-field` Prompt Field · `classic-random-pop` Random Pop · `classic-chaos-letters` Chaos Letters · `classic-random-rise` Random Rise · `classic-ransom-note` Ransom Note · `classic-editorial-typer` Editorial Typer
|
|
67
103
|
|
|
68
104
|
**Entrances** (`enter`): `slide` Slide · direction · `glide` Glide (in and out) · direction · `glide-stop` Glide Stop (in and out) · direction · `glide-floaty` Glide Floaty (in and out) · direction · `glide-endless` Glide Endless (in and out) · direction · `glide-diagonal` Glide Diagonal (in and out) · direction · `glide-arc` Glide Arc (in and out) · direction · `glide-close-pass` Glide Close Pass (in and out) · direction · `glide-fade-drift` Glide Fade Drift (in and out) · direction · `glide-bank` Glide Bank (in and out) · direction · `glide-swing-3d` Glide Swing 3D (in and out) · direction · `glide-linger` Glide Linger (in and out) · direction · `glide-soft-entry` Glide Soft Entry (in and out) · direction · `glide-blur` Glide Blur (in and out) · direction · `glide-spin-through` Glide Spin Through (in and out) · direction · `glide-depth-pass` Glide Depth Pass (in and out) · direction · `glide-tilt` Glide Tilt (in and out) · direction · `zoom-in` Zoom In · `mockup-close-up` Mockup Close-Up · `mockup-orbit-left` Orbit Left · `deck-drift` Deck Drift · `hero-punch` Hero Punch · `dark-spotlight` Dark Spotlight · `phone-app-swipe` Phone Swipe Reveal · `phone-app-punch` Phone App Punch In · `phone-app-tilt-drift` Phone Tilt Drift · `phone-app-cta-hold` CTA Hold Focus · `button-press` Button Press · `button-tap-flash` Tap Flash · `pop-overshoot` Pop Overshoot · `spring-in` Spring In · `rubber-wobble` Rubber Wobble · `tap-nudge` Tap Nudge · `whip-swing` Whip Swing · `flick-drop` Flick Drop · `jelly-shake` Jelly Shake · `heartbeat` Heartbeat · `impact-punch` Impact Punch · `pendulum-swing` Pendulum Swing · `loose-wiggle` Loose Wiggle · `shiver` Shiver · `float` Float · `flicker-in` Flicker In · `earthquake` Earthquake · `tumble-drop` Tumble Drop · `rubber-band` Rubber Band · `spring-twist` Spring Twist · `slingshot` Slingshot · `pendulum-in` Pendulum In · `smooth-overshoot-rise` Smooth Overshoot Rise · `spin-in` Spin In · `twist-in` Twist In · `roll-in` Roll In · `tilt-settle` Tilt Settle · `wind-up-spin` Wind-Up Spin
|
|
69
105
|
|
|
@@ -71,9 +107,15 @@ the first version and check every version against it.
|
|
|
71
107
|
|
|
72
108
|
**Transitions** (★ = preferred): None · ★ Cross Fade · Push · Parallax · Wipe · Zoom In · Zoom Out · ★ Advanced Zoom · ★ Advanced Movement · 3D Carousel · Louver Flip · ★ Circular Wipe · ★ Pixel Blocks · ★ Camera Whip · ★ Zoom Ruler · ★ Gradient Glow Stack · ★ Gradient Map · Scale Swap · Side by Side · Dip to Black · Radial Wipe
|
|
73
109
|
|
|
110
|
+
**Camera moves** (`camera.move`; spread over the camera's length): `cm-quiet-push` Quiet Push (Cinematic) · `cm-swoop-dive` Swoop Dive (Cinematic) · `cm-corkscrew` Corkscrew (Cinematic) · `cm-fast-orbit` Fast Orbit (Cinematic) · `cm-lateral-glide` Lateral Glide (Cinematic) · `cm-parallax-arc` Parallax Arc (Cinematic) · `cm-arc-closeup` Arc Close-Up (Cinematic) · `cm-slingshot` Slingshot (Editorial) · `cm-step-zoom` Step Zoom (Editorial) · `cm-handheld-breath` Handheld Breath (Editorial) · `cm-soft-land` Soft Land (Editorial) · `cm-documentary-follow` Documentary Follow (Editorial) · `cm-weave-run` Weave Run (Energy) · `cm-s-curve-drift` S-Curve Drift (Energy) · `cm-macro-sweep` Macro Sweep (Energy) · `cm-punch-through` Punch Through (Energy) · `cm-orbit-pulse` Orbit Pulse (Loops) · `cm-float` Float (Loops) · `cm-figure-eight` Figure Eight (Loops) · `cm-rack-to-near` Rack to Near (Focus) · `cm-rack-to-far` Rack to Far (Focus) · `cm-focus-chase` Focus Chase (Focus) · `cm-macro-bloom` Macro Bloom (Focus) · `cm-grand-reveal` Grand Reveal (Cinematic)
|
|
111
|
+
|
|
112
|
+
**Camera rigs** (`camera.rig`; the fields you can override: type orbit | dolly | crane | pushIn | truck | spiral | handheld | follow, aim center | path, radius 100–20000, height -8000–8000, startAngle -360–360, sweep -720–720, rise -8000–8000, distance -20000–20000, turns 0.25–8, tilt -80–80, roll -180–180, amplitude 0–2000, frequency 0–40): `heroOrbit` Hero Orbit (orbit) · `slowReveal` Slow Reveal (orbit) · `turntable` Turntable (orbit) · `craneDown` Crane Down (crane) · `riseReveal` Rise & Reveal (crane) · `pushIn` Push In (pushIn) · `pullBack` Pull Back (dolly) · `truckLeft` Truck Left (truck) · `productSpiral` Product Spiral (spiral) · `handheldDrift` Handheld Drift (handheld) · `droneFollow` Drone Follow (follow) · `chaseCam` Chase Cam (follow)
|
|
113
|
+
|
|
114
|
+
**Zoom feels** (`camera.feel`): `studio` Studio · `calm` Calm · `precise` Precise · `lively` Lively · `follow` Follow. Full camera layers (paths, Smart Zoom on a recording's clicks, the drawn cursor): references/video-camera.md.
|
|
115
|
+
|
|
74
116
|
**Overlay effects** (`effects`): `glow`, ★ `bloom`, `auraBlur`, `gradientBeam`, ★ `gradientSweep`, `fractalNoise`, ★ `rgbSplit`, `glitch`, `scanlines`, `flashBlink`, `vhs`, `fisheye`, `opticsCompensation`, `vignette`, ★ `echo`, `timeshift`, `radialBlur`, ★ `radialFastBlur`, `sharpen`, `mirror`, `waveWarp`, `warpStyles`, `shockwave`, `waveLines`, `waveRipple`, `fill`, `shapeRulers`, `copyColor`, `curves`, `turbulentDisplace`, `alphaChoker`, `extract`, ★ `colorama`, `posterize`, ★ `halftone`, ★ `fastCameraWiggle`, ★ `lightRays`, `longShadow`, `outlineBands`, `kaleidoscope`, `crtMatrix`, `pixelate`, `vectorFieldWarp`, `pixelStretch`, `neonGlow`, `edgeDetect`, `doubleExposure`, ★ `filmStock`, `fastBlur`, `gridLines`, `strip`, `ascii`, `objectDither`, `objectIsolines`, `objectWindow`, `objectSlitScan`, `objectCurtain`, `objectGrid`, `objectContour`, `objectNodes`, `objectLinks`, `objectFacets`, `objectMeasure`
|
|
75
117
|
|
|
76
|
-
**Layer effects** (`effects`): `mockupFrame`, ★ `runningGlow`, `depthMap`, `depth3d`, `depthScan`, `fresnelRim`, ★ `motionBlur`, ★ `motionTrail`, `autoFill`, ★ `repetile`, `regionDistort`, `bend`, `cornerPin`, `meshWarp`, `bezierWarp`, `rig`, ★ `droste`, `pryzm`, ★ `glass`, `chromaKey`, `layerMask`
|
|
118
|
+
**Layer effects** (`effects`): `mockupFrame`, ★ `runningGlow`, `depthMap`, `depth3d`, `depthScan`, `fresnelRim`, ★ `motionBlur`, `posterizeTime`, ★ `motionTrail`, `autoFill`, ★ `repetile`, `regionDistort`, `bend`, `cornerPin`, `meshWarp`, `bezierWarp`, `rig`, ★ `droste`, `pryzm`, ★ `glass`, `chromaKey`, `layerMask`
|
|
77
119
|
|
|
78
120
|
Effect params: references/video-effects.md.
|
|
79
121
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
## Contents
|
|
10
10
|
|
|
11
11
|
- [Camera layers (`mediaType: "camera"`) — a virtual camera over the whole stack](video-camera.md#camera-layers-mediatype-camera--a-virtual-camera-over-the-whole-stack)
|
|
12
|
+
- [A sequence's own camera](video-camera.md#a-sequences-own-camera)
|
|
13
|
+
- [Smart Zoom — the camera's Zoom mode](video-camera.md#smart-zoom--the-cameras-zoom-mode)
|
|
14
|
+
- [Drawn cursor](video-camera.md#drawn-cursor--a-screen-recording-with-no-cursor-in-its-picture)
|
|
12
15
|
- [Camera presets (save & load reusable camera moves)](video-camera.md#camera-presets-save--load-reusable-camera-moves)
|
|
13
16
|
- [Camera Rig — a parametric camera move](video-camera.md#camera-rig--a-parametric-camera-move)
|
|
14
17
|
|
|
@@ -18,7 +21,7 @@ A camera clip draws nothing. While the playhead is inside it, it poses a virtual
|
|
|
18
21
|
|
|
19
22
|
Rules:
|
|
20
23
|
|
|
21
|
-
- The camera lives on a channel marked `"kind": "camera"`. **A
|
|
24
|
+
- The camera lives on a channel marked `"kind": "camera"`. **A timeline may have at most one camera channel, and it must be LAST in its `channels` array** (the project's, and each sequence's for its own camera — see below) (channel order is z-order; last renders highest). The editor re-pins it on every write and on load, so a project authored with the camera elsewhere is silently reordered — emit it last to avoid surprising diffs. Put several camera clips on it to chain moves; the clip under the playhead wins, and outside every camera clip the camera is neutral.
|
|
22
25
|
- Camera clips are synthetic (no `url`) and are keyframed with their own property registry, using the ordinary `propertyKeyframes` shape (positions 0–100 % of the clip).
|
|
23
26
|
- Any channel can opt out with `"cameraLocked": true` — it renders exactly as if there were no camera (watermarks, fixed captions).
|
|
24
27
|
- A camera-locked layer is a 2D layer, as in After Effects: it composites in STACK order and never depth-tests against 3D. Above a model layer in the stack it draws over the model (a flash, a title at the comp plane needs no pulling toward the camera); at the very bottom of the stack (a full-frame backdrop) it stays behind everything, the Scene Rig's cyclorama included. 3D layers on either side of a locked layer still occlude each other by depth.
|
|
@@ -105,9 +108,106 @@ A camera item can ride a saved 3D bezier spline. It uses the same world-pixel po
|
|
|
105
108
|
|
|
106
109
|
Animating `camDolly` and `camZoom` in opposite directions is the vertigo effect.
|
|
107
110
|
|
|
108
|
-
**Depth of field** is physical and per layer: each layer's circle of confusion comes from its own distance to the camera (`aperture · |focusDistance − layerDistance| / layerDistance · blurLevel/100`), so a rack focus is just keyframed `camFocusDistance`. All the DOF fields above may be set either as a **base value on the camera item** (`item.camAperture = 40`) or as a keyframe track in `propertyKeyframes` — tracks win at the playhead. `camAperture: 0` or `camBlurLevel: 0` renders byte-identically to DOF off. There are no per-layer `dof*` fields — a project still carrying them animates nothing. Rendered by **`/render-onserver`** and the desktop render; the fast `/render` ignores camera layers along with everything else non-trivial.
|
|
111
|
+
**Depth of field** is physical and per layer: each layer's circle of confusion comes from its own distance to the camera (`aperture · |focusDistance − layerDistance| / layerDistance · blurLevel/100`), so a rack focus is just keyframed `camFocusDistance`. Both distances are **view depth from the camera**, so a camera that moves carries the sharp plane with it: under a dolly the subject at depth `z` (its `panZ`) sits at `perspectiveDistance − camDolly − z` (`perspectiveDistance = (H/2)/tan(14°)`, ≈ 2166 on a 1080-tall frame). To keep a subject sharp through a move, key `camFocusDistance` on the dolly's own keys with that value; under a rig, with the subject's depth along the rig's view at a few points of the move (the scene script does both for `dof.focus`). All the DOF fields above may be set either as a **base value on the camera item** (`item.camAperture = 40`) or as a keyframe track in `propertyKeyframes` — tracks win at the playhead. `camAperture: 0` or `camBlurLevel: 0` renders byte-identically to DOF off. There are no per-layer `dof*` fields — a project still carrying them animates nothing. Rendered by **`/render-onserver`** and the desktop render; the fast `/render` ignores camera layers along with everything else non-trivial.
|
|
109
112
|
|
|
110
|
-
**Screen
|
|
113
|
+
**Screen recordings get a camera clip on the camera channel.** A new recording gets a **Smart Zoom** camera, a clip named `Smart Zoom`; see the Smart Zoom section below. Projects made before it carry a clip named `Auto-zoom` with baked `camZoom`/`camX`/`camY` keys. So for "make the zoom less aggressive", change the Smart Zoom preset, or edit `camZoom` keys on an `Auto-zoom` clip. Never hunt for an adjustment layer.
|
|
114
|
+
|
|
115
|
+
### A sequence's own camera
|
|
116
|
+
|
|
117
|
+
A sequence (`mediaType: "sequence"`) can carry its own camera: a camera clip on a `"kind": "camera"` channel, LAST in
|
|
118
|
+
`sequence.channels`, timed in the sequence's own seconds. It films that sequence's layers exactly as the project camera
|
|
119
|
+
films the top level — its move, its rig, Smart Zoom, depth of field per layer, and `cameraLocked` channels inside the
|
|
120
|
+
sequence stay put. The project camera, if any, then films the sequence as a whole. Each scene of a film can so have its
|
|
121
|
+
own camera while a cross transition still blends the whole scenes. A GreenLight Motion scene script's real camera
|
|
122
|
+
(a scene's `camera` with `move`, `rig`, `zooms`, `keyframes` or `dof`) is built this way, with the scene's backdrop
|
|
123
|
+
channel camera-locked so a zoom or an orbit never shows its edge.
|
|
124
|
+
|
|
125
|
+
### Smart Zoom — the camera's Zoom mode
|
|
126
|
+
|
|
127
|
+
The Movement tab **Zoom** zooms the camera in over **zoom ranges** on the camera layer. They are automatic from a
|
|
128
|
+
screen recording's clicks, or added by hand. Nothing is baked into keyframes:
|
|
129
|
+
|
|
130
|
+
```jsonc
|
|
131
|
+
"cameraMoveMode": "smartZoom", // the Zoom tab (the stored id keeps its first name)
|
|
132
|
+
"smartZoom": {
|
|
133
|
+
"clipId": "<the recording's CLIP id>", // null: no recording, the ranges are placed by hand
|
|
134
|
+
"preset": "studio", // studio|calm|precise|lively|follow
|
|
135
|
+
"ranges": [ // optional. Absent = automatic, from the recording's clicks
|
|
136
|
+
{ "id": "r1", "startMs": 2700, "endMs": 5580, "mode": "follow" },
|
|
137
|
+
{ "id": "z2", "startMs": 9000, "endMs": 11400, "mode": "fixed", "point": { "x": 0.3, "y": 0.7 }, "zoom": 2 }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- **Ranges:**
|
|
143
|
+
- `startMs`/`endMs` are in the **recording's own time** (source ms, so trims and moves keep a zoom on its moment).
|
|
144
|
+
With no recording they are the camera clip's own time.
|
|
145
|
+
- `zoom` is 1–4; absent = the preset's (Studio 1.45).
|
|
146
|
+
- `mode: "follow"` aims at the clicks inside the range. `mode: "fixed"` centres the view exactly on `point` (0..1 of
|
|
147
|
+
the frame).
|
|
148
|
+
- `disabled: true` keeps a range but skips it.
|
|
149
|
+
- Writing `ranges` replaces the automatic list. Copy the automatic ones first (ids `r1`, `r2`…) to keep them.
|
|
150
|
+
- **In the editor:**
|
|
151
|
+
- **Add Zoom** (a timeline toolbar toggle, shown while a camera is in Zoom): with it ON, hovering the camera layer
|
|
152
|
+
shows a ghost zoom with a + and a click adds one there; a block drags to move it. With it OFF (the default) the
|
|
153
|
+
camera layer drags, trims and selects like any clip, and only the blocks answer: a click picks one.
|
|
154
|
+
- Either way, dragging a block's end resizes it and × removes it.
|
|
155
|
+
- With a block picked, dragging the preview moves the zoomed view and scroll or pinch zooms; both make the range
|
|
156
|
+
fixed. Delete removes it.
|
|
157
|
+
- The panel has the Feel (preset), the picked zoom's value, Follow the clicks again, and Back to automatic zooms.
|
|
158
|
+
|
|
159
|
+
- **How it decides:**
|
|
160
|
+
- It reads the recording clip's `recordingMeta` (its clicks and cursor track).
|
|
161
|
+
- It zooms in from 0.3 s before a click until 2.5 s after the last one, and moves part of the way toward each group of clicks.
|
|
162
|
+
- Every move is a spring, so the camera never stops and restarts between clicks.
|
|
163
|
+
- **It follows the recording's items on the timeline.** Trims, splits, moves and speed changes re-aim it on their own. Outside the recording's items, the camera rests at zoom 1, centred. It only acts inside its own camera clip's span.
|
|
164
|
+
- **Presets** (`smartZoom.js` `PRESETS`; an unknown id reads as `studio`):
|
|
165
|
+
|
|
166
|
+
| Preset | Look |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `studio` | Default, like Screen Studio: 1.45×, aims part-way |
|
|
169
|
+
| `calm` | 1.35×, slow; the clicked spot stays put |
|
|
170
|
+
| `precise` | 1.6×, clicks near the middle |
|
|
171
|
+
| `lively` | 1.7×, quick |
|
|
172
|
+
| `follow` | 1.5×, follows the cursor, not only clicks |
|
|
173
|
+
|
|
174
|
+
The recorder's setup panel picks one before the take. Its **No zoom** choice makes no camera at all.
|
|
175
|
+
- **Precedence:** it drives `camZoom`, `camX` and `camY`, so keys on those three render as nothing. `camRoll`, `camDolly`, tilt and depth of field compose as usual. A motion preset that animates zoom or pan flips the mode to `"free"`.
|
|
176
|
+
- **Changing it:**
|
|
177
|
+
- Patch `smartZoom.preset` on the live project to change the look, or `smartZoom.ranges` to place zooms.
|
|
178
|
+
- Set `"cameraMoveMode": "free"` to drive the camera by hand. `smartZoom` stays on the item, so setting the mode
|
|
179
|
+
back (the Zoom tab) restores it.
|
|
180
|
+
- A recording clip without `recordingMeta` (no Input Monitoring) has no automatic zooms, but ranges you add still
|
|
181
|
+
work.
|
|
182
|
+
|
|
183
|
+
### Drawn cursor — a screen recording with no cursor in its picture
|
|
184
|
+
|
|
185
|
+
The macOS recorder (Full screen / Area) and the agent template (`CURSOR=drawn`, its default) record the screen
|
|
186
|
+
**without** the cursor. The editor draws a smooth one back onto the recording's own picture, so effects, masks,
|
|
187
|
+
mockups, transitions and the camera treat it like a recorded cursor. Three fields make it:
|
|
188
|
+
|
|
189
|
+
```jsonc
|
|
190
|
+
// the recording CLIP
|
|
191
|
+
"recordingMeta": { /* clicks + a light path */ "cursorInVideo": false },
|
|
192
|
+
"recordingTrackUrl": "/media/<board folder>/screen_….glcursor", // the hand's full path + cursor shapes, beside the video
|
|
193
|
+
// its timeline ITEM (absent = drawn with the Studio look)
|
|
194
|
+
"recordingCursor": { "enabled": true, "preset": "studio" }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- **`recordingCursor`** fields; any set field overrides the preset's:
|
|
198
|
+
- `enabled`: false hides the cursor.
|
|
199
|
+
- `preset`: `studio`, `calm`, `precise`, `lively` or `follow`. The same ids as Smart Zoom; each sets a cursor look.
|
|
200
|
+
- `size`: 0.25–6, a multiple of the real cursor. Studio is 1.5.
|
|
201
|
+
- `smoothing`: `off`, `spring`, `zerolag` or `spline`.
|
|
202
|
+
- `clickEffect`: `press`, `ring` or `none`.
|
|
203
|
+
- `hideIdleMs`: fade after this long still; 0 = never.
|
|
204
|
+
- `tilt`: 0–40.
|
|
205
|
+
- `motionBlur`: 0–2; 0 = none.
|
|
206
|
+
- **Only for `recordingMeta.cursorInVideo === false`.** A recording that has the cursor in its picture (Windows,
|
|
207
|
+
Linux, Window capture, older takes) ignores `recordingCursor`; the cursor it has can't be removed.
|
|
208
|
+
- The cursor lands exactly on each click, presses on mouseDown and takes the real shape (hand over links, I-beam
|
|
209
|
+
over text) from the track. A missing or failed `.glcursor` falls back to the light path in `recordingMeta`.
|
|
210
|
+
- To change it, edit the live project: GET, set the item's `recordingCursor`, PUT with `expected_version`.
|
|
111
211
|
|
|
112
212
|
### Camera presets (save & load reusable camera moves)
|
|
113
213
|
|
|
@@ -149,7 +249,7 @@ An entry may set `sparse: true`, which drops the usual "seed every motion prop"
|
|
|
149
249
|
|
|
150
250
|
### Camera Rig — a parametric camera move
|
|
151
251
|
|
|
152
|
-
A camera item's `cameraMoveMode` picks one of
|
|
252
|
+
A camera item's `cameraMoveMode` picks one of four exclusive modes: `"free"` (position/rotation keyframes, the default), `"path"` (the hand-drawn motion path), `"rig"` (`item.cameraRig`) and `"smartZoom"` (the Movement tab **Zoom**: zoom ranges, from a screen recording's clicks or placed by hand — see Smart Zoom above). **A rig writes no path**, so switching modes never destroys a drawn curve.
|
|
153
253
|
|
|
154
254
|
```jsonc
|
|
155
255
|
"cameraMoveMode": "rig",
|
|
@@ -204,6 +304,8 @@ Keys use the same shape as every other track: `position` is 0–100 % of the cam
|
|
|
204
304
|
|
|
205
305
|
**`aim` picks what the camera faces.** `"center"` (the default, also when the key is absent) aims at the point the rig is built around — the comp centre, or the followed layer for `follow`. `"path"` faces the direction of travel (orient along path). It suits moves that travel toward or past the subject (`pushIn`, `dolly`, `crane`, `truck`); on an `orbit` or `spiral` it looks along the circle, roughly 90° away from the subject. A rig that does not move (zero `sweep`/`distance`, a `handheld` with `amplitude` 0) keeps the centre aim. `aim` is a choice about the shot's framing, not part of a preset: keep the current value when emitting a preset over an existing rig, as with `targets`.
|
|
206
306
|
|
|
307
|
+
**Flat layers have a front.** Text, shapes, pictures and video are flat cards: an orbit that swings more than about 75° off the front (`startAngle` + `sweep` past ±75°, with `startAngle: 0` = straight ahead) turns them edge-on, and past 90° the camera sees their backs and they read mirrored. Keep orbits of flat layers within about ±70° (`heroOrbit`, `slowReveal`); `turntable`'s full circle is for 3D models.
|
|
308
|
+
|
|
207
309
|
**`tilt` moves the AIM POINT, not the camera's own rotation**, so the subject stays framed as the rig travels. With `"aim": "path"` the same lift tips the gaze above or below the direction of travel. `handheld` jitter is a pure function of `seed` and progress — never wall-clock — so preview and export match frame for frame.
|
|
208
310
|
|
|
209
311
|
**Rendering.** Model layers render in the editor preview, the desktop Download, **`/render-onserver`** / **`/render-onserver-job`**, and **`/render-still`**. The fast `/render` does **not** support them (video/image only). Very heavy assets (≳900 k triangles) render slowly enough that an export can look hung — prefer decimated models.
|
|
@@ -57,6 +57,11 @@ and check every version against them.
|
|
|
57
57
|
- Vary two or three kinds of transition across a video: one kind everywhere looks like a template, a new one at
|
|
58
58
|
every cut looks random.
|
|
59
59
|
- Camera moves are slow and small unless they are the point. Fast motion looks best at 60 fps with motion blur.
|
|
60
|
+
- A real 3D camera earns its place on one or two moments, not every scene: a zoom onto the one thing a scene is about
|
|
61
|
+
(a number, the button a screen recording clicks; 1.3–2×, then back out), or a slow orbit or push that shows depth.
|
|
62
|
+
- Depth needs layers at different distances: a soft shape or picture far behind, the words at the front. Depth of
|
|
63
|
+
field reads only then; keep the words sharp.
|
|
64
|
+
- Keep an orbit in front of flat layers (within about ±70°): text turned further goes edge-on, then mirrored.
|
|
60
65
|
|
|
61
66
|
## Sound
|
|
62
67
|
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
- [3D Depth entry](#3d-depth-entry)
|
|
27
27
|
- [Depth Scan entry](#depth-scan-entry)
|
|
28
28
|
- [Motion Blur entry](#motion-blur-entry)
|
|
29
|
+
- [Posterize Time entry](#posterize-time-entry)
|
|
29
30
|
- [Motion Trail entry](#motion-trail-entry)
|
|
30
31
|
- [Grow Fill entry](#grow-fill-entry)
|
|
31
32
|
- [Rig (Puppet) entry](#rig-puppet-entry)
|
|
@@ -844,6 +845,23 @@ Rim light from the subject's SHAPE. Authored from the DEPTH EFFECTS section.
|
|
|
844
845
|
- Keyframe with `propertyKeyframes.mblurShutter`, `propertyKeyframes.mblurAmount`, and (velocity mode) `propertyKeyframes.mblurMaxBlur` / `propertyKeyframes.mblurFalloff`. `mblurSamples` is not keyframable — an animated tap count steps the kernel per integer.
|
|
845
846
|
- It cannot see motion INSIDE the clip's pixels (a person walking through a static video stays sharp) and does nothing on `model3d` layers.
|
|
846
847
|
|
|
848
|
+
### Posterize Time entry
|
|
849
|
+
|
|
850
|
+
```json
|
|
851
|
+
{ "id": "posterizeTime", "eid": "layer-posterize", "enabled": true, "params": { "ptFps": 12 } }
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
- The layer updates only `ptFps` times a second (1–60) while the project plays at full rate — the stop-motion /
|
|
855
|
+
"on twos" look. It is the layer's CLOCK, not a pixel pass: its video or GIF frame, every keyframe (transform,
|
|
856
|
+
effect params, masks, the motion path and Path Progress), text animators, a nested sequence's whole inner
|
|
857
|
+
timeline, models and particles all hold on each step. Preview (paused and playing), `/render-still` and every
|
|
858
|
+
export agree.
|
|
859
|
+
- **Not posterized:** the layer's sound, the timeline itself (which clip is active, cross transitions), layers
|
|
860
|
+
parented to it or following it, and a camera clip.
|
|
861
|
+
- Keyframe the rate with `propertyKeyframes.ptFps`. The rate is INTEGRATED, so a ramp from 6 to 24 speeds the steps
|
|
862
|
+
up smoothly and time never steps backwards.
|
|
863
|
+
- Costs nothing per frame beyond reading the time; a layer without it pays one array scan.
|
|
864
|
+
|
|
847
865
|
### Motion Trail entry
|
|
848
866
|
|
|
849
867
|
```json
|
|
@@ -109,17 +109,48 @@ A **text** item with a motion path can lay its glyphs out along the curve instea
|
|
|
109
109
|
"textPathTrimStart": 0, // % of curve length — usable window start
|
|
110
110
|
"textPathTrimEnd": 100, // % — window end
|
|
111
111
|
"textPathOffsetY": 0, // px perpendicular (baseline) offset; rows stack along the normal
|
|
112
|
-
"textPathRotate": true
|
|
112
|
+
"textPathRotate": true, // align each unit with the curve tangent
|
|
113
|
+
"textPathCorners": "smooth" // 'smooth' = units turn with the curve | 'sharp' = a unit never straddles a bend
|
|
113
114
|
}
|
|
114
115
|
```
|
|
115
116
|
|
|
116
117
|
- Placement is arc-length parameterised (kerning survives corners) and shares `evaluateObjectMotionPath`, so preview, WebCodecs export and `/render-onserver` agree.
|
|
117
|
-
- **Progress reuses `motionPathT` + its Path Progress track** and moves every glyph along the curve (not the block as one object). A closed untrimmed loop wraps like a marquee; an open or trimmed path extrapolates straight past its end tangents, so text can slide onto/off the curve.
|
|
118
|
+
- **Progress reuses `motionPathT` + its Path Progress track** and moves every glyph along the curve (not the block as one object). A closed untrimmed loop wraps like a marquee; an open or trimmed path extrapolates straight past its end tangents, so text can slide onto/off the curve. The trims move the WINDOW the run is laid out in — they do NOT hide letters (those run on past the window's ends), so keyed trims are not a write-on: use a Text Animator reveal for that. Letters that run past the path's own ends are cut at the text plate's edge — extend the path to run text off the frame.
|
|
118
119
|
- `textPathDirection` decides where the run rests at 0% and which way progress carries it: `forward` parks the text ON the first point and travels to the last, `backward` ends it on the last point and travels back to the first, `center` (default) keeps the legacy middle anchor and follows `textAlign` (start/centre/end of the trimmed window). Multi-line text stacks rows along the curve normal at line-height steps.
|
|
119
120
|
- `textPathTravel` sets how far 100% carries a forward/backward run: `fit` (default) stops when the run's far end reaches the far point, so the whole text stays on the path; `through` keeps going until the leading unit gets there, sliding the text off the end. Travel is computed per row (each row's run has its own length), and a run longer than the trimmed window has nowhere to travel under `fit`. Ignored by `center` (which always slides the full window) and by a wrapping closed loop (always a whole lap).
|
|
121
|
+
- `textPathCorners: "sharp"` treats bends as corners (a turn of 20°+ within ¾ of the type size, or half the unit if wider): a unit never takes an in-between angle on a bend — it belongs wholly to ONE side, turned to that side and laid on that side's straight line carried through the bend. The side is the one it RESTS on (Path Progress 0), so a glide or drift shorter than half a unit never turns a letter at a corner; a unit carried further (a marquee round a square) moves across when its centre passes the middle of the bend. Use it for text running or gliding round a square / ∩ path (rounded corners included); keep `smooth` for curves (an outline, a wave).
|
|
120
122
|
- While `textPathEnabled` is live (path has ≥2 points), `follow` is ignored — the glyph run owns the curve and the layer's own transform stays put. The caption background bar (`textBgEnabled`) is suppressed on a path; split/in-out animations, highlight, randomizer and word timings all compose per unit on the curve.
|
|
121
123
|
- `follow` is NOT required: draw a path, leave `follow: false`, set `textPathEnabled: true`.
|
|
122
124
|
|
|
125
|
+
#### Animated motion path (`motionPathPoints`)
|
|
126
|
+
|
|
127
|
+
The path ITSELF can be keyframed, like a Layer Mask outline: `propertyKeyframes.motionPathPoints` holds snapshots of the
|
|
128
|
+
points, and Follow Path and Text on Path both ride the snapshot at the current frame — so text can follow a tracked
|
|
129
|
+
(RotoScope) outline frame by frame.
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
"propertyKeyframes": {
|
|
133
|
+
"motionPathPoints": [
|
|
134
|
+
{ "position": 0, "value": "[{\"x\":-520,\"y\":0,\"z\":0,\"hIn\":{\"x\":0,\"y\":0,\"z\":0},\"hOut\":{\"x\":0,\"y\":0,\"z\":0},\"manual\":true}, …]", "easing": "Linear" },
|
|
135
|
+
{ "position": 100, "value": "[…the same points, moved…]", "easing": "Linear" }
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **Value:** a JSON string of the points in the WORLD space of `motionPath` (pixels, origin at the comp centre, +y up,
|
|
141
|
+
z toward the viewer, handles as offsets, optional `rx/ry/rz`). `closed`, `follow`, `aim` stay on `item.motionPath`.
|
|
142
|
+
- **Same point count in every key**, as for mask paths; the shorter list wins if they differ. Dense (per-frame) tracks
|
|
143
|
+
use LINEAR keys.
|
|
144
|
+
- **Keep `item.motionPath`** with the same points (any key's): the track is only kept while the layer has a path of
|
|
145
|
+
2+ points, and point counts / Text on Path liveness read it.
|
|
146
|
+
- **In the editor:** MOTION PATH ▸ **Animate path** keys the path at the playhead; from then on every canvas, 3D-view
|
|
147
|
+
or panel edit keys it where the playhead is (adding or deleting an anchor changes every key, split at the same spot
|
|
148
|
+
of the curve). **Copy path** from an ANIMATED source — a RotoScope mask, a keyed shape path or another animated
|
|
149
|
+
motion path (the menu shows `n◆`) — brings all its keys, at their absolute times on this layer's span, cut with an
|
|
150
|
+
interpolated key where the source runs past either end.
|
|
151
|
+
- **Text on Path:** the text plate is sized from the reach of ALL keys once, so the outline can move without the
|
|
152
|
+
texture being reallocated.
|
|
153
|
+
|
|
123
154
|
### Motion-track follow (`motionTracks` + `motionFollow`)
|
|
124
155
|
|
|
125
156
|
A video layer the user has motion-tracked in the editor carries the result on the item as `motionTracks: []` — each track `{ id, name, mode, refTime, region, sourceFps, sourceAspect, segments, samples }`, where `samples` rows are `[tSourceSeconds, cx, cy, scale, rotDeg, confidence]` in the clip's **source** seconds, normalised to the source frame (so a track survives trims, splits and speed changes). **The editor's own analysis has no endpoint** — it runs on decoded frames inside the app. Treat a track the USER made as read-only: preserve it verbatim when copying items, and never hand-edit its samples. An agent can, however, compute tracking data itself (OpenCV in Python) and write a NEW track into the project — the sample layout, the coordinate spaces and a worked recipe are in [tracking-data.md](tracking-data.md).
|
|
@@ -224,6 +224,8 @@ If you need to build the editor state by hand instead of using `/from-clips`, th
|
|
|
224
224
|
|
|
225
225
|
**Preview source.** `"previewOriginal": true` on a clip makes the editor preview play the full-res original instead of its `proxyUrl` — sharper when scale/crop keyframes zoom in, at a heavier decode cost. Preview-only: every render path already uses the original.
|
|
226
226
|
|
|
227
|
+
**Screen recordings.** A recording clip carries `recordingMeta` (its clicks and a light cursor path, in seconds of the file) and may carry `recordingTrackUrl` (the full-rate `.glcursor` beside it) and `recordingMeta.cursorInVideo: false` (no cursor in its picture: the editor draws one). Its camera is a Smart Zoom item, and its item's `recordingCursor` sets the drawn cursor's look. Both are in [video-camera.md](video-camera.md) ("Smart Zoom" and "Drawn cursor").
|
|
228
|
+
|
|
227
229
|
**Layer scale is three fields.** `zoom` is the uniform scale (0.1–20, default 1), and `scaleX` / `scaleY` (same range, both default 1) are per-axis multipliers on top of it — so a layer's drawn width is `zoom × scaleX` and its height `zoom × scaleY`. Use `zoom` alone for an ordinary resize; reach for the other two only to stretch or squash one axis (a wide title bar, a squash-and-stretch bounce). All three are the panel's Transform → Scale row (X / Y / XY) and all three inherit through layer parenting. Every one of them also has a keyframe track (`scale`, `scaleX`, `scaleY` — see [video-keyframes.md](video-keyframes.md)), which multiplies the base field rather than replacing it. Projects written before these existed load unchanged: a missing `scaleX`/`scaleY` is 1.
|
|
228
230
|
|
|
229
231
|
**Layer grade — the static `filter*` fields.** Every visual item carries a CSS-style grade applied to the layer before it composites: `filterBlur` (px, 0–10, default 0), `filterBrightness` / `filterContrast` / `filterSaturation` (%, 0–200, default 100), `filterHueRotate` (deg, 0–360, default 0), `filterGrayscale` / `filterSepia` / `filterInvert` (%, 0–100, default 0; Invert at 100 is a full negative). These are the layer's own base values; the matching keyframe tracks (`blur`, `brightness`, `contrast`, `hue-rotate`, `saturate`, `grayscale`, `sepia`, `invert`) **override** the static field while the track exists rather than multiplying it, so key one or set the other, not both. They are a cheap per-layer grade and independent of the Overlay Effects chain — reach for `effects[]` when you need a real grade (curves, LUT, colour balance).
|
|
@@ -254,13 +256,37 @@ Agent rules: when the user asks for "the project's colours", "brand colours" or
|
|
|
254
256
|
{ "colorTokens": ["#8b5cf6", "#0ea5e9", "rgba(0, 0, 0, 0.6)"] }
|
|
255
257
|
```
|
|
256
258
|
|
|
259
|
+
**Photo slots (pictures people may replace).** A picture layer (an item whose clip is `mediaType: "image"`) can carry
|
|
260
|
+
`"photoSlot": { "fill": "cover" | "fit", "focus": [x, y], "takes": "image" | "media", "series": true | false, "note": "…" }`
|
|
261
|
+
— a MARK only: no render path reads it. `fill` = cover the layer's box (edges may be cut) or show all of the photo;
|
|
262
|
+
`focus` = the point (0–1 inside the photo) a Cover crop keeps in view; `takes` = a photo, or a photo or a video;
|
|
263
|
+
`series: true` = one of the project's numbered slots, filled in PLAY order (by start time, not track order), `false` = a
|
|
264
|
+
slot on its own (a logo); `note` (≤ 300 chars) = what belongs there, for an agent. The box is the layer's own frame, so
|
|
265
|
+
moving or resizing the layer moves the slot. Optional `"photoSlotSettings": { "label": "Photos", "ifFewer": "reuse" | "keep" }`
|
|
266
|
+
at the `data` root names the series and says what slots with no photo show when people bring fewer photos than slots
|
|
267
|
+
(omitted while default). The timeline marks slot layers `SLOT n`; the user sets them in Export for agents ▸ Photo slots.
|
|
268
|
+
Agent rules: to let people put their own photo in a picture, add `photoSlot` to its item (`{}` is enough — every field
|
|
269
|
+
has a default); keep the user's existing marks when you rebuild a layer; a slot is never empty — its current picture is
|
|
270
|
+
the example until someone replaces it.
|
|
271
|
+
|
|
257
272
|
**Project background.** Set `"background": { …backgroundConfig… }` at the `data` root to fill the stage behind every channel — a solid colour, gradient, GLSL shader, pattern, or image. It takes the same `backgroundConfig` object as shape backgrounds; the full schema, the gradient/shader preset ids, and the shader authoring rules live in the **Backgrounds (project & shape)** section below. Omit it (or use `"type": "transparent"`) for a transparent stage. Baked by **`/render-onserver`**; the fast `/render` ignores it. A bare colour string (`"background": "#3366cc"`) is coerced to `{"type": "solid", "solidColor": …}`; prefer the explicit object.
|
|
258
273
|
|
|
259
274
|
**One live item per channel.** Treat every channel as one timeline track, not as a container of simultaneously composited layers. At any timeline time, a channel may have at most one active item. If two items' visible windows overlap, only one is guaranteed to render (the later-starting item commonly wins) and the save API does not reject the project. For an unsnapped item, its ordinary visible window is `[freeStart, freeStart + (trimEnd - trimStart))`; validate these windows before `PUT`. Put every simultaneously visible image, shape, text block, motion layer, or other visual on a separate channel. This rule also applies inside sequence timelines. Reuse one channel for consecutive, non-overlapping captions or shots only.
|
|
260
275
|
|
|
261
276
|
**Keep unsnapped items sorted by `freeStart`.** On an `unsnapped` channel the editor treats the item BEFORE a clip in the `items` array as "the clip before it" — cross transitions, the seam button and the layout's overlap all read array neighbours, not time neighbours. The editor re-sorts on every insert, move and paste; an API write does not. After adding or moving items, sort each unsnapped channel's `items` by `freeStart` before `PUT`. An out-of-order array looks fine until the user adds a cross transition: the clip is then snapped against the wrong neighbour and jumps elsewhere on the timeline (e.g. past the end), so it appears to vanish.
|
|
262
277
|
|
|
263
|
-
**Motion blur (render-only).** Set `"motionBlurEnabled": true` at the `data` root — a project-wide switch, not per layer — to bake cinematic
|
|
278
|
+
**Motion blur (render-only).** Set `"motionBlurEnabled": true` at the `data` root — a project-wide switch, not per layer — to bake cinematic shutter blur into full renders. The choice is made per output **frame**, not per layer: a frame where motion is detected is rendered as a multi-sample accumulation of the **whole composition** across a shutter window centred on the frame, so every layer that changes inside that window blurs (keyframed layers, text animators, motion layers, transitions, particles), not only the one that triggered it. A frame with no detected motion renders once and stays sharp, so cost only grows where motion exists.
|
|
279
|
+
|
|
280
|
+
A frame is accumulated when any of these change across its shutter window:
|
|
281
|
+
|
|
282
|
+
- the camera layer's pose (any camera track), or the frame enters or leaves a camera clip;
|
|
283
|
+
- a keyframed layer's resolved pose: its position, rotation or scale as drawn, from any track that places it (`translateX/Y/Z`, `xPercent`/`yPercent`, `rotate`, `rotateX/Y`, `scale`, `scaleX`/`scaleY`), and a live motion path's `motionPathT`, `motionPathOffsetX/Y/Z` or keyed path shape (adjustment-layer pan/zoom included);
|
|
284
|
+
- the parent of a parented layer, or the tracked point of a `motionFollow` layer;
|
|
285
|
+
- a `video` item with a speed curve (any `speedPoints` value other than 1): every frame of that item, the 1× stretches included;
|
|
286
|
+
- a particle system, or an `html` clip drawn live (the render default): every frame they are on screen;
|
|
287
|
+
- the inner timeline of a `sequence` clip, by the same rules, down to the 4-level nesting limit (parenting and `motionFollow` links inside a sequence are not read).
|
|
288
|
+
|
|
289
|
+
Nothing else triggers a frame by itself: text animators, MotionFast `motion` layers, 3D model animation, enter/exit and cross transitions, keyed opacity or effect params, and movement inside footage at normal speed. They blur on frames something in the list triggers and are sharp everywhere else.
|
|
264
290
|
|
|
265
291
|
**Strength** is `"motionBlurShutter"` — a **shutter angle in degrees** alongside `motionBlurEnabled`. Only these four values are meaningful; anything else (missing, non-numeric, an off-table angle) snaps to the nearest level, so `180` is the effective default:
|
|
266
292
|
|
|
@@ -271,7 +297,7 @@ Agent rules: when the user asks for "the project's colours", "brand colours" or
|
|
|
271
297
|
| `270` | Strong | 24 | ~24× |
|
|
272
298
|
| `360` | Max | 32 | ~32× |
|
|
273
299
|
|
|
274
|
-
The angle is how much of each frame interval the exposure covers, so wider = more blur *and* proportionally slower. The field is only written when blur is on, so projects that never enabled it round-trip unchanged. Table lives in `frontend/src/components/videoEditor/motionBlurLevels.js`. Honored by **`/render-onserver`**, **`/render-onserver-job`** and the desktop Download/Render — **ignored by the fast `/render`** and never applied in the editor preview (render-only by design). Worth enabling for projects with fast zooms/pans that would otherwise strobe.
|
|
300
|
+
The angle is how much of each frame interval the exposure covers, so wider = more blur *and* proportionally slower. The field is only written when blur is on, so projects that never enabled it round-trip unchanged. Table lives in `frontend/src/components/videoEditor/motionBlurLevels.js`. Honored by **`/render-onserver`**, **`/render-onserver-job`** and the desktop Download/Render — **ignored by the fast `/render`** and never applied in the editor preview (render-only by design). Worth enabling for projects with fast zooms/pans that would otherwise strobe. Renders get proportionally slower on accumulated frames (~16× each at the default), and a speed-ramped clip, a particle system or a live html clip makes every frame it covers an accumulated one.
|
|
275
301
|
|
|
276
302
|
**Renderer.** Every project renders on the engine the app is set to — one app-wide setting, not a project field. Read it with `GET /api/video-editor/engine` (`engine`: `"webgl"`, the default, or `"webgpu"`). Preview and the desktop Download use it; the headless renders (`/render-onserver`, `/render-onserver-job`, `/render-still`) take it as the request's `"renderer"` field, which defaults to `"webgl"`, so pass the engine there to render what the editor shows. It matters wherever shading is written by hand rather than picked from a list: an uploaded Advanced Transition package is authored for one engine and is refused on the other, and a **custom** adjustment/layer effect (`"id": "custom"`) cannot run on WebGPU at all — its body is compiled at runtime, so the effect is skipped with a reason. Building a new template or transition: [custom-builds.md](custom-builds.md).
|
|
277
303
|
|
|
@@ -387,6 +413,9 @@ GET /api/moodboards/{board_id}/video-projects/{project_id}/glvideo?models=<JSON
|
|
|
387
413
|
project) or to a chat: the GreenLight Motion connector's `open_video` page reads it in the user's browser, and
|
|
388
414
|
`get_video` / `edit_video` change it there.
|
|
389
415
|
|
|
416
|
+
- The window's **Photo slots** strip writes each picture's `photoSlot` mark into the project (one undo step, saved before
|
|
417
|
+
the export), and every slot becomes an `image` field of the template (its clip's `url` + `proxyUrl`); the marks travel
|
|
418
|
+
inside `project.json` with their layers.
|
|
390
419
|
- `template.fields[]` = `{ key, label, type: text|color|image|number|select|toggle, targets: [{ itemId | clipId | (neither = project), path }] }`
|
|
391
420
|
— e.g. `{ "key": "headline", "label": "Headline", "type": "text", "targets": [{ "itemId": "<item id>", "path": "textContent" }] }`,
|
|
392
421
|
a shape colour `path: "shapeFillColor"`, the background `{ "path": "background.solidColor" }`. Omit `template` to keep
|
|
@@ -403,7 +432,7 @@ GET /api/moodboards/{board_id}/video-projects/{project_id}/glvideo?models=<JSON
|
|
|
403
432
|
|
|
404
433
|
### Interface scenes (built-in animated scenes — "the UI kit")
|
|
405
434
|
|
|
406
|
-
The editor's **Presets** tab also lists the **Interface scenes** library (called GL Motion before 14.0.25):
|
|
435
|
+
The editor's **Presets** tab also lists the **Interface scenes** library (called GL Motion before 14.0.25): 172 ready-made animated scenes (controls, cards, charts, checkout, coupons, countdowns, galleries…) in one style, all transparent (they sit over the video). Search and category chips filter them; Light / Dark and an accent recolour them. A card's **HTML clip** button inserts the scene as one standalone transparent HTML page, saved into the board's media (`ui-kit/<id>.html`) and placed as an HTML clip (`mediaType: "html"`) at the scene's own length.
|
|
407
436
|
|
|
408
437
|
For a **whole film** — a story and its shots with the user's copy and pictures, a scenario card on the board, a voice-over, sound design and a render; with a GLEA delivery the film finished in a Video Editor project (a tutorial's interface as HTML scene clips, other films built with the editor's own layers) and rendered from it — use the `motion-film` workflow on the `greenlight` MCP server ([SKILL.md](../SKILL.md#ai-workflows-the-greenlight-mcp-server)) instead of inserting scenes one by one.
|
|
409
438
|
|