aivee 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +118 -3
- package/docs/architecture.md +401 -0
- package/docs/aspect-ratio-presets.md +72 -0
- package/docs/releasing.md +19 -0
- package/docs/usage.md +1193 -0
- package/package.json +52 -6
- package/requirements-otio.txt +1 -0
- package/src/capabilities.mjs +307 -0
- package/src/cli.mjs +646 -0
- package/src/doctor.mjs +133 -0
- package/src/edits/analysis-to-edl.mjs +43 -0
- package/src/edits/detect-silence.mjs +51 -0
- package/src/edits/find-mistake-candidates.mjs +42 -0
- package/src/edits/plan-shot-placements.mjs +105 -0
- package/src/edits/plan-silence-cuts.mjs +49 -0
- package/src/edits/retime-transcript.mjs +105 -0
- package/src/files.mjs +80 -0
- package/src/index.mjs +44 -0
- package/src/interchange/otio.mjs +204 -0
- package/src/interchange/otio_bridge.py +521 -0
- package/src/media/analysis-provider.mjs +75 -0
- package/src/media/compatibility.mjs +45 -0
- package/src/media/ffmpeg-capabilities.mjs +129 -0
- package/src/media/frame-rate.mjs +58 -0
- package/src/media/frame-time.mjs +22 -0
- package/src/media/preview-assets.mjs +350 -0
- package/src/media/probe.mjs +149 -0
- package/src/media/render-cuts.mjs +90 -0
- package/src/media/scene-detection.mjs +107 -0
- package/src/media/scene-review.mjs +205 -0
- package/src/project/assets.mjs +1194 -0
- package/src/project/create.mjs +100 -0
- package/src/project/presets.mjs +44 -0
- package/src/render/checkpoints.mjs +192 -0
- package/src/render/formats.mjs +163 -0
- package/src/render/hyperframes.mjs +590 -0
- package/src/render/process.mjs +111 -0
- package/src/render/progress.mjs +46 -0
- package/src/render/timeline-control.mjs +53 -0
- package/src/timeline/assemble.mjs +119 -0
- package/src/timeline/audio-effects.mjs +75 -0
- package/src/timeline/audio-mastering.mjs +236 -0
- package/src/timeline/audio-mix.mjs +105 -0
- package/src/timeline/audio-track-effects.mjs +139 -0
- package/src/timeline/audio-volume.mjs +63 -0
- package/src/timeline/background.mjs +8 -0
- package/src/timeline/blend-mode.mjs +34 -0
- package/src/timeline/caption-layouts.mjs +61 -0
- package/src/timeline/captions.mjs +80 -0
- package/src/timeline/edit.mjs +965 -0
- package/src/timeline/fingerprint.mjs +6 -0
- package/src/timeline/from-edl.mjs +31 -0
- package/src/timeline/history-store.mjs +281 -0
- package/src/timeline/history.mjs +69 -0
- package/src/timeline/inspect.mjs +117 -0
- package/src/timeline/keyframes.mjs +158 -0
- package/src/timeline/layers.mjs +18 -0
- package/src/timeline/mask.mjs +53 -0
- package/src/timeline/project.mjs +52 -0
- package/src/timeline/render.mjs +759 -0
- package/src/timeline/sidechain-render.mjs +209 -0
- package/src/timeline/source-fit.mjs +15 -0
- package/src/timeline/speech-bubble.mjs +26 -0
- package/src/timeline/speed-ramp.mjs +188 -0
- package/src/timeline/track-state.mjs +22 -0
- package/src/timeline/transition.mjs +34 -0
- package/src/timeline/validate.mjs +322 -0
- package/src/transcript/corrections.mjs +21 -0
- package/src/transcript/normalize.mjs +17 -0
- package/src/transcript/provider.mjs +42 -0
- package/src/transcript/subtitles.mjs +57 -0
- package/src/transcript/validate.mjs +39 -0
package/docs/usage.md
ADDED
|
@@ -0,0 +1,1193 @@
|
|
|
1
|
+
# Video Editing Usage Guide
|
|
2
|
+
|
|
3
|
+
This guide covers generic project creation, media registration, editing, rendering, transcripts, and OpenTimelineIO interchange. Avatar artwork and speech-driven character animation belong to consuming studios; use generic image and audio tracks in Engine timelines.
|
|
4
|
+
|
|
5
|
+
## Create a project
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm run new-video -- my-video
|
|
9
|
+
npm run new-video -- vertical-video 1080x1920 30 15
|
|
10
|
+
npm run new-video -- short-video --preset vertical --fit cover
|
|
11
|
+
node src/cli.mjs presets list
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default canvas is 1920×1080 at 30 fps for 10 seconds. Named presets are
|
|
15
|
+
`vertical` (9:16), `portrait-feed` (4:5), `square` (1:1), and `landscape`
|
|
16
|
+
(16:9). Use `presets list` to inspect their default dimensions and destinations.
|
|
17
|
+
Custom dimensions remain available with `WIDTHxHEIGHT`. A preset is recorded as
|
|
18
|
+
`canvasPreset` in project metadata. See
|
|
19
|
+
[`docs/aspect-ratio-presets.md`](aspect-ratio-presets.md) for the baseline
|
|
20
|
+
and platform references. Source media uses `contain` by default to preserve the
|
|
21
|
+
whole frame; select `cover` to crop overflow or `fill` to stretch. Set a
|
|
22
|
+
project-wide policy with `--fit`, then override it for individual video clips
|
|
23
|
+
with their `fit` field. Projects are created under `video-projects/` and
|
|
24
|
+
existing project folders are never overwritten. Each project starts with a
|
|
25
|
+
valid `timeline.json` containing ordered video, audio, and overlay tracks plus
|
|
26
|
+
all currently registered project assets. For an existing project missing that
|
|
27
|
+
file, initialize one from its metadata and media catalog with:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node src/cli.mjs timeline create video-projects/my-video
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use the reviewable `set-project-state` operation to change timeline duration,
|
|
34
|
+
the project-wide `sourceFit`, or the canvas background color; validation
|
|
35
|
+
refuses a duration that would clip any existing timeline item. `sourceFit: null`
|
|
36
|
+
clears the project override. `backgroundColor: null` makes the canvas
|
|
37
|
+
transparent; existing timelines without a background retain their black render
|
|
38
|
+
background. New projects default to `#10141c`.
|
|
39
|
+
Canvas width, height, and frame rate remain tied to project metadata and are
|
|
40
|
+
not editable through a timeline-only operation.
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{ "type": "set-project-state", "state": { "sourceFit": "cover", "durationSeconds": 30, "backgroundColor": "#f3f0eb" } }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The optional output path can create a separate timeline. Pass a duration as the
|
|
47
|
+
second argument to keep the default output path, or as the final argument after
|
|
48
|
+
an output path to override the project default.
|
|
49
|
+
|
|
50
|
+
Frame rates accept integers and rational values such as `30000/1001` for
|
|
51
|
+
NTSC-style 29.97 fps. Rendering passes the project frame rate to HyperFrames
|
|
52
|
+
and checks the output rate. Select MP4, WebM, MOV, or GIF by the output file
|
|
53
|
+
extension:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
node src/cli.mjs render video-projects/my-video renders/final.webm high
|
|
57
|
+
node src/cli.mjs timeline render video-projects/my-video timeline.json renders/final.mov standard
|
|
58
|
+
node src/cli.mjs timeline render video-projects/my-video timeline.json renders/final.mp4 high render-options.json
|
|
59
|
+
node src/cli.mjs timeline render-range video-projects/my-video timeline.json 12.5 24 renders/segment.mp4 standard
|
|
60
|
+
node src/cli.mjs render video-projects/my-video renders/frames high png-options.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`timeline render-range` renders a selected project-time window from the source
|
|
64
|
+
timeline without editing it. JavaScript consumers can use
|
|
65
|
+
`renderTimelineRangeAsync(projectPath, timeline, startSeconds, endSeconds,
|
|
66
|
+
outputPath, quality, options)` or extract a reusable segment with
|
|
67
|
+
`extractTimelineRange(timeline, startSeconds, endSeconds)` and pass it to
|
|
68
|
+
`renderTimelineAsync`. The synchronous `renderTimelineRange` API is also
|
|
69
|
+
available. A range must include at least one project frame and cannot cut
|
|
70
|
+
through a clip fade or edge transition.
|
|
71
|
+
|
|
72
|
+
`render-options.json` can choose a video `codec`, integer encoder `crf`, or
|
|
73
|
+
`videoBitrate`, for example `{ "encoder": { "codec": "h265", "crf": 26 } }`
|
|
74
|
+
or `{ "encoder": { "codec": "vp9", "videoBitrate": "8M" } }`. Bound
|
|
75
|
+
render resources with `{ "workers": 2, "lowMemoryMode": true }`; `workers`
|
|
76
|
+
also accepts `"auto"`, while numeric values must be 1–64. MP4 supports
|
|
77
|
+
H.264/H.265, MOV supports H.264/H.265/ProRes, and WebM supports VP9. ProRes
|
|
78
|
+
uses its HQ profile and does not accept CRF or bitrate overrides. The codec
|
|
79
|
+
override runs as a verified FFmpeg final encode. With an explicit codec and no
|
|
80
|
+
CRF/bitrate override, FFmpeg's codec default is used. Add `"timeoutSeconds": 900`
|
|
81
|
+
to bound rendering (1–86400 seconds). `renderTimelineAsync` includes FFmpeg
|
|
82
|
+
audio alignment, speed/reverse/effect proxy generation, sidechain processing,
|
|
83
|
+
HyperFrames capture, final encoding, output probing, and audio mastering in
|
|
84
|
+
that deadline. `renderProjectAsync` also applies cancellation and timeouts to
|
|
85
|
+
capture, final encoding, and output probing. Timeout terminates the active
|
|
86
|
+
media process and removes temporary output. The JavaScript async APIs
|
|
87
|
+
`renderProjectAsync`, `renderTimelineAsync`, and `renderEditedProjectAsync` accept an
|
|
88
|
+
`AbortSignal` as `signal`. CLI renders handle Ctrl+C by cancelling the active
|
|
89
|
+
render and its FFmpeg preprocessing processes. The synchronous `renderProject`
|
|
90
|
+
and `renderTimeline` APIs remain available without cancellation. JavaScript
|
|
91
|
+
consumers can pass an `onProgress` callback to async render APIs. Events report
|
|
92
|
+
pipeline stages such as proxy resolution, audio alignment, playback processing,
|
|
93
|
+
audio effects, sidechain processing, HyperFrames capture, encoding, output
|
|
94
|
+
validation, publishing, timeline audio validation, and mastering analysis,
|
|
95
|
+
encoding, validation, and publication. Each event
|
|
96
|
+
includes `stage` and `phase` (`started`, `completed`, or `skipped`); capture
|
|
97
|
+
events include HyperFrames phase progress, while FFmpeg video and audio
|
|
98
|
+
mastering events report elapsed media time and a percentage. Events may include stage details such as
|
|
99
|
+
codec, target, or completed/total counts. Callback failures emit a warning and
|
|
100
|
+
do not cancel the render. To render PNG frames, set
|
|
101
|
+
`{ "format": "png-sequence" }`; the output path is a new directory. Defaults
|
|
102
|
+
remain controlled by HyperFrames quality (`draft`, `standard`, or `high`).
|
|
103
|
+
Set `"colorProfile": "rec709"` or `"colorProfile": "rec2020-sdr"` to convert
|
|
104
|
+
the rendered SDR video from the renderer's BT.709 working output and write
|
|
105
|
+
matching color metadata. This requires FFmpeg's `colorspace` filter and
|
|
106
|
+
performs a final video encode. Set `"colorProfile": "hdr10"` for MP4 or MOV to
|
|
107
|
+
convert the BT.709 SDR render to 10-bit BT.2020/PQ and encode with HEVC Main 10.
|
|
108
|
+
This SDR-to-HDR conversion maps reference white to 203 nits; it cannot recover
|
|
109
|
+
highlight detail absent from the SDR render. HDR10 output requires FFmpeg's
|
|
110
|
+
`zscale` filter and `libx265`. `npm run doctor` reports filter and encoder
|
|
111
|
+
availability. Before rendering, the Engine checks required final encoders and
|
|
112
|
+
filters and reports a setup error before generating timeline proxies or
|
|
113
|
+
starting browser capture when one is missing.
|
|
114
|
+
|
|
115
|
+
For long renders, add `{ "resumeCheckpoint": true }` to the render options.
|
|
116
|
+
After a complete HyperFrames capture, the async renderer keeps a validated
|
|
117
|
+
intermediate if final encoding is interrupted or fails; retry the same render
|
|
118
|
+
to reuse it. The cache is content-fingerprinted, SHA-256 checked, removed after
|
|
119
|
+
success, limited to 8 GiB per capture by default, and expires after 72 hours.
|
|
120
|
+
Override those bounds with `checkpointMaxBytes` (1 MiB–100 GiB) and
|
|
121
|
+
`checkpointMaxAgeHours` (1–720). An interruption during browser capture still
|
|
122
|
+
requires that capture stage to run again. PNG sequences and synchronous render
|
|
123
|
+
APIs do not support checkpoints.
|
|
124
|
+
|
|
125
|
+
`createCaptionTrack` also accepts reusable text styling options such as
|
|
126
|
+
`fontFamily`, `fontWeight`, `lineHeight`, `verticalAlign`, `paddingPixels`,
|
|
127
|
+
`textOutline`, and `textShadow`. These settings compile to validated CSS values;
|
|
128
|
+
raw CSS is not accepted. `textOutline` uses `{ color, widthPixels }`, while
|
|
129
|
+
`textShadow` uses `{ color, blurPixels, offsetXPixels, offsetYPixels }`.
|
|
130
|
+
Choose `layoutPreset: 'subtitle' | 'social' | 'socialPop' | 'socialStack' | 'speechBubble' | 'lowerThird'`
|
|
131
|
+
for reusable placement and styling defaults; explicit options override the
|
|
132
|
+
selected preset.
|
|
133
|
+
`socialPop` enables timed word highlighting by default, with a brief pop when
|
|
134
|
+
each word becomes active. `socialStack` provides a two-line social caption
|
|
135
|
+
layout with three words per line and six words per caption by default. Set
|
|
136
|
+
`wordsPerLine` and `wordsPerCaption` to customize line and caption lengths.
|
|
137
|
+
For other layouts, word-timed transcripts can set
|
|
138
|
+
`wordHighlight: true` and choose `wordHighlightAnimation: 'scale' | 'pop'`;
|
|
139
|
+
`wordHighlightColor` customizes the highlight. Explicit options override preset
|
|
140
|
+
defaults. Caption presets wrap long text at word boundaries by default; set
|
|
141
|
+
`wordWrap: false` to keep a single line. Wrapped words preserve the layout's
|
|
142
|
+
horizontal alignment and vertical placement. Use `listCaptionLayoutPresets()`
|
|
143
|
+
or `resolveCaptionLayoutPreset(name)` to inspect available layouts. Cue-level
|
|
144
|
+
transcripts do not contain enough timing detail for word animations.
|
|
145
|
+
|
|
146
|
+
`speechBubble` renders each timed phrase on a rounded panel with a directional
|
|
147
|
+
tail. Customize its placement, typography, foreground, and fill with the same
|
|
148
|
+
caption options, and customize the tail with a partial `bubble` object. For
|
|
149
|
+
example, `{ "layoutPreset": "speechBubble", "x": 0.34, "backgroundColor":
|
|
150
|
+
"#ffffff", "bubble": { "tailSide": "left", "tailPosition": 0.35,
|
|
151
|
+
"tailSizePixels": 24, "cornerRadiusPixels": 30 } }`. `tailSide` accepts
|
|
152
|
+
`top`, `right`, `bottom`, or `left`; `tailPosition` is normalized from 0.08 to
|
|
153
|
+
0.92 along that edge. The layout defaults can be changed per generated caption
|
|
154
|
+
track through `timeline captions` options, or per existing overlay through the
|
|
155
|
+
JavaScript API and `timeline edit`. Standard overlay keyframes and transitions
|
|
156
|
+
can animate the bubble entrance, exit, and position.
|
|
157
|
+
|
|
158
|
+
GIF cannot carry audio, so timeline rendering rejects GIF requests when audio
|
|
159
|
+
clips are present. PNG image sequences export full-resolution RGBA frames to a
|
|
160
|
+
new directory and cannot carry audio or audio mastering. Optional CRF and
|
|
161
|
+
video-bitrate overrides are available for MP4, MOV, and WebM exports; the
|
|
162
|
+
default is still controlled by HyperFrames quality.
|
|
163
|
+
|
|
164
|
+
Media probes retain the container and separate audio/video stream start times
|
|
165
|
+
and time bases. Timeline source times use the media presentation timeline;
|
|
166
|
+
frame rounding helpers are available in the JavaScript API. If a linked video
|
|
167
|
+
and audio stream has a meaningful start/duration offset, rendering creates and
|
|
168
|
+
cleans a temporary timestamp-aligned audio proxy without changing the source.
|
|
169
|
+
Standalone audio with a non-zero stream start is also normalized to timeline
|
|
170
|
+
zero through a temporary proxy. Variable-frame-rate sources remain seconds-based. A synthetic 30/15 fps source
|
|
171
|
+
has been verified rendering at 30 fps on Windows; cross-platform VFR behavior
|
|
172
|
+
still needs validation.
|
|
173
|
+
|
|
174
|
+
## Editing workflow
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
# Inspect media and create a project
|
|
178
|
+
node src/cli.mjs probe path/to/source.mp4
|
|
179
|
+
node src/cli.mjs project create my-video 1080x1920 30 15 --background-color '#f3f0eb'
|
|
180
|
+
|
|
181
|
+
# Create or import a transcript
|
|
182
|
+
node src/cli.mjs transcribe video-projects/my-video path/to/source.mp4
|
|
183
|
+
node src/cli.mjs import-transcript video-projects/my-video path/to/source.mp4 path/to/captions.srt
|
|
184
|
+
node src/cli.mjs transcript export video-projects/my-video/transcripts/transcript.json vtt output/captions.vtt
|
|
185
|
+
|
|
186
|
+
# Propose edits, review the EDL, then apply approved cuts
|
|
187
|
+
node src/cli.mjs plan-silence video-projects/my-video/transcripts/transcript.json video-projects/my-video/edits/silence-edl.json
|
|
188
|
+
node src/cli.mjs mistake-candidates video-projects/my-video/transcripts/transcript.json
|
|
189
|
+
node src/cli.mjs apply-edl path/to/source.mp4 path/to/approved-edl.json path/to/clean-cut.mp4
|
|
190
|
+
node src/cli.mjs retime video-projects/my-video/transcripts/transcript.json path/to/approved-edl.json video-projects/my-video/transcripts/retimed.json
|
|
191
|
+
|
|
192
|
+
# Lint, preview, and render the composition
|
|
193
|
+
node src/cli.mjs lint video-projects/my-video
|
|
194
|
+
node src/cli.mjs preview video-projects/my-video
|
|
195
|
+
node src/cli.mjs render video-projects/my-video
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Assemble an edited video in one render
|
|
199
|
+
|
|
200
|
+
The generic timeline workflow can copy and fingerprint a source, use an
|
|
201
|
+
imported or generated transcript, apply a reviewed EDL, retime the transcript,
|
|
202
|
+
generate captions, and render the final composition:
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
node src/cli.mjs project create my-edit --preset landscape
|
|
206
|
+
node src/cli.mjs asset register video-projects/my-edit path/to/source.mp4 camera
|
|
207
|
+
node src/cli.mjs asset register-many video-projects/my-edit path/to/camera.mp4 path/to/music.wav path/to/logo.png
|
|
208
|
+
node src/cli.mjs transcribe video-projects/my-edit video-projects/my-edit/assets/camera.mp4
|
|
209
|
+
|
|
210
|
+
# Check copied media and restore a moved or damaged asset under its existing ID.
|
|
211
|
+
node src/cli.mjs asset verify video-projects/my-edit
|
|
212
|
+
node src/cli.mjs asset relink video-projects/my-edit camera path/to/replacement.mp4
|
|
213
|
+
node src/cli.mjs asset preview video-projects/my-edit camera
|
|
214
|
+
|
|
215
|
+
# Create a reusable draft proxy; request proxy use in timeline render options.
|
|
216
|
+
node src/cli.mjs asset proxy create video-projects/my-edit camera 960 28
|
|
217
|
+
# Normalize a VFR source proxy to the 30 fps project timeline.
|
|
218
|
+
node src/cli.mjs asset proxy create video-projects/my-edit camera 960 28 30
|
|
219
|
+
node src/cli.mjs timeline render video-projects/my-edit timeline.json renders/review.mp4 draft render-options.json
|
|
220
|
+
|
|
221
|
+
# Generate a proposal, review it, then save approved decisions as approved.json.
|
|
222
|
+
node src/cli.mjs detect-silence video-projects/my-edit/assets/camera.mp4 video-projects/my-edit/edits/silence-proposal.json
|
|
223
|
+
# Find candidate shot boundaries; inspect the analysis before editing.
|
|
224
|
+
node src/cli.mjs detect-scenes video-projects/my-edit/assets/camera.mp4 video-projects/my-edit/edits/scene-candidates.json
|
|
225
|
+
node src/cli.mjs edit video-projects/my-edit camera video-projects/my-edit/transcripts/transcript.json path/to/approved.json
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Consumers can inject a transcription provider without coupling the Engine to
|
|
229
|
+
one cloud vendor or credential store. The provider returns word/cue objects;
|
|
230
|
+
the Engine validates and normalizes them against the probed source duration:
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
await transcribeWithProvider(projectPath, mediaPath, {
|
|
234
|
+
id: 'my-provider',
|
|
235
|
+
async transcribe({ mediaPath, durationSeconds, options }) {
|
|
236
|
+
return providerClient.transcribe(mediaPath, { ...options, durationSeconds });
|
|
237
|
+
},
|
|
238
|
+
}, { granularity: 'word', providerOptions: { language: 'en' } });
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Provider credentials stay inside the consumer's provider client and are not
|
|
242
|
+
written to project metadata or the normalized transcript.
|
|
243
|
+
|
|
244
|
+
The JavaScript API exports `probeMediaAsync(path, { signal })` for cancellable
|
|
245
|
+
FFprobe inspection when a consumer needs media metadata without registering the
|
|
246
|
+
asset first. The CLI `probe` command uses the same asynchronous path and Ctrl+C
|
|
247
|
+
stops its active FFprobe process.
|
|
248
|
+
|
|
249
|
+
`asset register` probes and copies sources asynchronously. Ctrl+C cancels the
|
|
250
|
+
probe or staged copy and cleans the incomplete registration; source files and
|
|
251
|
+
existing project assets are left untouched. When `timeline.json` exists, the
|
|
252
|
+
new asset's probed metadata is added to its asset catalog without adding a clip;
|
|
253
|
+
the editing agent can then place it on a track. JavaScript consumers can use
|
|
254
|
+
`await registerMediaAssetAsync(projectPath, sourcePath, { id, signal, onProgress })`
|
|
255
|
+
to receive byte-based copy progress plus probe and publish stages. The CLI prints
|
|
256
|
+
copy progress to stderr and keeps the registered asset JSON on stdout.
|
|
257
|
+
Both synchronous and asynchronous registration now probe and fingerprint the
|
|
258
|
+
staged copy, so the recorded metadata describes the bytes committed to the
|
|
259
|
+
project. Registration prefers hard-link publication and falls back to an
|
|
260
|
+
exclusive copy when the filesystem does not support hard links; the async path
|
|
261
|
+
supports cancellation and reports fallback copy progress through the publish
|
|
262
|
+
stage.
|
|
263
|
+
`asset register-many` and `registerMediaAssetsAsync(projectPath, sources, {
|
|
264
|
+
signal, onProgress })` add multiple sources sequentially. Each source is an
|
|
265
|
+
independent transaction; the batch continues after per-source errors and
|
|
266
|
+
returns `{ registered, failed }` so partial success is explicit. Duplicate IDs
|
|
267
|
+
and existing destination conflicts are rejected before any files are copied.
|
|
268
|
+
The CLI prints batch and per-asset stage progress to stderr, writes the complete
|
|
269
|
+
result on stdout, and exits with status 1 if any source failed. Batch
|
|
270
|
+
registration does not add clips to the timeline. The async API returns batch
|
|
271
|
+
progress events at `asset-batch` and `asset-batch-item` stages.
|
|
272
|
+
`asset relink` emits the same copy/hash, probe, and publish progress for the
|
|
273
|
+
replacement source. Its CLI progress stays on stderr.
|
|
274
|
+
|
|
275
|
+
`asset verify` checks registered source and proxy SHA-256 identities against
|
|
276
|
+
the manifest and reports missing or modified files. The CLI hashes media
|
|
277
|
+
asynchronously and reports byte-based scan progress on stderr. Ctrl+C cancels
|
|
278
|
+
the scan; JavaScript consumers can pass `onProgress(event)` to
|
|
279
|
+
`verifyProjectAssetsAsync(projectPath, { signal, onProgress })` for the same
|
|
280
|
+
overall progress in their UI.
|
|
281
|
+
The synchronous `verifyProjectAssets(projectPath)` API remains available.
|
|
282
|
+
`asset relink` replaces
|
|
283
|
+
the media for an existing asset ID, refreshes its probe metadata and
|
|
284
|
+
fingerprint, and keeps
|
|
285
|
+
the project-relative path stable so existing timeline references continue to
|
|
286
|
+
work. If `timeline.json` exists, its asset metadata and source fingerprint are
|
|
287
|
+
refreshed as well; the timeline is revalidated before the replacement is saved.
|
|
288
|
+
Stale proxies are removed when the source fingerprint changes. Relinking only
|
|
289
|
+
accepts the original media type and extension, and preserves the replacement
|
|
290
|
+
source file. The CLI stages and probes the replacement asynchronously; Ctrl+C
|
|
291
|
+
cancels before commit or restores the prior asset, manifest, and timeline if
|
|
292
|
+
the commit fails. JavaScript consumers can use
|
|
293
|
+
`await relinkProjectAssetAsync(projectPath, assetId, sourcePath, { signal })`.
|
|
294
|
+
Asset registration and relinking update timeline metadata. When a persistent
|
|
295
|
+
timeline history sidecar exists, the Engine resets its snapshots automatically
|
|
296
|
+
and preserves the configured history limit because asset metadata changes form
|
|
297
|
+
a new undo baseline.
|
|
298
|
+
|
|
299
|
+
Use `asset list` to inspect all registered media, including assets that are not
|
|
300
|
+
yet referenced by a timeline. Video/audio records include container aliases
|
|
301
|
+
and metadata for every video and audio stream. The command returns versioned
|
|
302
|
+
manifest metadata as JSON without touching media files:
|
|
303
|
+
|
|
304
|
+
```sh
|
|
305
|
+
node src/cli.mjs asset list video-projects/my-edit
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The JavaScript API exposes the same operation as
|
|
309
|
+
`listProjectAssets(projectPath)`.
|
|
310
|
+
|
|
311
|
+
Use `asset unregister` to remove unused media from the project manifest and
|
|
312
|
+
timeline catalog. It refuses to unregister an asset referenced by any clip.
|
|
313
|
+
The source and proxy files stay on disk, so this operation does not discard
|
|
314
|
+
media; delete those files separately only when you have confirmed they are no
|
|
315
|
+
longer needed. The API returns the removed asset metadata and confirms that the
|
|
316
|
+
files were preserved:
|
|
317
|
+
|
|
318
|
+
```sh
|
|
319
|
+
node src/cli.mjs asset unregister video-projects/my-edit unused-intro
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The JavaScript API exposes the same operation as
|
|
323
|
+
`unregisterProjectAsset(projectPath, assetId)`.
|
|
324
|
+
|
|
325
|
+
`asset proxy create` generates an H.264, video-only proxy for a registered
|
|
326
|
+
video asset. The optional arguments set maximum dimension, CRF, and output
|
|
327
|
+
frame rate (defaults: 960 and 28, with no frame-rate conversion); valid ranges
|
|
328
|
+
are 160–3840 pixels, CRF 18–40, and frame rate 1–240 fps. A specified frame
|
|
329
|
+
rate normalizes source timestamps to a CFR proxy, which can help browser
|
|
330
|
+
previews of VFR media. Proxies preserve smaller source dimensions instead of
|
|
331
|
+
upscaling and force a keyframe every second to make timeline seeking faster.
|
|
332
|
+
Match the proxy frame rate to the project and enable proxy rendering. The
|
|
333
|
+
Engine records the proxy profile, fingerprint, and media metadata
|
|
334
|
+
in the asset manifest and reuses a matching current proxy. To use proxies for
|
|
335
|
+
draft review, set
|
|
336
|
+
`{ "useProxies": true }` in the timeline render options JSON. This switches
|
|
337
|
+
video clips to the smallest current proxy and leaves audio clips on registered
|
|
338
|
+
source media. `asset verify` checks proxy files too; a modified proxy is
|
|
339
|
+
regenerated when the same proxy settings are requested again. Regular final
|
|
340
|
+
renders continue using the original assets unless proxy use is explicitly
|
|
341
|
+
requested. The CLI uses cancellable FFmpeg processing; Ctrl+C stops proxy
|
|
342
|
+
generation and removes its staged output. JavaScript consumers can call
|
|
343
|
+
`await createAssetProxyAsync(projectPath, assetId, { signal, ...options })`.
|
|
344
|
+
Pass `onProgress(event)` to receive proxy preflight, FFmpeg encoding percentage,
|
|
345
|
+
integrity validation, and publication events. The CLI reports proxy progress on
|
|
346
|
+
stderr while keeping the final proxy record as JSON on stdout.
|
|
347
|
+
|
|
348
|
+
`asset preview` creates cached thumbnails and/or an audio waveform for a
|
|
349
|
+
registered asset. It returns source-relative thumbnail timestamps and paths,
|
|
350
|
+
plus min/max waveform samples that preview clients can draw at any zoom level.
|
|
351
|
+
Preview files live under `assets/previews/` and are keyed by the source hash and
|
|
352
|
+
generation settings; changed source media cannot silently reuse stale previews.
|
|
353
|
+
Pass an options JSON file to override `thumbnailIntervalSeconds` (default 2,
|
|
354
|
+
0.25–60), `thumbnailMaxDimension` (default 320, 80–1280), or `waveformPoints`
|
|
355
|
+
(default 2000, 32–20000). The JavaScript API is
|
|
356
|
+
`await generateAssetPreview(projectPath, assetId, { signal, ...options })`;
|
|
357
|
+
thumbnail extraction, waveform decoding, and media probing respond to the
|
|
358
|
+
signal. Pass `onProgress(event)` to receive integrity hashing, media probing,
|
|
359
|
+
thumbnail extraction, waveform decoding, and cache publication status. The CLI
|
|
360
|
+
writes these updates to stderr and keeps preview JSON on stdout; Ctrl+C cancels
|
|
361
|
+
active preview processes.
|
|
362
|
+
|
|
363
|
+
Media-analysis providers use the same injected approach. Their findings are
|
|
364
|
+
time-checked against the probed source and returned as suggestions for an AI or
|
|
365
|
+
human editor to review; the Engine does not turn them into automatic cuts:
|
|
366
|
+
|
|
367
|
+
For a local first-pass shot map, `detect-scenes` uses FFmpeg's scene-change
|
|
368
|
+
score and returns review-required `scene-cut` findings in the same normalized
|
|
369
|
+
analysis format. It accepts an optional JSON file with `threshold` (0.05–1,
|
|
370
|
+
default 0.35), `minimumSpacingSeconds` (0–60, default 0.5), and
|
|
371
|
+
`maximumFindings` (1–10000, default 2000). Raising the threshold finds only
|
|
372
|
+
more pronounced visual changes. This detects visual differences; it does not
|
|
373
|
+
identify semantic topics, camera quality, or whether a cut is editorially
|
|
374
|
+
useful. The JavaScript API is `detectSceneChangesAsync(mediaPath, options)` and
|
|
375
|
+
supports `AbortSignal` cancellation.
|
|
376
|
+
|
|
377
|
+
```sh
|
|
378
|
+
node src/cli.mjs detect-scenes path/to/source.mp4 edits/scene-candidates.json
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Scene candidates can guide a provider or editing agent when selecting ranges
|
|
382
|
+
for a rough cut, B-roll, or chapter markers. They do not create or apply edits
|
|
383
|
+
by themselves.
|
|
384
|
+
|
|
385
|
+
Generate a complete shot-review folder with a representative JPEG thumbnail for
|
|
386
|
+
each detected shot, a contact sheet, and `scene-review.json` mapping shot
|
|
387
|
+
indexes and time ranges to the images:
|
|
388
|
+
|
|
389
|
+
```sh
|
|
390
|
+
node src/cli.mjs scene-review path/to/source.mp4 video-projects/review/source-shots
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The default pack is bounded to 300 shots and a 60-frame contact sheet. Use an
|
|
394
|
+
options JSON file to adjust detection settings or set `maximumShots`,
|
|
395
|
+
`thumbnailSize`, `contactSheetLimit`, and `columns`; the contact sheet is capped
|
|
396
|
+
at 64 megapixels. The schema v2 pack includes the source SHA-256 identity. It is
|
|
397
|
+
staged and published as a new directory; an existing output directory is never
|
|
398
|
+
replaced.
|
|
399
|
+
All findings remain review-required, and the thumbnails do not infer scene
|
|
400
|
+
meaning. The API exposes `createSceneReviewPackAsync(mediaPath, outputDir, options)`
|
|
401
|
+
with cancellation and progress callbacks; the CLI reports progress on stderr
|
|
402
|
+
while writing the review manifest to stdout.
|
|
403
|
+
|
|
404
|
+
An editing agent can turn explicitly selected review shots into a dry-run B-roll
|
|
405
|
+
insertion plan. Register the same source media used to create the scene-review
|
|
406
|
+
pack as a video asset, then provide only the shot, timeline position, and
|
|
407
|
+
optional source trim selections:
|
|
408
|
+
|
|
409
|
+
```json
|
|
410
|
+
[
|
|
411
|
+
{
|
|
412
|
+
"shotIndex": 2,
|
|
413
|
+
"timelineStartSeconds": 8,
|
|
414
|
+
"sourceOffsetSeconds": 0.25,
|
|
415
|
+
"durationSeconds": 2,
|
|
416
|
+
"clipId": "market-broll-01"
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```sh
|
|
422
|
+
node src/cli.mjs timeline plan-broll timeline.json scene-review.json broll-source video-broll placements.json edits/broll-plan.json
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The command writes a `reviewRequired` plan with validated `add-clip` operations,
|
|
426
|
+
a before/after preview, and SHA-256 identities for the timeline and source.
|
|
427
|
+
It verifies the registered asset matches the media used by the scene-review
|
|
428
|
+
pack. It does not change the timeline or choose semantically relevant shots.
|
|
429
|
+
The selected source ranges must fit within their reviewed shot. Review the saved plan,
|
|
430
|
+
then explicitly apply it through the undoable command:
|
|
431
|
+
|
|
432
|
+
```sh
|
|
433
|
+
node src/cli.mjs timeline apply-plan timeline.json edits/broll-plan.json
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Application refuses if the timeline has changed since planning. The JavaScript
|
|
437
|
+
APIs are `planSceneReviewPlacements(timeline, sceneReview, assetId, trackId,
|
|
438
|
+
placements)` and `applyTimelineReviewPlan(timelinePath, plan)`.
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
const { analysis } = await analyzeMediaWithProvider(mediaPath, {
|
|
442
|
+
id: 'scene-detector',
|
|
443
|
+
async analyze({ mediaPath, durationSeconds, media, options }) {
|
|
444
|
+
return detector.inspect(mediaPath, { durationSeconds, media, ...options });
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Each normalized finding has a stable result-local ID, kebab-case type, start
|
|
450
|
+
time, optional end time, optional confidence, and optional description.
|
|
451
|
+
Providers may attach a validated `remove-range` suggestion with source bounds
|
|
452
|
+
and a reason. Convert explicit suggestions into a review-required EDL draft,
|
|
453
|
+
then approve it only after review:
|
|
454
|
+
|
|
455
|
+
```sh
|
|
456
|
+
node src/cli.mjs analysis-to-edl edits/analysis.json edits/candidate-edl.json
|
|
457
|
+
node src/cli.mjs approve-edl edits/candidate-edl.json edits/approved-edl.json
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Candidate EDLs carry `reviewStatus: "needs-review"`; rendering, timeline
|
|
461
|
+
construction, and transcript retiming reject them until the explicit approval
|
|
462
|
+
command writes a separate approved file. Confidence filtering is optional via
|
|
463
|
+
an options JSON file containing `minConfidence` from 0 to 1.
|
|
464
|
+
|
|
465
|
+
For CLI workflows, pass a trusted JavaScript module that exports a provider as
|
|
466
|
+
its default export (or as `mediaAnalysisProvider` / `provider`). The module
|
|
467
|
+
receives normalized media metadata and optional provider settings; suggestions
|
|
468
|
+
are written to a new JSON file and existing output is never overwritten:
|
|
469
|
+
|
|
470
|
+
```sh
|
|
471
|
+
node src/cli.mjs analyze path/to/source.mp4 path/to/provider.mjs edits/analysis.json provider-options.json
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Keep provider credentials in the provider's own runtime configuration. The
|
|
475
|
+
Engine validates the results and does not store provider credentials.
|
|
476
|
+
|
|
477
|
+
The EDL is a JSON object with `schemaVersion`, `sourceDurationSeconds`, and
|
|
478
|
+
`cuts` entries containing source-time `sourceStart`, `sourceEnd`, and `reason`.
|
|
479
|
+
The `edit` command refuses to overwrite existing timeline, approved EDL,
|
|
480
|
+
retimed transcript, or render artifacts. It stores the source SHA-256 and
|
|
481
|
+
artifact paths in timeline lineage. To edit transcript words before rendering,
|
|
482
|
+
apply corrections with `transcript correct` and provide the corrected file to
|
|
483
|
+
`edit`. You can also build and render a generic timeline directly with the
|
|
484
|
+
JavaScript API (`createProjectTimeline`, `timelineFromEdl`, `editTimeline`,
|
|
485
|
+
`editTimelineBatch`, `editTimelineFile`, `undoTimelineFile`,
|
|
486
|
+
`redoTimelineFile`, `detachAudioFromTimelineClip`, `createTimelineHistory`,
|
|
487
|
+
`createCaptionTrack`, and `renderTimeline`).
|
|
488
|
+
|
|
489
|
+
Use `timeline inspect` to give an editing agent a compact, read-only view of the
|
|
490
|
+
project format, assets, ordered tracks, clip source and timeline ranges,
|
|
491
|
+
transitions, and audio settings before it proposes an edit:
|
|
492
|
+
|
|
493
|
+
```sh
|
|
494
|
+
node src/cli.mjs timeline inspect timeline.json
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The JavaScript API exports the same view through
|
|
498
|
+
`inspectTimeline(timeline)`. It validates the timeline first and does not write
|
|
499
|
+
or modify project files. Computed clip end times in the summary are rounded to
|
|
500
|
+
six decimal places to avoid floating-point display noise.
|
|
501
|
+
|
|
502
|
+
Generic edit operations can add/remove/reorder tracks and add/remove/duplicate clips, detach clip audio,
|
|
503
|
+
move clips within and between compatible tracks, trim/slip/split clips,
|
|
504
|
+
insert a synchronized gap or ripple-delete a time range across every track,
|
|
505
|
+
apply crossfades or clip-edge transitions, and preserve timeline validation at
|
|
506
|
+
each step. Gap insertion trims clips at the insertion point and shifts their
|
|
507
|
+
remaining segments and later clips while keeping source ranges, keyframes,
|
|
508
|
+
speed ramps, captions, and track synchronization aligned. Ripple deletion
|
|
509
|
+
trims or splits clips that straddle the cut, retimes later clips, and shortens
|
|
510
|
+
the project. Adding a clip or inserting a gap can extend project duration;
|
|
511
|
+
ordinary removal leaves it unchanged so existing downstream timing is preserved.
|
|
512
|
+
Cross-track moves preserve the clip id and all clip metadata, and require the
|
|
513
|
+
source and destination tracks to have the same kind. To move a video clip to a
|
|
514
|
+
different video layer, use `move-track` in an operation file:
|
|
515
|
+
|
|
516
|
+
```json
|
|
517
|
+
{
|
|
518
|
+
"type": "move-track",
|
|
519
|
+
"trackId": "video-main",
|
|
520
|
+
"clipId": "interview-shot",
|
|
521
|
+
"targetTrackId": "video-broll",
|
|
522
|
+
"timelineStart": 8
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
For AI-proposed arbitrary time ranges, use `ripple-delete-range` with explicit
|
|
527
|
+
project-time boundaries. It applies the same cut to every track, preserves the
|
|
528
|
+
left/right clip segments, and retimes later clips, keyframes, audio automation,
|
|
529
|
+
and markers together:
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
{ "type": "ripple-delete-range", "startSeconds": 12.4, "endSeconds": 13.1 }
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
The JavaScript API exposes this as
|
|
536
|
+
`rippleDeleteTimelineRange(timeline, startSeconds, endSeconds)`.
|
|
537
|
+
|
|
538
|
+
Use `set-track-state` to show or hide video/overlay tracks, mute or solo audio
|
|
539
|
+
tracks, lock either track kind, or adjust audio-track volume through the same
|
|
540
|
+
reviewable operation workflow.
|
|
541
|
+
Partial state updates preserve other settings, and `null` clears an override:
|
|
542
|
+
|
|
543
|
+
```json
|
|
544
|
+
{ "type": "set-track-state", "trackId": "music", "state": { "muted": true, "volume": 0.35 } }
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Disable an individual clip without removing it from the timeline with
|
|
548
|
+
`set-clip-state`. Re-enable it with `enabled: true`, or clear the override with
|
|
549
|
+
`null`:
|
|
550
|
+
|
|
551
|
+
```json
|
|
552
|
+
{ "type": "set-clip-state", "trackId": "video-main", "clipId": "unused-take", "state": { "enabled": false } }
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
The JavaScript API exposes the same operation through
|
|
556
|
+
`setTimelineClipState(timeline, trackId, clipId, state)`.
|
|
557
|
+
|
|
558
|
+
Add a project-time marker for chapter points or review notes with `set-marker`.
|
|
559
|
+
Markers are stored in the edit timeline and returned by `inspectTimeline`; they
|
|
560
|
+
do not render into the video. Gap insertion moves markers at or after the
|
|
561
|
+
insertion point, and ripple deletion removes markers inside the cut and shifts
|
|
562
|
+
later markers with the retained edit:
|
|
563
|
+
|
|
564
|
+
```json
|
|
565
|
+
{
|
|
566
|
+
"type": "set-marker",
|
|
567
|
+
"marker": {
|
|
568
|
+
"id": "review-audio-mix",
|
|
569
|
+
"timeSeconds": 12.5,
|
|
570
|
+
"label": "Review music level",
|
|
571
|
+
"color": "#f59e0b"
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Use `remove-marker` with the marker id to delete it. The JavaScript API exposes
|
|
577
|
+
these operations as `setTimelineMarker(timeline, marker)` and
|
|
578
|
+
`removeTimelineMarker(timeline, markerId)`. Markers use stable kebab-case ids,
|
|
579
|
+
non-empty labels of at most 200 characters, optional six-digit hex colors, and
|
|
580
|
+
times within the project duration. OpenTimelineIO maps them to root-stack
|
|
581
|
+
markers; other interchange adapters may not preserve or display them.
|
|
582
|
+
|
|
583
|
+
To process every clip on a music track with the same chain, set its effects at
|
|
584
|
+
track level:
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"type": "set-track-audio-effects",
|
|
589
|
+
"trackId": "music",
|
|
590
|
+
"audioEffects": [
|
|
591
|
+
{ "type": "lowpass", "frequencyHz": 16000 },
|
|
592
|
+
{ "type": "balance", "value": 0.15 }
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Visual tracks accept `visible`; audio tracks accept `muted`, `solo`, `volume`, and
|
|
598
|
+
project-time `volumeKeyframes` through `set-track-state`. Both track kinds accept
|
|
599
|
+
`locked`. Lock or unlock it with a standalone operation:
|
|
600
|
+
|
|
601
|
+
```json
|
|
602
|
+
{ "type": "set-track-state", "trackId": "approved-footage", "state": { "locked": true } }
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
A locked track rejects clip edits and track-setting changes. Synchronized gap
|
|
606
|
+
insertion and ripple deletion are refused when they would change locked track
|
|
607
|
+
content. Unlock the track with `{"locked": false}` before editing it. Lock state
|
|
608
|
+
is preserved in Engine OpenTimelineIO metadata; OTIO itself has no portable
|
|
609
|
+
track-lock behavior.
|
|
610
|
+
|
|
611
|
+
Use the dedicated
|
|
612
|
+
`set-track-audio-effects` operation to replace or clear a track effect chain.
|
|
613
|
+
Its JavaScript API is
|
|
614
|
+
`setTimelineTrackAudioEffects(timeline, trackId, effects)`; pass `null` or an
|
|
615
|
+
empty array to clear it. Clip effects run first; then a track with effects is
|
|
616
|
+
mixed to one temporary stem and its chain runs across that full timeline.
|
|
617
|
+
Clip/track gain, fades, and timing ducking are preserved.
|
|
618
|
+
|
|
619
|
+
When one or more audio tracks have `solo: true`, draft and final timeline renders
|
|
620
|
+
include only unmuted solo tracks. Clear the solo override with `null` to return
|
|
621
|
+
to the full mix. Solo state is carried in Engine metadata during OpenTimelineIO
|
|
622
|
+
round trips.
|
|
623
|
+
|
|
624
|
+
Use `set-overlay` to reviewably change caption text, word timings, layout,
|
|
625
|
+
styling, or composition reference after adding the overlay clip. The operation
|
|
626
|
+
accepts validated overlay fields only, and `null` clears optional overrides:
|
|
627
|
+
|
|
628
|
+
```json
|
|
629
|
+
{
|
|
630
|
+
"type": "set-overlay",
|
|
631
|
+
"trackId": "captions",
|
|
632
|
+
"clipId": "caption-01",
|
|
633
|
+
"properties": { "text": "A clearer caption", "fontSize": 48, "textAlign": "center" }
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
The JavaScript API is `setTimelineOverlay(timeline, trackId, clipId, properties)`.
|
|
638
|
+
Use `preview-edit` to inspect caption and layout changes before writing them.
|
|
639
|
+
|
|
640
|
+
Review the exact result before writing an edit. `preview-edit` prints the
|
|
641
|
+
duration change and each added, removed, or updated track and clip as JSON; it
|
|
642
|
+
does not write a timeline file:
|
|
643
|
+
|
|
644
|
+
```sh
|
|
645
|
+
node src/cli.mjs timeline preview-edit timeline.json edits/insert-gap.json
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
After reviewing the preview, apply the same operation plan to a new timeline
|
|
649
|
+
file. For example, save this as `edits/insert-gap.json`:
|
|
650
|
+
|
|
651
|
+
```json
|
|
652
|
+
{
|
|
653
|
+
"type": "insert-gap",
|
|
654
|
+
"timelineStart": 12.5,
|
|
655
|
+
"durationSeconds": 2
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Then write the result to a new timeline file:
|
|
660
|
+
|
|
661
|
+
```sh
|
|
662
|
+
node src/cli.mjs timeline edit timeline.json edits/insert-gap.json timeline-edited.json
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
The operation file can also be a JSON array. The Engine applies each operation
|
|
666
|
+
in order, validates each intermediate timeline, and only writes the final file
|
|
667
|
+
if the entire batch succeeds:
|
|
668
|
+
|
|
669
|
+
```json
|
|
670
|
+
[
|
|
671
|
+
{ "type": "move", "trackId": "video-main", "clipId": "intro", "timelineStart": 0.5 },
|
|
672
|
+
{ "type": "transition", "trackId": "video-main", "clipId": "intro", "edge": "in", "transition": { "schemaVersion": 1, "type": "fade", "durationSeconds": 0.4 } }
|
|
673
|
+
]
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Single-operation files remain supported. Existing output files are never
|
|
677
|
+
overwritten. The JavaScript API exposes the same behavior through
|
|
678
|
+
`editTimelineBatch(timeline, operations)`. `previewTimelineEdit(timeline,
|
|
679
|
+
operations)` returns the same validated change plan without modifying the input.
|
|
680
|
+
|
|
681
|
+
Replace a clip's media source through a reviewable `replace-asset` operation.
|
|
682
|
+
The clip keeps its timeline placement and visual/audio settings; provide new
|
|
683
|
+
source bounds when the old range does not fit the replacement. The operation
|
|
684
|
+
can also clear an incompatible playback rate or speed ramp with `null`, or
|
|
685
|
+
select/clear an audio stream:
|
|
686
|
+
|
|
687
|
+
```json
|
|
688
|
+
{
|
|
689
|
+
"type": "replace-asset",
|
|
690
|
+
"trackId": "video-main",
|
|
691
|
+
"clipId": "b-roll-01",
|
|
692
|
+
"assetId": "b-roll-alternate",
|
|
693
|
+
"sourceIn": 2,
|
|
694
|
+
"sourceOut": 5,
|
|
695
|
+
"playbackRate": null,
|
|
696
|
+
"reverse": false,
|
|
697
|
+
"speedRamp": null
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Use `timeline preview-edit` first to inspect the resulting clip changes. The
|
|
702
|
+
same operation is available as `replaceTimelineClipAsset` in the JavaScript API.
|
|
703
|
+
|
|
704
|
+
Detach the selected video clip's source audio into a synchronized audio clip.
|
|
705
|
+
This creates an audio track when the requested one does not exist and keeps
|
|
706
|
+
the video clip's source range, timeline placement, and playback rate or speed
|
|
707
|
+
ramp. Set `audioStreamIndex` to a zero-based ordinal to select a language or mic
|
|
708
|
+
track from a multi-audio source; registered assets expose each stream's codec,
|
|
709
|
+
channel layout, timing, language, and title. Edit the new audio clip
|
|
710
|
+
independently to create an L-cut or J-cut:
|
|
711
|
+
|
|
712
|
+
```json
|
|
713
|
+
{
|
|
714
|
+
"type": "detach-audio",
|
|
715
|
+
"trackId": "video-main",
|
|
716
|
+
"clipId": "interview-shot",
|
|
717
|
+
"audioTrackId": "interview-audio",
|
|
718
|
+
"audioClipId": "interview-shot-audio",
|
|
719
|
+
"audioStreamIndex": 1
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
For interactive workflows, wrap a timeline in bounded in-memory undo/redo
|
|
724
|
+
history. Failed edits leave the history untouched, each returned timeline is an
|
|
725
|
+
isolated snapshot, and applying a new edit after undo clears the redo branch:
|
|
726
|
+
|
|
727
|
+
```js
|
|
728
|
+
const history = createTimelineHistory(timeline, { limit: 100 });
|
|
729
|
+
history.applyBatch([
|
|
730
|
+
{ type: 'move', trackId: 'video-main', clipId: 'intro', timelineStart: 1 },
|
|
731
|
+
{ type: 'transition', trackId: 'video-main', clipId: 'intro', edge: 'in', transition: { schemaVersion: 1, type: 'fade', durationSeconds: 0.4 } },
|
|
732
|
+
]);
|
|
733
|
+
const reviewed = history.undo();
|
|
734
|
+
const restored = history.redo();
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
The history stores timeline snapshots in memory; save approved project files
|
|
738
|
+
separately through the consumer's normal persistence flow. For CLI agents and
|
|
739
|
+
other one-shot processes, persistent history operates directly on a timeline
|
|
740
|
+
file and keeps a bounded hidden sidecar beside it:
|
|
741
|
+
|
|
742
|
+
```sh
|
|
743
|
+
node src/cli.mjs timeline preview-edit timeline.json edits/move-intro.json
|
|
744
|
+
node src/cli.mjs timeline project-edit timeline.json edits/move-intro.json
|
|
745
|
+
node src/cli.mjs timeline undo timeline.json
|
|
746
|
+
node src/cli.mjs timeline redo timeline.json
|
|
747
|
+
node src/cli.mjs timeline history timeline.json
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
The first edit creates `.timeline.json.history.json`; the default history limit
|
|
751
|
+
is 20 snapshots. Pass a limit on the first `project-edit` call to choose a
|
|
752
|
+
different bound. A failed or interrupted two-file update is recovered from its
|
|
753
|
+
atomic history journal on the next command; a lock serializes concurrent CLI
|
|
754
|
+
edits and stale locks from exited processes are cleared. If the timeline was edited outside
|
|
755
|
+
that history, the CLI reports the mismatch; use
|
|
756
|
+
`timeline history timeline.json reset [limit]` to begin a new history from the
|
|
757
|
+
current timeline.
|
|
758
|
+
|
|
759
|
+
For a crossfade between adjacent clips, apply a timeline edit before rendering:
|
|
760
|
+
|
|
761
|
+
```js
|
|
762
|
+
const transitioned = editTimeline(timeline, {
|
|
763
|
+
type: 'crossfade',
|
|
764
|
+
trackId: 'video-main',
|
|
765
|
+
clipId: 'intro',
|
|
766
|
+
incomingClipId: 'main',
|
|
767
|
+
durationSeconds: 0.35,
|
|
768
|
+
});
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
Set `allowOverlap: true` on the track and place the incoming clip so it overlaps
|
|
772
|
+
the outgoing clip by `durationSeconds`. The operation verifies that timing and
|
|
773
|
+
configures opacity fades for video/text or volume fades for audio. Tracks still
|
|
774
|
+
control compositing order.
|
|
775
|
+
|
|
776
|
+
For reusable clip-edge transitions, assign a versioned fade or directional wipe
|
|
777
|
+
to a video or overlay clip:
|
|
778
|
+
|
|
779
|
+
```js
|
|
780
|
+
const transitioned = editTimeline(timeline, {
|
|
781
|
+
type: 'transition',
|
|
782
|
+
trackId: 'video-main',
|
|
783
|
+
clipId: 'main',
|
|
784
|
+
edge: 'in',
|
|
785
|
+
transition: { schemaVersion: 1, type: 'wipe', direction: 'left', durationSeconds: 0.5 },
|
|
786
|
+
});
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Use `edge: 'out'` for the clip end, or `transition: null` to remove the edge
|
|
790
|
+
transition. Wipes accept `left`, `right`, `up`, and `down`; both clip edges can
|
|
791
|
+
have transitions when their durations do not overlap. Fade transitions cannot
|
|
792
|
+
share opacity animation with that clip, and wipes cannot share a static mask.
|
|
793
|
+
|
|
794
|
+
Video clips and text/composition overlays can define clip-local visual
|
|
795
|
+
keyframes. `x`, `y`, `width`, and `height` are normalized to the canvas;
|
|
796
|
+
`opacity` ranges from 0 to 1, and `rotationDegrees` rotates around the element
|
|
797
|
+
center. Video clips can animate `objectPositionX` and `objectPositionY` from 0
|
|
798
|
+
to 1 to reframe media inside its existing `object-fit` viewport, and `zoom`
|
|
799
|
+
from 1× to 4× to scale the media inside a clipped, fixed-size viewport. Use
|
|
800
|
+
object-position keyframes to pan the zoom focus without moving the clip's
|
|
801
|
+
timeline bounds. Visual filter values include `brightness`, `contrast`, and `saturation`
|
|
802
|
+
(0–5), `blurPixels` (0–100), and `hueDegrees` (-360–360). Each keyframe time is
|
|
803
|
+
relative to its clip and the first keyframe must be at 0. Values interpolate to
|
|
804
|
+
the next keyframe; `ease` is optional and uses an allowlisted GSAP ease. The
|
|
805
|
+
last value holds through the clip end. Opacity keyframes cannot be combined
|
|
806
|
+
with clip fade fields. Use an `eases` map for per-property easing when one
|
|
807
|
+
keyframe changes multiple properties with different curve segments. Trim and
|
|
808
|
+
split operations rebase these easing ranges to preserve motion continuity.
|
|
809
|
+
An easing slice is represented as `{ type: 'range', name: 'power2.out',
|
|
810
|
+
from: 0.5, to: 1 }`, where `from` and `to` select a normalized part of the
|
|
811
|
+
allowlisted curve. The validator rejects unrecognized ease names and ranges.
|
|
812
|
+
|
|
813
|
+
Video and overlay clips also accept validated static masks: `circle` with
|
|
814
|
+
`centerX`, `centerY`, and `radius`; `ellipse` with `centerX`, `centerY`,
|
|
815
|
+
`radiusX`, and `radiusY`; or `rect` with normalized `x`, `y`, `width`, and
|
|
816
|
+
`height`. These are timeline data, not arbitrary CSS values. Animate a mask
|
|
817
|
+
with the `maskProgress` keyframe property from 0 to 1; it scales the chosen
|
|
818
|
+
shape from its center to its configured bounds. The first keyframe must include
|
|
819
|
+
`maskProgress`, and the clip must define a static mask.
|
|
820
|
+
|
|
821
|
+
Video and overlay clips may set an allowlisted CSS `blendMode` to composite
|
|
822
|
+
against lower visual layers. Supported modes are `normal`, `multiply`,
|
|
823
|
+
`screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`,
|
|
824
|
+
`hard-light`, `soft-light`, `difference`, `exclusion`, `hue`, `saturation`,
|
|
825
|
+
`color`, and `luminosity`. For example, `blendMode: 'screen'` can lighten a
|
|
826
|
+
lower layer while preserving its dark areas. Blend modes are isolated to the
|
|
827
|
+
project canvas and cannot be applied to audio clips.
|
|
828
|
+
|
|
829
|
+
```js
|
|
830
|
+
const clip = {
|
|
831
|
+
id: 'b-roll',
|
|
832
|
+
assetId: 'camera',
|
|
833
|
+
sourceIn: 0,
|
|
834
|
+
sourceOut: 4,
|
|
835
|
+
timelineStart: 2,
|
|
836
|
+
durationSeconds: 4,
|
|
837
|
+
keyframes: [
|
|
838
|
+
{ time: 0, properties: { x: 0.5, y: 0.1, width: 0.4, height: 0.8, opacity: 0 } },
|
|
839
|
+
{ time: 1, properties: { x: 0.3, opacity: 1 }, ease: 'power2.out' },
|
|
840
|
+
{ time: 4, properties: { x: 0.1, rotationDegrees: -3 } },
|
|
841
|
+
],
|
|
842
|
+
};
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
Animated bounds must stay within the canvas. Keyframes are part of the
|
|
846
|
+
schema-version-1 timeline object and use the same JavaScript renderer as other
|
|
847
|
+
timeline operations. AI workflows can replace or clear clip keyframes with the
|
|
848
|
+
reviewable `set-keyframes` timeline operation. It works in a batch and with
|
|
849
|
+
`timeline preview-edit`, so proposed motion can be inspected before saving:
|
|
850
|
+
|
|
851
|
+
```json
|
|
852
|
+
{
|
|
853
|
+
"type": "set-keyframes",
|
|
854
|
+
"trackId": "video",
|
|
855
|
+
"clipId": "b-roll",
|
|
856
|
+
"keyframes": [
|
|
857
|
+
{ "time": 0, "properties": { "x": 0.5, "width": 0.4 } },
|
|
858
|
+
{ "time": 2, "properties": { "x": 0.2, "width": 0.6 }, "ease": "power2.out" }
|
|
859
|
+
]
|
|
860
|
+
}
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
Set `keyframes` to `null` to remove the animation. The JavaScript API exposes
|
|
864
|
+
`setTimelineClipKeyframes(timeline, trackId, clipId, keyframes)`.
|
|
865
|
+
|
|
866
|
+
Use `set-framing` to reviewably change a video's static fit or normalized
|
|
867
|
+
viewport after adding it to the timeline. Partial updates preserve unspecified
|
|
868
|
+
fields; set a field to `null` to restore its inherited/default value:
|
|
869
|
+
|
|
870
|
+
```json
|
|
871
|
+
{
|
|
872
|
+
"type": "set-framing",
|
|
873
|
+
"trackId": "video",
|
|
874
|
+
"clipId": "b-roll",
|
|
875
|
+
"framing": { "fit": "cover", "x": 0.1, "width": 0.8 }
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
The JavaScript API exposes `setTimelineClipFraming(timeline, trackId, clipId,
|
|
880
|
+
framing)`. Timeline validation checks the resulting crop and viewport bounds.
|
|
881
|
+
|
|
882
|
+
Set a constant `playbackRate` from `0.1` to `5` on video or audio clips. The
|
|
883
|
+
source range must equal `durationSeconds * playbackRate`. Rendering creates a
|
|
884
|
+
temporary FFmpeg proxy for the selected source range (video is re-encoded;
|
|
885
|
+
audio uses pitch-preserving `atempo`) and removes it afterward. Original assets
|
|
886
|
+
and the saved timeline remain unchanged.
|
|
887
|
+
|
|
888
|
+
Set `reverse: true` on a video or audio clip to play its selected source range
|
|
889
|
+
backward. It can combine with a constant `playbackRate`, but not with
|
|
890
|
+
`speedRamp`. Rendering creates a temporary reversed media proxy and removes it
|
|
891
|
+
afterward. FFmpeg's reverse filters buffer the selected clip, so memory use
|
|
892
|
+
grows with the clip's duration and resolution. Reverse playback is not represented by the
|
|
893
|
+
OpenTimelineIO interchange profile. Detaching a reversed video clip copies the
|
|
894
|
+
reverse setting to its audio clip. Trim and split preserve the reverse source
|
|
895
|
+
direction. To change it as a reviewable edit, use the
|
|
896
|
+
`set-reverse` timeline operation with the CLI or `setTimelineClipReverse` API:
|
|
897
|
+
|
|
898
|
+
```json
|
|
899
|
+
{ "type": "set-reverse", "trackId": "video", "clipId": "shot-1", "reverse": true }
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
The `doctor` command and `getEngineCapabilities()` report whether the FFmpeg
|
|
903
|
+
`reverse` and `areverse` filters are available. Rendering checks the filter
|
|
904
|
+
needed by each reversed track before it creates temporary media.
|
|
905
|
+
|
|
906
|
+
For a smooth speed ramp, set source-relative `speedRamp` points instead of a
|
|
907
|
+
constant `playbackRate`:
|
|
908
|
+
|
|
909
|
+
```js
|
|
910
|
+
import { speedRampDuration } from 'aivee';
|
|
911
|
+
|
|
912
|
+
const speedRamp = [
|
|
913
|
+
{ sourceOffset: 0, playbackRate: 1, ease: 'easeInOutCubic' },
|
|
914
|
+
{ sourceOffset: 2, playbackRate: 2 },
|
|
915
|
+
];
|
|
916
|
+
const clip = {
|
|
917
|
+
sourceIn: 0,
|
|
918
|
+
sourceOut: 2,
|
|
919
|
+
durationSeconds: speedRampDuration(speedRamp),
|
|
920
|
+
speedRamp,
|
|
921
|
+
};
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
The `ease` field on a point controls the outgoing segment. Supported curves are
|
|
925
|
+
`linear`, `easeIn`, `easeOut`, `easeInOut`, `easeInCubic`, `easeOutCubic`, and
|
|
926
|
+
`easeInOutCubic`; omitted easing defaults to `linear`. The renderer samples
|
|
927
|
+
eased rate curves into bounded linear segments for video timestamp mapping and
|
|
928
|
+
audio automation. The clip duration is `integral(ds / playbackRate(s))` over
|
|
929
|
+
its source range; the first point starts at offset 0 and the last ends at
|
|
930
|
+
`sourceOut - sourceIn`. Use the reviewable `set-speed` edit operation to set a
|
|
931
|
+
constant rate, replace a speed ramp, or reset to normal speed. It recalculates
|
|
932
|
+
clip duration and scales clip-local animation, fades, transitions, word timing,
|
|
933
|
+
and volume automation to preserve their relative timing:
|
|
934
|
+
|
|
935
|
+
```json
|
|
936
|
+
{
|
|
937
|
+
"type": "set-speed",
|
|
938
|
+
"trackId": "video-main",
|
|
939
|
+
"clipId": "shot-1",
|
|
940
|
+
"speed": { "playbackRate": 1.5 }
|
|
941
|
+
}
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Pass `null` for `playbackRate` or `speedRamp` to reset to normal speed. The
|
|
945
|
+
same operation is exposed as `setTimelineClipSpeed`.
|
|
946
|
+
Rates must stay between 0.1× and 5×. Use the exported `speedRampDuration`
|
|
947
|
+
helper to compute the required clip duration. The video proxy maps frame
|
|
948
|
+
timestamps through the curve. Audio uses pitch-preserving FFmpeg `rubberband`
|
|
949
|
+
commands sampled at up to 50 ms source intervals, then pads or trims to the
|
|
950
|
+
clip's exact timeline duration. Audio speed ramps require an FFmpeg build with
|
|
951
|
+
the `rubberband` filter; the renderer reports a clear error when it is missing.
|
|
952
|
+
The `doctor` command reports whether the filter is available. Trim and split
|
|
953
|
+
operations rebase speed curves and visual keyframes at the edit point.
|
|
954
|
+
|
|
955
|
+
Audio tracks and clips accept `volume` from 0 to 1, and audio clips accept
|
|
956
|
+
`fadeInSeconds` and `fadeOutSeconds`.
|
|
957
|
+
|
|
958
|
+
Use `set-audio-state` to reviewably change an audio clip's static volume, fade
|
|
959
|
+
durations, or selected source audio stream. Partial updates preserve unspecified
|
|
960
|
+
fields; `null` restores the inherited/default value:
|
|
961
|
+
|
|
962
|
+
```json
|
|
963
|
+
{
|
|
964
|
+
"type": "set-audio-state",
|
|
965
|
+
"trackId": "narration",
|
|
966
|
+
"clipId": "voice-over",
|
|
967
|
+
"state": { "volume": 0.8, "fadeInSeconds": 0.2, "audioStreamIndex": 0 }
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
The JavaScript API is
|
|
972
|
+
`setTimelineClipAudioState(timeline, trackId, clipId, state)`. Timeline
|
|
973
|
+
validation checks the selected stream and clip-bound fade durations.
|
|
974
|
+
|
|
975
|
+
Video clips can select a zero-based video-stream ordinal with
|
|
976
|
+
`videoStreamIndex`. Registered assets expose stream ordinals and their FFmpeg
|
|
977
|
+
stream indexes in `videoStreams`. Use `set-video-state` to reviewably change or
|
|
978
|
+
clear the selection; rendering creates a temporary clip proxy for the chosen
|
|
979
|
+
stream and leaves the source media unchanged:
|
|
980
|
+
|
|
981
|
+
```json
|
|
982
|
+
{
|
|
983
|
+
"type": "set-video-state",
|
|
984
|
+
"trackId": "video-main",
|
|
985
|
+
"clipId": "interview-shot",
|
|
986
|
+
"state": { "videoStreamIndex": 1 }
|
|
987
|
+
}
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
The JavaScript API is
|
|
991
|
+
`setTimelineClipVideoState(timeline, trackId, clipId, state)`. The same
|
|
992
|
+
selection can be supplied when replacing a clip asset with `videoStreamIndex`.
|
|
993
|
+
|
|
994
|
+
For changing clip gain over time, use `volumeKeyframes`. Times are relative to
|
|
995
|
+
the audio clip, begin at 0, and interpolate linearly between points. The last
|
|
996
|
+
value holds through the clip end. Trim, split, gap insertion, and ripple delete
|
|
997
|
+
preserve the curve. Use `set-volume-keyframes` or the
|
|
998
|
+
`setTimelineClipVolumeKeyframes` API to reviewably replace the curve; pass
|
|
999
|
+
`null` or an empty array to clear it.
|
|
1000
|
+
|
|
1001
|
+
```json
|
|
1002
|
+
{
|
|
1003
|
+
"type": "set-volume-keyframes",
|
|
1004
|
+
"trackId": "narration",
|
|
1005
|
+
"clipId": "voice-over",
|
|
1006
|
+
"volumeKeyframes": [
|
|
1007
|
+
{ "time": 0, "volume": 1 },
|
|
1008
|
+
{ "time": 2.5, "volume": 0.35 },
|
|
1009
|
+
{ "time": 4, "volume": 1 }
|
|
1010
|
+
]
|
|
1011
|
+
}
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
Audio tracks can also define `volumeKeyframes` to automate the track fader over
|
|
1015
|
+
project time. These points start at time 0, use linear interpolation, and hold
|
|
1016
|
+
the last value. Clip and track automation multiply together with static track
|
|
1017
|
+
and clip gain. `set-track-state` can replace the track curve; pass `null` to
|
|
1018
|
+
clear it. Gap insertion and ripple deletion preserve its timing:
|
|
1019
|
+
|
|
1020
|
+
```json
|
|
1021
|
+
{
|
|
1022
|
+
"type": "set-track-state",
|
|
1023
|
+
"trackId": "music",
|
|
1024
|
+
"state": {
|
|
1025
|
+
"volumeKeyframes": [
|
|
1026
|
+
{ "time": 0, "volume": 0.7 },
|
|
1027
|
+
{ "time": 5, "volume": 0.25 },
|
|
1028
|
+
{ "time": 12, "volume": 0.7 }
|
|
1029
|
+
]
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
Audio clips can also define an ordered `audioEffects` array. The supported
|
|
1035
|
+
effects are `equalizer` (`frequencyHz`, `gainDB`, optional `q`), `highpass` or
|
|
1036
|
+
`lowpass` (`frequencyHz`, optional `order` 1–2), `compressor` (`thresholdDB`,
|
|
1037
|
+
`ratio`, optional attack/release and makeup gain), and `denoise`
|
|
1038
|
+
(`reductionDB`, 0.01–30). `balance` takes a `value` from `-1` (left) to `1`
|
|
1039
|
+
(right), with `0` centered. Effects are applied to a temporary clip proxy,
|
|
1040
|
+
leaving the registered source unchanged. Denoise requires the `afftdn` filter;
|
|
1041
|
+
stereo balance requires `stereotools`. `doctor` and `capabilities` report these
|
|
1042
|
+
and other audio filter availability. Render preflight checks all filters
|
|
1043
|
+
required by selected clip/track effects, sidechain, and mastering settings
|
|
1044
|
+
before creating temporary media.
|
|
1045
|
+
Audio tracks may define the same `audioEffects` array; the Engine mixes the
|
|
1046
|
+
track's clips to a temporary stem, then applies the track chain across that
|
|
1047
|
+
complete stem. Use `set-track-audio-effects` to replace or clear the track
|
|
1048
|
+
chain without editing the timeline file by hand.
|
|
1049
|
+
Use the reviewable `set-audio-effects` operation or the
|
|
1050
|
+
`setTimelineClipAudioEffects` API to apply or replace this ordered list. Pass
|
|
1051
|
+
`null` or an empty array to clear it.
|
|
1052
|
+
|
|
1053
|
+
```json
|
|
1054
|
+
{
|
|
1055
|
+
"audioEffects": [
|
|
1056
|
+
{ "type": "highpass", "frequencyHz": 80 },
|
|
1057
|
+
{ "type": "denoise", "reductionDB": 12 },
|
|
1058
|
+
{ "type": "compressor", "thresholdDB": -20, "ratio": 3 },
|
|
1059
|
+
{ "type": "balance", "value": -0.2 }
|
|
1060
|
+
]
|
|
1061
|
+
}
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
Keep the final `audioMastering` stage for whole-mix processing such as loudness,
|
|
1065
|
+
final equalization, limiting, and output channel layout.
|
|
1066
|
+
|
|
1067
|
+
To duck a music track while narration clips are active, add this to the timeline:
|
|
1068
|
+
|
|
1069
|
+
```js
|
|
1070
|
+
timeline.audioMix = {
|
|
1071
|
+
// Clip-timing ducking remains available without signal analysis.
|
|
1072
|
+
ducking: [{
|
|
1073
|
+
sourceTrackId: 'narration',
|
|
1074
|
+
targetTrackId: 'music',
|
|
1075
|
+
attenuation: 0.22,
|
|
1076
|
+
attackSeconds: 0.12,
|
|
1077
|
+
releaseSeconds: 0.3,
|
|
1078
|
+
}],
|
|
1079
|
+
// Or compress the music from the actual narration signal.
|
|
1080
|
+
sidechain: [{
|
|
1081
|
+
sourceTrackId: 'narration',
|
|
1082
|
+
targetTrackId: 'music',
|
|
1083
|
+
thresholdDb: -24,
|
|
1084
|
+
ratio: 4,
|
|
1085
|
+
attackSeconds: 0.01,
|
|
1086
|
+
releaseSeconds: 0.25,
|
|
1087
|
+
}],
|
|
1088
|
+
};
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
`attenuation` is a multiplier on the target's current volume while source clips
|
|
1092
|
+
are active. This timing-based option compiles with track/clip gain and fades to
|
|
1093
|
+
one automation envelope. `sidechain` instead listens to the source track's
|
|
1094
|
+
rendered signal and applies FFmpeg sidechain compression to target clips. Its
|
|
1095
|
+
defaults are -24 dB threshold, 4:1 ratio, 10 ms attack, and 250 ms release.
|
|
1096
|
+
Threshold is limited to -60–0 dB, ratio to 1:1–20:1, attack to 0.01–2000 ms,
|
|
1097
|
+
and release to 0.01–9000 ms. The detector stem uses source track/clip gain and
|
|
1098
|
+
fades; output track/clip gain and fades remain applied by the timeline renderer.
|
|
1099
|
+
This is broadband compression, not speech recognition or phoneme-aware audio
|
|
1100
|
+
mixing. Final settings still need listening review on varied real material.
|
|
1101
|
+
Use the reviewable `set-audio-mix` edit operation or
|
|
1102
|
+
`setTimelineAudioMix(timeline, audioMix)` API to replace these rules; pass
|
|
1103
|
+
`null` to clear them.
|
|
1104
|
+
|
|
1105
|
+
Optional final-mix loudness mastering is configured on the timeline:
|
|
1106
|
+
|
|
1107
|
+
```js
|
|
1108
|
+
timeline.audioMastering = {
|
|
1109
|
+
integratedLoudnessLUFS: -16,
|
|
1110
|
+
truePeakDBTP: -1.5,
|
|
1111
|
+
loudnessRangeLU: 11,
|
|
1112
|
+
equalizer: [{ frequencyHz: 120, gainDB: 2, q: 1 }],
|
|
1113
|
+
limiter: { limitDBFS: -1, attackMs: 5, releaseMs: 80 },
|
|
1114
|
+
channelLayout: 'stereo',
|
|
1115
|
+
};
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
Defaults are -16 LUFS, -1.5 dBTP, and 11 LU. The renderer analyzes the final
|
|
1119
|
+
mix and uses two-pass FFmpeg `loudnorm` when the measurements are finite;
|
|
1120
|
+
unmeasurable non-silent audio falls back to single-pass dynamic normalization.
|
|
1121
|
+
Fully silent audio is re-encoded without loudness filtering because it has no
|
|
1122
|
+
measurable level to normalize. Before replacing the unmastered render, the
|
|
1123
|
+
encoded output is checked for duration, dimensions, and audio presence. GIF
|
|
1124
|
+
output cannot be mastered because GIF has
|
|
1125
|
+
no audio stream. Accepted ranges are -70 to -5 LUFS, -9 to 0 dBTP, and 1 to
|
|
1126
|
+
50 LU, respectively.
|
|
1127
|
+
Equalizer bands accept 20–20000 Hz, ±18 dB, and Q values from 0.1 to 10 (up to
|
|
1128
|
+
10 bands). The optional limiter accepts a -20 to 0 dBFS ceiling, 0.1–80 ms
|
|
1129
|
+
attack, and 10–1000 ms release. Equalization is applied before loudness analysis
|
|
1130
|
+
and normalization; the limiter runs at the end of the final mix chain. These
|
|
1131
|
+
controls need listening review against varied real material before mix presets
|
|
1132
|
+
can be recommended.
|
|
1133
|
+
`channelLayout` can request a mono or stereo final mix; the encoded channel count
|
|
1134
|
+
is checked before the mastered render replaces its input. Use the reviewable
|
|
1135
|
+
`set-audio-mastering` edit operation or
|
|
1136
|
+
`setTimelineAudioMastering(timeline, settings)` API to replace settings; pass
|
|
1137
|
+
`null` to clear mastering.
|
|
1138
|
+
|
|
1139
|
+
Silence and repeated-word detection produce proposals only. Review and save the
|
|
1140
|
+
approved cuts in the versioned EDL format before applying them. Source media,
|
|
1141
|
+
transcripts, EDLs, and render outputs are kept separate; commands refuse to
|
|
1142
|
+
overwrite existing outputs.
|
|
1143
|
+
|
|
1144
|
+
The CLI and JavaScript package exports are the engine interface. Editing agents,
|
|
1145
|
+
plugins, and creator-facing workflows can be built as separate consumers. See
|
|
1146
|
+
[`docs/architecture.md`](architecture.md) for contracts and runtime
|
|
1147
|
+
requirements, and [`docs/handoff.md`](handoff.md) for the full project
|
|
1148
|
+
context, validation state, and consolidated remaining-work checklist.
|
|
1149
|
+
|
|
1150
|
+
## OpenTimelineIO interchange
|
|
1151
|
+
|
|
1152
|
+
The optional OTIO bridge imports and exports standard external-media clips and
|
|
1153
|
+
gaps. Install the pinned Python adapter, then use a project timeline:
|
|
1154
|
+
|
|
1155
|
+
```powershell
|
|
1156
|
+
python -m pip install -r requirements-otio.txt
|
|
1157
|
+
node src/cli.mjs timeline export-otio video-projects/my-edit timeline.json review.otio
|
|
1158
|
+
node src/cli.mjs timeline import-otio video-projects/my-edit edit.otio imported-timeline.json
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
The bridge uses the maintained OpenTimelineIO Python library; the Node.js API
|
|
1162
|
+
and other Engine features work without it. OTIO files reference media by path
|
|
1163
|
+
and do not embed media. The profile supports video/audio tracks, nested track
|
|
1164
|
+
stacks and nested compositions inside tracks flattened into ordered Engine
|
|
1165
|
+
tracks, cuts, gaps, root-stack markers, standard `SMPTE_Dissolve` transitions
|
|
1166
|
+
on import, track enabled state, and project duration including trailing gaps.
|
|
1167
|
+
Engine track lock and solo state are stored in the namespaced metadata; OTIO has
|
|
1168
|
+
no portable track-lock state. Imported dissolves become overlapping clips with
|
|
1169
|
+
video opacity fades or audio crossfades. Constant-rate retiming uses OTIO's
|
|
1170
|
+
`LinearTimeWarp`; imported clip duration is calculated from the source span and
|
|
1171
|
+
playback rate. Variable speed ramps and retimed crossfades remain unsupported.
|
|
1172
|
+
Import applies a trimmed track's source range to its clips and gaps; it rejects
|
|
1173
|
+
trims that partially cut transitions. Trimmed stacks are applied to direct tracks;
|
|
1174
|
+
nested or combined stack/track trims remain unsupported. Import also rejects
|
|
1175
|
+
custom transition types, other overlaps, third-party effects, nested-composition
|
|
1176
|
+
trims combined with parent-track trims, and remote media references. Clip enabled
|
|
1177
|
+
state is retained on import and export. Export converts explicitly faded two-clip overlaps into standard
|
|
1178
|
+
`SMPTE_Dissolve` transitions. Unmarked or chained overlaps are rejected rather
|
|
1179
|
+
than silently reinterpreted. Standalone fades, clip-edge transitions, clip
|
|
1180
|
+
settings, track-level audio effects and automation, and overlay tracks are
|
|
1181
|
+
retained in namespaced metadata for Engine round trips. Crossfade participants
|
|
1182
|
+
with additional Engine-only clip settings are rejected because splitting those
|
|
1183
|
+
clips would change the settings' timing or scope.
|
|
1184
|
+
Crossfade offsets and source ranges are derived from the overlapping Engine
|
|
1185
|
+
clips.
|
|
1186
|
+
Standard OTIO playback still represents only the interoperable subset; other
|
|
1187
|
+
editors may ignore or remove Engine metadata, and may not reproduce those
|
|
1188
|
+
settings when rendering.
|
|
1189
|
+
|
|
1190
|
+
Run `npm run doctor` or `node src/cli.mjs capabilities` to check whether the
|
|
1191
|
+
selected Python environment has OpenTimelineIO and its `otio_json` adapter.
|
|
1192
|
+
Set `AIVE_OTIO_PYTHON` to the Python command for a separate environment when
|
|
1193
|
+
needed; the JavaScript capability API accepts the same override as `otioPython`.
|