greenlight-motion 0.3.4 → 0.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 +15 -10
- package/dist/bundle-inputs.json +23 -1
- package/dist/greenlight-motion-script.mjs +549 -80
- package/dist/greenlight-motion.js +1078 -747
- package/dist/greenlight-motion.mjs +42670 -40592
- package/dist/script-inputs.json +3 -1
- package/package.json +1 -1
- package/schema/capabilities.json +1 -1
- package/skill/SKILL.md +10 -10
- package/skill/references/scene-script.md +2 -2
- package/skill/references/video-3d.md +107 -100
- package/skill/references/video-backgrounds.md +89 -41
- package/skill/references/video-camera.md +158 -83
- package/skill/references/video-design.md +39 -7
- package/skill/references/video-effects.md +936 -620
- package/skill/references/video-grid.md +24 -24
- package/skill/references/video-keyframes.md +145 -52
- package/skill/references/video-particles.md +48 -29
- package/skill/references/video-paths.md +69 -65
- package/skill/references/video-projects.md +159 -89
- package/skill/references/video-shapes.md +94 -31
- package/skill/references/video-text-kinetic.md +78 -27
- package/skill/references/video-text-presets.md +738 -17
- package/skill/references/video-text.md +171 -79
- package/skill/references/video-transitions.md +72 -17
|
@@ -4,18 +4,26 @@
|
|
|
4
4
|
> here. `frontend/src/…` paths are files of the engine's public source repository. MotionFast templates (`mediaType: "motion"`)
|
|
5
5
|
> stay in GreenLight Dash.
|
|
6
6
|
|
|
7
|
-
# GreenLight Dash — Camera layers, paths, rigs and presets
|
|
7
|
+
# GreenLight Dash — Camera layers, depth of field, paths, rigs and presets
|
|
8
8
|
|
|
9
9
|
> Read [video-projects.md](video-projects.md) for the shared project, clip, channel, timing and versioned-write contracts. Load only the sections needed for the task.
|
|
10
10
|
|
|
11
11
|
## Contents
|
|
12
12
|
|
|
13
|
-
- [Camera layers (`mediaType: "camera"`) — a virtual camera over the whole stack](
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
13
|
+
- [Camera layers (`mediaType: "camera"`) — a virtual camera over the whole stack](#camera-layers-mediatype-camera--a-virtual-camera-over-the-whole-stack)
|
|
14
|
+
- [Camera properties](#camera-properties)
|
|
15
|
+
- [Camera Motion Paths (`cameraPath` + `camPathT`)](#camera-motion-paths-camerapath--campatht)
|
|
16
|
+
- [Depth of field](#depth-of-field) — per layer for flat layers, per pixel for model layers, the cyclorama and occluding plates
|
|
17
|
+
- [Screen recordings' camera](#screen-recordings-camera)
|
|
18
|
+
- [A sequence's own camera](#a-sequences-own-camera)
|
|
19
|
+
- [Smart Zoom — the camera's Zoom mode](#smart-zoom--the-cameras-zoom-mode)
|
|
20
|
+
- [Drawn cursor](#drawn-cursor--a-screen-recording-with-no-cursor-in-its-picture)
|
|
21
|
+
- [Camera presets (save & load reusable camera moves)](#camera-presets-save--load-reusable-camera-moves)
|
|
22
|
+
- [Built-in camera moves](#built-in-camera-moves)
|
|
23
|
+
- [Camera Rig — a parametric camera move](#camera-rig--a-parametric-camera-move)
|
|
24
|
+
- [Rig parameters](#rig-parameters)
|
|
25
|
+
- [Built-in rigs](#built-in-rigs)
|
|
26
|
+
- [Mode precedence](#mode-precedence)
|
|
19
27
|
|
|
20
28
|
### Camera layers (`mediaType: "camera"`) — a virtual camera over the whole stack
|
|
21
29
|
|
|
@@ -23,12 +31,13 @@ A camera clip draws nothing. While the playhead is inside it, it poses a virtual
|
|
|
23
31
|
|
|
24
32
|
Rules:
|
|
25
33
|
|
|
26
|
-
- 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
|
|
27
|
-
- Camera clips are synthetic (no `url`) and are keyframed with their own property registry, using the ordinary `propertyKeyframes` shape
|
|
34
|
+
- 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 [A sequence's own camera](#a-sequences-own-camera)); channel order is z-order, so 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.
|
|
35
|
+
- Camera clips are synthetic (no `url`) and are keyframed with their own property registry (below), using the ordinary `propertyKeyframes` shape: `position` 0–100 % of the clip, `value` a string.
|
|
28
36
|
- Any channel can opt out with `"cameraLocked": true` — it renders exactly as if there were no camera (watermarks, fixed captions).
|
|
37
|
+
- In a 3D Scene the Camera layer films the world too, depth of field included, unless the Scene names its own `rig.scene.camera` (world3d.md (GreenLight Dash only)).
|
|
29
38
|
- 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.
|
|
30
39
|
|
|
31
|
-
```
|
|
40
|
+
```jsonc
|
|
32
41
|
{
|
|
33
42
|
"clips": [
|
|
34
43
|
{ "id": "cam1", "source": "LOCAL", "mediaType": "camera", "url": "",
|
|
@@ -50,7 +59,9 @@ Rules:
|
|
|
50
59
|
}
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
Camera properties
|
|
62
|
+
#### Camera properties
|
|
63
|
+
|
|
64
|
+
Every field below can be a **base value on the camera item** (`item.camAperture = 40`) or a keyframe track in `propertyKeyframes`; a track wins at the playhead.
|
|
54
65
|
|
|
55
66
|
| Property | Unit | Neutral | Meaning |
|
|
56
67
|
| --- | --- | --- | --- |
|
|
@@ -58,31 +69,39 @@ Camera properties (the `CAMERA_KF_PROPS` registry in `frontend/src/components/vi
|
|
|
58
69
|
| `camX` | % of frame width | `0` | Positive pans the camera **right**, so content slides left. |
|
|
59
70
|
| `camY` | % of frame height | `0` | Positive pans the camera **down**, so content slides up. |
|
|
60
71
|
| `camRoll` | deg | `0` | Cants the horizon clockwise (dutch angle). |
|
|
61
|
-
| `camDolly` | px | `0` | Moves the viewpoint in
|
|
62
|
-
| `camTiltX` / `camTiltY` | deg | `0` | Pitch / yaw. |
|
|
72
|
+
| `camDolly` | px | `0` | Moves the viewpoint in (positive) or out. Unlike `camZoom` this DOES change perspective — layers at different `panZ`/`translateZ` separate. |
|
|
73
|
+
| `camTiltX` / `camTiltY` | deg | `0` | Pitch / yaw, any angle. Past ±90° the camera orbits behind the comp plane and sees the layers from the back, mirrored. |
|
|
63
74
|
| `camDofEnabled` | 0/1 | `0` | Depth-of-field master switch. |
|
|
64
75
|
| `camFocusDistance` | px | `0` | Distance to the sharp plane. `0` = the comp plane (`(H/2)/tan(14°)`), so it survives a canvas resize. |
|
|
65
|
-
| `camAperture` | px | `24` | Larger = shallower. `0` disables the blur entirely. |
|
|
76
|
+
| `camAperture` | px | `24` | The blur's radius in px (not its width) for something far behind the focus plane, at `camBlurLevel` 100. Larger = shallower. `0` disables the blur entirely. |
|
|
66
77
|
| `camBlurLevel` | % | `100` | Scales the computed circle of confusion. `0` disables. |
|
|
67
78
|
| `camHighlightGain` | % | `0` | Bloom on bright pixels, so highlights form bokeh discs. |
|
|
68
79
|
| `camFocusX` / `camFocusY` | % | `50` | Focus point. Also the centre of the focus area when one is set. |
|
|
69
80
|
| `camFocusWidth` | % of frame width | `0` | `0` = the point is only a depth probe. Above `0` a screen-space region around the point stays sharp and eases to the layer's full blur by its edge. |
|
|
70
|
-
| `camPathT` | % | `0` | Progress along `cameraPath
|
|
81
|
+
| `camPathT` | % | `0` | Progress along `cameraPath` or a Camera Rig's move (0–100). |
|
|
71
82
|
| `camPathOffsetX/Y/Z` | px | `0` | Constant displacement from `cameraPath`, on world axes (+X right, +Y **down**, +Z toward the viewer). The aim is computed **after** it, so a camera pushed off a shared path still faces its target. No-op unless the path is live. |
|
|
83
|
+
| `camFollowTarget` | 1-based index | `1` | Which `cameraRig.targets` entry a `follow` rig looks at (see [Camera Rig](#camera-rig--a-parametric-camera-move)). |
|
|
84
|
+
|
|
85
|
+
Animating `camDolly` and `camZoom` in opposite directions is the vertigo effect.
|
|
72
86
|
|
|
73
87
|
#### Camera Motion Paths (`cameraPath` + `camPathT`)
|
|
74
88
|
|
|
75
|
-
A camera item can ride a saved 3D bezier spline. It uses the same world-pixel point/handle convention as an object Motion Path: origin at comp centre, `x` right, `y` up, `z` toward the viewer; `hIn`/`hOut` are offsets from the anchor
|
|
89
|
+
A camera item can ride a saved 3D bezier spline. It uses the same world-pixel point/handle convention as an [object Motion Path](video-paths.md#object-motion-paths-motionpath--motionpatht): origin at comp centre, `x` right, `y` up, `z` toward the viewer; `hIn`/`hOut` are offsets from the anchor. **Write both handles and `manual: true` on every anchor** — an anchor with no handles is a corner the renderer flies straight through, since nothing auto-smooths a stored path but a hand edit in the editor. Compute them with the [tangent rule](video-shapes.md#writing-the-handles) (each handle also carries `z`).
|
|
76
90
|
|
|
77
91
|
```json
|
|
78
92
|
{
|
|
93
|
+
"cameraMoveMode": "path",
|
|
79
94
|
"cameraPath": {
|
|
80
95
|
"points": [
|
|
81
|
-
{ "x": -700, "y": 120, "z": 1850
|
|
96
|
+
{ "x": -700, "y": 120, "z": 1850,
|
|
97
|
+
"hIn": { "x": -233.3, "y": 13.3, "z": 133.3 },
|
|
98
|
+
"hOut": { "x": 233.3, "y": -13.3, "z": -133.3 }, "manual": true },
|
|
82
99
|
{ "x": 0, "y": 80, "z": 1450,
|
|
83
100
|
"hIn": { "x": -220, "y": 0, "z": 120 },
|
|
84
101
|
"hOut": { "x": 220, "y": 0, "z": -120 }, "manual": true },
|
|
85
|
-
{ "x": 620, "y": -100, "z": 1100
|
|
102
|
+
{ "x": 620, "y": -100, "z": 1100,
|
|
103
|
+
"hIn": { "x": -206.7, "y": 60, "z": 116.7 },
|
|
104
|
+
"hOut": { "x": 206.7, "y": -60, "z": -116.7 }, "manual": true }
|
|
86
105
|
],
|
|
87
106
|
"closed": false,
|
|
88
107
|
"follow": true,
|
|
@@ -99,20 +118,33 @@ A camera item can ride a saved 3D bezier spline. It uses the same world-pixel po
|
|
|
99
118
|
}
|
|
100
119
|
```
|
|
101
120
|
|
|
102
|
-
- A camera path is live
|
|
103
|
-
- `camPathT` is 0–100
|
|
104
|
-
- `aim: "tangent"` looks along the curve. `aim: "target"` looks at `targetId
|
|
105
|
-
-
|
|
121
|
+
- **A path drives the camera only while `cameraMoveMode` is `"path"` or absent**, and the path is live: at least two points and `follow: true`, or one point on `aim: "captured"`, which holds the camera there. An explicit `"free"`, `"rig"` or `"smartZoom"` ignores the path. Applying a camera preset that animates the pose writes `"free"`, so set `"cameraMoveMode": "path"` whenever you add a path to an existing camera.
|
|
122
|
+
- Provide a `camPathT` progress track (a `Linear` 0→100 is the sensible default) unless the camera is intentionally parked on one point. `camPathT` is 0–100; open paths run first→last, closed paths wrap 100 back to point one.
|
|
123
|
+
- `aim: "tangent"` looks along the curve. `aim: "target"` looks at `targetId` (a timeline item id), or the composition centre when `targetId` is null/missing. `aim: "free"` leaves orientation to `camTiltX`, `camTiltY`, and `camRoll` base values/keyframes. `aim: "captured"` reads it from each point: `rx`, `ry`, `rz` are Tilt X, Tilt Y and Roll in degrees, interpolated between points, and the `camTilt`/`camRoll` tracks are ignored.
|
|
124
|
+
- **Timing a path by points** — segments are walked at a uniform rate, so point `i` of `n` is reached where `camPathT` equals `i / (n - 1) * 100` (`i / n * 100` when closed). To land on a point at a chosen moment, key that value at that clip position, with `"easing": "Linear"`.
|
|
125
|
+
- Following owns the camera's position on the curve. Lens zoom and depth of field remain independent; free aim also keeps the manual orientation tracks authoritative.
|
|
106
126
|
- **Offset from path** — `camPathOffsetX/Y/Z` (table above). Two cameras, or a camera and a layer, can then ride one shared curve at a fixed distance apart. Keyframable like every other camera field.
|
|
107
|
-
- **Reusing another curve**: layer motion paths and camera paths share one world-pixel space, so `points`/`closed` transfer literally between them.
|
|
127
|
+
- **Reusing another curve**: layer motion paths and camera paths share one world-pixel space, so `points`/`closed` transfer literally between them.
|
|
108
128
|
- `keyframesPerPoint` / `keyframesPerPointCount` / `keyframesPerPointClosed` are the same "one key per anchor" bookkeeping the object path uses: one evenly-spaced `camPathT` key per anchor (a closed path adding the final 100 key back to anchor one), so the camera reaches each point on a keyframe and the pacing between points can be shaped per segment. Omit it and author any valid progress track directly.
|
|
109
129
|
- The path lives on the camera **item**, so it trims, duplicates, saves, undoes, and renders with that camera clip.
|
|
110
130
|
|
|
111
|
-
|
|
131
|
+
#### Depth of field
|
|
112
132
|
|
|
113
|
-
|
|
133
|
+
Depth of field is physical and needs `camDofEnabled: 1`. Each layer's circle of confusion comes from its distance `d` to the camera, as a blur radius in px: `CoC = camAperture · |camFocusDistance − d| / d · camBlurLevel/100`, capped at 64 px. A rack focus is therefore just a keyframed `camFocusDistance`.
|
|
114
134
|
|
|
115
|
-
|
|
135
|
+
- **Flat layers** (text, shapes, pictures, video) take one CoC per layer: the depth of their plane.
|
|
136
|
+
- **Model layers, the Scene Rig's cyclorama and occluding shadow plates blur per pixel.** They hand the compositor their depth buffer, and every pixel blurs by the CoC of its own distance (capped at 64 px, still held sharp inside the focus area). A character in focus stays sharp in the same model as a wall metres behind it, so a whole set, backdrop included, can live in one model layer.
|
|
137
|
+
- **The gather is depth-aware:** a blurred background never smears over a sharp foreground edge, a sharp foreground leaves no halo on the blurred background, and a foreground in front of the focus plane blurs out over what is behind it and past its own silhouette. Taps read the picture prefiltered, so thin bright detail under a wide blur (lettering far behind the focus) averages instead of sparkling; flat layers get the same.
|
|
138
|
+
- **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, key it with the subject's depth along the rig's view at a few points of the move (a GreenLight Motion scene script's `dof.focus` does both).
|
|
139
|
+
- **In a 3D Scene** the same numbers set Final's thin lens, so Final blurs as Draft does (see world3d.md (GreenLight Dash only)).
|
|
140
|
+
- **Matching a real lens.** For a lens of focal length `f` mm at f-number `N` on a sensor `S` mm wide, with the focus `F` in px (a composition pixel is a millimetre of a 3D Scene) and the frame `W` px wide: `camAperture = f² · W / (2 · N · (F − f) · S)`, at `camBlurLevel` 100. Past the field's 300, raise `camBlurLevel` instead (a `.blend` import keys it this way).
|
|
141
|
+
- **Focus area:** `camFocusWidth` above 0 keeps a screen region around `camFocusX`/`camFocusY` sharp whatever its depth, easing to the full blur by its edge.
|
|
142
|
+
- `camAperture: 0` or `camBlurLevel: 0` renders byte-identically to DOF off. There are no per-layer `dof*` fields — `dof*` fields on a layer animate nothing. The Focus presets in [Built-in camera moves](#built-in-camera-moves) are ready rack-focus moves.
|
|
143
|
+
- Rendered by **`/render-onserver`** and the desktop render; the fast `/render` ignores camera layers along with everything else non-trivial.
|
|
144
|
+
|
|
145
|
+
#### Screen recordings' camera
|
|
146
|
+
|
|
147
|
+
A screen recording gets a **Smart Zoom** camera, a clip named `Smart Zoom` (see [Smart Zoom](#smart-zoom--the-cameras-zoom-mode)). A recording's camera can instead be 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.
|
|
116
148
|
|
|
117
149
|
### A sequence's own camera
|
|
118
150
|
|
|
@@ -126,11 +158,11 @@ channel camera-locked so a zoom or an orbit never shows its edge.
|
|
|
126
158
|
|
|
127
159
|
### Smart Zoom — the camera's Zoom mode
|
|
128
160
|
|
|
129
|
-
|
|
130
|
-
screen recording's clicks, or
|
|
161
|
+
Smart Zoom (`"cameraMoveMode": "smartZoom"`) zooms the camera in over **zoom ranges** on the camera layer. They are
|
|
162
|
+
automatic from a screen recording's clicks, or placed by hand. Nothing is baked into keyframes:
|
|
131
163
|
|
|
132
164
|
```jsonc
|
|
133
|
-
"cameraMoveMode": "smartZoom",
|
|
165
|
+
"cameraMoveMode": "smartZoom",
|
|
134
166
|
"smartZoom": {
|
|
135
167
|
"clipId": "<the recording's CLIP id>", // null: no recording, the ranges are placed by hand
|
|
136
168
|
"preset": "studio", // studio|calm|precise|lively|follow
|
|
@@ -143,27 +175,20 @@ screen recording's clicks, or added by hand. Nothing is baked into keyframes:
|
|
|
143
175
|
|
|
144
176
|
- **Ranges:**
|
|
145
177
|
- `startMs`/`endMs` are in the **recording's own time** (source ms, so trims and moves keep a zoom on its moment).
|
|
146
|
-
With no recording they are the camera clip's own time.
|
|
178
|
+
With no recording they are the camera clip's own time. A range shorter than 100 ms is dropped.
|
|
147
179
|
- `zoom` is 1–4; absent = the preset's (Studio 1.45).
|
|
148
180
|
- `mode: "follow"` aims at the clicks inside the range. `mode: "fixed"` centres the view exactly on `point` (0..1 of
|
|
149
181
|
the frame).
|
|
150
182
|
- `disabled: true` keeps a range but skips it.
|
|
151
183
|
- Writing `ranges` replaces the automatic list. Copy the automatic ones first (ids `r1`, `r2`…) to keep them.
|
|
152
|
-
|
|
153
|
-
- **Add Zoom** (a timeline toolbar toggle, shown while a camera is in Zoom): with it ON, hovering the camera layer
|
|
154
|
-
shows a ghost zoom with a + and a click adds one there; a block drags to move it. With it OFF (the default) the
|
|
155
|
-
camera layer drags, trims and selects like any clip, and only the blocks answer: a click picks one.
|
|
156
|
-
- Either way, dragging a block's end resizes it and × removes it.
|
|
157
|
-
- With a block picked, dragging the preview moves the zoomed view and scroll or pinch zooms; both make the range
|
|
158
|
-
fixed. Delete removes it.
|
|
159
|
-
- The panel has the Feel (preset), the picked zoom's value, Follow the clicks again, and Back to automatic zooms.
|
|
184
|
+
Removing `ranges` goes back to the automatic zooms.
|
|
160
185
|
|
|
161
186
|
- **How it decides:**
|
|
162
187
|
- It reads the recording clip's `recordingMeta` (its clicks and cursor track).
|
|
163
188
|
- 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.
|
|
164
189
|
- Every move is a spring, so the camera never stops and restarts between clicks.
|
|
165
190
|
- **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.
|
|
166
|
-
- **Presets** (
|
|
191
|
+
- **Presets** (an unknown id reads as `studio`):
|
|
167
192
|
|
|
168
193
|
| Preset | Look |
|
|
169
194
|
|---|---|
|
|
@@ -173,12 +198,12 @@ screen recording's clicks, or added by hand. Nothing is baked into keyframes:
|
|
|
173
198
|
| `lively` | 1.7×, quick |
|
|
174
199
|
| `follow` | 1.5×, follows the cursor, not only clicks |
|
|
175
200
|
|
|
176
|
-
The recorder
|
|
201
|
+
The recorder sets one before the take; a take recorded with no zoom gets no camera at all.
|
|
177
202
|
- **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"`.
|
|
178
203
|
- **Changing it:**
|
|
179
204
|
- Patch `smartZoom.preset` on the live project to change the look, or `smartZoom.ranges` to place zooms.
|
|
180
205
|
- Set `"cameraMoveMode": "free"` to drive the camera by hand. `smartZoom` stays on the item, so setting the mode
|
|
181
|
-
back
|
|
206
|
+
back to `"smartZoom"` restores it.
|
|
182
207
|
- A recording clip without `recordingMeta` (no Input Monitoring) has no automatic zooms, but ranges you add still
|
|
183
208
|
work.
|
|
184
209
|
|
|
@@ -206,7 +231,7 @@ mockups, transitions and the camera treat it like a recorded cursor. Three field
|
|
|
206
231
|
- `tilt`: 0–40.
|
|
207
232
|
- `motionBlur`: 0–2; 0 = none.
|
|
208
233
|
- **Only for `recordingMeta.cursorInVideo === false`.** A recording that has the cursor in its picture (Windows,
|
|
209
|
-
Linux, Window capture
|
|
234
|
+
Linux, Window capture) ignores `recordingCursor`; the cursor it has can't be removed.
|
|
210
235
|
- The cursor lands exactly on each click, presses on mouseDown and takes the real shape (hand over links, I-beam
|
|
211
236
|
over text) from the track. A missing or failed `.glcursor` falls back to the light path in `recordingMeta`.
|
|
212
237
|
- To change it, edit the live project: GET, set the item's `recordingCursor`, PUT with `expected_version`.
|
|
@@ -224,34 +249,65 @@ Global JSON files under `media/camera-presets/` — a separate directory from `v
|
|
|
224
249
|
"base": { "camDofEnabled": 1, "camAperture": 40, "camFocusWidth": 35 } }
|
|
225
250
|
```
|
|
226
251
|
|
|
227
|
-
`base` is optional and holds the preset's **un-keyframed** camera settings — depth of
|
|
228
|
-
field is normally dialled in as static values rather than animated, so saving only
|
|
229
|
-
`keyframes` would lose the whole look. On apply, `base` is written onto the camera item
|
|
230
|
-
as base fields while `keyframes` become its `propertyKeyframes`; a property that is
|
|
231
|
-
keyframed never appears in `base` (its track already carries it). A preset may have
|
|
232
|
-
`base` and no `keyframes` — a pure lens/DOF setup with no motion.
|
|
252
|
+
`base` is optional and holds the preset's **un-keyframed** camera settings — depth of field is normally dialled in as static values rather than animated, so saving only `keyframes` would lose the whole look. On apply, `base` is written onto the camera item as base fields while `keyframes` become its `propertyKeyframes`; a property that is keyframed never appears in `base` (its track already carries it). A preset may have `base` and an empty `keyframes` list — a pure lens/DOF setup with no motion.
|
|
233
253
|
|
|
234
254
|
Endpoints:
|
|
235
255
|
|
|
236
256
|
```
|
|
237
257
|
GET /api/camera-presets → { "presets": [{ "id", "name", "description", "duration", "keyframes", "base", "updated_at" }] }
|
|
238
|
-
POST /api/camera-presets { "name", "duration?", "keyframes", "base?" } #
|
|
258
|
+
POST /api/camera-presets { "name", "duration?", "keyframes", "base?" } # keyframes is required: send [] for a base-only preset
|
|
239
259
|
DELETE /api/camera-presets/{preset_id}
|
|
240
260
|
```
|
|
241
261
|
|
|
242
|
-
Keyframe `position` is 0–100 % of the preset's own `duration`. To apply one: expand each snapshot into per-property tracks on a camera item (`{position, value, easing}` per property). Because positions are percentages, a preset spreads across whatever length the camera clip already has — applying one never needs to resize it.
|
|
262
|
+
Keyframe `position` is 0–100 % of the preset's own `duration`. To apply one: expand each snapshot into per-property tracks on a camera item (`{position, value, easing}` per property, plus `bezier` when the easing is `Custom`). Because positions are percentages, a preset spreads across whatever length the camera clip already has — applying one never needs to resize it.
|
|
263
|
+
|
|
264
|
+
**Applying a pose preset also sets `"cameraMoveMode": "free"`.** A Camera Rig, or a live Motion Path, overwrites the very props the preset animates (see [Mode precedence](#mode-precedence)), so a preset applied on top of one renders as nothing. Set the mode alongside the tracks whenever the preset touches `camX`, `camY`, `camDolly`, `camTiltX`, `camTiltY` or `camRoll`; leave the mode alone for a preset that only animates depth of field or `camPathT`, which compose with a rig. `cameraRig` and `cameraPath` stay on the item, so the mode flip is reversible.
|
|
243
265
|
|
|
244
|
-
|
|
266
|
+
A preset key may carry a `bezier: [x1,y1,x2,y2]` alongside `easing: "Custom"` — a per-segment curve. Saving a camera as a preset and applying it both keep the curve, so a hand-shaped camera move round-trips instead of degrading to `Smooth`.
|
|
245
267
|
|
|
246
|
-
|
|
268
|
+
#### Built-in camera moves
|
|
247
269
|
|
|
248
|
-
|
|
270
|
+
The editor's 24 built-in moves, as compact snapshots. Each line is `id`, category, duration in seconds and its keys as `[position, easing of the segment after the key, values set at this key]` (plus `{"bezier": […]}` before the values on a `Custom` key). To turn one into tracks:
|
|
249
271
|
|
|
250
|
-
|
|
272
|
+
1. **Values carry forward:** each key holds every value set at it or at an earlier key.
|
|
273
|
+
2. **The first key resets the pose:** unless the move is `sparse`, a move that animates any of `camZoom`, `camX`, `camY`, `camRoll`, `camDolly`, `camTiltX`, `camTiltY` first sets all seven to neutral (`camZoom` 1, the rest 0), so it starts from a clean pose. A depth-of-field field it uses starts from its neutral in the [Camera properties](#camera-properties) table.
|
|
274
|
+
3. **A key whose easing is `null` uses `Smooth`** (on the last key it plays no segment).
|
|
275
|
+
4. Write every value as a string, one track per property with a key at every position, and set `"cameraMoveMode": "free"` when the move touches the pose (above).
|
|
276
|
+
|
|
277
|
+
```jsonc
|
|
278
|
+
{"id": "cm-quiet-push", "cat": "Cinematic", "dur": 6, "k": [[0, "AE Smooth", {"camZoom": 1.02, "camY": 1}], [100, null, {"camZoom": 1.16, "camDolly": 80, "camY": 0}]]}
|
|
279
|
+
{"id": "cm-swoop-dive", "cat": "Cinematic", "dur": 4.5, "k": [[0, "Expo Out", {"camY": 9, "camTiltX": -14, "camZoom": 1.35, "camDolly": -120, "camRoll": -3}], [100, null, {"camY": -3, "camTiltX": 3, "camZoom": 1.1, "camDolly": 160, "camRoll": 0}]]}
|
|
280
|
+
{"id": "cm-corkscrew", "cat": "Cinematic", "dur": 5, "k": [[0, "AE Smooth", {"camRoll": -22, "camTiltY": -10, "camZoom": 1.6, "camX": -5, "camY": -3}], [100, null, {"camRoll": 0, "camTiltY": 4, "camZoom": 1.08, "camX": 1, "camY": 0}]]}
|
|
281
|
+
{"id": "cm-fast-orbit", "cat": "Cinematic", "dur": 3, "k": [[0, "Linear", {"camTiltY": -20, "camX": -8, "camZoom": 1.12}], [100, null, {"camTiltY": 20, "camX": 8, "camZoom": 1.12}]]}
|
|
282
|
+
{"id": "cm-lateral-glide", "cat": "Cinematic", "dur": 6, "k": [[0, "Smooth", {"camX": -7, "camY": 0.8, "camTiltY": -1.8, "camZoom": 1.08}], [50, "Smooth", {"camX": 0, "camY": 0, "camTiltY": 0, "camZoom": 1.1}], [100, null, {"camX": 7, "camY": -0.8, "camTiltY": 1.8, "camZoom": 1.12}]]}
|
|
283
|
+
{"id": "cm-parallax-arc", "cat": "Cinematic", "dur": 7, "k": [[0, "Smooth", {"camX": -8, "camTiltY": -4.5, "camDolly": -80, "camZoom": 1.08}], [50, "Smooth", {"camX": 0, "camTiltY": 0, "camDolly": 170, "camZoom": 1.13}], [100, null, {"camX": 8, "camTiltY": 4.5, "camDolly": -80, "camZoom": 1.08}]]}
|
|
284
|
+
{"id": "cm-arc-closeup", "cat": "Cinematic", "dur": 6, "k": [[0, "Cubic Out", {"camX": -11, "camY": 2, "camTiltY": -9, "camDolly": -140, "camZoom": 0.96}], [30, "Smooth", {"camX": -4, "camY": 1, "camTiltY": -4, "camDolly": 40, "camZoom": 1.06}], [65, "Smooth", {"camX": 5, "camY": -1, "camTiltY": 2, "camDolly": 230, "camZoom": 1.26}], [100, null, {"camX": 1.5, "camY": 0, "camTiltY": 3, "camDolly": 160, "camZoom": 1.16}]]}
|
|
285
|
+
{"id": "cm-grand-reveal", "cat": "Cinematic", "dur": 3.5, "k": [[0, "Linear", {"camZoom": 2.3, "camRoll": -5, "camY": -4, "camDolly": 220}], [55, "Smooth", {}], [100, null, {"camZoom": 1, "camRoll": 0, "camY": 0, "camDolly": 0}]]}
|
|
286
|
+
{"id": "cm-slingshot", "cat": "Editorial", "dur": 1.8, "k": [[0, "Impulse", {"camZoom": 1.05}], [100, null, {"camZoom": 1.75, "camDolly": 260, "camY": -1}]]}
|
|
287
|
+
{"id": "cm-step-zoom", "cat": "Editorial", "dur": 2.4, "k": [[0, "Hold", {}], [25, "Hold", {"camZoom": 1.16, "camX": 2}], [50, "Hold", {"camZoom": 1.32, "camX": -1.5}], [75, "Hold", {"camZoom": 1.5, "camX": 0}], [100, null, {}]]}
|
|
288
|
+
{"id": "cm-handheld-breath", "cat": "Editorial", "dur": 8, "k": [[0, "Sine Smooth", {"camX": -0.4, "camY": 0.4, "camRoll": -0.15, "camZoom": 1.05}], [19, "Sine Smooth", {"camX": 1.1, "camY": -0.6, "camRoll": 0.3, "camZoom": 1.062}], [43, "Sine Smooth", {"camX": -0.8, "camY": 0.9, "camRoll": -0.25, "camZoom": 1.054}], [68, "Sine Smooth", {"camX": 0.6, "camY": 0.2, "camRoll": 0.2, "camZoom": 1.068}], [87, "Sine Smooth", {"camX": -0.9, "camY": -0.5, "camRoll": -0.22, "camZoom": 1.058}], [100, null, {"camX": -0.4, "camY": 0.4, "camRoll": -0.15, "camZoom": 1.05}]]}
|
|
289
|
+
{"id": "cm-soft-land", "cat": "Editorial", "dur": 4.2, "k": [[0, "Expo Out", {"camY": 9, "camTiltX": -8, "camDolly": -130, "camZoom": 0.94, "camRoll": -2}], [60, "Smooth", {"camY": -0.6, "camTiltX": 1.2, "camDolly": 30, "camZoom": 1.04, "camRoll": 0.4}], [100, null, {"camY": 0, "camTiltX": 0.6, "camDolly": 0, "camZoom": 1.01, "camRoll": 0}]]}
|
|
290
|
+
{"id": "cm-documentary-follow", "cat": "Editorial", "dur": 5, "k": [[0, "Smooth", {"camX": -3, "camY": 1.5, "camRoll": -0.15, "camZoom": 1.06}], [38, "Smooth", {"camX": 1, "camY": -0.5, "camRoll": 0.25, "camZoom": 1.1}], [72, "Smooth", {"camX": -0.5, "camY": 0.8, "camRoll": -0.1, "camZoom": 1.08}], [100, null, {"camX": 2.5, "camY": 0, "camRoll": 0.1, "camZoom": 1.12}]]}
|
|
291
|
+
{"id": "cm-weave-run", "cat": "Energy", "dur": 5.5, "k": [[0, "Sine Smooth", {"camX": -8, "camRoll": -3, "camDolly": -150, "camZoom": 1.1, "camTiltY": -4}], [30, "Sine Smooth", {"camX": 6, "camRoll": 3, "camDolly": 0, "camTiltY": 3}], [60, "Sine Smooth", {"camX": -5, "camRoll": -2.5, "camDolly": 140, "camTiltY": -3}], [100, null, {"camX": 7, "camRoll": 2, "camDolly": 300, "camTiltY": 4}]]}
|
|
292
|
+
{"id": "cm-s-curve-drift", "cat": "Energy", "dur": 6.5, "k": [[0, "Smooth", {"camX": -10, "camY": 3, "camTiltY": -6, "camDolly": -90, "camZoom": 1.05, "camRoll": -1}], [33, "Smooth", {"camX": 4, "camY": -1.5, "camTiltY": 1, "camDolly": 60, "camZoom": 1.12, "camRoll": 0.5}], [66, "Smooth", {"camX": -3, "camY": 1, "camTiltY": -2, "camDolly": 160, "camZoom": 1.18, "camRoll": -0.5}], [100, null, {"camX": 10, "camY": -2, "camTiltY": 5, "camDolly": 90, "camZoom": 1.1, "camRoll": 1}]]}
|
|
293
|
+
{"id": "cm-macro-sweep", "cat": "Energy", "dur": 5, "k": [[0, "Smooth", {"camZoom": 1.5, "camX": -8, "camTiltY": -5, "camY": 1}], [50, "Smooth", {"camZoom": 1.62, "camX": 2, "camTiltY": 1, "camY": -0.5}], [100, null, {"camZoom": 1.46, "camX": 9, "camTiltY": 5, "camY": 1}]]}
|
|
294
|
+
{"id": "cm-punch-through", "cat": "Energy", "dur": 8.7, "sparse": true, "k": [[0, "Linear", {"camDolly": 98}], [71, "Custom", {"bezier": [0.8187, 0.1079, 0.9089, 0.302]}, {"camDolly": 234.7692}], [100, "AE In (arrive)", {"camDolly": 2600}]]}
|
|
295
|
+
{"id": "cm-orbit-pulse", "cat": "Loops", "dur": 7, "k": [[0, "Sine Smooth", {"camTiltY": -13, "camX": -5, "camY": 1, "camZoom": 1.06}], [50, "Sine Smooth", {"camTiltY": 13, "camX": 5, "camY": -1, "camZoom": 1.2}], [100, null, {"camTiltY": -13, "camX": -5, "camY": 1, "camZoom": 1.06}]]}
|
|
296
|
+
{"id": "cm-float", "cat": "Loops", "dur": 5.5, "k": [[0, "Sine Smooth", {"camY": 2.2, "camRoll": -1.6, "camZoom": 1.1}], [50, "Sine Smooth", {"camY": -2.2, "camRoll": 1.6, "camZoom": 1.17}], [100, null, {"camY": 2.2, "camRoll": -1.6, "camZoom": 1.1}]]}
|
|
297
|
+
{"id": "cm-figure-eight", "cat": "Loops", "dur": 6, "k": [[0, "Sine Smooth", {"camX": -11, "camY": 0, "camRoll": 0, "camZoom": 1.15}], [25, "Sine Smooth", {"camX": 0, "camY": 7, "camRoll": 2.5}], [50, "Sine Smooth", {"camX": 11, "camY": 0, "camRoll": 0}], [75, "Sine Smooth", {"camX": 0, "camY": -7, "camRoll": -2.5}], [100, null, {"camX": -11, "camY": 0, "camRoll": 0}]]}
|
|
298
|
+
{"id": "cm-rack-to-near", "cat": "Focus", "dur": 3.5, "k": [[0, "Smooth", {"camDofEnabled": 1, "camFocusDistance": 2800, "camFocusX": 68, "camFocusY": 48, "camAperture": 36, "camBlurLevel": 110, "camHighlightGain": 18}], [22, "Smooth", {}], [78, "Smooth", {"camFocusDistance": 1350, "camFocusX": 35, "camFocusY": 55}], [100, null, {}]]}
|
|
299
|
+
{"id": "cm-rack-to-far", "cat": "Focus", "dur": 3.5, "k": [[0, "Smooth", {"camDofEnabled": 1, "camFocusDistance": 1350, "camFocusX": 35, "camFocusY": 55, "camAperture": 36, "camBlurLevel": 110, "camHighlightGain": 18}], [22, "Smooth", {}], [78, "Smooth", {"camFocusDistance": 2800, "camFocusX": 68, "camFocusY": 48}], [100, null, {}]]}
|
|
300
|
+
{"id": "cm-focus-chase", "cat": "Focus", "dur": 6, "k": [[0, "Smooth", {"camDofEnabled": 1, "camFocusDistance": 1450, "camFocusX": 28, "camFocusY": 55, "camAperture": 30, "camBlurLevel": 95, "camHighlightGain": 12}], [45, "Smooth", {"camFocusDistance": 2600, "camFocusX": 72, "camFocusY": 42}], [100, null, {"camFocusDistance": 1750, "camFocusX": 48, "camFocusY": 50}]]}
|
|
301
|
+
{"id": "cm-macro-bloom", "cat": "Focus", "dur": 4.5, "k": [[0, "Smooth", {"camZoom": 1.05, "camDolly": 0, "camDofEnabled": 1, "camFocusDistance": 2500, "camFocusX": 50, "camFocusY": 50, "camAperture": 28, "camBlurLevel": 100, "camHighlightGain": 15}], [52, "Smooth", {"camZoom": 1.16, "camDolly": 100, "camFocusDistance": 1650, "camAperture": 46, "camBlurLevel": 125, "camHighlightGain": 32}], [100, null, {"camZoom": 1.23, "camDolly": 180, "camFocusDistance": 1150, "camAperture": 38, "camBlurLevel": 110, "camHighlightGain": 22}]]}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- **Loops** (`cm-orbit-pulse`, `cm-float`, `cm-figure-eight`) end on their first snapshot, so they repeat seamlessly.
|
|
305
|
+
- **Focus:** `cm-rack-to-near`, `cm-rack-to-far` and `cm-focus-chase` animate depth of field only, so they compose with the camera's pose and a rig. `cm-macro-bloom` also pushes `camZoom` and `camDolly`, so it resets the pose and flips a rig to `"free"`.
|
|
306
|
+
- **`cm-punch-through`** (8.7 s, dolly only, `sparse`) writes one `camDolly` track and nothing else, so it composes with the pose the camera already holds: a linear creep from dolly 98 to 235 over the first 71 %, then one custom segment that detonates to 2600. On a 1080-tall project `perspectiveDistance ≈ 2166`, so the last instant flies **through** the comp plane and the frame blows out — that is the move, not a bug; park layers on negative `panZ` to stop the camera short of them.
|
|
251
307
|
|
|
252
308
|
### Camera Rig — a parametric camera move
|
|
253
309
|
|
|
254
|
-
A camera item's `cameraMoveMode` picks one of four exclusive modes: `"free"` (position/rotation keyframes
|
|
310
|
+
A camera item's `cameraMoveMode` picks one of four exclusive modes: `"free"` (position/rotation keyframes), `"path"` (the drawn [camera path](#camera-motion-paths-camerapath--campatht)), `"rig"` (`item.cameraRig`) and `"smartZoom"` (Smart Zoom: zoom ranges, from a screen recording's clicks or placed by hand — see [Smart Zoom](#smart-zoom--the-cameras-zoom-mode)). An unknown value reads as `"free"`. An **absent** field also behaves as free, except that a live `cameraPath` still drives the camera. **A rig writes no path**, so switching modes never destroys a drawn curve.
|
|
255
311
|
|
|
256
312
|
```jsonc
|
|
257
313
|
"cameraMoveMode": "rig",
|
|
@@ -269,47 +325,66 @@ A camera item's `cameraMoveMode` picks one of four exclusive modes: `"free"` (po
|
|
|
269
325
|
|
|
270
326
|
Every type reads `radius`, `height`, `startAngle`, `tilt` and `roll`. On top of those: `orbit` adds `sweep`; `crane` adds `sweep` + `rise`; `spiral` adds `sweep` + `rise` + `turns`; `dolly`, `pushIn` and `truck` add `distance` (and read **no** `sweep`); `handheld` adds `amplitude` + `frequency`; `follow` adds `sweep` plus the `targets` list. The rest are stored and ignored.
|
|
271
327
|
|
|
272
|
-
**`follow` — a drone that rides an object.** The rig's orbit maths are re-based onto a followed layer's position: the camera keeps looking at it while `radius`/`height`/`startAngle`/`sweep` place and circle it, and a **moving** target drags the whole move with it. `targets` is an array of layer **item ids** (any positioned layer — not audio/voice); the keyframable camera track **`camFollowTarget`** (1-based, default `1`) names which entry the camera looks at. A key from `1` to `2` pans the look-at from one object to the next smoothly with the key's easing — **fractional values blend the two targets' positions**, so the hand-off is a camera move, not a cut. With an empty `targets` list (or dangling ids) the rig degrades to a plain orbit of the comp centre. `camFollowTarget`, like `camPathT`, is a driver track: the rig reads it and never overwrites it. A target
|
|
273
|
-
|
|
274
|
-
Parameter ranges (`CAMERA_RIG_PARAM_META` / `CAMERA_RIG_DEFAULTS`, `cameraRig.js`) — **values outside the range are clamped silently**, and the UI label differs from the JSON key for four of them:
|
|
328
|
+
**`follow` — a drone that rides an object.** The rig's orbit maths are re-based onto a followed layer's position: the camera keeps looking at it while `radius`/`height`/`startAngle`/`sweep` place and circle it, and a **moving** target drags the whole move with it. `targets` is an array of layer **item ids** (any positioned layer — not audio/voice); the keyframable camera track **`camFollowTarget`** (1-based, default `1`) names which entry the camera looks at. A key from `1` to `2` pans the look-at from one object to the next smoothly with the key's easing — **fractional values blend the two targets' positions**, so the hand-off is a camera move, not a cut. With an empty `targets` list (or dangling ids) the rig degrades to a plain orbit of the comp centre. `camFollowTarget`, like `camPathT`, is a driver track: the rig reads it and never overwrites it. A target is followed where it actually renders — through its position tracks, an object Motion Path (`motionPath` + `motionPathT`) or keyframe parenting.
|
|
275
329
|
|
|
276
|
-
|
|
277
|
-
| --- | --- | --- | --- |
|
|
278
|
-
| `radius` | Radius | `100`–`20000` px | `1800` |
|
|
279
|
-
| `height` | Height | `−8000`–`8000` px | `400` (positive films from below the subject — a low hero angle; negative from above) |
|
|
280
|
-
| `startAngle` | **Start** | `−360`–`360`° | `−40` |
|
|
281
|
-
| `sweep` | Sweep | `−720`–`720`° | `90` |
|
|
282
|
-
| `rise` | Rise | `−8000`–`8000` px | `0` |
|
|
283
|
-
| `distance` | **Travel** | `−20000`–`20000` px | `1200` |
|
|
284
|
-
| `turns` | Turns | `0.25`–`8` × | `1` |
|
|
285
|
-
| `tilt` | Tilt | `−80`–`80`° | `−8` |
|
|
286
|
-
| `roll` | Roll | `−180`–`180`° | `0` |
|
|
287
|
-
| `amplitude` | **Amount** | `0`–`2000` px | `0` |
|
|
288
|
-
| `frequency` | **Rate** | `0`–`40` × | `0` |
|
|
330
|
+
#### Rig parameters
|
|
289
331
|
|
|
290
|
-
|
|
332
|
+
**Values outside the range are clamped silently**:
|
|
291
333
|
|
|
292
|
-
|
|
334
|
+
| Key | Range | Default |
|
|
335
|
+
| --- | --- | --- |
|
|
336
|
+
| `radius` | `100`–`20000` px | `1800` |
|
|
337
|
+
| `height` | `−8000`–`8000` px | `400` (positive films from below the subject — a low hero angle; negative from above) |
|
|
338
|
+
| `startAngle` | `−360`–`360`° | `−40` (`0` = straight ahead) |
|
|
339
|
+
| `sweep` | `−720`–`720`° | `90` |
|
|
340
|
+
| `rise` | `−8000`–`8000` px | `0` |
|
|
341
|
+
| `distance` | `−20000`–`20000` px | `1200` |
|
|
342
|
+
| `turns` | `0.25`–`8` × | `1` |
|
|
343
|
+
| `tilt` | `−80`–`80`° | `−8` |
|
|
344
|
+
| `roll` | `−180`–`180`° | `0` |
|
|
345
|
+
| `amplitude` | `0`–`2000` px | `0` |
|
|
346
|
+
| `frequency` | `0`–`40` × | `0` |
|
|
293
347
|
|
|
294
|
-
|
|
348
|
+
`seed` (default `7`, an integer clamped to 0–100000) only re-rolls the `handheld` jitter. `targets` (default `[]`, max 50 ids) is the `follow` type's hand-off list; junk entries are dropped by the sanitizer. `aim` is `"center"` (default) or `"path"`.
|
|
295
349
|
|
|
296
|
-
**`radius` sets the framing.** The project camera films the frame at its own size from the perspective distance `(height / 2) / tan(14°)` (
|
|
350
|
+
**`radius` sets the framing.** The project camera films the frame at its own size from the perspective distance `(height / 2) / tan(14°)` (a 28° field of view): about **2166 px for a 1080-tall frame** and **3850 px for a 1920-tall one (9:16)**. A rig closer than that crops in, farther pulls back — so the default `1800` already crops a 16:9 frame a little and a vertical 9:16 one hard; set `radius` near the frame's distance unless the push is the point.
|
|
297
351
|
|
|
298
352
|
**Rig parameters are NOT keyframable** — keying `radius` would rebuild the whole move every frame. **`camPathT` (Path Progress) is the track that carries the move**, and a rig with no `camPathT` keys sits frozen at its start pose. Always emit `camPathT` 0 → 100 alongside a `cameraRig`:
|
|
299
353
|
|
|
300
|
-
```
|
|
354
|
+
```json
|
|
301
355
|
"propertyKeyframes": { "camPathT": [
|
|
302
|
-
{ "position": 0, "value": 0, "easing": "Linear" },
|
|
303
|
-
{ "position": 100, "value": 100, "easing": "Linear" }
|
|
356
|
+
{ "position": 0, "value": "0", "easing": "Linear" },
|
|
357
|
+
{ "position": 100, "value": "100", "easing": "Linear" }
|
|
304
358
|
] }
|
|
305
359
|
```
|
|
306
360
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
**`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`.
|
|
361
|
+
`Linear` progress gives constant speed along the move, which is what the editor itself writes; an eased progress (`"Smooth"`, `"AE Smooth"`) makes the camera crawl at both ends, so use one only when that slow start and landing is the intent. A key without a numeric `position` is dropped when the project loads, which leaves the rig frozen exactly as if no track had been sent.
|
|
310
362
|
|
|
311
|
-
|
|
363
|
+
**`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. 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`.
|
|
312
364
|
|
|
313
365
|
**`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.
|
|
314
366
|
|
|
315
|
-
**
|
|
367
|
+
**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` (−40° → +50°) stays inside; `slowReveal` (−120° → +30°) and `chaseCam` (from 160°) start past it, and `turntable`'s full circle is for 3D models.
|
|
368
|
+
|
|
369
|
+
#### Built-in rigs
|
|
370
|
+
|
|
371
|
+
The editor's twelve rig presets. Fields not listed take the defaults in [Rig parameters](#rig-parameters), so a preset is applied **whole**: write every field, or a partial rig keeps the previous shot's radius. Follow presets carry **no `targets`** — which layers to ride is project wiring, and applying any preset keeps the current list (and `aim`); do the same when you emit one over an existing rig.
|
|
372
|
+
|
|
373
|
+
| Preset | `type` | `radius` | `height` | `startAngle` | Type-specific | `tilt` |
|
|
374
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
375
|
+
| `heroOrbit` | orbit | 1800 | 400 | −40 | sweep 90 | −8 |
|
|
376
|
+
| `slowReveal` | orbit | 2400 | 220 | −120 | sweep 150 | −4 |
|
|
377
|
+
| `turntable` | orbit | 1500 | 300 | 0 | sweep 360 | −6 |
|
|
378
|
+
| `craneDown` | crane | 1700 | 1400 | −30 | sweep 55, rise −1100 | 14 |
|
|
379
|
+
| `riseReveal` | crane | 1900 | 150 | −60 | sweep 80, rise 1200 | −16 |
|
|
380
|
+
| `pushIn` | pushIn | 900 | 260 | 0 | distance 1800 | −3 |
|
|
381
|
+
| `pullBack` | dolly | 2600 | 380 | 12 | distance −1700 | −6 |
|
|
382
|
+
| `truckLeft` | truck | 1600 | 300 | 0 | distance 2200 | −4 |
|
|
383
|
+
| `productSpiral` | spiral | 2800 | 240 | −20 | sweep 300, turns 1, rise 800 | −16 |
|
|
384
|
+
| `handheldDrift` | handheld | 1500 | 320 | −8 | amplitude 90, frequency 1.5 | −5 |
|
|
385
|
+
| `droneFollow` | follow | 1600 | 420 | −35 | sweep 110 | −7 |
|
|
386
|
+
| `chaseCam` | follow | 950 | 170 | 160 | sweep 35 | −4 |
|
|
387
|
+
|
|
388
|
+
#### Mode precedence
|
|
389
|
+
|
|
390
|
+
**A rig or a live path BEATS the pose keyframes.** The camera reads its tracks first; then a rig (mode `"rig"`) or a live path (mode `"path"` or absent) overwrites `camX`, `camY`, `camDolly` — plus `camTiltX`/`camTiltY`/`camRoll` whenever an aim is computed. So on a rigged camera, `camX`/`camY`/`camDolly`/tilt/roll keyframes are stored, drawn in the timeline, and **render as nothing**. `camZoom`, the whole depth-of-field family, `camPathT` and `camFollowTarget` are untouched by both and compose freely. To emit a pose move, emit `"cameraMoveMode": "free"` explicitly with it (omitting the field leaves a live `cameraPath` in control). The editor enforces the same rule by flipping the mode to `"free"` when a camera preset that animates those props is applied, keeping `cameraRig` and `cameraPath` on the item so switching back restores the move.
|
|
@@ -30,7 +30,7 @@ and check every version against them.
|
|
|
30
30
|
- Shapes and pictures enter in 0.4–0.8 s; exits are faster than entrances.
|
|
31
31
|
- A text animation takes at least 0.8–1 s over the whole line, first unit in to last unit settled, unless the video
|
|
32
32
|
needs snap (a beat hit, a glitch flick): faster text flashes past before it is read. The arithmetic per kind of
|
|
33
|
-
animation:
|
|
33
|
+
animation: [Text animation pace](#text-animation-pace).
|
|
34
34
|
|
|
35
35
|
## Easing by intent
|
|
36
36
|
|
|
@@ -38,23 +38,51 @@ and check every version against them.
|
|
|
38
38
|
Camera and big moves: `AE Smooth`.
|
|
39
39
|
- Scene changes on the beat, a logo hit, a screen swipe: `Punch` (slow wind-up, hard hit, long tail).
|
|
40
40
|
- A layer that passes through (in, glide, out): `Smooth Glide` with the `glide` expression — the Glide keyframe
|
|
41
|
-
presets build it (video-keyframes.md).
|
|
41
|
+
presets build it ([video-keyframes.md](video-keyframes.md)).
|
|
42
42
|
- Playful arrivals: `Back Out` or `Overshoot`; a real spring: the `spring` expression (Snappy for buttons, icons and
|
|
43
|
-
pop-ins, Gentle for mascots, big cards and the camera). Exits: `Ease In` or `AE Out (leave)
|
|
43
|
+
pop-ins, Gentle for mascots, big cards and the camera). Exits: `Ease In` or `AE Out (leave)`; the Kinetic recipes'
|
|
44
|
+
`Anticipate` exits (wind back, then snap away) are the exception and stay as tuned.
|
|
44
45
|
- `Linear` only for constant motion: tickers, rotation, progress along a path.
|
|
45
46
|
|
|
46
47
|
## Text animation
|
|
47
48
|
|
|
48
49
|
- Animate words or letters, never a whole line at once — the text presets do this.
|
|
49
|
-
- One family per video. Calm presets for lines and captions:
|
|
50
|
-
(`classic-
|
|
51
|
-
-
|
|
52
|
-
|
|
50
|
+
- One family per video. Calm presets for lines and captions: Classic Fade Up (`classic-fade-up`), Classic Rise (Mask)
|
|
51
|
+
(`classic-rise-mask`), Glide Text (`glide-text`); Focus Type (`focus-type`) for a title. All are in
|
|
52
|
+
[video-text-presets.md](video-text-presets.md).
|
|
53
|
+
- Punchy presets that tilt or overshoot ([video-text-kinetic.md](video-text-kinetic.md): Swing Pop (`swing-pop`), Whip (`whip`),
|
|
54
|
+
Letter Vortex (`letter-vortex`)…) only on short titles, hooks and calls to action — up to about 5 words, or 2–4 short
|
|
55
|
+
stacked lines — and only when the video wants energy. On a longer line or a caption under speech they read cheap.
|
|
53
56
|
- Glide Continuous (`glide-continuous`) and Glide Soft Stop (`glide-soft-stop`) never let the text rest: use them
|
|
54
57
|
only when the user asks for drifting text.
|
|
55
58
|
- On a coloured card, pick a preset without a colour band (Classic Rise (Mask), Classic Fade Up): a band's colour can
|
|
56
59
|
clash with the card.
|
|
57
60
|
|
|
61
|
+
## Text animation pace
|
|
62
|
+
|
|
63
|
+
By default, text that animates in, animates out or gets a one-time pass (a sweep, a highlight, a typed line) takes
|
|
64
|
+
**at least 0.8–1 s**, counted from the moment its first unit starts to the moment its last unit settles. Faster than
|
|
65
|
+
that, the words flash past before anyone can read them. In a Video Editor project the rule holds however the motion is
|
|
66
|
+
built:
|
|
67
|
+
|
|
68
|
+
- **Advanced Animations on Time** (`textAnimators[].range.drive: "time"`): the span is
|
|
69
|
+
`(units − 1) × stagger + duration`. For example, 4 words with `stagger` 0.06 and `duration` 0.8 take 0.98 s. For
|
|
70
|
+
moves (rise, slide, scale, rotate, blur), reach the span with `duration`, because a long `stagger` makes a long line
|
|
71
|
+
crawl. A typewriter (`duration` 0) gets its pace from `stagger`.
|
|
72
|
+
- **Keyframes** (`txa:` range tracks such as Start or Offset, or a text layer's own position, scale and opacity
|
|
73
|
+
tracks): the first and last key of one move sit at least 0.8 s apart. Key positions are % of the layer, so a move
|
|
74
|
+
from `p1` to `p2` lasts `(p2 − p1) / 100 × layer seconds`. On a layer with a speed curve, a `txa:` key's position is
|
|
75
|
+
a % of the clip's source length (`trimEnd − trimStart`) instead, so time the move on that clock
|
|
76
|
+
([video-text.md](video-text.md)).
|
|
77
|
+
- **Layer entrances and exits** on a text layer: `transitionInDuration` / `transitionOutDuration` of 0.8 or more
|
|
78
|
+
(the most they take is 3 s).
|
|
79
|
+
- **Recipes** from [video-text-kinetic.md](video-text-kinetic.md) and [video-text-presets.md](video-text-presets.md) keep their tuned timing.
|
|
80
|
+
On very short text (one or two words), where the span works out under 0.8 s, raise `duration` until it reaches 0.8 s.
|
|
81
|
+
|
|
82
|
+
Go faster only when the edit really needs it: the user asks for snappy or fast text, a word hits on a beat or a fast
|
|
83
|
+
cut, a glitch or scramble flicks, the layer is too short to fit a 0.8 s move, or the animator uses `drive: "voice"`
|
|
84
|
+
(the speech sets the pace there).
|
|
85
|
+
|
|
58
86
|
## Transitions and camera
|
|
59
87
|
|
|
60
88
|
- Vary two or three kinds of transition across a video: one kind everywhere looks like a template, a new one at
|
|
@@ -70,6 +98,10 @@ and check every version against them.
|
|
|
70
98
|
|
|
71
99
|
- A soft whoosh on each transition and on the first headline is a good base. `data-1` or `selection-data-click` for
|
|
72
100
|
UI-like pops, `cinematic-transition-13` for one big reveal.
|
|
101
|
+
- Take sound effects from the user's Sound Library first
|
|
102
|
+
(media.md (GreenLight Dash only)). The ids above are GreenLight Motion library sounds, not
|
|
103
|
+
Sound Library files: `GET /api/motion-sounds/v1/<id>.wav` serves one (e.g. `/api/motion-sounds/v1/data-1.wav`). Save
|
|
104
|
+
it into the board folder and use the board url as an `audio` clip's `url`.
|
|
73
105
|
|
|
74
106
|
## What to check in every version
|
|
75
107
|
|