@pixerate/editor 0.160.0 → 0.162.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/dist/agent/index.cjs +36 -35
- package/dist/agent/index.js +7 -6
- package/dist/animation/index.cjs +5 -4
- package/dist/animation/index.js +4 -3
- package/dist/animation-editor/index.cjs +62 -61
- package/dist/animation-editor/index.js +6 -5
- package/dist/canvas/index.cjs +33 -32
- package/dist/canvas/index.js +6 -5
- package/dist/chart/index.cjs +88 -87
- package/dist/chart/index.js +7 -6
- package/dist/{chunk-QMI5HZWA.cjs → chunk-236TKNUR.cjs} +3 -3
- package/dist/{chunk-6UPGM3JB.js → chunk-2TKHLX6H.js} +1 -1
- package/dist/{chunk-3YDQLQH2.cjs → chunk-32MMQYCQ.cjs} +81 -81
- package/dist/{chunk-MHEXLG3M.cjs → chunk-3GJH2MGH.cjs} +37 -37
- package/dist/{chunk-AKDWAWSY.cjs → chunk-3IHNXRUV.cjs} +45 -45
- package/dist/{chunk-VKF3PWKR.cjs → chunk-3OSXHLIT.cjs} +259 -28
- package/dist/{chunk-TVO6W2Q6.cjs → chunk-4NGVHHJB.cjs} +17 -17
- package/dist/chunk-4VU4CP7R.js +769 -0
- package/dist/chunk-6DM3PUQD.cjs +767 -0
- package/dist/{chunk-DBIVICAO.js → chunk-7CKPUUN4.js} +1 -1
- package/dist/{chunk-57JLBGJ5.cjs → chunk-7YBDRJXT.cjs} +8 -8
- package/dist/{chunk-KY3FYKP3.js → chunk-BQWGN5WH.js} +1 -1
- package/dist/{chunk-LEUZ4FRC.cjs → chunk-CIPIESX4.cjs} +3 -3
- package/dist/chunk-CJE5MOIS.js +347 -0
- package/dist/{chunk-5QBRAIVK.js → chunk-CX6JLDFC.js} +3 -3
- package/dist/{chunk-R3QY7PPL.js → chunk-DTSTNRPH.js} +1 -1
- package/dist/{chunk-6LUYFHHN.cjs → chunk-EQ46F75O.cjs} +51 -51
- package/dist/{chunk-UKCJ43YL.cjs → chunk-ES3UEVUT.cjs} +130 -898
- package/dist/{chunk-UM3RPYC5.js → chunk-EVPZ5IKN.js} +1 -1
- package/dist/{chunk-XNQ5EFB4.cjs → chunk-EY22AY6R.cjs} +16 -16
- package/dist/chunk-FKUKJ3XA.cjs +347 -0
- package/dist/{chunk-YABNLNY7.js → chunk-GLMP22FT.js} +3 -3
- package/dist/{chunk-SUF6WRXR.cjs → chunk-GTELFJRE.cjs} +2 -2
- package/dist/{chunk-72BGL3I2.js → chunk-GWV4T5NZ.js} +1 -1
- package/dist/{chunk-AY65VQSU.js → chunk-H3OEM3Q2.js} +1 -1
- package/dist/{chunk-4YWAEV4W.cjs → chunk-HDGBM7EA.cjs} +26 -26
- package/dist/{chunk-H5XX4KHB.js → chunk-HLOKB7WW.js} +5 -3
- package/dist/{chunk-WNAAWLZ5.js → chunk-KGTF55R3.js} +2 -2
- package/dist/{chunk-WPPJH6X4.cjs → chunk-LL3H5XUH.cjs} +6 -4
- package/dist/{chunk-4T44CJ5K.cjs → chunk-NA4UVVJ4.cjs} +15 -15
- package/dist/{chunk-Q6PA362P.js → chunk-NO5QQDHZ.js} +2 -2
- package/dist/{chunk-AA34BA7G.js → chunk-QYZGBVNC.js} +30 -798
- package/dist/chunk-S6X2QRVU.cjs +769 -0
- package/dist/{chunk-SSCL7QQ4.js → chunk-XDCFJEU7.js} +2 -2
- package/dist/{chunk-IQ6KKXOU.js → chunk-YN4AYYBJ.js} +243 -12
- package/dist/chunk-ZGOHKS3X.js +767 -0
- package/dist/color/index.cjs +6 -2
- package/dist/color/index.d.cts +1 -0
- package/dist/color/index.d.ts +1 -0
- package/dist/color/index.js +28 -24
- package/dist/color/suggest.d.cts +12 -0
- package/dist/color/suggest.d.ts +12 -0
- package/dist/effects/index.cjs +44 -771
- package/dist/effects/index.js +34 -761
- package/dist/effects/ui.d.cts +20 -3
- package/dist/effects/ui.d.ts +20 -3
- package/dist/export/index.cjs +6 -5
- package/dist/export/index.js +5 -4
- package/dist/extensions/index.cjs +9 -8
- package/dist/extensions/index.js +8 -7
- package/dist/gradient/index.cjs +28 -26
- package/dist/gradient/index.js +7 -5
- package/dist/image-editor/index.cjs +638 -637
- package/dist/image-editor/index.js +5 -4
- package/dist/index.cjs +15 -14
- package/dist/index.js +9 -8
- package/dist/paint-editor/index.cjs +17 -16
- package/dist/paint-editor/index.js +6 -5
- package/dist/typography/accessibility.d.cts +44 -0
- package/dist/typography/accessibility.d.ts +44 -0
- package/dist/typography/catalog.d.cts +98 -0
- package/dist/typography/catalog.d.ts +98 -0
- package/dist/typography/index.cjs +1099 -0
- package/dist/typography/index.d.cts +16 -0
- package/dist/typography/index.d.ts +16 -0
- package/dist/typography/index.js +1099 -0
- package/dist/typography/metrics.d.cts +31 -0
- package/dist/typography/metrics.d.ts +31 -0
- package/dist/typography/picker.d.cts +132 -0
- package/dist/typography/picker.d.ts +132 -0
- package/dist/typography/registry.d.cts +66 -0
- package/dist/typography/registry.d.ts +66 -0
- package/dist/typography/scale.d.cts +62 -0
- package/dist/typography/scale.d.ts +62 -0
- package/dist/typography/segment.d.cts +26 -0
- package/dist/typography/segment.d.ts +26 -0
- package/dist/typography/stack.d.cts +26 -0
- package/dist/typography/stack.d.ts +26 -0
- package/dist/typography/tools.d.cts +34 -0
- package/dist/typography/tools.d.ts +34 -0
- package/dist/typography/types.d.cts +96 -0
- package/dist/typography/types.d.ts +96 -0
- package/dist/typography/weight.d.cts +13 -0
- package/dist/typography/weight.d.ts +13 -0
- package/dist/vector/index.cjs +950 -949
- package/dist/vector/index.js +5 -4
- package/dist/video-editor/audio.d.cts +9 -0
- package/dist/video-editor/audio.d.ts +9 -0
- package/dist/video-editor/check.d.cts +1 -1
- package/dist/video-editor/check.d.ts +1 -1
- package/dist/video-editor/controller.d.cts +47 -0
- package/dist/video-editor/controller.d.ts +47 -0
- package/dist/video-editor/dialogue-tools.d.cts +19 -0
- package/dist/video-editor/dialogue-tools.d.ts +19 -0
- package/dist/video-editor/effect-authoring.d.cts +54 -0
- package/dist/video-editor/effect-authoring.d.ts +54 -0
- package/dist/video-editor/evaluate.d.cts +11 -0
- package/dist/video-editor/evaluate.d.ts +11 -0
- package/dist/video-editor/export.d.cts +7 -1
- package/dist/video-editor/export.d.ts +7 -1
- package/dist/video-editor/index.cjs +1857 -531
- package/dist/video-editor/index.d.cts +7 -0
- package/dist/video-editor/index.d.ts +7 -0
- package/dist/video-editor/index.js +1516 -190
- package/dist/video-editor/lut.d.cts +12 -2
- package/dist/video-editor/lut.d.ts +12 -2
- package/dist/video-editor/mask.d.cts +48 -0
- package/dist/video-editor/mask.d.ts +48 -0
- package/dist/video-editor/mix.d.cts +107 -0
- package/dist/video-editor/mix.d.ts +107 -0
- package/dist/video-editor/music-tools.d.cts +19 -0
- package/dist/video-editor/music-tools.d.ts +19 -0
- package/dist/video-editor/parse.d.cts +3 -1
- package/dist/video-editor/parse.d.ts +3 -1
- package/dist/video-editor/plan.d.cts +75 -0
- package/dist/video-editor/plan.d.ts +75 -0
- package/dist/video-editor/render.d.cts +1 -1
- package/dist/video-editor/render.d.ts +1 -1
- package/dist/video-editor/timeline.d.cts +2 -0
- package/dist/video-editor/timeline.d.ts +2 -0
- package/dist/video-editor/tools.d.cts +90 -0
- package/dist/video-editor/tools.d.ts +90 -0
- package/dist/video-editor/transcript-controller.d.cts +106 -0
- package/dist/video-editor/transcript-controller.d.ts +106 -0
- package/dist/video-editor/transcript.d.cts +40 -0
- package/dist/video-editor/transcript.d.ts +40 -0
- package/dist/video-editor/types.d.cts +62 -2
- package/dist/video-editor/types.d.ts +62 -2
- package/dist/video-editor/ui.d.cts +21 -1
- package/dist/video-editor/ui.d.ts +21 -1
- package/package.json +11 -1
- package/skills/README.md +3 -0
- package/skills/pixerate-bgm-alignment/SKILL.md +82 -0
- package/skills/pixerate-talking-head-edit/SKILL.md +80 -0
- package/skills/pixerate-typography/SKILL.md +91 -0
- package/skills/pixerate-video-editor/SKILL.md +40 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7;
|
|
2
2
|
|
|
3
|
+
var _chunk6DM3PUQDcjs = require('../chunk-6DM3PUQD.cjs');
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
|
|
@@ -134,7 +135,20 @@
|
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
|
|
137
|
-
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
var _chunk3OSXHLITcjs = require('../chunk-3OSXHLIT.cjs');
|
|
138
152
|
require('../chunk-PY4GWSE2.cjs');
|
|
139
153
|
|
|
140
154
|
|
|
@@ -154,11 +168,15 @@ require('../chunk-2BSBFBOA.cjs');
|
|
|
154
168
|
var _chunkFJ2MSI43cjs = require('../chunk-FJ2MSI43.cjs');
|
|
155
169
|
|
|
156
170
|
|
|
157
|
-
var
|
|
158
|
-
|
|
159
|
-
|
|
171
|
+
var _chunkGTELFJREcjs = require('../chunk-GTELFJRE.cjs');
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
var _chunkDWJJI6RTcjs = require('../chunk-DWJJI6RT.cjs');
|
|
176
|
+
require('../chunk-CIPIESX4.cjs');
|
|
160
177
|
require('../chunk-A6QWKBJP.cjs');
|
|
161
|
-
require('../chunk-
|
|
178
|
+
require('../chunk-ES3UEVUT.cjs');
|
|
179
|
+
require('../chunk-S6X2QRVU.cjs');
|
|
162
180
|
require('../chunk-IPJ3TEYR.cjs');
|
|
163
181
|
|
|
164
182
|
// src/video-editor/parse.ts
|
|
@@ -197,7 +215,7 @@ function parseVideoEase(v, path) {
|
|
|
197
215
|
if (v[0] < 0 || v[0] > 1 || v[2] < 0 || v[2] > 1) fail(path, "must have x1 and x2 between 0 and 1");
|
|
198
216
|
return [v[0], v[1], v[2], v[3]];
|
|
199
217
|
}
|
|
200
|
-
return oneOf(v,
|
|
218
|
+
return oneOf(v, _chunk3OSXHLITcjs.VIDEO_EASE_NAMES, path);
|
|
201
219
|
}
|
|
202
220
|
function parseVideoKeyframes(v, path, { min, max } = {}) {
|
|
203
221
|
if (!isObject(v)) return fail(path, "must be a number or keyframes { keys: [{ t, v, ease }] }");
|
|
@@ -235,7 +253,7 @@ function parseVideoTimeRemap(v, speed, path) {
|
|
|
235
253
|
if (typeof remap === "number" || remap === void 0) return fail(path, "must be keyframes { keys: [{ t, v, ease }] } (v: ms of source after the in point)");
|
|
236
254
|
if (speed !== void 0 && speed !== 1) fail(path, 'cannot be used with a speed; a ramp sets the speed itself (remove "speed")');
|
|
237
255
|
try {
|
|
238
|
-
|
|
256
|
+
_chunk3OSXHLITcjs.checkVideoTimeRemap.call(void 0, remap, path);
|
|
239
257
|
} catch (e) {
|
|
240
258
|
throw new Error(e.message.replace(/\.?$/, "."));
|
|
241
259
|
}
|
|
@@ -245,37 +263,66 @@ function parseVideoClipAnimation(v, path) {
|
|
|
245
263
|
if (v === void 0) return void 0;
|
|
246
264
|
if (!isObject(v)) return fail(path, "must be an object { effect, duration }");
|
|
247
265
|
return compact({
|
|
248
|
-
effect: oneOf(v.effect,
|
|
266
|
+
effect: oneOf(v.effect, _chunk3OSXHLITcjs.VIDEO_CLIP_ANIMATION_EFFECTS, `${path}.effect`, false),
|
|
249
267
|
duration: num(v.duration, `${path}.duration`, { optional: true, positive: true })
|
|
250
268
|
});
|
|
251
269
|
}
|
|
252
270
|
function parseVideoGrade(v, path) {
|
|
253
271
|
if (v === void 0) return void 0;
|
|
254
272
|
if (!isObject(v)) return fail(path, "must be an object such as { exposure, contrast, saturation }");
|
|
255
|
-
const known = /* @__PURE__ */ new Set([...
|
|
273
|
+
const known = /* @__PURE__ */ new Set([..._chunk3OSXHLITcjs.VIDEO_GRADE_CONTROLS.map((c) => c.key), "lut"]);
|
|
256
274
|
const unknown = Object.keys(v).find((k) => !known.has(k));
|
|
257
275
|
if (unknown) fail(`${path}.${unknown}`, `is not a grade setting; use ${[...known].join(", ")}`);
|
|
258
|
-
return compact({ ...Object.fromEntries(
|
|
276
|
+
return compact({ ...Object.fromEntries(_chunk3OSXHLITcjs.VIDEO_GRADE_CONTROLS.map((c) => [c.key, num(v[c.key], `${path}.${c.key}`, { optional: true, min: c.min, max: c.max })])), lut: parseVideoGradeLut(v.lut, `${path}.lut`) });
|
|
259
277
|
}
|
|
260
278
|
function parseVideoGradeLut(v, path) {
|
|
261
279
|
if (v === void 0) return void 0;
|
|
262
280
|
if (!isObject(v)) return fail(path, "must be an object { src, strength } naming a .cube file");
|
|
263
|
-
const unknown = Object.keys(v).find((k) => !["src", "strength", "name"].includes(k));
|
|
264
|
-
if (unknown) fail(`${path}.${unknown}`, "is not a LUT setting; use src, strength, name");
|
|
265
|
-
return compact({ src: str(v.src, `${path}.src`), strength: num(v.strength, `${path}.strength`, { optional: true, min: 0, max: 1 }), name: str(v.name, `${path}.name`, true) });
|
|
281
|
+
const unknown = Object.keys(v).find((k) => !["src", "strength", "name", "preserveSkinTones"].includes(k));
|
|
282
|
+
if (unknown) fail(`${path}.${unknown}`, "is not a LUT setting; use src, strength, name, preserveSkinTones");
|
|
283
|
+
return compact({ src: str(v.src, `${path}.src`), strength: num(v.strength, `${path}.strength`, { optional: true, min: 0, max: 1 }), name: str(v.name, `${path}.name`, true), preserveSkinTones: num(v.preserveSkinTones, `${path}.preserveSkinTones`, { optional: true, min: 0, max: 1 }) });
|
|
284
|
+
}
|
|
285
|
+
var MASK_KEYS = ["shape", "x", "y", "width", "height", "rotation", "feather", "cornerRadius", "inverted", "points"];
|
|
286
|
+
function parseVideoClipMask(v, path) {
|
|
287
|
+
if (v === void 0) return void 0;
|
|
288
|
+
if (!isObject(v)) return fail(path, 'must be an object such as { shape: "ellipse", x: 0.5, y: 0.5, width: 0.4, height: 0.6, feather: 20 }');
|
|
289
|
+
const unknown = Object.keys(v).find((k) => !MASK_KEYS.includes(k));
|
|
290
|
+
if (unknown) fail(`${path}.${unknown}`, `is not a mask setting; use ${MASK_KEYS.join(", ")}`);
|
|
291
|
+
const shape = oneOf(v.shape, _chunk3OSXHLITcjs.VIDEO_MASK_SHAPES, `${path}.shape`, false);
|
|
292
|
+
let points;
|
|
293
|
+
if (v.points !== void 0) {
|
|
294
|
+
if (shape !== "polygon") fail(`${path}.points`, "is only for polygon masks");
|
|
295
|
+
if (!Array.isArray(v.points) || v.points.length < 3) fail(`${path}.points`, "must be a list of at least 3 { x, y } points");
|
|
296
|
+
points = v.points.map((p, i) => {
|
|
297
|
+
if (!isObject(p)) return fail(`${path}.points[${i}]`, "must be an object { x, y }");
|
|
298
|
+
return { x: num(p.x, `${path}.points[${i}].x`), y: num(p.y, `${path}.points[${i}].y`) };
|
|
299
|
+
});
|
|
300
|
+
} else if (shape === "polygon") fail(`${path}.points`, "is required for a polygon mask: at least 3 { x, y } points as fractions of the picture");
|
|
301
|
+
return compact({
|
|
302
|
+
shape,
|
|
303
|
+
x: num(v.x, `${path}.x`, { optional: true }),
|
|
304
|
+
y: num(v.y, `${path}.y`, { optional: true }),
|
|
305
|
+
width: num(v.width, `${path}.width`, { optional: true, min: 0 }),
|
|
306
|
+
height: num(v.height, `${path}.height`, { optional: true, min: 0 }),
|
|
307
|
+
rotation: num(v.rotation, `${path}.rotation`, { optional: true }),
|
|
308
|
+
feather: num(v.feather, `${path}.feather`, { optional: true, min: 0 }),
|
|
309
|
+
cornerRadius: num(v.cornerRadius, `${path}.cornerRadius`, { optional: true, min: 0, max: 1 }),
|
|
310
|
+
inverted: bool(v.inverted, `${path}.inverted`),
|
|
311
|
+
points
|
|
312
|
+
});
|
|
266
313
|
}
|
|
267
314
|
function parseVideoTransition(v, path) {
|
|
268
315
|
if (v === void 0) return void 0;
|
|
269
316
|
if (!isObject(v)) return fail(path, "must be an object { type, duration }");
|
|
270
|
-
const type = oneOf(v.type,
|
|
317
|
+
const type = oneOf(v.type, _chunk3OSXHLITcjs.VIDEO_TRANSITION_TYPES, `${path}.type`, false);
|
|
271
318
|
if (type === "effect" && v.effect === void 0) fail(`${path}.effect`, 'is required for an effect transition: { effect: "<effect id>", values }');
|
|
272
319
|
if (type !== "effect" && v.effect !== void 0) fail(`${path}.effect`, 'is only for transitions of type "effect"');
|
|
273
320
|
return compact({
|
|
274
321
|
type,
|
|
275
322
|
duration: num(v.duration, `${path}.duration`, { positive: true }),
|
|
276
323
|
color: str(v.color, `${path}.color`, true),
|
|
277
|
-
direction: oneOf(v.direction,
|
|
278
|
-
audioCurve: oneOf(v.audioCurve,
|
|
324
|
+
direction: oneOf(v.direction, _chunk3OSXHLITcjs.VIDEO_WIPE_DIRECTIONS, `${path}.direction`),
|
|
325
|
+
audioCurve: oneOf(v.audioCurve, _chunk3OSXHLITcjs.VIDEO_FADE_CURVES, `${path}.audioCurve`),
|
|
279
326
|
effect: v.effect === void 0 ? void 0 : parseVideoEffectRef(v.effect, `${path}.effect`, false)
|
|
280
327
|
});
|
|
281
328
|
}
|
|
@@ -368,7 +415,7 @@ function parseVideoAssetProxies(v, path) {
|
|
|
368
415
|
}
|
|
369
416
|
function parseVideoClip(v, path = "clip") {
|
|
370
417
|
if (!isObject(v)) return fail(path, "must be an object");
|
|
371
|
-
const type = oneOf(_nullishCoalesce(v.type, () => ( "media")),
|
|
418
|
+
const type = oneOf(_nullishCoalesce(v.type, () => ( "media")), _chunk3OSXHLITcjs.VIDEO_CLIP_TYPES, `${path}.type`, false);
|
|
372
419
|
const base = {
|
|
373
420
|
id: str(v.id, `${path}.id`),
|
|
374
421
|
name: str(v.name, `${path}.name`, true),
|
|
@@ -376,15 +423,16 @@ function parseVideoClip(v, path = "clip") {
|
|
|
376
423
|
duration: num(v.duration, `${path}.duration`, { positive: true }),
|
|
377
424
|
opacity: parseVideoAnimatable(v.opacity, `${path}.opacity`, { optional: true, min: 0, max: 1 }),
|
|
378
425
|
transform: parseVideoTransform(v.transform, `${path}.transform`),
|
|
379
|
-
fit: oneOf(v.fit,
|
|
380
|
-
blend: oneOf(v.blend,
|
|
426
|
+
fit: oneOf(v.fit, _chunk3OSXHLITcjs.VIDEO_FITS, `${path}.fit`),
|
|
427
|
+
blend: oneOf(v.blend, _chunk3OSXHLITcjs.VIDEO_BLEND_MODES, `${path}.blend`),
|
|
381
428
|
grade: parseVideoGrade(v.grade, `${path}.grade`),
|
|
382
429
|
animateIn: parseVideoClipAnimation(v.animateIn, `${path}.animateIn`),
|
|
383
430
|
animateOut: parseVideoClipAnimation(v.animateOut, `${path}.animateOut`),
|
|
384
431
|
fadeIn: num(v.fadeIn, `${path}.fadeIn`, { optional: true, min: 0 }),
|
|
385
432
|
fadeOut: num(v.fadeOut, `${path}.fadeOut`, { optional: true, min: 0 }),
|
|
386
433
|
transition: parseVideoTransition(v.transition, `${path}.transition`),
|
|
387
|
-
effects: parseVideoClipEffects(v.effects, `${path}.effects`)
|
|
434
|
+
effects: parseVideoClipEffects(v.effects, `${path}.effects`),
|
|
435
|
+
mask: parseVideoClipMask(v.mask, `${path}.mask`)
|
|
388
436
|
};
|
|
389
437
|
if (type === "media")
|
|
390
438
|
return compact({
|
|
@@ -396,8 +444,8 @@ function parseVideoClip(v, path = "clip") {
|
|
|
396
444
|
volume: parseVideoAnimatable(v.volume, `${path}.volume`, { optional: true, min: 0, max: 4 }),
|
|
397
445
|
muted: bool(v.muted, `${path}.muted`),
|
|
398
446
|
timeRemap: parseVideoTimeRemap(v.timeRemap, v.speed, `${path}.timeRemap`),
|
|
399
|
-
fadeInCurve: oneOf(v.fadeInCurve,
|
|
400
|
-
fadeOutCurve: oneOf(v.fadeOutCurve,
|
|
447
|
+
fadeInCurve: oneOf(v.fadeInCurve, _chunk3OSXHLITcjs.VIDEO_FADE_CURVES, `${path}.fadeInCurve`),
|
|
448
|
+
fadeOutCurve: oneOf(v.fadeOutCurve, _chunk3OSXHLITcjs.VIDEO_FADE_CURVES, `${path}.fadeOutCurve`)
|
|
401
449
|
});
|
|
402
450
|
if (type === "sequence") {
|
|
403
451
|
for (const key of ["speed", "timeRemap"])
|
|
@@ -409,8 +457,8 @@ function parseVideoClip(v, path = "clip") {
|
|
|
409
457
|
in: num(v.in, `${path}.in`, { optional: true, min: 0 }),
|
|
410
458
|
volume: parseVideoAnimatable(v.volume, `${path}.volume`, { optional: true, min: 0, max: 4 }),
|
|
411
459
|
muted: bool(v.muted, `${path}.muted`),
|
|
412
|
-
fadeInCurve: oneOf(v.fadeInCurve,
|
|
413
|
-
fadeOutCurve: oneOf(v.fadeOutCurve,
|
|
460
|
+
fadeInCurve: oneOf(v.fadeInCurve, _chunk3OSXHLITcjs.VIDEO_FADE_CURVES, `${path}.fadeInCurve`),
|
|
461
|
+
fadeOutCurve: oneOf(v.fadeOutCurve, _chunk3OSXHLITcjs.VIDEO_FADE_CURVES, `${path}.fadeOutCurve`)
|
|
414
462
|
});
|
|
415
463
|
}
|
|
416
464
|
if (type === "text")
|
|
@@ -424,6 +472,13 @@ function parseVideoClip(v, path = "clip") {
|
|
|
424
472
|
fontWeight: num(v.fontWeight, `${path}.fontWeight`, { optional: true, min: 100, max: 1e3 }),
|
|
425
473
|
background: str(v.background, `${path}.background`, true)
|
|
426
474
|
});
|
|
475
|
+
if (type === "adjustment") {
|
|
476
|
+
const scope = oneOf(v.scope, _chunk3OSXHLITcjs.VIDEO_ADJUSTMENT_SCOPES, `${path}.scope`);
|
|
477
|
+
const targetTrack = str(v.targetTrack, `${path}.targetTrack`, true);
|
|
478
|
+
if (scope === "track" && !targetTrack) fail(`${path}.targetTrack`, 'is required when scope is "track": the id of the track whose pictures the adjustment changes');
|
|
479
|
+
if (targetTrack !== void 0 && scope !== "track") fail(`${path}.targetTrack`, 'is only for scope "track"');
|
|
480
|
+
return compact({ type, ...base, scope, targetTrack });
|
|
481
|
+
}
|
|
427
482
|
if (type === "effect") {
|
|
428
483
|
const ref = parseVideoEffectRef({ effect: v.effect, provider: v.provider, name: v.name === void 0 ? void 0 : v.name, values: v.values }, path);
|
|
429
484
|
return compact({ type, ...base, ...ref, name: base.name });
|
|
@@ -521,9 +576,9 @@ function createVideoProject(options = {}) {
|
|
|
521
576
|
// src/video-editor/check.ts
|
|
522
577
|
function videoClipKeyframes(clip) {
|
|
523
578
|
const out = [
|
|
524
|
-
{ property: "opacity", keys:
|
|
525
|
-
{ property: "volume", keys: clip.type === "media" || clip.type === "sequence" ?
|
|
526
|
-
...
|
|
579
|
+
{ property: "opacity", keys: _chunk3OSXHLITcjs.videoKeysOf.call(void 0, clip.opacity) },
|
|
580
|
+
{ property: "volume", keys: clip.type === "media" || clip.type === "sequence" ? _chunk3OSXHLITcjs.videoKeysOf.call(void 0, clip.volume) : [] },
|
|
581
|
+
..._chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES.map((p) => ({ property: p, keys: _chunk3OSXHLITcjs.videoKeysOf.call(void 0, _optionalChain([clip, 'access', _2 => _2.transform, 'optionalAccess', _3 => _3[p]])) }))
|
|
527
582
|
];
|
|
528
583
|
return out.filter((o) => o.keys.length);
|
|
529
584
|
}
|
|
@@ -545,13 +600,14 @@ var VIDEO_ISSUE_CODES = [
|
|
|
545
600
|
"unused-asset",
|
|
546
601
|
"unused-sequence",
|
|
547
602
|
"marker-past-end",
|
|
603
|
+
"missing-target-track",
|
|
548
604
|
"empty"
|
|
549
605
|
];
|
|
550
606
|
var videoIssueKey = (i) => [i.code, _nullishCoalesce(i.sequence, () => ( "")), _nullishCoalesce(i.track, () => ( "")), _nullishCoalesce(i.clip, () => ( "")), _nullishCoalesce(i.asset, () => ( "")), _nullishCoalesce(i.marker, () => ( ""))].join("|");
|
|
551
607
|
function checkVideoProject(project) {
|
|
552
608
|
const issues = [];
|
|
553
|
-
const assets =
|
|
554
|
-
const sequences =
|
|
609
|
+
const assets = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, project);
|
|
610
|
+
const sequences = _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, project);
|
|
555
611
|
const used = /* @__PURE__ */ new Set();
|
|
556
612
|
const usedSequences = /* @__PURE__ */ new Set();
|
|
557
613
|
const cycles = /* @__PURE__ */ new Map();
|
|
@@ -559,7 +615,7 @@ function checkVideoProject(project) {
|
|
|
559
615
|
if ((asset.kind === "video" || asset.kind === "audio") && asset.duration === void 0)
|
|
560
616
|
issues.push({ code: "unknown-duration", severity: "warning", asset: asset.id, message: `Asset "${asset.id}" has no duration, so clips of it cannot be checked against its length; set "duration" in ms.` });
|
|
561
617
|
const checkTrack = (track, sequence) => {
|
|
562
|
-
const clips =
|
|
618
|
+
const clips = _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, track);
|
|
563
619
|
const where = sequence ? ` in sequence "${sequence}"` : "";
|
|
564
620
|
for (let i = 0; i < clips.length; i++) {
|
|
565
621
|
const clip = clips[i];
|
|
@@ -569,13 +625,13 @@ function checkVideoProject(project) {
|
|
|
569
625
|
const seq = sequences.get(clip.sequence);
|
|
570
626
|
if (!seq) issues.push({ code: "missing-sequence", severity: "error", ...at, message: `Compound clip "${clip.id}" plays sequence "${clip.sequence}", which is not in the project; add the sequence or delete the clip.` });
|
|
571
627
|
else {
|
|
572
|
-
if (!cycles.has(seq.id)) cycles.set(seq.id,
|
|
628
|
+
if (!cycles.has(seq.id)) cycles.set(seq.id, _chunk3OSXHLITcjs.findVideoSequenceCycle.call(void 0, project, seq.id));
|
|
573
629
|
const cycle = cycles.get(seq.id);
|
|
574
630
|
if (cycle) issues.push({ code: "sequence-cycle", severity: "error", ...at, message: `Compound clip "${clip.id}" plays sequence "${seq.id}", which contains itself (${cycle.join(" \u2192 ")}), so it is left out of the picture and sound; remove the compound clip that closes the loop.` });
|
|
575
|
-
const length =
|
|
576
|
-
const range =
|
|
577
|
-
if (range.out > length +
|
|
578
|
-
issues.push({ code: "source-overrun", severity: "error", ...at, message: `Compound clip "${clip.id}" plays to ${
|
|
631
|
+
const length = _chunk3OSXHLITcjs.videoSequenceDuration.call(void 0, seq);
|
|
632
|
+
const range = _chunk3OSXHLITcjs.videoClipSourceRange.call(void 0, clip);
|
|
633
|
+
if (range.out > length + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
634
|
+
issues.push({ code: "source-overrun", severity: "error", ...at, message: `Compound clip "${clip.id}" plays to ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, range.out)} of sequence "${seq.id}", which is only ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, length)} long; shorten the clip or move its in point.` });
|
|
579
635
|
}
|
|
580
636
|
} else if (clip.type === "media") {
|
|
581
637
|
used.add(clip.asset);
|
|
@@ -584,28 +640,30 @@ function checkVideoProject(project) {
|
|
|
584
640
|
else {
|
|
585
641
|
if (track.kind === "audio" && asset.kind === "image") issues.push({ code: "wrong-track", severity: "error", ...at, message: `Clip "${clip.id}" is an image on audio track "${track.id}"; move it to a video track.` });
|
|
586
642
|
if (track.kind === "video" && asset.kind === "audio") issues.push({ code: "wrong-track", severity: "error", ...at, message: `Clip "${clip.id}" is audio on video track "${track.id}"; move it to an audio track.` });
|
|
587
|
-
const range =
|
|
588
|
-
if (asset.duration !== void 0 && range.out > asset.duration +
|
|
589
|
-
issues.push({ code: "source-overrun", severity: "error", ...at, message: `Clip "${clip.id}" plays to ${
|
|
643
|
+
const range = _chunk3OSXHLITcjs.videoClipSourceRange.call(void 0, clip);
|
|
644
|
+
if (asset.duration !== void 0 && range.out > asset.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
645
|
+
issues.push({ code: "source-overrun", severity: "error", ...at, message: `Clip "${clip.id}" plays to ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, range.out)} of "${asset.id}", which is only ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, asset.duration)} long; shorten the clip or move its in point.` });
|
|
590
646
|
}
|
|
591
647
|
} else if (track.kind === "audio") issues.push({ code: "wrong-track", severity: "error", ...at, message: `Clip "${clip.id}" is a ${clip.type} clip on audio track "${track.id}"; move it to a video track.` });
|
|
648
|
+
if (clip.type === "adjustment" && (_nullishCoalesce(clip.scope, () => ( "underneath"))) === "track" && !(sequence ? _nullishCoalesce(_optionalChain([sequences, 'access', _4 => _4.get, 'call', _5 => _5(sequence), 'optionalAccess', _6 => _6.tracks]), () => ( [])) : project.tracks).some((t) => t.id === clip.targetTrack))
|
|
649
|
+
issues.push({ code: "missing-target-track", severity: "warning", ...at, message: `Adjustment clip "${clip.id}" changes track "${clip.targetTrack}", which is not ${sequence ? `in sequence "${sequence}"` : "in the project"}, so it changes nothing; set its targetTrack to a track beside it or change its scope.` });
|
|
592
650
|
const next = clips[i + 1];
|
|
593
|
-
if (next && next.start <
|
|
594
|
-
issues.push({ code: "overlap", severity: "error", ...at, message: `Clips "${clip.id}" and "${next.id}" overlap on track "${track.id}"${where} from ${
|
|
595
|
-
if ((_nullishCoalesce(clip.fadeIn, () => ( 0))) + (_nullishCoalesce(clip.fadeOut, () => ( 0))) > clip.duration +
|
|
651
|
+
if (next && next.start < _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip) - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
652
|
+
issues.push({ code: "overlap", severity: "error", ...at, message: `Clips "${clip.id}" and "${next.id}" overlap on track "${track.id}"${where} from ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, next.start)}; move one of them.` });
|
|
653
|
+
if ((_nullishCoalesce(clip.fadeIn, () => ( 0))) + (_nullishCoalesce(clip.fadeOut, () => ( 0))) > clip.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
596
654
|
issues.push({ code: "fades-too-long", severity: "warning", ...at, message: `Clip "${clip.id}"'s fades (${_nullishCoalesce(clip.fadeIn, () => ( 0))} + ${_nullishCoalesce(clip.fadeOut, () => ( 0))} ms) are longer than the clip (${clip.duration} ms); shorten them.` });
|
|
597
|
-
const anim = (clip.animateIn ? _nullishCoalesce(clip.animateIn.duration, () => (
|
|
598
|
-
if (anim > clip.duration +
|
|
655
|
+
const anim = (clip.animateIn ? _nullishCoalesce(clip.animateIn.duration, () => ( _chunk3OSXHLITcjs.VIDEO_CLIP_ANIMATION_DURATION)) : 0) + (clip.animateOut ? _nullishCoalesce(clip.animateOut.duration, () => ( _chunk3OSXHLITcjs.VIDEO_CLIP_ANIMATION_DURATION)) : 0);
|
|
656
|
+
if (anim > clip.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
599
657
|
issues.push({ code: "animations-too-long", severity: "warning", ...at, message: `Clip "${clip.id}"'s entrance and exit (${anim} ms together) are longer than the clip (${clip.duration} ms), so both are shortened; give them shorter durations.` });
|
|
600
658
|
for (const which of ["animateIn", "animateOut"]) {
|
|
601
659
|
const a = clip[which];
|
|
602
660
|
if (!a) continue;
|
|
603
|
-
const keyed =
|
|
661
|
+
const keyed = _chunk3OSXHLITcjs.videoClipAnimationProperties.call(void 0, a).filter((p) => _chunk3OSXHLITcjs.videoKeysOf.call(void 0, p === "opacity" ? clip.opacity : _optionalChain([clip, 'access', _7 => _7.transform, 'optionalAccess', _8 => _8[p]])).length);
|
|
604
662
|
if (keyed.length)
|
|
605
663
|
issues.push({ code: "animation-keyed", severity: "note", ...at, message: `Clip "${clip.id}"'s ${a.effect} ${which === "animateIn" ? "entrance" : "exit"} leaves ${keyed.join(", ")} alone, because the clip keys ${keyed.length > 1 ? "them" : "it"} itself; remove those keyframes or the animation.` });
|
|
606
664
|
}
|
|
607
665
|
for (const { property, keys } of videoClipKeyframes(clip)) {
|
|
608
|
-
const outside = keys.filter((k) => k.t < -
|
|
666
|
+
const outside = keys.filter((k) => k.t < -_chunk3OSXHLITcjs.VIDEO_TIME_EPSILON || k.t > clip.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON);
|
|
609
667
|
if (outside.length)
|
|
610
668
|
issues.push({
|
|
611
669
|
code: "keyframes-outside-clip",
|
|
@@ -616,20 +674,20 @@ function checkVideoProject(project) {
|
|
|
616
674
|
}
|
|
617
675
|
if (clip.transition) {
|
|
618
676
|
const d = clip.transition.duration;
|
|
619
|
-
const before =
|
|
677
|
+
const before = _chunk3OSXHLITcjs.videoClipBefore.call(void 0, track, clip);
|
|
620
678
|
if (!before) {
|
|
621
679
|
issues.push({ code: "transition-without-cut", severity: "warning", ...at, message: `Clip "${clip.id}" has a transition but no clip ends where it starts on track "${track.id}", so it plays as nothing; butt a clip up against it or remove the transition.` });
|
|
622
680
|
continue;
|
|
623
681
|
}
|
|
624
|
-
if (d > clip.duration +
|
|
682
|
+
if (d > clip.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON || d > before.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) {
|
|
625
683
|
issues.push({ code: "transition-too-long", severity: "error", ...at, message: `The ${d} ms transition into "${clip.id}" is longer than "${clip.id}" (${clip.duration} ms) or "${before.id}" (${before.duration} ms); make it at most ${Math.min(clip.duration, before.duration)} ms.` });
|
|
626
684
|
continue;
|
|
627
685
|
}
|
|
628
686
|
if (clip.transition.type === "dip") continue;
|
|
629
|
-
const outgoing =
|
|
630
|
-
const incoming =
|
|
687
|
+
const outgoing = _chunk3OSXHLITcjs.videoClipHandles.call(void 0, before, assets, sequences).after;
|
|
688
|
+
const incoming = _chunk3OSXHLITcjs.videoClipHandles.call(void 0, clip, assets, sequences).before;
|
|
631
689
|
const half = d / 2;
|
|
632
|
-
if (outgoing < half -
|
|
690
|
+
if (outgoing < half - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON || incoming < half - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) {
|
|
633
691
|
const fits = 2 * Math.min(outgoing, incoming);
|
|
634
692
|
issues.push({
|
|
635
693
|
code: "transition-handles",
|
|
@@ -646,9 +704,9 @@ function checkVideoProject(project) {
|
|
|
646
704
|
for (const sequence of _nullishCoalesce(project.sequences, () => ( [])))
|
|
647
705
|
if (!usedSequences.has(sequence.id)) issues.push({ code: "unused-sequence", severity: "note", sequence: sequence.id, message: `Sequence "${sequence.id}" is not played by any compound clip.` });
|
|
648
706
|
for (const asset of project.assets) if (!used.has(asset.id)) issues.push({ code: "unused-asset", severity: "note", asset: asset.id, message: `Asset "${asset.id}" is not used by any clip.` });
|
|
649
|
-
const end =
|
|
707
|
+
const end = _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, project);
|
|
650
708
|
for (const marker of _nullishCoalesce(project.markers, () => ( [])))
|
|
651
|
-
if (marker.time > end +
|
|
709
|
+
if (marker.time > end + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) issues.push({ code: "marker-past-end", severity: "note", marker: marker.id, message: `Marker "${marker.id}" at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, marker.time)} is past the end (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, end)}).` });
|
|
652
710
|
if (!project.tracks.some((t) => t.clips.length)) issues.push({ code: "empty", severity: "note", message: "The project has no clips yet; add assets and then clips." });
|
|
653
711
|
const rank = { error: 0, warning: 1, note: 2 };
|
|
654
712
|
return issues.sort((a, b) => rank[a.severity] - rank[b.severity]);
|
|
@@ -702,7 +760,7 @@ function mergeSpans(spans) {
|
|
|
702
760
|
const out = [];
|
|
703
761
|
for (const [from, to] of [...spans].sort((a, b) => a[0] - b[0])) {
|
|
704
762
|
const last = out[out.length - 1];
|
|
705
|
-
if (last && from <= last[1] +
|
|
763
|
+
if (last && from <= last[1] + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) last[1] = Math.max(last[1], to);
|
|
706
764
|
else out.push([from, to]);
|
|
707
765
|
}
|
|
708
766
|
return out;
|
|
@@ -716,16 +774,16 @@ function clipAnimatables(clip) {
|
|
|
716
774
|
// A speed ramp is keys in clip time too, so trims and splits keep it on the same frames of source.
|
|
717
775
|
{ get: () => c.timeRemap, set: (v) => c.timeRemap = v }
|
|
718
776
|
];
|
|
719
|
-
for (const p of
|
|
777
|
+
for (const p of _chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES) out.push({ get: () => _optionalChain([c, 'access', _9 => _9.transform, 'optionalAccess', _10 => _10[p]]), set: (v) => c.transform && (c.transform[p] = v) });
|
|
720
778
|
return out;
|
|
721
779
|
}
|
|
722
780
|
function retimeClipKeys(clip, fn) {
|
|
723
781
|
for (const a of clipAnimatables(clip)) {
|
|
724
782
|
const v = a.get();
|
|
725
|
-
if (
|
|
783
|
+
if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, v)) a.set(fn(v));
|
|
726
784
|
}
|
|
727
785
|
}
|
|
728
|
-
var OPTIONAL_CLIP_FIELDS = ["name", "effects", "values", "provider", "opacity", "transform", "fit", "blend", "grade", "animateIn", "animateOut", "fadeIn", "fadeOut", "fadeInCurve", "fadeOutCurve", "transition", "in", "speed", "timeRemap", "volume", "muted", "color", "fontSize", "fontFamily", "fontWeight", "background"];
|
|
786
|
+
var OPTIONAL_CLIP_FIELDS = ["name", "effects", "mask", "scope", "targetTrack", "values", "provider", "opacity", "transform", "fit", "blend", "grade", "animateIn", "animateOut", "fadeIn", "fadeOut", "fadeInCurve", "fadeOutCurve", "transition", "in", "speed", "timeRemap", "volume", "muted", "color", "fontSize", "fontFamily", "fontWeight", "background"];
|
|
729
787
|
var TIME_FIELDS = { slip_clip: ["delta"], roll_edit: ["delta"], slide_clip: ["delta"], lift_range: ["from", "to"], extract_range: ["from", "to"], split_at: ["at"] };
|
|
730
788
|
var Editor = (_class = class {
|
|
731
789
|
constructor(p) {;_class.prototype.__init.call(this);
|
|
@@ -739,8 +797,8 @@ var Editor = (_class = class {
|
|
|
739
797
|
return t;
|
|
740
798
|
}
|
|
741
799
|
clip(id) {
|
|
742
|
-
const at =
|
|
743
|
-
const nested = at ? null :
|
|
800
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, this.p, id);
|
|
801
|
+
const nested = at ? null : _chunk3OSXHLITcjs.findVideoSequenceClip.call(void 0, this.p, id);
|
|
744
802
|
if (nested) refuse(`Clip "${id}" is inside sequence "${nested.sequence.id}", and clips inside sequences cannot be edited yet; edit the compound clip that plays the sequence instead (list_video_sequences shows which).`);
|
|
745
803
|
if (!at) {
|
|
746
804
|
const ids = this.p.tracks.flatMap((t) => t.clips.map((c) => c.id));
|
|
@@ -757,12 +815,18 @@ var Editor = (_class = class {
|
|
|
757
815
|
return a;
|
|
758
816
|
}
|
|
759
817
|
fits(track, start, duration, ignore, what) {
|
|
760
|
-
const hit =
|
|
818
|
+
const hit = _chunk3OSXHLITcjs.videoOverlap.call(void 0, track, start, start + duration, ignore);
|
|
761
819
|
if (hit) {
|
|
762
|
-
const free =
|
|
763
|
-
refuse(`${what} at ${
|
|
820
|
+
const free = _chunk3OSXHLITcjs.nextFreeVideoSlot.call(void 0, track, start, duration, ignore);
|
|
821
|
+
refuse(`${what} at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, start)}\u2013${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, start + duration)} would overlap clip "${hit.id}" (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, hit.start)}\u2013${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, hit))}) on track "${track.id}"; the next free place is ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, free)}, or use mode "insert" to push later clips along or "overwrite" to replace what is there.`);
|
|
764
822
|
}
|
|
765
823
|
}
|
|
824
|
+
/** An adjustment clip's target track must exist. */
|
|
825
|
+
adjustmentTarget(clip) {
|
|
826
|
+
if (clip.type !== "adjustment" || clip.targetTrack === void 0) return;
|
|
827
|
+
const ids = _chunk3OSXHLITcjs.allVideoTracks.call(void 0, this.p).map(({ track }) => track.id);
|
|
828
|
+
if (!ids.includes(clip.targetTrack)) refuse(`Adjustment clip "${clip.id}" targets track ${_chunkEBDZWIOPcjs.namedWithSuggestion.call(void 0, clip.targetTrack, ids)}, which does not exist; the tracks are ${ids.map((t) => `"${t}"`).join(", ")}.`);
|
|
829
|
+
}
|
|
766
830
|
kindFits(track, clip) {
|
|
767
831
|
const asset = clip.type === "media" ? this.asset(clip.asset) : null;
|
|
768
832
|
if (clip.type === "sequence") {
|
|
@@ -771,19 +835,19 @@ var Editor = (_class = class {
|
|
|
771
835
|
return;
|
|
772
836
|
}
|
|
773
837
|
if (track.kind === "audio" && (clip.type !== "media" || asset.kind === "image")) refuse(`Track "${track.id}" is an audio track; ${clip.type === "media" ? "images" : `${clip.type} clips`} go on a video track.`);
|
|
774
|
-
if (track.kind === "video" && _optionalChain([asset, 'optionalAccess',
|
|
838
|
+
if (track.kind === "video" && _optionalChain([asset, 'optionalAccess', _11 => _11.kind]) === "audio") refuse(`Track "${track.id}" is a video track; audio asset "${asset.id}" goes on an audio track.`);
|
|
775
839
|
}
|
|
776
840
|
/** Moves every clip on `track` starting at or after `from` by `delta`. */
|
|
777
841
|
shift(track, from, delta, except = /* @__PURE__ */ new Set()) {
|
|
778
842
|
if (!delta) return;
|
|
779
|
-
for (const c of track.clips) if (!except.has(c.id) && c.start >= from -
|
|
843
|
+
for (const c of track.clips) if (!except.has(c.id) && c.start >= from - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) c.start = Math.max(0, c.start + delta);
|
|
780
844
|
track.clips.sort((a, b) => a.start - b.start);
|
|
781
845
|
}
|
|
782
846
|
get frame() {
|
|
783
847
|
return 1e3 / this.p.fps;
|
|
784
848
|
}
|
|
785
849
|
ids() {
|
|
786
|
-
return
|
|
850
|
+
return _chunk3OSXHLITcjs.allVideoClipIds.call(void 0, this.p);
|
|
787
851
|
}
|
|
788
852
|
/**
|
|
789
853
|
* The tracks a ripple on `sources` moves, top to bottom: the sources, and every unlocked
|
|
@@ -791,8 +855,8 @@ var Editor = (_class = class {
|
|
|
791
855
|
*/
|
|
792
856
|
rippleTracks(sources) {
|
|
793
857
|
const set = new Set(sources);
|
|
794
|
-
if ([...set].some(
|
|
795
|
-
for (const t of this.p.tracks) if (
|
|
858
|
+
if ([...set].some(_chunk3OSXHLITcjs.videoTrackSyncLocked)) {
|
|
859
|
+
for (const t of this.p.tracks) if (_chunk3OSXHLITcjs.videoTrackSyncLocked.call(void 0, t) && !t.locked) set.add(t);
|
|
796
860
|
}
|
|
797
861
|
return this.p.tracks.filter((t) => set.has(t));
|
|
798
862
|
}
|
|
@@ -809,7 +873,7 @@ var Editor = (_class = class {
|
|
|
809
873
|
return open;
|
|
810
874
|
}
|
|
811
875
|
range(from, to) {
|
|
812
|
-
if (!(to - from >
|
|
876
|
+
if (!(to - from > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)) refuse(`"to" (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, to)}) must be after "from" (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, from)}).`);
|
|
813
877
|
}
|
|
814
878
|
/** Moves a media or compound clip's in point by `delta` of timeline time (stills, titles and colours have none). */
|
|
815
879
|
moveIn(clip, delta) {
|
|
@@ -818,18 +882,18 @@ var Editor = (_class = class {
|
|
|
818
882
|
if (clip.in < 1e-9) delete clip.in;
|
|
819
883
|
return;
|
|
820
884
|
}
|
|
821
|
-
if (clip.type !== "media" || _optionalChain([
|
|
822
|
-
clip.in = Math.max(0, (_nullishCoalesce(clip.in, () => ( 0))) + delta *
|
|
885
|
+
if (clip.type !== "media" || _optionalChain([_chunk3OSXHLITcjs.videoAssetMap.call(void 0, this.p), 'access', _12 => _12.get, 'call', _13 => _13(clip.asset), 'optionalAccess', _14 => _14.kind]) === "image") return;
|
|
886
|
+
clip.in = Math.max(0, (_nullishCoalesce(clip.in, () => ( 0))) + delta * _chunk3OSXHLITcjs.videoClipSpeed.call(void 0, clip));
|
|
823
887
|
if (clip.in < 1e-9) delete clip.in;
|
|
824
888
|
}
|
|
825
889
|
/** Moves a clip's head `delta` later in time (start, in point and keys); the caller sets start and duration. */
|
|
826
890
|
trimHead(clip, delta) {
|
|
827
891
|
if (!isRamped(clip)) this.moveIn(clip, delta);
|
|
828
|
-
retimeClipKeys(clip, (v) =>
|
|
892
|
+
retimeClipKeys(clip, (v) => _chunk3OSXHLITcjs.retimeVideoAnimatable.call(void 0, v, -delta));
|
|
829
893
|
}
|
|
830
894
|
/** Cuts `clip` at `at` (inside it): the left part keeps the id, the right part is returned. */
|
|
831
895
|
cut(track, clip, at, id) {
|
|
832
|
-
const end =
|
|
896
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip);
|
|
833
897
|
const right = { ...structuredClone(clip), id: _nullishCoalesce(id, () => ( freshId(`${clip.id}-`, this.ids()))), start: at, duration: end - at };
|
|
834
898
|
delete right.transition;
|
|
835
899
|
delete right.fadeIn;
|
|
@@ -837,9 +901,9 @@ var Editor = (_class = class {
|
|
|
837
901
|
delete right.animateIn;
|
|
838
902
|
const leftValues = clipAnimatables(clip);
|
|
839
903
|
clipAnimatables(right).forEach((r, i) => {
|
|
840
|
-
const [l, rr] =
|
|
841
|
-
if (
|
|
842
|
-
if (
|
|
904
|
+
const [l, rr] = _chunk3OSXHLITcjs.splitVideoAnimatable.call(void 0, leftValues[i].get(), at - clip.start);
|
|
905
|
+
if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, l)) leftValues[i].set(l);
|
|
906
|
+
if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, rr)) r.set(rr);
|
|
843
907
|
});
|
|
844
908
|
if (!isRamped(right)) this.moveIn(right, at - clip.start);
|
|
845
909
|
clip.duration = at - clip.start;
|
|
@@ -853,7 +917,7 @@ var Editor = (_class = class {
|
|
|
853
917
|
/** Cuts every clip on `tracks` that runs across `at`; returns the new right-hand parts. */
|
|
854
918
|
cutAcross(tracks, at) {
|
|
855
919
|
const made = [];
|
|
856
|
-
for (const t of tracks) for (const c of [...t.clips]) if (c.start < at -
|
|
920
|
+
for (const t of tracks) for (const c of [...t.clips]) if (c.start < at - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON && _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c) > at + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) made.push(this.cut(t, c, at));
|
|
857
921
|
return made;
|
|
858
922
|
}
|
|
859
923
|
/**
|
|
@@ -871,10 +935,10 @@ var Editor = (_class = class {
|
|
|
871
935
|
*/
|
|
872
936
|
close(tracks, from, to) {
|
|
873
937
|
for (const t of tracks) {
|
|
874
|
-
const hit =
|
|
938
|
+
const hit = _chunk3OSXHLITcjs.videoOverlap.call(void 0, t, from, to);
|
|
875
939
|
if (hit)
|
|
876
940
|
refuse(
|
|
877
|
-
`Rippling out ${
|
|
941
|
+
`Rippling out ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, from)}\u2013${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, to)} would pull track "${t.id}" out of sync: clip "${hit.id}" (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, hit.start)}\u2013${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, hit))}) is in that stretch. Extract the range across "${t.id}" too (extract_range with its id in "tracks"), turn its sync lock off (update_track with syncLocked false) or lock it, or lift instead of rippling.`
|
|
878
942
|
);
|
|
879
943
|
}
|
|
880
944
|
for (const t of tracks) this.shift(t, to, from - to);
|
|
@@ -886,10 +950,10 @@ var Editor = (_class = class {
|
|
|
886
950
|
clear(track, from, to, report) {
|
|
887
951
|
for (const c of [...track.clips]) {
|
|
888
952
|
const start = c.start;
|
|
889
|
-
const end =
|
|
890
|
-
if (end <= from +
|
|
891
|
-
const coversHead = start >= from -
|
|
892
|
-
const coversTail = end <= to +
|
|
953
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c);
|
|
954
|
+
if (end <= from + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON || start >= to - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) continue;
|
|
955
|
+
const coversHead = start >= from - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON;
|
|
956
|
+
const coversTail = end <= to + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON;
|
|
893
957
|
if (coversHead && coversTail) {
|
|
894
958
|
track.clips = track.clips.filter((x) => x !== c);
|
|
895
959
|
report.removed.push(c.id);
|
|
@@ -912,8 +976,8 @@ var Editor = (_class = class {
|
|
|
912
976
|
/** Clamps a slip, roll or slide to `[lo, hi]`, refusing when it cannot move that way at all. */
|
|
913
977
|
clamp(delta, lo, hi, why) {
|
|
914
978
|
const applied = Math.min(Math.max(delta, Math.min(0, lo)), Math.max(0, hi));
|
|
915
|
-
if (Math.abs(delta) >
|
|
916
|
-
return Math.abs(applied) <=
|
|
979
|
+
if (Math.abs(delta) > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON && Math.abs(applied) <= _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(why());
|
|
980
|
+
return Math.abs(applied) <= _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON ? 0 : applied;
|
|
917
981
|
}
|
|
918
982
|
/** Places a clip on a track by `mode`, returning what it cut, trimmed, removed and rippled. */
|
|
919
983
|
place(track, clip, mode, outcome) {
|
|
@@ -923,14 +987,14 @@ var Editor = (_class = class {
|
|
|
923
987
|
outcome.rippled = tracks.map((t) => t.id);
|
|
924
988
|
} else if (mode === "overwrite") {
|
|
925
989
|
const report = { removed: [], trimmed: [], created: [] };
|
|
926
|
-
this.clear(track, clip.start,
|
|
990
|
+
this.clear(track, clip.start, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip), report);
|
|
927
991
|
Object.assign(outcome, report);
|
|
928
992
|
} else this.fits(track, clip.start, clip.duration, /* @__PURE__ */ new Set([clip.id]), `Clip "${clip.id}"`);
|
|
929
993
|
track.clips.push(clip);
|
|
930
994
|
track.clips.sort((a, b) => a.start - b.start);
|
|
931
995
|
}
|
|
932
996
|
placement(mode, ripple) {
|
|
933
|
-
if (mode !== void 0 && !
|
|
997
|
+
if (mode !== void 0 && !_chunk3OSXHLITcjs.VIDEO_PLACEMENT_MODES.includes(mode)) refuse(`"mode" must be ${_chunk3OSXHLITcjs.VIDEO_PLACEMENT_MODES.map((m) => `"${m}"`).join(", ")}.`);
|
|
934
998
|
if (ripple && mode !== void 0 && mode !== "insert") refuse(`"ripple" means mode "insert", so it cannot go with mode "${mode}"; pass one or the other.`);
|
|
935
999
|
return _nullishCoalesce(mode, () => ( (ripple ? "insert" : "refuse")));
|
|
936
1000
|
}
|
|
@@ -967,7 +1031,7 @@ var Editor = (_class = class {
|
|
|
967
1031
|
}
|
|
968
1032
|
case "remove_asset": {
|
|
969
1033
|
const asset = this.asset(e.asset);
|
|
970
|
-
const users =
|
|
1034
|
+
const users = _chunk3OSXHLITcjs.allVideoTracks.call(void 0, this.p).flatMap(({ track: t }) => t.clips.filter((c) => c.type === "media" && c.asset === asset.id).map((c) => c.id));
|
|
971
1035
|
if (users.length) refuse(`Asset "${asset.id}" is used by clip${users.length > 1 ? "s" : ""} ${users.map((u) => `"${u}"`).join(", ")}; delete ${users.length > 1 ? "them" : "it"} first.`);
|
|
972
1036
|
this.p.assets = this.p.assets.filter((a) => a !== asset);
|
|
973
1037
|
return this.outcomes.push({ op: e.op, asset: asset.id });
|
|
@@ -1020,14 +1084,15 @@ var Editor = (_class = class {
|
|
|
1020
1084
|
else draft.duration = (asset.duration - (_nullishCoalesce(draft.in, () => ( 0)))) / (_nullishCoalesce(draft.speed, () => ( 1)));
|
|
1021
1085
|
if (!(draft.duration > 0)) refuse(`"in" (${draft.in} ms) is at or past the end of "${asset.id}" (${asset.duration} ms).`);
|
|
1022
1086
|
} else if (draft.type === "sequence") {
|
|
1023
|
-
const length =
|
|
1087
|
+
const length = _chunk3OSXHLITcjs.videoSequenceDuration.call(void 0, _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p).get(draft.sequence)) - (_nullishCoalesce(draft.in, () => ( 0)));
|
|
1024
1088
|
if (!(length > 0)) refuse(`"in" (${_nullishCoalesce(draft.in, () => ( 0))} ms) is at or past the end of sequence "${draft.sequence}".`);
|
|
1025
1089
|
draft.duration = length;
|
|
1026
1090
|
} else draft.duration = VIDEO_DEFAULT_TITLE_DURATION;
|
|
1027
1091
|
}
|
|
1028
|
-
const end =
|
|
1092
|
+
const end = _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, track).reduce((m, c) => Math.max(m, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c)), 0);
|
|
1029
1093
|
draft.start ??= end;
|
|
1030
1094
|
const clip = parseVideoClip(draft, "clip");
|
|
1095
|
+
this.adjustmentTarget(clip);
|
|
1031
1096
|
const outcome = { op: e.op, clip: clip.id, track: track.id };
|
|
1032
1097
|
this.place(track, clip, mode, outcome);
|
|
1033
1098
|
return this.outcomes.push(outcome);
|
|
@@ -1050,20 +1115,20 @@ var Editor = (_class = class {
|
|
|
1050
1115
|
case "trim_clip": {
|
|
1051
1116
|
const { track, clip } = this.clip(e.clip);
|
|
1052
1117
|
this.unlocked(track);
|
|
1053
|
-
const speed =
|
|
1054
|
-
const end =
|
|
1118
|
+
const speed = _chunk3OSXHLITcjs.videoClipSpeed.call(void 0, clip);
|
|
1119
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip);
|
|
1055
1120
|
const minimum = 1e3 / this.p.fps;
|
|
1056
|
-
const assets =
|
|
1121
|
+
const assets = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, this.p);
|
|
1057
1122
|
if (e.edge === "start") {
|
|
1058
1123
|
const delta = e.to - clip.start;
|
|
1059
|
-
if (clip.duration - delta < minimum -
|
|
1124
|
+
if (clip.duration - delta < minimum - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(`Trimming the start to ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, e.to)} would leave "${clip.id}" shorter than a frame; it ends at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, end)}.`);
|
|
1060
1125
|
if (clip.type === "media" && !isRamped(clip) || clip.type === "sequence") {
|
|
1061
1126
|
const sourceIn = (_nullishCoalesce(clip.in, () => ( 0))) + delta * speed;
|
|
1062
|
-
if (sourceIn < -
|
|
1127
|
+
if (sourceIn < -_chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(`"${clip.id}" starts ${Math.round((_nullishCoalesce(clip.in, () => ( 0))) / speed)} ms into its source, so its start can move at most that far earlier (to ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, clip.start - (_nullishCoalesce(clip.in, () => ( 0))) / speed)}).`);
|
|
1063
1128
|
clip.in = Math.max(0, sourceIn);
|
|
1064
1129
|
if (clip.in === 0) delete clip.in;
|
|
1065
1130
|
}
|
|
1066
|
-
retimeClipKeys(clip, (v) =>
|
|
1131
|
+
retimeClipKeys(clip, (v) => _chunk3OSXHLITcjs.retimeVideoAnimatable.call(void 0, v, -delta));
|
|
1067
1132
|
if (e.ripple) {
|
|
1068
1133
|
const tracks = this.rippleTracks([track]);
|
|
1069
1134
|
if (delta > 0) {
|
|
@@ -1084,17 +1149,17 @@ var Editor = (_class = class {
|
|
|
1084
1149
|
}
|
|
1085
1150
|
} else {
|
|
1086
1151
|
const duration = e.to - clip.start;
|
|
1087
|
-
if (duration < minimum -
|
|
1152
|
+
if (duration < minimum - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(`Trimming the end to ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, e.to)} would leave "${clip.id}" shorter than a frame; it starts at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, clip.start)}.`);
|
|
1088
1153
|
if (clip.type === "media" && !isRamped(clip)) {
|
|
1089
1154
|
const asset = assets.get(clip.asset);
|
|
1090
1155
|
const out = (_nullishCoalesce(clip.in, () => ( 0))) + duration * speed;
|
|
1091
|
-
if (_optionalChain([asset, 'optionalAccess',
|
|
1092
|
-
refuse(`"${clip.id}" can end at most at ${
|
|
1156
|
+
if (_optionalChain([asset, 'optionalAccess', _15 => _15.duration]) !== void 0 && out > asset.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
1157
|
+
refuse(`"${clip.id}" can end at most at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, clip.start + (asset.duration - (_nullishCoalesce(clip.in, () => ( 0)))) / speed)}, where "${asset.id}" runs out.`);
|
|
1093
1158
|
}
|
|
1094
1159
|
if (clip.type === "sequence") {
|
|
1095
|
-
const seq =
|
|
1096
|
-
const length = seq ?
|
|
1097
|
-
if ((_nullishCoalesce(clip.in, () => ( 0))) + duration > length +
|
|
1160
|
+
const seq = _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p).get(clip.sequence);
|
|
1161
|
+
const length = seq ? _chunk3OSXHLITcjs.videoSequenceDuration.call(void 0, seq) : Infinity;
|
|
1162
|
+
if ((_nullishCoalesce(clip.in, () => ( 0))) + duration > length + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(`"${clip.id}" can end at most at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, clip.start + length - (_nullishCoalesce(clip.in, () => ( 0))))}, where sequence "${clip.sequence}" ends.`);
|
|
1098
1163
|
}
|
|
1099
1164
|
if (e.ripple) {
|
|
1100
1165
|
const tracks = this.rippleTracks([track]);
|
|
@@ -1116,9 +1181,9 @@ var Editor = (_class = class {
|
|
|
1116
1181
|
case "split_clip": {
|
|
1117
1182
|
const { track, clip } = this.clip(e.clip);
|
|
1118
1183
|
this.unlocked(track);
|
|
1119
|
-
const end =
|
|
1184
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip);
|
|
1120
1185
|
const minimum = 1e3 / this.p.fps;
|
|
1121
|
-
if (e.at < clip.start + minimum -
|
|
1186
|
+
if (e.at < clip.start + minimum - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON || e.at > end - minimum + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(`${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, e.at)} is not inside "${clip.id}" (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, clip.start)}\u2013${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, end)}), at least a frame from either end.`);
|
|
1122
1187
|
if (e.id !== void 0 && this.ids().includes(e.id)) refuse(`There is already a clip "${e.id}".`);
|
|
1123
1188
|
const right = this.cut(track, clip, e.at, e.id);
|
|
1124
1189
|
return this.outcomes.push({ op: e.op, clips: [clip.id, right.id], clip: right.id, track: track.id });
|
|
@@ -1129,12 +1194,12 @@ var Editor = (_class = class {
|
|
|
1129
1194
|
for (const track of new Set(found.map((f) => f.track))) track.clips = track.clips.filter((c) => !found.some((f) => f.clip === c));
|
|
1130
1195
|
if (!e.ripple) return this.outcomes.push({ op: e.op, clips: found.map((f) => f.clip.id) });
|
|
1131
1196
|
const rippled = /* @__PURE__ */ new Set();
|
|
1132
|
-
const groups = [...new Set(found.map((f) => f.track))].filter((t) => !
|
|
1133
|
-
if (found.some((f) =>
|
|
1197
|
+
const groups = [...new Set(found.map((f) => f.track))].filter((t) => !_chunk3OSXHLITcjs.videoTrackSyncLocked.call(void 0, t)).map((t) => [t]);
|
|
1198
|
+
if (found.some((f) => _chunk3OSXHLITcjs.videoTrackSyncLocked.call(void 0, f.track))) groups.push(found.filter((f) => _chunk3OSXHLITcjs.videoTrackSyncLocked.call(void 0, f.track)).map((f) => f.track));
|
|
1134
1199
|
for (const sources of groups) {
|
|
1135
1200
|
const tracks = this.rippleTracks(sources);
|
|
1136
1201
|
tracks.forEach((t) => rippled.add(t));
|
|
1137
|
-
const spans = mergeSpans(found.filter((f) => sources.includes(f.track)).map((f) => [f.clip.start,
|
|
1202
|
+
const spans = mergeSpans(found.filter((f) => sources.includes(f.track)).map((f) => [f.clip.start, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, f.clip)]));
|
|
1138
1203
|
for (const [from, to] of spans.reverse()) this.close(tracks, from, to);
|
|
1139
1204
|
}
|
|
1140
1205
|
return this.outcomes.push({ op: e.op, clips: found.map((f) => f.clip.id), rippled: this.p.tracks.filter((t) => rippled.has(t)).map((t) => t.id) });
|
|
@@ -1150,17 +1215,18 @@ var Editor = (_class = class {
|
|
|
1150
1215
|
if ("timeRemap" in set) refuse("Set a speed ramp with set_time_remap, which checks it against the media and moves later clips if asked.");
|
|
1151
1216
|
let stretch = 1;
|
|
1152
1217
|
if (at.clip.type === "media" && !("duration" in set)) {
|
|
1153
|
-
const was =
|
|
1154
|
-
const now = "speed" in set ? set.speed : _optionalChain([e, 'access',
|
|
1218
|
+
const was = _chunk3OSXHLITcjs.videoClipSpeed.call(void 0, at.clip);
|
|
1219
|
+
const now = "speed" in set ? set.speed : _optionalChain([e, 'access', _16 => _16.remove, 'optionalAccess', _17 => _17.includes, 'call', _18 => _18("speed")]) ? 1 : was;
|
|
1155
1220
|
if (now !== was) {
|
|
1156
1221
|
set.duration = at.clip.duration * was / now;
|
|
1157
1222
|
stretch = was / now;
|
|
1158
1223
|
}
|
|
1159
1224
|
}
|
|
1160
1225
|
const next = { ...structuredClone(at.clip), ...set };
|
|
1161
|
-
if (stretch !== 1) retimeClipKeys(next, (v) =>
|
|
1226
|
+
if (stretch !== 1) retimeClipKeys(next, (v) => _chunk3OSXHLITcjs.retimeVideoAnimatable.call(void 0, v, 0, stretch));
|
|
1162
1227
|
for (const k of _nullishCoalesce(e.remove, () => ( []))) delete next[k];
|
|
1163
1228
|
const clip = parseVideoClip(next, "set");
|
|
1229
|
+
this.adjustmentTarget(clip);
|
|
1164
1230
|
if (clip.type === "media" && clip.asset !== at.clip.asset) this.kindFits(at.track, clip);
|
|
1165
1231
|
if (clip.start !== at.clip.start || clip.duration !== at.clip.duration) this.fits(at.track, clip.start, clip.duration, /* @__PURE__ */ new Set([clip.id]), `Clip "${clip.id}"`);
|
|
1166
1232
|
at.track.clips[at.index] = clip;
|
|
@@ -1171,17 +1237,17 @@ var Editor = (_class = class {
|
|
|
1171
1237
|
const { track, clip } = this.clip(e.clip);
|
|
1172
1238
|
this.unlocked(track);
|
|
1173
1239
|
if (clip.type === "sequence") refuse(`Clip "${clip.id}" is a compound clip, which plays its sequence at normal speed; ramp the clips inside the sequence instead.`);
|
|
1174
|
-
if (clip.type !== "media") refuse(`Clip "${clip.id}" is a ${
|
|
1240
|
+
if (clip.type !== "media") refuse(`Clip "${clip.id}" is a ${_chunk3OSXHLITcjs.videoClipTypeLabel.call(void 0, clip)}, so it has no source to ramp; only video and audio clips change speed.`);
|
|
1175
1241
|
const asset = this.asset(clip.asset);
|
|
1176
1242
|
if (asset.kind === "image") refuse(`Clip "${clip.id}" is a still ("${asset.id}"), so it has no motion to ramp.`);
|
|
1177
1243
|
const media = clip;
|
|
1178
|
-
const end =
|
|
1244
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, media);
|
|
1179
1245
|
const duration = _nullishCoalesce(e.duration, () => ( media.duration));
|
|
1180
|
-
if (!(duration >= 1e3 / this.p.fps -
|
|
1246
|
+
if (!(duration >= 1e3 / this.p.fps - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)) refuse(`"duration" must be at least a frame (${Math.ceil(1e3 / this.p.fps)} ms).`);
|
|
1181
1247
|
if (e.keys === null) {
|
|
1182
1248
|
if (!media.timeRemap) refuse(`Clip "${clip.id}" has no speed ramp to remove.`);
|
|
1183
1249
|
const from = (_nullishCoalesce(media.in, () => ( 0))) + media.timeRemap.keys[0].v;
|
|
1184
|
-
if (asset.duration !== void 0 && from + duration > asset.duration +
|
|
1250
|
+
if (asset.duration !== void 0 && from + duration > asset.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
1185
1251
|
refuse(`At normal speed from ${Math.round(from)} ms, "${clip.id}" would run past the end of "${asset.id}" (${asset.duration} ms); give a duration of at most ${Math.floor(asset.duration - from)} ms.`);
|
|
1186
1252
|
delete media.timeRemap;
|
|
1187
1253
|
media.in = from;
|
|
@@ -1189,17 +1255,17 @@ var Editor = (_class = class {
|
|
|
1189
1255
|
} else {
|
|
1190
1256
|
if (!Array.isArray(e.keys) || !e.keys.length) refuse('"keys" must be a list of at least one { t, v, ease } (or null to remove the ramp).');
|
|
1191
1257
|
const remap = parseVideoTimeRemap({ keys: e.keys }, void 0, "keys");
|
|
1192
|
-
remap.keys =
|
|
1193
|
-
|
|
1194
|
-
const span =
|
|
1195
|
-
if (asset.duration !== void 0 && (_nullishCoalesce(media.in, () => ( 0))) + span.to > asset.duration +
|
|
1258
|
+
remap.keys = _chunk3OSXHLITcjs.sortVideoKeys.call(void 0, remap.keys);
|
|
1259
|
+
_chunk3OSXHLITcjs.checkVideoTimeRemap.call(void 0, remap, "keys");
|
|
1260
|
+
const span = _chunk3OSXHLITcjs.videoTimeRemapSpan.call(void 0, remap);
|
|
1261
|
+
if (asset.duration !== void 0 && (_nullishCoalesce(media.in, () => ( 0))) + span.to > asset.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON)
|
|
1196
1262
|
refuse(`The ramp plays ${Math.round(span.to)} ms of source from the in point (${_nullishCoalesce(media.in, () => ( 0))} ms), past the end of "${asset.id}" (${asset.duration} ms); it can use at most ${Math.floor(asset.duration - (_nullishCoalesce(media.in, () => ( 0))))} ms.`);
|
|
1197
1263
|
media.timeRemap = remap;
|
|
1198
1264
|
delete media.speed;
|
|
1199
1265
|
}
|
|
1200
1266
|
const newEnd = media.start + duration;
|
|
1201
1267
|
const outcome = { op: e.op, clip: clip.id, track: track.id };
|
|
1202
|
-
if (Math.abs(newEnd - end) >
|
|
1268
|
+
if (Math.abs(newEnd - end) > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) {
|
|
1203
1269
|
if (e.ripple) {
|
|
1204
1270
|
const tracks = this.rippleTracks([track]);
|
|
1205
1271
|
if (newEnd < end) {
|
|
@@ -1228,11 +1294,11 @@ var Editor = (_class = class {
|
|
|
1228
1294
|
case "slip_clip": {
|
|
1229
1295
|
const { track, clip } = this.clip(e.clip);
|
|
1230
1296
|
this.unlocked(track);
|
|
1231
|
-
if (clip.type !== "media" && clip.type !== "sequence") refuse(`Clip "${clip.id}" is a ${
|
|
1297
|
+
if (clip.type !== "media" && clip.type !== "sequence") refuse(`Clip "${clip.id}" is a ${_chunk3OSXHLITcjs.videoClipTypeLabel.call(void 0, clip)}, so it has no source to slip; only video, audio and compound clips slip.`);
|
|
1232
1298
|
const source = clip.type === "sequence" ? `sequence "${clip.sequence}"` : `"${this.asset(clip.asset).id}"`;
|
|
1233
1299
|
if (clip.type === "media" && this.asset(clip.asset).kind === "image") refuse(`Clip "${clip.id}" is a still ("${clip.asset}"), so it has no source to slip; only video and audio clips slip.`);
|
|
1234
|
-
const handles =
|
|
1235
|
-
const { lead, tail } =
|
|
1300
|
+
const handles = _chunk3OSXHLITcjs.videoClipHandles.call(void 0, clip, _chunk3OSXHLITcjs.videoAssetMap.call(void 0, this.p), _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p));
|
|
1301
|
+
const { lead, tail } = _chunk3OSXHLITcjs.videoClipOverhang.call(void 0, track, clip);
|
|
1236
1302
|
const delta = this.clamp(
|
|
1237
1303
|
e.delta,
|
|
1238
1304
|
lead - handles.before,
|
|
@@ -1250,13 +1316,13 @@ var Editor = (_class = class {
|
|
|
1250
1316
|
this.unlocked(track);
|
|
1251
1317
|
const L = left.clip;
|
|
1252
1318
|
const R = right.clip;
|
|
1253
|
-
if (Math.abs(
|
|
1254
|
-
if (Math.abs(
|
|
1255
|
-
refuse(`"${L.id}" ends at ${
|
|
1319
|
+
if (Math.abs(_chunk3OSXHLITcjs.videoClipEnd.call(void 0, L) - R.start) > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) {
|
|
1320
|
+
if (Math.abs(_chunk3OSXHLITcjs.videoClipEnd.call(void 0, R) - L.start) <= _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) refuse(`"${R.id}" comes before "${L.id}"; pass left "${R.id}" and right "${L.id}".`);
|
|
1321
|
+
refuse(`"${L.id}" ends at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, L))} but "${R.id}" starts at ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, R.start)}; a roll needs two clips that meet at a cut (use trim_clip to move one edge).`);
|
|
1256
1322
|
}
|
|
1257
|
-
const assets =
|
|
1258
|
-
const lo = Math.max(-(L.duration - this.frame), -(
|
|
1259
|
-
const hi = Math.min(R.duration - this.frame,
|
|
1323
|
+
const assets = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, this.p);
|
|
1324
|
+
const lo = Math.max(-(L.duration - this.frame), -(_chunk3OSXHLITcjs.videoClipHandles.call(void 0, R, assets, _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p)).before - _chunk3OSXHLITcjs.videoClipOverhang.call(void 0, track, R).lead));
|
|
1325
|
+
const hi = Math.min(R.duration - this.frame, _chunk3OSXHLITcjs.videoClipHandles.call(void 0, L, assets, _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p)).after - _chunk3OSXHLITcjs.videoClipOverhang.call(void 0, track, L).tail);
|
|
1260
1326
|
const delta = this.clamp(
|
|
1261
1327
|
e.delta,
|
|
1262
1328
|
lo,
|
|
@@ -1272,18 +1338,18 @@ var Editor = (_class = class {
|
|
|
1272
1338
|
case "slide_clip": {
|
|
1273
1339
|
const { track, clip } = this.clip(e.clip);
|
|
1274
1340
|
this.unlocked(track);
|
|
1275
|
-
const before =
|
|
1276
|
-
const after =
|
|
1341
|
+
const before = _chunk3OSXHLITcjs.videoClipBefore.call(void 0, track, clip);
|
|
1342
|
+
const after = _chunk3OSXHLITcjs.videoClipAfter.call(void 0, track, clip);
|
|
1277
1343
|
if (!before && !after) refuse(`"${clip.id}" has no clip right before or after it on track "${track.id}", so there is nothing to slide against; use move_clip.`);
|
|
1278
|
-
const assets =
|
|
1279
|
-
const end =
|
|
1344
|
+
const assets = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, this.p);
|
|
1345
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip);
|
|
1280
1346
|
const others = track.clips.filter((c) => c !== clip);
|
|
1281
1347
|
const room = {
|
|
1282
|
-
earlier: before ? before.duration - this.frame : clip.start - others.filter((c) =>
|
|
1283
|
-
later: after ? after.duration - this.frame : others.filter((c) => c.start >= end -
|
|
1348
|
+
earlier: before ? before.duration - this.frame : clip.start - others.filter((c) => _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c) <= clip.start + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON).reduce((m, c) => Math.max(m, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c)), 0),
|
|
1349
|
+
later: after ? after.duration - this.frame : others.filter((c) => c.start >= end - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON).reduce((m, c) => Math.min(m, c.start - end), Infinity)
|
|
1284
1350
|
};
|
|
1285
|
-
const lo = -Math.min(room.earlier, after ?
|
|
1286
|
-
const hi = Math.min(room.later, before ?
|
|
1351
|
+
const lo = -Math.min(room.earlier, after ? _chunk3OSXHLITcjs.videoClipHandles.call(void 0, after, assets, _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p)).before - _chunk3OSXHLITcjs.videoClipOverhang.call(void 0, track, after).lead : Infinity);
|
|
1352
|
+
const hi = Math.min(room.later, before ? _chunk3OSXHLITcjs.videoClipHandles.call(void 0, before, assets, _chunk3OSXHLITcjs.videoSequenceMap.call(void 0, this.p)).after - _chunk3OSXHLITcjs.videoClipOverhang.call(void 0, track, before).tail : Infinity);
|
|
1287
1353
|
const delta = this.clamp(
|
|
1288
1354
|
e.delta,
|
|
1289
1355
|
lo,
|
|
@@ -1298,7 +1364,7 @@ var Editor = (_class = class {
|
|
|
1298
1364
|
this.trimHead(after, delta);
|
|
1299
1365
|
}
|
|
1300
1366
|
track.clips.sort((a, b) => a.start - b.start);
|
|
1301
|
-
return this.outcomes.push({ op: e.op, clip: clip.id, clips: [_optionalChain([before, 'optionalAccess',
|
|
1367
|
+
return this.outcomes.push({ op: e.op, clip: clip.id, clips: [_optionalChain([before, 'optionalAccess', _19 => _19.id]), clip.id, _optionalChain([after, 'optionalAccess', _20 => _20.id])].filter((x) => !!x), track: track.id, delta });
|
|
1302
1368
|
}
|
|
1303
1369
|
case "lift_range":
|
|
1304
1370
|
case "extract_range": {
|
|
@@ -1316,29 +1382,29 @@ var Editor = (_class = class {
|
|
|
1316
1382
|
}
|
|
1317
1383
|
case "split_at": {
|
|
1318
1384
|
const tracks = this.rangeTracks(e.tracks);
|
|
1319
|
-
const lefts = tracks.flatMap((t) => t.clips.filter((c) => c.start < e.at -
|
|
1320
|
-
if (!lefts.length) refuse(`No clip runs across ${
|
|
1385
|
+
const lefts = tracks.flatMap((t) => t.clips.filter((c) => c.start < e.at - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON && _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c) > e.at + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON).map((c) => c.id));
|
|
1386
|
+
if (!lefts.length) refuse(`No clip runs across ${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, e.at)} on ${e.tracks ? `track${tracks.length > 1 ? "s" : ""} ${tracks.map((t) => `"${t.id}"`).join(", ")}` : "the unlocked tracks"}, so there is nothing to split there.`);
|
|
1321
1387
|
const created = this.cutAcross(tracks, e.at).map((c) => c.id);
|
|
1322
1388
|
return this.outcomes.push({ op: e.op, clips: lefts, created });
|
|
1323
1389
|
}
|
|
1324
1390
|
case "set_keyframes": {
|
|
1325
1391
|
const { track, clip } = this.clip(e.clip);
|
|
1326
1392
|
this.unlocked(track);
|
|
1327
|
-
if (!
|
|
1393
|
+
if (!_chunk3OSXHLITcjs.VIDEO_KEYFRAME_PROPERTIES.includes(e.property)) refuse(`"${e.property}" cannot be keyframed; use one of ${_chunk3OSXHLITcjs.VIDEO_KEYFRAME_PROPERTIES.join(", ")}.`);
|
|
1328
1394
|
if (e.property === "volume" && clip.type !== "media" && clip.type !== "sequence") refuse(`Clip "${clip.id}" is a ${clip.type} clip and has no volume; volume keys go on media and compound clips.`);
|
|
1329
|
-
const limits =
|
|
1330
|
-
const transform =
|
|
1395
|
+
const limits = _chunk3OSXHLITcjs.VIDEO_KEYFRAME_LIMITS[e.property];
|
|
1396
|
+
const transform = _chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES.includes(e.property);
|
|
1331
1397
|
const c = clip;
|
|
1332
|
-
const current = transform ? _optionalChain([c, 'access',
|
|
1398
|
+
const current = transform ? _optionalChain([c, 'access', _21 => _21.transform, 'optionalAccess', _22 => _22[e.property]]) : c[e.property];
|
|
1333
1399
|
let value;
|
|
1334
1400
|
if (e.keys === null) {
|
|
1335
|
-
const v =
|
|
1336
|
-
value =
|
|
1401
|
+
const v = _chunk3OSXHLITcjs.videoValueAt.call(void 0, current, 0, limits.default);
|
|
1402
|
+
value = _chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, current) ? v === limits.default ? void 0 : v : current;
|
|
1337
1403
|
} else {
|
|
1338
1404
|
if (!Array.isArray(e.keys) || !e.keys.length) refuse('"keys" must list at least one key { t, v }; pass null to remove the keyframes.');
|
|
1339
1405
|
let sorted = [];
|
|
1340
1406
|
try {
|
|
1341
|
-
sorted =
|
|
1407
|
+
sorted = _chunk3OSXHLITcjs.sortVideoKeys.call(void 0, e.keys);
|
|
1342
1408
|
} catch (error) {
|
|
1343
1409
|
refuse(error.message);
|
|
1344
1410
|
}
|
|
@@ -1357,12 +1423,12 @@ var Editor = (_class = class {
|
|
|
1357
1423
|
this.unlocked(track);
|
|
1358
1424
|
let at = 0;
|
|
1359
1425
|
const moved = [];
|
|
1360
|
-
for (const clip of
|
|
1361
|
-
if (clip.start >= (_nullishCoalesce(e.from, () => ( 0))) -
|
|
1426
|
+
for (const clip of _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, track)) {
|
|
1427
|
+
if (clip.start >= (_nullishCoalesce(e.from, () => ( 0))) - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON && clip.start > at + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) {
|
|
1362
1428
|
clip.start = Math.max(at, _nullishCoalesce(e.from, () => ( 0)));
|
|
1363
1429
|
moved.push(clip.id);
|
|
1364
1430
|
}
|
|
1365
|
-
at = Math.max(at,
|
|
1431
|
+
at = Math.max(at, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip));
|
|
1366
1432
|
}
|
|
1367
1433
|
return this.outcomes.push({ op: e.op, track: track.id, clips: moved });
|
|
1368
1434
|
}
|
|
@@ -1411,7 +1477,7 @@ function applyVideoEdits(project, edits) {
|
|
|
1411
1477
|
editor.run(edit);
|
|
1412
1478
|
} catch (error) {
|
|
1413
1479
|
const message = error.message;
|
|
1414
|
-
return { ok: false, error: `${list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess',
|
|
1480
|
+
return { ok: false, error: `${list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _23 => _23.op])}): ` : ""}${message}${/Nothing was changed\.$/.test(message) ? "" : " Nothing was changed."}` };
|
|
1415
1481
|
}
|
|
1416
1482
|
}
|
|
1417
1483
|
let next;
|
|
@@ -1428,6 +1494,432 @@ function applyVideoEdits(project, edits) {
|
|
|
1428
1494
|
return { ok: true, project: next, issues, outcomes: editor.outcomes };
|
|
1429
1495
|
}
|
|
1430
1496
|
|
|
1497
|
+
// src/video-editor/transcript.ts
|
|
1498
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1499
|
+
function cueTime(s) {
|
|
1500
|
+
const m = s.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{1,2})[.,](\d{1,3})$/);
|
|
1501
|
+
if (!m) throw new Error(`"${s.trim()}" is not a cue time (HH:MM:SS,mmm).`);
|
|
1502
|
+
return ((Number(_nullishCoalesce(m[1], () => ( 0))) * 60 + Number(m[2])) * 60 + Number(m[3])) * 1e3 + Number(m[4].padEnd(3, "0"));
|
|
1503
|
+
}
|
|
1504
|
+
function cueWords(text, start, end) {
|
|
1505
|
+
const words = text.replace(/<[^>]+>/g, "").split(/\s+/).filter(Boolean);
|
|
1506
|
+
const step = (end - start) / Math.max(1, words.length);
|
|
1507
|
+
return words.map((word, i) => ({ word, start: Math.round(start + i * step), end: Math.round(start + (i + 1) * step) }));
|
|
1508
|
+
}
|
|
1509
|
+
function parseVideoTranscript(input) {
|
|
1510
|
+
let data = input;
|
|
1511
|
+
if (typeof input === "string") {
|
|
1512
|
+
const text = input.replace(/^/, "").trim();
|
|
1513
|
+
if (text.startsWith("{") || text.startsWith("[")) {
|
|
1514
|
+
try {
|
|
1515
|
+
data = JSON.parse(text);
|
|
1516
|
+
} catch (e2) {
|
|
1517
|
+
throw new Error("The transcript looks like JSON but is not valid JSON.");
|
|
1518
|
+
}
|
|
1519
|
+
} else {
|
|
1520
|
+
const words2 = [];
|
|
1521
|
+
for (const block of text.replace(/\r\n?/g, "\n").split(/\n{2,}/)) {
|
|
1522
|
+
const lines = block.split("\n");
|
|
1523
|
+
const at = lines.findIndex((l) => l.includes("-->"));
|
|
1524
|
+
if (at < 0) continue;
|
|
1525
|
+
const [a, b] = lines[at].split("-->");
|
|
1526
|
+
words2.push(...cueWords(lines.slice(at + 1).join(" "), cueTime(a), cueTime(b.trim().split(/\s+/)[0])));
|
|
1527
|
+
}
|
|
1528
|
+
if (!words2.length) throw new Error("No words found: expected Whisper-style JSON with word timings, SubRip (.srt) or WebVTT.");
|
|
1529
|
+
return { words: words2, approximate: true };
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const seconds = (v) => typeof v === "number" && Number.isFinite(v) ? Math.round(v * 1e3) : NaN;
|
|
1533
|
+
const fromList = (list, toMs) => list.map((w, i) => {
|
|
1534
|
+
if (!isRecord(w)) throw new Error(`Word ${i + 1} is not an object { word, start, end }.`);
|
|
1535
|
+
const word = String(_nullishCoalesce(_nullishCoalesce(w.word, () => ( w.text)), () => ( ""))).trim();
|
|
1536
|
+
const start = toMs(w.start);
|
|
1537
|
+
const end = toMs(w.end);
|
|
1538
|
+
if (!word || !Number.isFinite(start) || !Number.isFinite(end)) throw new Error(`Word ${i + 1} needs a word and numeric start and end.`);
|
|
1539
|
+
return { word, start, end: Math.max(start, end) };
|
|
1540
|
+
});
|
|
1541
|
+
let words = [];
|
|
1542
|
+
if (Array.isArray(data)) words = fromList(data, (v) => typeof v === "number" ? v : NaN);
|
|
1543
|
+
else if (isRecord(data) && Array.isArray(data.words)) words = fromList(data.words, seconds);
|
|
1544
|
+
else if (isRecord(data) && Array.isArray(data.segments)) {
|
|
1545
|
+
const segments = data.segments.filter(isRecord);
|
|
1546
|
+
if (segments.some((s) => Array.isArray(s.words))) words = fromList(segments.flatMap((s) => Array.isArray(s.words) ? s.words : []), seconds);
|
|
1547
|
+
else {
|
|
1548
|
+
for (const s of segments) words.push(...cueWords(String(_nullishCoalesce(s.text, () => ( ""))), seconds(s.start), seconds(s.end)));
|
|
1549
|
+
if (!words.length) throw new Error("No words found in the segments.");
|
|
1550
|
+
return { words, approximate: true };
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
if (!words.length) throw new Error("No words found: expected Whisper-style JSON with word timings, SubRip (.srt) or WebVTT.");
|
|
1554
|
+
return { words: words.sort((a, b) => a.start - b.start), approximate: false };
|
|
1555
|
+
}
|
|
1556
|
+
function videoTranscriptTrack(project, track) {
|
|
1557
|
+
if (track !== void 0) {
|
|
1558
|
+
if (!project.tracks.some((t) => t.id === track)) throw new Error(`There is no track "${track}"; the tracks are ${project.tracks.map((t) => `"${t.id}"`).join(", ")}.`);
|
|
1559
|
+
return track;
|
|
1560
|
+
}
|
|
1561
|
+
const assets = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, project);
|
|
1562
|
+
const transcribed = (t) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access', _24 => _24.get, 'call', _25 => _25(c.asset), 'optionalAccess', _26 => _26.transcript, 'optionalAccess', _27 => _27.length]));
|
|
1563
|
+
return _nullishCoalesce(_optionalChain([(_nullishCoalesce(project.tracks.find((t) => t.kind === "audio" && transcribed(t)), () => ( project.tracks.find(transcribed)))), 'optionalAccess', _28 => _28.id]), () => ( null));
|
|
1564
|
+
}
|
|
1565
|
+
function videoTimelineTranscript(project, options = {}) {
|
|
1566
|
+
const id = videoTranscriptTrack(project, options.track);
|
|
1567
|
+
if (!id) return [];
|
|
1568
|
+
const track = project.tracks.find((t) => t.id === id);
|
|
1569
|
+
const assets = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, project);
|
|
1570
|
+
const out = [];
|
|
1571
|
+
for (const clip of _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, track)) {
|
|
1572
|
+
if (clip.type !== "media") continue;
|
|
1573
|
+
const words = _optionalChain([assets, 'access', _29 => _29.get, 'call', _30 => _30(clip.asset), 'optionalAccess', _31 => _31.transcript]);
|
|
1574
|
+
if (!_optionalChain([words, 'optionalAccess', _32 => _32.length])) continue;
|
|
1575
|
+
const speed = _chunk3OSXHLITcjs.videoClipSpeed.call(void 0, clip);
|
|
1576
|
+
const from = _nullishCoalesce(clip.in, () => ( 0));
|
|
1577
|
+
const to = from + clip.duration * speed;
|
|
1578
|
+
const toTimeline = (s) => Math.min(_chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip), Math.max(clip.start, clip.start + (s - from) / speed));
|
|
1579
|
+
words.forEach((w, index) => {
|
|
1580
|
+
const mid = (w.start + w.end) / 2;
|
|
1581
|
+
if (mid < from || mid >= to) return;
|
|
1582
|
+
out.push({ word: w.word, start: toTimeline(w.start), end: toTimeline(w.end), clip: clip.id, track: track.id, index });
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
return out;
|
|
1586
|
+
}
|
|
1587
|
+
var normalizeVideoWord = (word) => word.toLowerCase().replace(/^[^\p{L}\p{N}']+|[^\p{L}\p{N}']+$/gu, "");
|
|
1588
|
+
var rangeOf = (words, i, n) => ({ from: words[i].start, to: words[i + n - 1].end, text: words.slice(i, i + n).map((w) => w.word).join(" ") });
|
|
1589
|
+
function findVideoTranscriptText(words, query) {
|
|
1590
|
+
const q = query.split(/\s+/).map(normalizeVideoWord).filter(Boolean);
|
|
1591
|
+
if (!q.length) throw new Error("Search for at least one word.");
|
|
1592
|
+
const norm = words.map((w) => normalizeVideoWord(w.word));
|
|
1593
|
+
const out = [];
|
|
1594
|
+
for (let i = 0; i + q.length <= norm.length; i++) if (q.every((x, k) => norm[i + k] === x)) out.push(rangeOf(words, i, q.length));
|
|
1595
|
+
return out;
|
|
1596
|
+
}
|
|
1597
|
+
var VIDEO_DEFAULT_FILLERS = ["um", "umm", "uh", "uhh", "uhm", "erm", "er", "ah", "hmm", "mm", "you know", "i mean"];
|
|
1598
|
+
function findVideoFillers(words, fillers = VIDEO_DEFAULT_FILLERS) {
|
|
1599
|
+
const phrases = fillers.map((f) => f.split(/\s+/).map(normalizeVideoWord).filter(Boolean)).filter((p) => p.length).sort((a, b) => b.length - a.length);
|
|
1600
|
+
const norm = words.map((w) => normalizeVideoWord(w.word));
|
|
1601
|
+
const out = [];
|
|
1602
|
+
for (let i = 0; i < norm.length; ) {
|
|
1603
|
+
const hit = phrases.find((p) => i + p.length <= norm.length && p.every((x, k) => norm[i + k] === x));
|
|
1604
|
+
if (hit) {
|
|
1605
|
+
out.push(rangeOf(words, i, hit.length));
|
|
1606
|
+
i += hit.length;
|
|
1607
|
+
} else i++;
|
|
1608
|
+
}
|
|
1609
|
+
return out;
|
|
1610
|
+
}
|
|
1611
|
+
function findVideoTranscriptPauses(words, options = {}) {
|
|
1612
|
+
const min = _nullishCoalesce(options.min, () => ( 700));
|
|
1613
|
+
const keep = _nullishCoalesce(options.keep, () => ( 150));
|
|
1614
|
+
const out = [];
|
|
1615
|
+
for (let i = 1; i < words.length; i++) {
|
|
1616
|
+
const a = words[i - 1];
|
|
1617
|
+
const b = words[i];
|
|
1618
|
+
if (a.clip !== b.clip || b.start - a.end < min) continue;
|
|
1619
|
+
let from = a.end + keep;
|
|
1620
|
+
let to = b.start - keep;
|
|
1621
|
+
if (options.fps) {
|
|
1622
|
+
const f = 1e3 / options.fps;
|
|
1623
|
+
from = _chunk3OSXHLITcjs.snapToVideoFrame.call(void 0, Math.ceil(from / f - 1e-9) * f, options.fps);
|
|
1624
|
+
to = _chunk3OSXHLITcjs.snapToVideoFrame.call(void 0, Math.floor(to / f + 1e-9) * f, options.fps);
|
|
1625
|
+
}
|
|
1626
|
+
if (to - from > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) out.push({ from, to, text: `(pause after "${a.word}")` });
|
|
1627
|
+
}
|
|
1628
|
+
return out;
|
|
1629
|
+
}
|
|
1630
|
+
function mergeVideoRanges(ranges) {
|
|
1631
|
+
const sorted = ranges.filter((r) => r.to - r.from > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON).map((r) => ({ from: r.from, to: r.to })).sort((a, b) => a.from - b.from);
|
|
1632
|
+
const out = [];
|
|
1633
|
+
for (const r of sorted) {
|
|
1634
|
+
const last = out[out.length - 1];
|
|
1635
|
+
if (last && r.from <= last.to + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) last.to = Math.max(last.to, r.to);
|
|
1636
|
+
else out.push(r);
|
|
1637
|
+
}
|
|
1638
|
+
return out;
|
|
1639
|
+
}
|
|
1640
|
+
function videoRemoveRangesEdits(ranges, tracks) {
|
|
1641
|
+
return mergeVideoRanges(ranges).reverse().map((r) => ({ op: "extract_range", from: r.from, to: r.to, ...tracks ? { tracks } : {} }));
|
|
1642
|
+
}
|
|
1643
|
+
function videoWordsRange(words, a, b) {
|
|
1644
|
+
const [i, j] = a <= b ? [a, b] : [b, a];
|
|
1645
|
+
return { from: words[i].start, to: words[j].end };
|
|
1646
|
+
}
|
|
1647
|
+
function videoTranscriptEdit(assetId, text) {
|
|
1648
|
+
return { op: "update_asset", asset: assetId, set: { transcript: parseVideoTranscript(text).words } };
|
|
1649
|
+
}
|
|
1650
|
+
function videoWordAt(words, time) {
|
|
1651
|
+
let lo = 0;
|
|
1652
|
+
let hi = words.length - 1;
|
|
1653
|
+
while (lo <= hi) {
|
|
1654
|
+
const mid = lo + hi >> 1;
|
|
1655
|
+
if (words[mid].end <= time) lo = mid + 1;
|
|
1656
|
+
else if (words[mid].start > time) hi = mid - 1;
|
|
1657
|
+
else return mid;
|
|
1658
|
+
}
|
|
1659
|
+
return -1;
|
|
1660
|
+
}
|
|
1661
|
+
function findVideoRepetitions(words, options = {}) {
|
|
1662
|
+
const max = Math.max(1, _nullishCoalesce(options.maxPhrase, () => ( 3)));
|
|
1663
|
+
const maxGap = _nullishCoalesce(options.maxGap, () => ( 1e3));
|
|
1664
|
+
const norm = words.map((w) => normalizeVideoWord(w.word));
|
|
1665
|
+
const out = [];
|
|
1666
|
+
for (let i = 0; i < norm.length; i++) {
|
|
1667
|
+
for (let n = Math.min(max, Math.floor((norm.length - i) / 2)); n >= 1; n--) {
|
|
1668
|
+
let same = true;
|
|
1669
|
+
for (let k = 0; k < n && same; k++) same = !!norm[i + k] && norm[i + k] === norm[i + n + k];
|
|
1670
|
+
if (!same || words[i + n].start - words[i + n - 1].end > maxGap) continue;
|
|
1671
|
+
out.push({ from: words[i].start, to: words[i + n].start, text: words.slice(i, i + n).map((w) => w.word).join(" ") });
|
|
1672
|
+
i += n - 1;
|
|
1673
|
+
break;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return out;
|
|
1677
|
+
}
|
|
1678
|
+
var VIDEO_DISFLUENCY_KINDS = ["filler", "pause", "repetition"];
|
|
1679
|
+
function detectVideoDisfluencies(words, options = {}) {
|
|
1680
|
+
const kinds = new Set(_nullishCoalesce(options.kinds, () => ( VIDEO_DISFLUENCY_KINDS)));
|
|
1681
|
+
const startIndex = (time) => Math.max(0, words.findIndex((w) => w.start >= time - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON));
|
|
1682
|
+
const out = [];
|
|
1683
|
+
if (kinds.has("filler"))
|
|
1684
|
+
for (const r of findVideoFillers(words, options.fillers)) {
|
|
1685
|
+
const first = startIndex(r.from);
|
|
1686
|
+
out.push({ ...r, kind: "filler", words: [first, first + r.text.split(" ").length - 1] });
|
|
1687
|
+
}
|
|
1688
|
+
if (kinds.has("repetition"))
|
|
1689
|
+
for (const r of findVideoRepetitions(words, options.repetitions)) {
|
|
1690
|
+
const first = startIndex(r.from);
|
|
1691
|
+
out.push({ ...r, kind: "repetition", words: [first, first + r.text.split(" ").length - 1] });
|
|
1692
|
+
}
|
|
1693
|
+
if (kinds.has("pause"))
|
|
1694
|
+
for (const r of findVideoTranscriptPauses(words, options.pauses)) {
|
|
1695
|
+
const after = words.findIndex((w) => w.start >= r.to - _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON);
|
|
1696
|
+
out.push({ ...r, kind: "pause", words: [Math.max(0, after - 1), Math.max(0, after)] });
|
|
1697
|
+
}
|
|
1698
|
+
return out.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
1699
|
+
}
|
|
1700
|
+
function videoCutsWithMargin(ranges, keep) {
|
|
1701
|
+
return ranges.map((r) => ({ ...r, from: r.from + keep, to: r.to - keep })).filter((r) => r.to - r.from > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/video-editor/mix.ts
|
|
1705
|
+
var VIDEO_LOUDNESS_HOP = 100;
|
|
1706
|
+
function kWeighting(sampleRate) {
|
|
1707
|
+
let K = Math.tan(Math.PI * 1681.974450955533 / sampleRate);
|
|
1708
|
+
const Vh = 10 ** (3.999843853973347 / 20);
|
|
1709
|
+
const Vb = Vh ** 0.4996667741545416;
|
|
1710
|
+
let Q = 0.7071752369554196;
|
|
1711
|
+
let a0 = 1 + K / Q + K * K;
|
|
1712
|
+
const shelf = { b: [(Vh + Vb * K / Q + K * K) / a0, 2 * (K * K - Vh) / a0, (Vh - Vb * K / Q + K * K) / a0], a: [2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0] };
|
|
1713
|
+
K = Math.tan(Math.PI * 38.13547087602444 / sampleRate);
|
|
1714
|
+
Q = 0.5003270373238773;
|
|
1715
|
+
a0 = 1 + K / Q + K * K;
|
|
1716
|
+
const highPass = { b: [1, -2, 1], a: [2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0] };
|
|
1717
|
+
return [shelf, highPass];
|
|
1718
|
+
}
|
|
1719
|
+
function videoLoudnessEnergy(channels, sampleRate) {
|
|
1720
|
+
const length = channels.reduce((n, c) => Math.max(n, c.length), 0);
|
|
1721
|
+
const hopSamples = Math.max(1, Math.round(sampleRate * VIDEO_LOUDNESS_HOP / 1e3));
|
|
1722
|
+
const hops = Math.max(1, Math.ceil(length / hopSamples));
|
|
1723
|
+
const energy = new Float32Array(hops);
|
|
1724
|
+
const filters = kWeighting(sampleRate);
|
|
1725
|
+
for (const channel of channels) {
|
|
1726
|
+
const x1 = [0, 0];
|
|
1727
|
+
const x2 = [0, 0];
|
|
1728
|
+
const y1 = [0, 0];
|
|
1729
|
+
const y2 = [0, 0];
|
|
1730
|
+
for (let i = 0; i < channel.length; i++) {
|
|
1731
|
+
let v = Number.isFinite(channel[i]) ? channel[i] : 0;
|
|
1732
|
+
for (let f = 0; f < 2; f++) {
|
|
1733
|
+
const { b, a } = filters[f];
|
|
1734
|
+
const y = b[0] * v + b[1] * x1[f] + b[2] * x2[f] - a[0] * y1[f] - a[1] * y2[f];
|
|
1735
|
+
x2[f] = x1[f];
|
|
1736
|
+
x1[f] = v;
|
|
1737
|
+
y2[f] = y1[f];
|
|
1738
|
+
y1[f] = y;
|
|
1739
|
+
v = y;
|
|
1740
|
+
}
|
|
1741
|
+
energy[Math.floor(i / hopSamples)] += v * v;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
for (let h = 0; h < hops; h++) energy[h] /= Math.min(hopSamples, length - h * hopSamples) || 1;
|
|
1745
|
+
return { hop: VIDEO_LOUDNESS_HOP, energy };
|
|
1746
|
+
}
|
|
1747
|
+
var lufs = (meanSquare) => -0.691 + 10 * Math.log10(meanSquare);
|
|
1748
|
+
function videoLoudnessOf(loudness, from = 0, to = Infinity) {
|
|
1749
|
+
const { hop, energy } = loudness;
|
|
1750
|
+
const a = Math.max(0, Math.floor(from / hop));
|
|
1751
|
+
const b = Math.min(energy.length, Math.ceil(to / hop));
|
|
1752
|
+
if (b <= a) return null;
|
|
1753
|
+
const blocks = [];
|
|
1754
|
+
if (b - a < 4) {
|
|
1755
|
+
let sum = 0;
|
|
1756
|
+
for (let h = a; h < b; h++) sum += energy[h];
|
|
1757
|
+
blocks.push(sum / (b - a));
|
|
1758
|
+
} else
|
|
1759
|
+
for (let h = a; h + 4 <= b; h++) blocks.push((energy[h] + energy[h + 1] + energy[h + 2] + energy[h + 3]) / 4);
|
|
1760
|
+
const absolute = blocks.filter((z) => z > 0 && lufs(z) > -70);
|
|
1761
|
+
if (!absolute.length) return null;
|
|
1762
|
+
const threshold = lufs(absolute.reduce((s, z) => s + z, 0) / absolute.length) - 10;
|
|
1763
|
+
const gated = absolute.filter((z) => lufs(z) > threshold);
|
|
1764
|
+
if (!gated.length) return null;
|
|
1765
|
+
return Math.round(lufs(gated.reduce((s, z) => s + z, 0) / gated.length) * 10) / 10;
|
|
1766
|
+
}
|
|
1767
|
+
function measureVideoLoudness(channels, sampleRate) {
|
|
1768
|
+
return videoLoudnessOf(videoLoudnessEnergy(channels, sampleRate));
|
|
1769
|
+
}
|
|
1770
|
+
function videoAudioLoudness(peaks, from = 0, to = Infinity) {
|
|
1771
|
+
if (!peaks.loudness) throw new Error("These peaks have no loudness data; build them again with buildVideoAudioPeaks (or decodeVideoAudioPeaks), which measures it.");
|
|
1772
|
+
return videoLoudnessOf(peaks.loudness, from, to);
|
|
1773
|
+
}
|
|
1774
|
+
function videoLoudnessGain(measured, target = -14, options = {}) {
|
|
1775
|
+
let db = target - measured;
|
|
1776
|
+
let limitedBy;
|
|
1777
|
+
const maxDb = 20 * Math.log10(_nullishCoalesce(options.maxGain, () => ( 4)));
|
|
1778
|
+
if (db > maxDb) {
|
|
1779
|
+
db = maxDb;
|
|
1780
|
+
limitedBy = "max-gain";
|
|
1781
|
+
}
|
|
1782
|
+
if (options.peakDb !== void 0 && options.peakDb > -100) {
|
|
1783
|
+
const room = (_nullishCoalesce(options.ceiling, () => ( -1))) - options.peakDb;
|
|
1784
|
+
if (db > room) {
|
|
1785
|
+
db = room;
|
|
1786
|
+
limitedBy = "peak";
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return { gain: Math.round(10 ** (db / 20) * 1e3) / 1e3, gainDb: Math.round(db * 10) / 10, ...limitedBy ? { limitedBy } : {} };
|
|
1790
|
+
}
|
|
1791
|
+
function videoSpeechRanges(words, mergeGap = 500) {
|
|
1792
|
+
const merged = [];
|
|
1793
|
+
for (const r of mergeVideoRanges(words.map((w) => ({ from: w.start, to: w.end })))) {
|
|
1794
|
+
const last = merged[merged.length - 1];
|
|
1795
|
+
if (last && r.from - last.to < mergeGap) last.to = Math.max(last.to, r.to);
|
|
1796
|
+
else merged.push({ ...r });
|
|
1797
|
+
}
|
|
1798
|
+
return merged;
|
|
1799
|
+
}
|
|
1800
|
+
function videoDuckingKeys(clip, speech, options = {}) {
|
|
1801
|
+
const duckDb = _nullishCoalesce(options.duckDb, () => ( -14));
|
|
1802
|
+
const attack = Math.max(0, _nullishCoalesce(options.attack, () => ( 200)));
|
|
1803
|
+
const release = Math.max(0, _nullishCoalesce(options.release, () => ( 400)));
|
|
1804
|
+
const gap = Math.max(_nullishCoalesce(options.mergeGap, () => ( 600)), attack + release);
|
|
1805
|
+
const base = _nullishCoalesce(options.base, () => ( 1));
|
|
1806
|
+
const low = Math.round(base * 10 ** (duckDb / 20) * 1e4) / 1e4;
|
|
1807
|
+
const end = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip);
|
|
1808
|
+
const ranges = [];
|
|
1809
|
+
for (const r of mergeVideoRanges(speech)) {
|
|
1810
|
+
if (r.to <= clip.start - release || r.from >= end + attack) continue;
|
|
1811
|
+
const last = ranges[ranges.length - 1];
|
|
1812
|
+
if (last && r.from - last.to < gap) last.to = Math.max(last.to, r.to);
|
|
1813
|
+
else ranges.push({ ...r });
|
|
1814
|
+
}
|
|
1815
|
+
if (!ranges.length) return [];
|
|
1816
|
+
const keys = [];
|
|
1817
|
+
const at = (t) => Math.round(Math.min(clip.duration, Math.max(0, t - clip.start)));
|
|
1818
|
+
const push = (t, v, ease) => {
|
|
1819
|
+
const last = keys[keys.length - 1];
|
|
1820
|
+
if (last && t <= last.t) {
|
|
1821
|
+
last.v = v;
|
|
1822
|
+
if (ease) last.ease = ease;
|
|
1823
|
+
else delete last.ease;
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
keys.push({ t, v, ...ease ? { ease } : {} });
|
|
1827
|
+
};
|
|
1828
|
+
for (const r of ranges) {
|
|
1829
|
+
const down = at(r.from - attack);
|
|
1830
|
+
const held = at(r.from);
|
|
1831
|
+
if (held > down) push(down, base, "ease-in-out");
|
|
1832
|
+
push(held, low);
|
|
1833
|
+
const hold = at(r.to);
|
|
1834
|
+
const up = at(r.to + release);
|
|
1835
|
+
if (up > hold) {
|
|
1836
|
+
push(hold, low, "ease-in-out");
|
|
1837
|
+
push(up, base);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return keys;
|
|
1841
|
+
}
|
|
1842
|
+
function findVideoAudioBeats(peaks, from, to, options = {}) {
|
|
1843
|
+
const res = Math.max(1, _nullishCoalesce(options.resolution, () => ( 10)));
|
|
1844
|
+
const n = Math.max(2, Math.ceil((to - from) / res));
|
|
1845
|
+
const spans = videoAudioPeakSpans(peaks, from, to, n);
|
|
1846
|
+
const env = new Float32Array(n);
|
|
1847
|
+
for (let i = 0; i < n; i++) env[i] = Math.max(Math.abs(spans[2 * i]), Math.abs(spans[2 * i + 1]));
|
|
1848
|
+
const onset = new Float32Array(n);
|
|
1849
|
+
for (let i = 1; i < n; i++) onset[i] = Math.max(0, env[i] - env[i - 1]);
|
|
1850
|
+
const win = Math.max(1, Math.round(500 / res));
|
|
1851
|
+
const k = _nullishCoalesce(options.sensitivity, () => ( 1.5));
|
|
1852
|
+
const minGap = Math.max(1, Math.round((_nullishCoalesce(options.minInterval, () => ( 250))) / res));
|
|
1853
|
+
const onsets = [];
|
|
1854
|
+
const strength = [];
|
|
1855
|
+
let lastAt = -Infinity;
|
|
1856
|
+
for (let i = 1; i < n - 1; i++) {
|
|
1857
|
+
if (!(onset[i] > 0 && onset[i] >= onset[i - 1] && onset[i] >= onset[i + 1])) continue;
|
|
1858
|
+
let sum = 0;
|
|
1859
|
+
let sq = 0;
|
|
1860
|
+
let count = 0;
|
|
1861
|
+
for (let j = Math.max(0, i - win); j < Math.min(n, i + win + 1); j++) {
|
|
1862
|
+
sum += onset[j];
|
|
1863
|
+
sq += onset[j] * onset[j];
|
|
1864
|
+
count++;
|
|
1865
|
+
}
|
|
1866
|
+
const mean = sum / count;
|
|
1867
|
+
const sd = Math.sqrt(Math.max(0, sq / count - mean * mean));
|
|
1868
|
+
if (onset[i] < mean + k * sd || onset[i] < 0.02) continue;
|
|
1869
|
+
if (i - lastAt < minGap) {
|
|
1870
|
+
if (onset[i] > strength[strength.length - 1]) {
|
|
1871
|
+
onsets[onsets.length - 1] = from + i * res;
|
|
1872
|
+
strength[strength.length - 1] = onset[i];
|
|
1873
|
+
lastAt = i;
|
|
1874
|
+
}
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
onsets.push(from + i * res);
|
|
1878
|
+
strength.push(onset[i]);
|
|
1879
|
+
lastAt = i;
|
|
1880
|
+
}
|
|
1881
|
+
const minLag = Math.max(1, Math.round(6e4 / (_nullishCoalesce(options.maxBpm, () => ( 200))) / res));
|
|
1882
|
+
const maxLag = Math.min(n - 1, Math.round(6e4 / (_nullishCoalesce(options.minBpm, () => ( 60))) / res));
|
|
1883
|
+
let bestLag = 0;
|
|
1884
|
+
let best = 0;
|
|
1885
|
+
let total = 0;
|
|
1886
|
+
for (let i = 0; i < n; i++) total += onset[i] * onset[i];
|
|
1887
|
+
for (let lag = minLag; lag <= maxLag; lag++) {
|
|
1888
|
+
let s = 0;
|
|
1889
|
+
for (let i = 0; i + lag < n; i++) s += onset[i] * onset[i + lag];
|
|
1890
|
+
s /= n - lag;
|
|
1891
|
+
if (s > best * 1.0001) {
|
|
1892
|
+
best = s;
|
|
1893
|
+
bestLag = lag;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
const repeats = onsets.length >= 3 && bestLag > 0 && best * n > 0.2 * total;
|
|
1897
|
+
if (!repeats) return { onsets, bpm: null, beats: [] };
|
|
1898
|
+
const period = bestLag * res;
|
|
1899
|
+
const bpm = Math.round(6e4 / period * 10) / 10;
|
|
1900
|
+
let phase = onsets[0];
|
|
1901
|
+
let agree = -1;
|
|
1902
|
+
for (const o of onsets) {
|
|
1903
|
+
let a = 0;
|
|
1904
|
+
for (const p of onsets) {
|
|
1905
|
+
const d = Math.abs(((p - o) % period + period) % period);
|
|
1906
|
+
if (Math.min(d, period - d) <= res * 2) a++;
|
|
1907
|
+
}
|
|
1908
|
+
if (a > agree) {
|
|
1909
|
+
agree = a;
|
|
1910
|
+
phase = o;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
const beats = [];
|
|
1914
|
+
for (let t = phase - Math.floor((phase - from) / period) * period; t <= to + 1e-6; t += period) if (t >= from - 1e-6) beats.push(Math.round(t * 10) / 10);
|
|
1915
|
+
return { onsets, bpm, beats };
|
|
1916
|
+
}
|
|
1917
|
+
function nearestVideoBeat(beats, time, tolerance = Infinity) {
|
|
1918
|
+
let best = null;
|
|
1919
|
+
for (const b of beats) if (Math.abs(b - time) <= tolerance && (best === null || Math.abs(b - time) < Math.abs(best - time))) best = b;
|
|
1920
|
+
return best;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1431
1923
|
// src/video-editor/audio.ts
|
|
1432
1924
|
var VIDEO_PEAK_BLOCK = 32;
|
|
1433
1925
|
function buildVideoAudioPeaks(channels, sampleRate, blockSize = VIDEO_PEAK_BLOCK) {
|
|
@@ -1467,7 +1959,7 @@ function buildVideoAudioPeaks(channels, sampleRate, blockSize = VIDEO_PEAK_BLOCK
|
|
|
1467
1959
|
}
|
|
1468
1960
|
levels.push(up);
|
|
1469
1961
|
}
|
|
1470
|
-
return { sampleRate, length, blockSize, levels };
|
|
1962
|
+
return { sampleRate, length, blockSize, levels, loudness: videoLoudnessEnergy(channels, sampleRate) };
|
|
1471
1963
|
}
|
|
1472
1964
|
var videoAudioPeaksDuration = (peaks) => peaks.length * 1e3 / peaks.sampleRate;
|
|
1473
1965
|
function videoAudioPeakRange(peaks, from, to) {
|
|
@@ -1517,7 +2009,7 @@ function videoClipWaveform(clip, peaks, columns) {
|
|
|
1517
2009
|
const step = clip.duration / Math.max(1, n);
|
|
1518
2010
|
for (let c = 0; c < n; c++) {
|
|
1519
2011
|
const { min, max } = videoAudioPeakRange(peaks, start + c * step * speed, start + (c + 1) * step * speed);
|
|
1520
|
-
const gain =
|
|
2012
|
+
const gain = _chunk3OSXHLITcjs.videoClipSoundGain.call(void 0, clip, (c + 0.5) * step);
|
|
1521
2013
|
out[2 * c] = min * gain;
|
|
1522
2014
|
out[2 * c + 1] = max * gain;
|
|
1523
2015
|
}
|
|
@@ -1574,7 +2066,7 @@ async function decodeVideoAudioPeaks(src, options = {}) {
|
|
|
1574
2066
|
let buffer;
|
|
1575
2067
|
try {
|
|
1576
2068
|
buffer = await new Offline(1, 1, 44100).decodeAudioData(data);
|
|
1577
|
-
} catch (
|
|
2069
|
+
} catch (e3) {
|
|
1578
2070
|
throw new Error(`"${src}" has no sound this browser can decode.`);
|
|
1579
2071
|
}
|
|
1580
2072
|
return buildVideoAudioPeaks(Array.from({ length: buffer.numberOfChannels }, (_, i) => buffer.getChannelData(i)), buffer.sampleRate);
|
|
@@ -1645,6 +2137,50 @@ var VideoAudioPeaksCache = (_class2 = class {
|
|
|
1645
2137
|
}
|
|
1646
2138
|
}, _class2);
|
|
1647
2139
|
|
|
2140
|
+
// src/video-editor/plan.ts
|
|
2141
|
+
function clipIndex(project) {
|
|
2142
|
+
const out = /* @__PURE__ */ new Map();
|
|
2143
|
+
for (const { track } of _chunk3OSXHLITcjs.allVideoTracks.call(void 0, project)) for (const c of track.clips) out.set(c.id, c);
|
|
2144
|
+
return out;
|
|
2145
|
+
}
|
|
2146
|
+
function diffVideoProjects(before, after) {
|
|
2147
|
+
const a = clipIndex(before);
|
|
2148
|
+
const b = clipIndex(after);
|
|
2149
|
+
const clipsAdded = [...b.keys()].filter((id) => !a.has(id));
|
|
2150
|
+
const clipsRemoved = [...a.keys()].filter((id) => !b.has(id));
|
|
2151
|
+
const clipsChanged = [...b.keys()].filter((id) => a.has(id) && JSON.stringify(a.get(id)) !== JSON.stringify(b.get(id)));
|
|
2152
|
+
const durationBefore = _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, before);
|
|
2153
|
+
const durationAfter = _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, after);
|
|
2154
|
+
return { durationBefore, durationAfter, durationDelta: durationAfter - durationBefore, clipsAdded, clipsRemoved, clipsChanged };
|
|
2155
|
+
}
|
|
2156
|
+
function planVideoEdits(project, input) {
|
|
2157
|
+
if (!_optionalChain([input, 'access', _33 => _33.title, 'optionalAccess', _34 => _34.trim, 'call', _35 => _35()])) return { ok: false, error: 'A plan needs a title saying what it does (e.g. "Remove 12 filler words").' };
|
|
2158
|
+
if (!input.edits.length) return { ok: false, error: "The plan has no edits; there is nothing to propose." };
|
|
2159
|
+
const result = applyVideoEdits(project, input.edits);
|
|
2160
|
+
if (!result.ok) return { ok: false, error: result.error };
|
|
2161
|
+
const known = new Set(checkVideoProject(project).map(videoIssueKey));
|
|
2162
|
+
const summary = { ...diffVideoProjects(project, result.project), newIssues: result.issues.filter((i) => !known.has(videoIssueKey(i))), edits: input.edits.length };
|
|
2163
|
+
const plan = {
|
|
2164
|
+
id: _nullishCoalesce(input.id, () => ( "plan")),
|
|
2165
|
+
title: input.title.trim(),
|
|
2166
|
+
...input.description ? { description: input.description } : {},
|
|
2167
|
+
...input.source ? { source: input.source } : {},
|
|
2168
|
+
edits: structuredClone([...input.edits]),
|
|
2169
|
+
..._optionalChain([input, 'access', _36 => _36.ranges, 'optionalAccess', _37 => _37.length]) ? { ranges: input.ranges.map((r) => ({ ...r })) } : {},
|
|
2170
|
+
summary
|
|
2171
|
+
};
|
|
2172
|
+
return { ok: true, plan, project: result.project };
|
|
2173
|
+
}
|
|
2174
|
+
function describeVideoEditPlan(plan) {
|
|
2175
|
+
const s = plan.summary;
|
|
2176
|
+
const parts = [];
|
|
2177
|
+
if (_optionalChain([plan, 'access', _38 => _38.ranges, 'optionalAccess', _39 => _39.length])) parts.push(`${plan.ranges.length} stretch${plan.ranges.length > 1 ? "es" : ""}`);
|
|
2178
|
+
const delta = s.durationDelta;
|
|
2179
|
+
const length = Math.abs(delta) < 0.5 ? "same length" : `${(Math.abs(delta) / 1e3).toFixed(1)} s ${delta < 0 ? "shorter" : "longer"}`;
|
|
2180
|
+
const clips = [s.clipsAdded.length && `${s.clipsAdded.length} added`, s.clipsChanged.length && `${s.clipsChanged.length} changed`, s.clipsRemoved.length && `${s.clipsRemoved.length} removed`].filter(Boolean).join(", ");
|
|
2181
|
+
return `${plan.title} (${parts.length ? `${parts.join(", ")}, ` : ""}${length})${clips ? `: clips ${clips}` : ""}`;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
1648
2184
|
// src/video-editor/scopes.ts
|
|
1649
2185
|
var VIDEO_SCOPE_KINDS = ["histogram", "waveform", "vectorscope"];
|
|
1650
2186
|
var videoLuma = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
@@ -2018,180 +2554,184 @@ function readVideoScopesPanel(feed, kinds = VIDEO_SCOPE_KINDS, options = {}) {
|
|
|
2018
2554
|
return { ok: true, time: snap.time, complete: snap.complete, images, stats, notes: videoExposureNotes(stats) };
|
|
2019
2555
|
}
|
|
2020
2556
|
|
|
2021
|
-
// src/video-editor/transcript.ts
|
|
2022
|
-
var
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
const words = text.replace(/<[^>]+>/g, "").split(/\s+/).filter(Boolean);
|
|
2030
|
-
const step = (end - start) / Math.max(1, words.length);
|
|
2031
|
-
return words.map((word, i) => ({ word, start: Math.round(start + i * step), end: Math.round(start + (i + 1) * step) }));
|
|
2032
|
-
}
|
|
2033
|
-
function parseVideoTranscript(input) {
|
|
2034
|
-
let data = input;
|
|
2035
|
-
if (typeof input === "string") {
|
|
2036
|
-
const text = input.replace(/^/, "").trim();
|
|
2037
|
-
if (text.startsWith("{") || text.startsWith("[")) {
|
|
2038
|
-
try {
|
|
2039
|
-
data = JSON.parse(text);
|
|
2040
|
-
} catch (e3) {
|
|
2041
|
-
throw new Error("The transcript looks like JSON but is not valid JSON.");
|
|
2042
|
-
}
|
|
2043
|
-
} else {
|
|
2044
|
-
const words2 = [];
|
|
2045
|
-
for (const block of text.replace(/\r\n?/g, "\n").split(/\n{2,}/)) {
|
|
2046
|
-
const lines = block.split("\n");
|
|
2047
|
-
const at = lines.findIndex((l) => l.includes("-->"));
|
|
2048
|
-
if (at < 0) continue;
|
|
2049
|
-
const [a, b] = lines[at].split("-->");
|
|
2050
|
-
words2.push(...cueWords(lines.slice(at + 1).join(" "), cueTime(a), cueTime(b.trim().split(/\s+/)[0])));
|
|
2051
|
-
}
|
|
2052
|
-
if (!words2.length) throw new Error("No words found: expected Whisper-style JSON with word timings, SubRip (.srt) or WebVTT.");
|
|
2053
|
-
return { words: words2, approximate: true };
|
|
2054
|
-
}
|
|
2557
|
+
// src/video-editor/transcript-controller.ts
|
|
2558
|
+
var TranscriptVideoController = (_class4 = class {
|
|
2559
|
+
constructor(controller, options = {}) {;_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);_class4.prototype.__init11.call(this);_class4.prototype.__init12.call(this);_class4.prototype.__init13.call(this);_class4.prototype.__init14.call(this);_class4.prototype.__init15.call(this);
|
|
2560
|
+
this.controller = controller;
|
|
2561
|
+
this.options = options;
|
|
2562
|
+
this.track = options.track;
|
|
2563
|
+
this.state = this.compute();
|
|
2564
|
+
this.connect();
|
|
2055
2565
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
else {
|
|
2072
|
-
for (const s of segments) words.push(...cueWords(String(_nullishCoalesce(s.text, () => ( ""))), seconds(s.start), seconds(s.end)));
|
|
2073
|
-
if (!words.length) throw new Error("No words found in the segments.");
|
|
2074
|
-
return { words, approximate: true };
|
|
2075
|
-
}
|
|
2566
|
+
|
|
2567
|
+
|
|
2568
|
+
__init9() {this.listeners = /* @__PURE__ */ new Set()}
|
|
2569
|
+
__init10() {this.unsubscribe = null}
|
|
2570
|
+
|
|
2571
|
+
__init11() {this.selection = null}
|
|
2572
|
+
__init12() {this.error = null}
|
|
2573
|
+
|
|
2574
|
+
/** Words and disfluencies are worked out once per project. */
|
|
2575
|
+
__init13() {this.cache = null}
|
|
2576
|
+
__init14() {this.getState = () => this.state}
|
|
2577
|
+
connect() {
|
|
2578
|
+
if (this.unsubscribe) return;
|
|
2579
|
+
this.unsubscribe = this.controller.subscribe(() => this.refresh());
|
|
2580
|
+
this.refresh();
|
|
2076
2581
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2582
|
+
/** Listens for changes. Subscribing after `destroy` follows the editor again (React StrictMode remounts). */
|
|
2583
|
+
__init15() {this.subscribe = (listener) => {
|
|
2584
|
+
this.connect();
|
|
2585
|
+
this.listeners.add(listener);
|
|
2586
|
+
return () => {
|
|
2587
|
+
this.listeners.delete(listener);
|
|
2588
|
+
};
|
|
2589
|
+
}}
|
|
2590
|
+
/** Stops following the editor. */
|
|
2591
|
+
destroy() {
|
|
2592
|
+
_optionalChain([this, 'access', _40 => _40.unsubscribe, 'optionalCall', _41 => _41()]);
|
|
2593
|
+
this.unsubscribe = null;
|
|
2594
|
+
this.listeners.clear();
|
|
2084
2595
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
words.
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2596
|
+
words(project) {
|
|
2597
|
+
const c = this.cache;
|
|
2598
|
+
if (c && c.project === project && c.track === this.track) return c;
|
|
2599
|
+
let words = [];
|
|
2600
|
+
let trackId = null;
|
|
2601
|
+
try {
|
|
2602
|
+
trackId = videoTranscriptTrack(project, this.track);
|
|
2603
|
+
words = videoTimelineTranscript(project, { track: this.track });
|
|
2604
|
+
} catch (e4) {
|
|
2605
|
+
}
|
|
2606
|
+
const disfluencies = detectVideoDisfluencies(words, { ...this.options.disfluencies, pauses: { fps: project.fps, ..._optionalChain([this, 'access', _42 => _42.options, 'access', _43 => _43.disfluencies, 'optionalAccess', _44 => _44.pauses]) } });
|
|
2607
|
+
const flags = {};
|
|
2608
|
+
for (const d of disfluencies)
|
|
2609
|
+
for (let i = d.words[0]; i <= d.words[1]; i++) {
|
|
2610
|
+
if (d.kind === "pause" && i !== d.words[0]) continue;
|
|
2611
|
+
const f = flags[i] ??= [];
|
|
2612
|
+
if (!f.includes(d.kind)) f.push(d.kind);
|
|
2613
|
+
}
|
|
2614
|
+
this.cache = { project, track: this.track, words, trackId, text: words.map((w) => w.word).join(" "), disfluencies, flags };
|
|
2615
|
+
return this.cache;
|
|
2616
|
+
}
|
|
2617
|
+
compute() {
|
|
2618
|
+
const editor = this.controller.getState();
|
|
2619
|
+
const c = this.words(editor.project);
|
|
2620
|
+
if (this.selection && (this.selection[0] >= c.words.length || this.selection[1] >= c.words.length)) this.selection = null;
|
|
2621
|
+
return { track: c.trackId, words: c.words, text: c.text, current: videoWordAt(c.words, editor.time), selection: this.selection, disfluencies: c.disfluencies, flags: c.flags, editable: !editor.readOnly, error: this.error };
|
|
2622
|
+
}
|
|
2623
|
+
refresh() {
|
|
2624
|
+
const next = this.compute();
|
|
2625
|
+
const prev = this.state;
|
|
2626
|
+
if (next.words === prev.words && next.current === prev.current && next.selection === prev.selection && next.editable === prev.editable && next.error === prev.error && next.disfluencies === prev.disfluencies) return;
|
|
2627
|
+
this.state = next;
|
|
2628
|
+
this.listeners.forEach((l) => l(next));
|
|
2629
|
+
}
|
|
2630
|
+
/** Follows another track's words (`undefined`: the default track). */
|
|
2631
|
+
setTrack(track) {
|
|
2632
|
+
this.track = track;
|
|
2633
|
+
this.selection = null;
|
|
2634
|
+
this.refresh();
|
|
2635
|
+
}
|
|
2636
|
+
/** Moves the playhead to the start of a word. */
|
|
2637
|
+
seekToWord(index) {
|
|
2638
|
+
const w = this.state.words[index];
|
|
2639
|
+
if (w) this.controller.seek(w.start);
|
|
2640
|
+
}
|
|
2641
|
+
/** Selects words `a` to `b` (either order), or clears the selection with `null`. */
|
|
2642
|
+
select(a, b = a) {
|
|
2643
|
+
const n = this.state.words.length;
|
|
2644
|
+
this.selection = a === null || b === null || a < 0 || b < 0 || a >= n || b >= n ? null : a <= b ? [a, b] : [b, a];
|
|
2645
|
+
this.error = null;
|
|
2646
|
+
this.refresh();
|
|
2647
|
+
}
|
|
2648
|
+
/** Index of the word playing at a timeline time, or -1. */
|
|
2649
|
+
wordAt(time) {
|
|
2650
|
+
return videoWordAt(this.state.words, time);
|
|
2651
|
+
}
|
|
2652
|
+
/** Where word `index` begins in `text` (characters). */
|
|
2653
|
+
textOffset(index) {
|
|
2654
|
+
let offset = 0;
|
|
2655
|
+
for (let i = 0; i < Math.min(index, this.state.words.length); i++) offset += this.state.words[i].word.length + 1;
|
|
2656
|
+
return offset;
|
|
2657
|
+
}
|
|
2658
|
+
/** The word a caret at `offset` characters into `text` is in (or just after), as an index; -1 when there are no words. */
|
|
2659
|
+
wordAtOffset(offset) {
|
|
2660
|
+
const { words } = this.state;
|
|
2661
|
+
let at = 0;
|
|
2662
|
+
for (let i = 0; i < words.length; i++) {
|
|
2663
|
+
at += words[i].word.length;
|
|
2664
|
+
if (offset <= at) return i;
|
|
2665
|
+
at += 1;
|
|
2666
|
+
}
|
|
2667
|
+
return words.length - 1;
|
|
2108
2668
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
function findVideoTranscriptText(words, query) {
|
|
2114
|
-
const q = query.split(/\s+/).map(normalizeVideoWord).filter(Boolean);
|
|
2115
|
-
if (!q.length) throw new Error("Search for at least one word.");
|
|
2116
|
-
const norm = words.map((w) => normalizeVideoWord(w.word));
|
|
2117
|
-
const out = [];
|
|
2118
|
-
for (let i = 0; i + q.length <= norm.length; i++) if (q.every((x, k) => norm[i + k] === x)) out.push(rangeOf(words, i, q.length));
|
|
2119
|
-
return out;
|
|
2120
|
-
}
|
|
2121
|
-
var VIDEO_DEFAULT_FILLERS = ["um", "umm", "uh", "uhh", "uhm", "erm", "er", "ah", "hmm", "mm", "you know", "i mean"];
|
|
2122
|
-
function findVideoFillers(words, fillers = VIDEO_DEFAULT_FILLERS) {
|
|
2123
|
-
const phrases = fillers.map((f) => f.split(/\s+/).map(normalizeVideoWord).filter(Boolean)).filter((p) => p.length).sort((a, b) => b.length - a.length);
|
|
2124
|
-
const norm = words.map((w) => normalizeVideoWord(w.word));
|
|
2125
|
-
const out = [];
|
|
2126
|
-
for (let i = 0; i < norm.length; ) {
|
|
2127
|
-
const hit = phrases.find((p) => i + p.length <= norm.length && p.every((x, k) => norm[i + k] === x));
|
|
2128
|
-
if (hit) {
|
|
2129
|
-
out.push(rangeOf(words, i, hit.length));
|
|
2130
|
-
i += hit.length;
|
|
2131
|
-
} else i++;
|
|
2669
|
+
/** The timeline time a caret at `offset` characters into `text` stands for: the start of its word. */
|
|
2670
|
+
timeAtOffset(offset) {
|
|
2671
|
+
const i = this.wordAtOffset(offset);
|
|
2672
|
+
return i < 0 ? null : this.state.words[i].start;
|
|
2132
2673
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
const keep = _nullishCoalesce(options.keep, () => ( 150));
|
|
2138
|
-
const out = [];
|
|
2139
|
-
for (let i = 1; i < words.length; i++) {
|
|
2140
|
-
const a = words[i - 1];
|
|
2141
|
-
const b = words[i];
|
|
2142
|
-
if (a.clip !== b.clip || b.start - a.end < min) continue;
|
|
2143
|
-
let from = a.end + keep;
|
|
2144
|
-
let to = b.start - keep;
|
|
2145
|
-
if (options.fps) {
|
|
2146
|
-
const f = 1e3 / options.fps;
|
|
2147
|
-
from = _chunkVKF3PWKRcjs.snapToVideoFrame.call(void 0, Math.ceil(from / f - 1e-9) * f, options.fps);
|
|
2148
|
-
to = _chunkVKF3PWKRcjs.snapToVideoFrame.call(void 0, Math.floor(to / f + 1e-9) * f, options.fps);
|
|
2149
|
-
}
|
|
2150
|
-
if (to - from > _chunkVKF3PWKRcjs.VIDEO_TIME_EPSILON) out.push({ from, to, text: `(pause after "${a.word}")` });
|
|
2674
|
+
fail(message) {
|
|
2675
|
+
this.error = message;
|
|
2676
|
+
this.refresh();
|
|
2677
|
+
return { ok: false, error: message };
|
|
2151
2678
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2679
|
+
/** Cuts ranges out (or proposes a plan to, with `plan`), as one undo step. */
|
|
2680
|
+
cut(ranges, title, plan) {
|
|
2681
|
+
if (!ranges.length) return this.fail("There is nothing to cut.");
|
|
2682
|
+
const merged = mergeVideoRanges(ranges);
|
|
2683
|
+
const edits = videoRemoveRangesEdits(merged);
|
|
2684
|
+
if (plan) {
|
|
2685
|
+
const r2 = this.controller.proposePlan({ title, edits, ranges, source: "transcript" });
|
|
2686
|
+
if (!r2.ok) return this.fail(r2.error);
|
|
2687
|
+
this.error = null;
|
|
2688
|
+
this.select(null);
|
|
2689
|
+
return { ok: true, plan: r2.plan };
|
|
2690
|
+
}
|
|
2691
|
+
const r = this.controller.apply(edits, title);
|
|
2692
|
+
if (!r.ok) return this.fail(r.error);
|
|
2693
|
+
this.error = null;
|
|
2694
|
+
this.select(null);
|
|
2695
|
+
return { ok: true, applied: r };
|
|
2161
2696
|
}
|
|
2162
|
-
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2697
|
+
/** Deletes the selected words, cutting their stretch out of the timeline (or proposing it with `planFirst`). */
|
|
2698
|
+
deleteSelection(options = {}) {
|
|
2699
|
+
const sel = this.state.selection;
|
|
2700
|
+
if (!sel) return this.fail("Select the words to delete first.");
|
|
2701
|
+
const { words } = this.state;
|
|
2702
|
+
const range = videoWordsRange(words, sel[0], sel[1]);
|
|
2703
|
+
const text = words.slice(sel[0], sel[1] + 1).map((w) => w.word).join(" ");
|
|
2704
|
+
return this.cut([{ ...range, text, reason: "deleted" }], sel[0] === sel[1] ? `Delete "${text}"` : `Delete ${sel[1] - sel[0] + 1} words`, _nullishCoalesce(_nullishCoalesce(options.plan, () => ( this.options.planFirst)), () => ( false)));
|
|
2705
|
+
}
|
|
2706
|
+
/** Cuts one disfluency out at once (its index in `disfluencies`), as one undo step. */
|
|
2707
|
+
removeDisfluency(index) {
|
|
2708
|
+
const d = this.state.disfluencies[index];
|
|
2709
|
+
if (!d) return this.fail(`There is no disfluency ${index}; there are ${this.state.disfluencies.length}.`);
|
|
2710
|
+
return this.cut([{ from: d.from, to: d.to, text: d.text, reason: d.kind }], d.kind === "pause" ? "Tighten pause" : `Remove "${d.text}"`, false);
|
|
2711
|
+
}
|
|
2712
|
+
/** Proposes one plan that cuts every disfluency of the given kinds (default all), for review. */
|
|
2713
|
+
planCleanup(kinds) {
|
|
2714
|
+
const found = this.state.disfluencies.filter((d) => !kinds || kinds.includes(d.kind));
|
|
2715
|
+
if (!found.length) return this.fail(kinds ? `No ${kinds.join(", ")} found.` : "No fillers, long pauses or repetitions found.");
|
|
2716
|
+
const counts = ["filler", "pause", "repetition"].map((k) => [k, found.filter((d) => d.kind === k).length]).filter(([, n]) => n);
|
|
2717
|
+
const title = `Clean up ${counts.map(([k, n]) => `${n} ${k}${n > 1 ? "s" : ""}`).join(", ")}`;
|
|
2718
|
+
return this.cut(
|
|
2719
|
+
found.map((d) => ({ from: d.from, to: d.to, text: d.text, reason: d.kind })),
|
|
2720
|
+
title,
|
|
2721
|
+
true
|
|
2722
|
+
);
|
|
2182
2723
|
}
|
|
2183
|
-
|
|
2184
|
-
}
|
|
2724
|
+
}, _class4);
|
|
2185
2725
|
|
|
2186
2726
|
// src/video-editor/proxy.ts
|
|
2187
2727
|
var VIDEO_PROXY_SCALES = [0.5, 0.25];
|
|
2188
2728
|
function videoAssetProxy(asset, scale) {
|
|
2189
|
-
if (!_optionalChain([asset, 'optionalAccess',
|
|
2729
|
+
if (!_optionalChain([asset, 'optionalAccess', _45 => _45.proxies, 'optionalAccess', _46 => _46.length]) || !(scale > 0) || scale >= 1) return null;
|
|
2190
2730
|
let best = null;
|
|
2191
2731
|
for (const p of asset.proxies) if (p.scale >= scale - 1e-9 && p.scale < 1 && (!best || p.scale < best.scale)) best = p;
|
|
2192
2732
|
return best;
|
|
2193
2733
|
}
|
|
2194
|
-
var videoPreviewSrc = (asset, scale, fallback = _nullishCoalesce(_optionalChain([asset, 'optionalAccess',
|
|
2734
|
+
var videoPreviewSrc = (asset, scale, fallback = _nullishCoalesce(_optionalChain([asset, 'optionalAccess', _47 => _47.src]), () => ( ""))) => _nullishCoalesce(_optionalChain([videoAssetProxy, 'call', _48 => _48(asset, scale), 'optionalAccess', _49 => _49.src]), () => ( fallback));
|
|
2195
2735
|
function resolveVideoPreviewScale(scale, shown) {
|
|
2196
2736
|
if (scale === void 0 || scale === "auto") return Math.min(1, Math.max(0, shown));
|
|
2197
2737
|
if (scale === "full") return 1;
|
|
@@ -2240,7 +2780,7 @@ function videoProxyFfmpeg(asset, options = {}) {
|
|
|
2240
2780
|
output
|
|
2241
2781
|
];
|
|
2242
2782
|
const proxy = { scale, src: output, ...asset.width && asset.height ? { width: Math.trunc(asset.width * scale / 2) * 2, height: Math.trunc(asset.height * scale / 2) * 2 } : {} };
|
|
2243
|
-
return { asset: asset.id, args, command: ["ffmpeg", ...args].map(
|
|
2783
|
+
return { asset: asset.id, args, command: ["ffmpeg", ...args].map(_chunk3OSXHLITcjs.videoShellQuote).join(" "), proxy };
|
|
2244
2784
|
}
|
|
2245
2785
|
function videoProxyPlan(project, options = {}) {
|
|
2246
2786
|
const scale = _nullishCoalesce(options.scale, () => ( 0.5));
|
|
@@ -2250,7 +2790,7 @@ function videoProxyPlan(project, options = {}) {
|
|
|
2250
2790
|
if (!a) throw new Error(`There is no asset "${id}"; the assets are ${project.assets.map((x) => `"${x.id}"`).join(", ") || "none"}.`);
|
|
2251
2791
|
if (a.kind !== "video") throw new Error(`Asset "${id}" is ${a.kind === "image" ? "an image" : "audio"}; only video assets have proxies.`);
|
|
2252
2792
|
}
|
|
2253
|
-
return project.assets.filter((a) => a.kind === "video" && (!wanted || wanted.has(a.id)) && !_optionalChain([a, 'access',
|
|
2793
|
+
return project.assets.filter((a) => a.kind === "video" && (!wanted || wanted.has(a.id)) && !_optionalChain([a, 'access', _50 => _50.proxies, 'optionalAccess', _51 => _51.some, 'call', _52 => _52((p) => Math.abs(p.scale - scale) < 1e-9)])).map((a) => videoProxyFfmpeg(a, { ...options, scale }));
|
|
2254
2794
|
}
|
|
2255
2795
|
var VIDEO_PREVIEW_SCALE_OPTIONS = [
|
|
2256
2796
|
{ value: "auto", label: "Auto", scale: "auto" },
|
|
@@ -2258,7 +2798,7 @@ var VIDEO_PREVIEW_SCALE_OPTIONS = [
|
|
|
2258
2798
|
...VIDEO_PROXY_SCALES.map((s) => ({ value: String(s), label: s === 0.5 ? "\xBD" : s === 0.25 ? "\xBC" : `${s * 100}%`, scale: s }))
|
|
2259
2799
|
];
|
|
2260
2800
|
var parseVideoPreviewScale = (value) => value === "full" ? "full" : Number(value) > 0 && Number(value) <= 1 ? Number(value) : "auto";
|
|
2261
|
-
var videoProjectHasProxies = (project) => project.assets.some((a) => !!_optionalChain([a, 'access',
|
|
2801
|
+
var videoProjectHasProxies = (project) => project.assets.some((a) => !!_optionalChain([a, 'access', _53 => _53.proxies, 'optionalAccess', _54 => _54.length]));
|
|
2262
2802
|
|
|
2263
2803
|
// src/video-editor/frame-cache.ts
|
|
2264
2804
|
var videoSourceFrame = (sourceTime, fps) => Math.max(0, Math.floor(sourceTime * fps / 1e3 + 1e-6));
|
|
@@ -2266,7 +2806,7 @@ var videoSourceFrameTime = (frame, fps) => (frame + 0.5) * 1e3 / fps;
|
|
|
2266
2806
|
var videoFrameCacheKey = (src, frame) => `${frame}@${src}`;
|
|
2267
2807
|
function videoFrameRequestsAt(project, time, srcFor = (l) => l.src) {
|
|
2268
2808
|
const out = [];
|
|
2269
|
-
for (const layer of
|
|
2809
|
+
for (const layer of _chunk3OSXHLITcjs.videoFrameLeafLayers.call(void 0, _chunk3OSXHLITcjs.videoFrameAt.call(void 0, project, time).layers)) {
|
|
2270
2810
|
if (layer.type !== "media" || layer.assetKind !== "video") continue;
|
|
2271
2811
|
const src = srcFor(layer);
|
|
2272
2812
|
if (!src) continue;
|
|
@@ -2302,7 +2842,7 @@ function videoCacheEvictions(entries, options) {
|
|
|
2302
2842
|
return d >= 0 ? d : -d * behind;
|
|
2303
2843
|
};
|
|
2304
2844
|
const out = [];
|
|
2305
|
-
for (const e of entries.filter((x) => !_optionalChain([options, 'access',
|
|
2845
|
+
for (const e of entries.filter((x) => !_optionalChain([options, 'access', _55 => _55.protect, 'optionalAccess', _56 => _56.has, 'call', _57 => _57(x.key)])).sort((a, b) => cost(b) - cost(a))) {
|
|
2306
2846
|
if (total <= options.maxBytes) break;
|
|
2307
2847
|
out.push(e.key);
|
|
2308
2848
|
total -= e.bytes;
|
|
@@ -2314,26 +2854,26 @@ var defaultCapture = async (source) => {
|
|
|
2314
2854
|
const image = await createImageBitmap(source);
|
|
2315
2855
|
return { image, width: image.width, height: image.height };
|
|
2316
2856
|
};
|
|
2317
|
-
var VideoFrameCache = (
|
|
2318
|
-
constructor(options = {}) {;
|
|
2857
|
+
var VideoFrameCache = (_class5 = class {
|
|
2858
|
+
constructor(options = {}) {;_class5.prototype.__init16.call(this);_class5.prototype.__init17.call(this);_class5.prototype.__init18.call(this);_class5.prototype.__init19.call(this);_class5.prototype.__init20.call(this);_class5.prototype.__init21.call(this);_class5.prototype.__init22.call(this);_class5.prototype.__init23.call(this);_class5.prototype.__init24.call(this);_class5.prototype.__init25.call(this);_class5.prototype.__init26.call(this);_class5.prototype.__init27.call(this);
|
|
2319
2859
|
this.options = options;
|
|
2320
2860
|
this.maxBytes = _nullishCoalesce(options.maxBytes, () => ( 512 * 1024 * 1024));
|
|
2321
2861
|
this.prefetch = _nullishCoalesce(options.prefetch, () => ( 2e3));
|
|
2322
2862
|
this.decode = options.decode !== void 0 ? options.decode : typeof document !== "undefined" ? createVideoElementDecoder() : null;
|
|
2323
2863
|
}
|
|
2324
2864
|
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2865
|
+
__init16() {this.entries = /* @__PURE__ */ new Map()}
|
|
2866
|
+
__init17() {this.inFlight = /* @__PURE__ */ new Set()}
|
|
2867
|
+
__init18() {this.queue = []}
|
|
2868
|
+
__init19() {this.protect = /* @__PURE__ */ new Set()}
|
|
2869
|
+
__init20() {this.listeners = /* @__PURE__ */ new Set()}
|
|
2870
|
+
__init21() {this.playhead = 0}
|
|
2871
|
+
__init22() {this.direction = 1}
|
|
2872
|
+
__init23() {this.hits = 0}
|
|
2873
|
+
__init24() {this.misses = 0}
|
|
2874
|
+
__init25() {this.bytes = 0}
|
|
2875
|
+
__init26() {this.generation = 0}
|
|
2876
|
+
__init27() {this.destroyed = false}
|
|
2337
2877
|
|
|
2338
2878
|
|
|
2339
2879
|
|
|
@@ -2343,7 +2883,7 @@ var VideoFrameCache = (_class4 = class {
|
|
|
2343
2883
|
const entry = this.entries.get(videoFrameCacheKey(src, videoSourceFrame(sourceTime, fps)));
|
|
2344
2884
|
if (entry) this.hits++;
|
|
2345
2885
|
else this.misses++;
|
|
2346
|
-
return _optionalChain([entry, 'optionalAccess',
|
|
2886
|
+
return _optionalChain([entry, 'optionalAccess', _58 => _58.frame, 'access', _59 => _59.image]);
|
|
2347
2887
|
}
|
|
2348
2888
|
/** Whether a frame is cached, without counting it. */
|
|
2349
2889
|
has(src, sourceTime, fps) {
|
|
@@ -2351,7 +2891,7 @@ var VideoFrameCache = (_class4 = class {
|
|
|
2351
2891
|
}
|
|
2352
2892
|
/** Keeps a decoded frame (replacing any with the same key), then evicts to the budget. */
|
|
2353
2893
|
put(request, frame) {
|
|
2354
|
-
if (this.destroyed) return void _optionalChain([frame, 'access',
|
|
2894
|
+
if (this.destroyed) return void _optionalChain([frame, 'access', _60 => _60.image, 'access', _61 => _61.close, 'optionalCall', _62 => _62()]);
|
|
2355
2895
|
const old = this.entries.get(request.key);
|
|
2356
2896
|
if (old) this.drop(old);
|
|
2357
2897
|
const bytes = Math.max(1, frame.width * frame.height * 4);
|
|
@@ -2450,7 +2990,7 @@ var VideoFrameCache = (_class4 = class {
|
|
|
2450
2990
|
(frame) => {
|
|
2451
2991
|
this.inFlight.delete(r.key);
|
|
2452
2992
|
if (generation === this.generation || this.bytes + frame.width * frame.height * 4 <= this.maxBytes) this.put(r, frame);
|
|
2453
|
-
else _optionalChain([frame, 'access',
|
|
2993
|
+
else _optionalChain([frame, 'access', _63 => _63.image, 'access', _64 => _64.close, 'optionalCall', _65 => _65()]);
|
|
2454
2994
|
},
|
|
2455
2995
|
() => this.inFlight.delete(r.key)
|
|
2456
2996
|
).finally(() => this.pump());
|
|
@@ -2463,12 +3003,12 @@ var VideoFrameCache = (_class4 = class {
|
|
|
2463
3003
|
drop(e) {
|
|
2464
3004
|
this.entries.delete(e.key);
|
|
2465
3005
|
this.bytes -= e.bytes;
|
|
2466
|
-
_optionalChain([e, 'access',
|
|
3006
|
+
_optionalChain([e, 'access', _66 => _66.frame, 'access', _67 => _67.image, 'access', _68 => _68.close, 'optionalCall', _69 => _69()]);
|
|
2467
3007
|
}
|
|
2468
3008
|
emit() {
|
|
2469
3009
|
for (const l of [...this.listeners]) l();
|
|
2470
3010
|
}
|
|
2471
|
-
},
|
|
3011
|
+
}, _class5);
|
|
2472
3012
|
function createVideoElementDecoder(options = {}) {
|
|
2473
3013
|
const videos = /* @__PURE__ */ new Map();
|
|
2474
3014
|
const make = _nullishCoalesce(options.createVideo, () => ( (() => {
|
|
@@ -2510,12 +3050,12 @@ function createVideoElementDecoder(options = {}) {
|
|
|
2510
3050
|
}
|
|
2511
3051
|
|
|
2512
3052
|
// src/video-editor/preview.ts
|
|
2513
|
-
var VideoPreviewEngine = (
|
|
2514
|
-
constructor(source, canvas2, options = {}) {;
|
|
3053
|
+
var VideoPreviewEngine = (_class6 = class {
|
|
3054
|
+
constructor(source, canvas2, options = {}) {;_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);_class6.prototype.__init38.call(this);_class6.prototype.__init39.call(this);_class6.prototype.__init40.call(this);_class6.prototype.__init41.call(this);_class6.prototype.__init42.call(this);_class6.prototype.__init43.call(this);
|
|
2515
3055
|
this.source = source;
|
|
2516
3056
|
this.options = options;
|
|
2517
3057
|
this.canvas = canvas2;
|
|
2518
|
-
this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access',
|
|
3058
|
+
this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access', _74 => _74.getContext, 'optionalCall', _75 => _75("2d")]), () => ( null));
|
|
2519
3059
|
this.unsubscribe = source.subscribe(() => this.update());
|
|
2520
3060
|
this.lutCache = options.luts !== void 0 ? options.luts : _nullishCoalesce(source.luts, () => ( null));
|
|
2521
3061
|
this.unsubscribeLuts = this.lutCache ? this.lutCache.subscribe(() => this.redraw()) : () => {
|
|
@@ -2536,31 +3076,31 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2536
3076
|
|
|
2537
3077
|
|
|
2538
3078
|
|
|
2539
|
-
|
|
2540
|
-
|
|
3079
|
+
__init28() {this.media = /* @__PURE__ */ new Map()}
|
|
3080
|
+
__init29() {this.images = /* @__PURE__ */ new Map()}
|
|
2541
3081
|
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
3082
|
+
__init30() {this.scheduled = false}
|
|
3083
|
+
__init31() {this.destroyed = false}
|
|
3084
|
+
__init32() {this.filterHost = null}
|
|
3085
|
+
__init33() {this.filters = /* @__PURE__ */ new Set()}
|
|
2546
3086
|
|
|
2547
3087
|
|
|
2548
|
-
|
|
2549
|
-
|
|
3088
|
+
__init34() {this.drawn = null}
|
|
3089
|
+
__init35() {this.complete = true}
|
|
2550
3090
|
|
|
2551
3091
|
|
|
2552
3092
|
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
3093
|
+
__init36() {this.srcFor = (layer) => layer.src}
|
|
3094
|
+
__init37() {this.playing = false}
|
|
3095
|
+
__init38() {this.fps = 30}
|
|
2556
3096
|
|
|
2557
|
-
|
|
3097
|
+
__init39() {this.scratchCanvases = /* @__PURE__ */ new Map()}
|
|
2558
3098
|
|
|
2559
3099
|
|
|
2560
3100
|
/** The canvas's pixels and the frame they show, or `null` when its context cannot be read. */
|
|
2561
3101
|
readPixels() {
|
|
2562
3102
|
const ctx = this.ctx;
|
|
2563
|
-
if (!_optionalChain([ctx, 'optionalAccess',
|
|
3103
|
+
if (!_optionalChain([ctx, 'optionalAccess', _76 => _76.getImageData]) || !this.drawn) return null;
|
|
2564
3104
|
return { ...this.drawn, pixels: ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) };
|
|
2565
3105
|
}
|
|
2566
3106
|
createMedia(kind) {
|
|
@@ -2598,8 +3138,8 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2598
3138
|
entry.media.pause();
|
|
2599
3139
|
entry.media.removeEventListener("seeked", entry.onReady);
|
|
2600
3140
|
entry.media.removeEventListener("loadeddata", entry.onReady);
|
|
2601
|
-
_optionalChain([entry, 'access',
|
|
2602
|
-
_optionalChain([entry, 'access',
|
|
3141
|
+
_optionalChain([entry, 'access', _77 => _77.media, 'access', _78 => _78.removeAttribute, 'optionalCall', _79 => _79("src")]);
|
|
3142
|
+
_optionalChain([entry, 'access', _80 => _80.media, 'access', _81 => _81.load, 'optionalCall', _82 => _82()]);
|
|
2603
3143
|
this.media.delete(entry.clip);
|
|
2604
3144
|
}
|
|
2605
3145
|
/** Brings the media elements in step with the editor and draws the frame. */
|
|
@@ -2607,13 +3147,13 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2607
3147
|
if (this.destroyed) return;
|
|
2608
3148
|
const state = this.source.getState();
|
|
2609
3149
|
const { project, time, playing } = state;
|
|
2610
|
-
const frame =
|
|
3150
|
+
const frame = _chunk3OSXHLITcjs.videoFrameAt.call(void 0, project, time);
|
|
2611
3151
|
const scale = resolveVideoPreviewScale(_nullishCoalesce(this.options.previewScale, () => ( state.previewScale)), this.resolutionFor(project.width));
|
|
2612
3152
|
const assets = new Map(project.assets.map((a) => [a.id, a]));
|
|
2613
3153
|
this.srcFor = (layer) => videoPreviewSrc(assets.get(_nullishCoalesce(layer.asset, () => ( ""))), scale, layer.src);
|
|
2614
3154
|
this.playing = playing;
|
|
2615
3155
|
this.fps = project.fps;
|
|
2616
|
-
_optionalChain([this, 'access',
|
|
3156
|
+
_optionalChain([this, 'access', _83 => _83.frameCache, 'optionalAccess', _84 => _84.update, 'call', _85 => _85(project, time, { playing, srcFor: this.srcFor })]);
|
|
2617
3157
|
const tolerance = (_nullishCoalesce(this.options.tolerance, () => ( 200))) / 1e3;
|
|
2618
3158
|
const active = /* @__PURE__ */ new Set();
|
|
2619
3159
|
const sync = (clip, src, kind, sourceTime, speed, gain) => {
|
|
@@ -2637,10 +3177,10 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2637
3177
|
}
|
|
2638
3178
|
};
|
|
2639
3179
|
const sounding = new Map(frame.audio.map((a) => [a.clip, a]));
|
|
2640
|
-
for (const layer of
|
|
3180
|
+
for (const layer of _chunk3OSXHLITcjs.videoFrameLeafLayers.call(void 0, frame.layers)) {
|
|
2641
3181
|
if (layer.type !== "media" || layer.assetKind !== "video" || !layer.src) continue;
|
|
2642
3182
|
const sound = sounding.get(layer.clip);
|
|
2643
|
-
sync(layer.clip, this.srcFor(layer), "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess',
|
|
3183
|
+
sync(layer.clip, this.srcFor(layer), "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess', _86 => _86.speed]), () => ( 1)), sound ? sound.gain : null);
|
|
2644
3184
|
sounding.delete(layer.clip);
|
|
2645
3185
|
}
|
|
2646
3186
|
for (const a of sounding.values()) sync(a.clip, a.src, "audio", a.sourceTime, a.speed, a.gain);
|
|
@@ -2649,9 +3189,9 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2649
3189
|
}
|
|
2650
3190
|
/** With effects, the frame is drawn at the start of its frame, so effect pictures are cached once per frame. */
|
|
2651
3191
|
drawable(project, frame) {
|
|
2652
|
-
if (!_optionalChain([this, 'access',
|
|
2653
|
-
const at =
|
|
2654
|
-
return at === frame.time ? frame : { ...
|
|
3192
|
+
if (!_optionalChain([this, 'access', _87 => _87.effects, 'optionalAccess', _88 => _88.available]) || !_chunk3OSXHLITcjs.videoLayersUseEffects.call(void 0, frame.layers)) return frame;
|
|
3193
|
+
const at = _chunk3OSXHLITcjs.videoEffectFrameTime.call(void 0, frame.time, project.fps);
|
|
3194
|
+
return at === frame.time ? frame : { ..._chunk3OSXHLITcjs.videoFrameAt.call(void 0, project, at), time: frame.time };
|
|
2655
3195
|
}
|
|
2656
3196
|
redraw() {
|
|
2657
3197
|
if (this.scheduled || this.destroyed) return;
|
|
@@ -2660,10 +3200,10 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2660
3200
|
this.scheduled = false;
|
|
2661
3201
|
if (this.destroyed) return;
|
|
2662
3202
|
const { project, time } = this.source.getState();
|
|
2663
|
-
this.draw(this.drawable(project,
|
|
3203
|
+
this.draw(this.drawable(project, _chunk3OSXHLITcjs.videoFrameAt.call(void 0, project, time)));
|
|
2664
3204
|
});
|
|
2665
3205
|
}
|
|
2666
|
-
|
|
3206
|
+
__init40() {this.sourceFor = (layer) => {
|
|
2667
3207
|
if (layer.assetKind === "image" && layer.src) {
|
|
2668
3208
|
const img = this.image(layer.src);
|
|
2669
3209
|
return img.complete ? img : null;
|
|
@@ -2671,7 +3211,7 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2671
3211
|
const entry = this.media.get(layer.clip);
|
|
2672
3212
|
const ready = !!entry && entry.media.readyState >= 2;
|
|
2673
3213
|
const cache = this.frameCache;
|
|
2674
|
-
const src = _nullishCoalesce(_optionalChain([entry, 'optionalAccess',
|
|
3214
|
+
const src = _nullishCoalesce(_optionalChain([entry, 'optionalAccess', _89 => _89.src]), () => ( this.srcFor(layer)));
|
|
2675
3215
|
const sourceTime = _nullishCoalesce(layer.sourceTime, () => ( 0));
|
|
2676
3216
|
if (cache && src && (!this.playing || !ready || entry.media.seeking)) {
|
|
2677
3217
|
const hit = cache.get(src, sourceTime, this.fps);
|
|
@@ -2703,7 +3243,7 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2703
3243
|
if (!source) this.complete = false;
|
|
2704
3244
|
return source;
|
|
2705
3245
|
};
|
|
2706
|
-
|
|
3246
|
+
_chunk3OSXHLITcjs.drawVideoFrame.call(void 0, this.ctx, frame, sourceFor, resolution, {
|
|
2707
3247
|
defineSvgFilter: this.defineSvgFilter,
|
|
2708
3248
|
lutFor: this.lutFor,
|
|
2709
3249
|
scratch: this.scratch,
|
|
@@ -2713,16 +3253,16 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2713
3253
|
}
|
|
2714
3254
|
});
|
|
2715
3255
|
this.drawn = { time: frame.time, complete: this.complete };
|
|
2716
|
-
_optionalChain([this, 'access',
|
|
2717
|
-
_optionalChain([this, 'access',
|
|
3256
|
+
_optionalChain([this, 'access', _90 => _90.options, 'access', _91 => _91.onFrame, 'optionalCall', _92 => _92(frame)]);
|
|
3257
|
+
_optionalChain([this, 'access', _93 => _93.pixelsTarget, 'optionalAccess', _94 => _94.drawn, 'call', _95 => _95(frame.time, this.complete)]);
|
|
2718
3258
|
}
|
|
2719
3259
|
/** A grade's LUT once loaded; while it loads (or if it fails) the frame is drawn without it and counts as incomplete. */
|
|
2720
|
-
|
|
2721
|
-
const lut = _optionalChain([this, 'access',
|
|
3260
|
+
__init41() {this.lutFor = (src) => {
|
|
3261
|
+
const lut = _optionalChain([this, 'access', _96 => _96.lutCache, 'optionalAccess', _97 => _97.get, 'call', _98 => _98(src)]);
|
|
2722
3262
|
if (!lut) this.complete = false;
|
|
2723
3263
|
return lut;
|
|
2724
3264
|
}}
|
|
2725
|
-
|
|
3265
|
+
__init42() {this.scratch = (width, height, slot) => {
|
|
2726
3266
|
if (!this.scratchCanvases.has(slot)) {
|
|
2727
3267
|
let made = null;
|
|
2728
3268
|
if (this.options.createScratch) made = this.options.createScratch(width, height, slot);
|
|
@@ -2740,7 +3280,7 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2740
3280
|
if (c.height !== height) c.height = height;
|
|
2741
3281
|
return s;
|
|
2742
3282
|
}}
|
|
2743
|
-
|
|
3283
|
+
__init43() {this.defineSvgFilter = (id, markup) => {
|
|
2744
3284
|
if (this.options.defineSvgFilter === null) return;
|
|
2745
3285
|
if (this.options.defineSvgFilter) return this.options.defineSvgFilter(id, markup);
|
|
2746
3286
|
if (this.filters.has(id) || typeof document === "undefined") return;
|
|
@@ -2763,23 +3303,23 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
2763
3303
|
this.unsubscribeLuts();
|
|
2764
3304
|
this.unsubscribeFrames();
|
|
2765
3305
|
this.unsubscribeEffects();
|
|
2766
|
-
_optionalChain([this, 'access',
|
|
3306
|
+
_optionalChain([this, 'access', _99 => _99.filterHost, 'optionalAccess', _100 => _100.remove, 'call', _101 => _101()]);
|
|
2767
3307
|
this.filterHost = null;
|
|
2768
3308
|
this.filters.clear();
|
|
2769
3309
|
this.unsubscribe();
|
|
2770
3310
|
for (const entry of [...this.media.values()]) this.release(entry);
|
|
2771
3311
|
this.images.clear();
|
|
2772
3312
|
}
|
|
2773
|
-
},
|
|
3313
|
+
}, _class6);
|
|
2774
3314
|
|
|
2775
3315
|
// src/video-editor/controller.ts
|
|
2776
|
-
var VideoEditorController = (
|
|
3316
|
+
var VideoEditorController = (_class7 = class _VideoEditorController {
|
|
2777
3317
|
|
|
2778
3318
|
|
|
2779
3319
|
/** Sound levels per asset, loaded on demand for waveforms and agents. */
|
|
2780
3320
|
|
|
2781
3321
|
/** The pixels the preview last drew, for scopes and `get_video_scopes` (a `VideoPreviewEngine` fills it). */
|
|
2782
|
-
|
|
3322
|
+
__init44() {this.previewPixels = new VideoPreviewPixels()}
|
|
2783
3323
|
/** Grades' LUTs, loaded on demand for the preview, SVG frames and agents. */
|
|
2784
3324
|
|
|
2785
3325
|
/** The host's frame cache, if it gave one (the preview fills and reads it). */
|
|
@@ -2790,30 +3330,34 @@ var VideoEditorController = (_class6 = class {
|
|
|
2790
3330
|
|
|
2791
3331
|
|
|
2792
3332
|
|
|
2793
|
-
|
|
3333
|
+
__init45() {this.selection = []}
|
|
2794
3334
|
|
|
2795
3335
|
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3336
|
+
__init46() {this.error = null}
|
|
3337
|
+
__init47() {this.revision = 0}
|
|
3338
|
+
__init48() {this.applying = false}
|
|
2799
3339
|
|
|
2800
|
-
|
|
2801
|
-
|
|
3340
|
+
__init49() {this.listeners = /* @__PURE__ */ new Set()}
|
|
3341
|
+
__init50() {this.unsubscribers = []}
|
|
2802
3342
|
|
|
2803
|
-
|
|
3343
|
+
__init51() {this.plans = []}
|
|
3344
|
+
/** The project each open plan was worked out against. */
|
|
3345
|
+
__init52() {this.planBasis = /* @__PURE__ */ new Map()}
|
|
3346
|
+
__init53() {this.planCount = 0}
|
|
3347
|
+
constructor(options = {}) {;_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);
|
|
2804
3348
|
this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkFJ2MSI43cjs.HistoryManager)()));
|
|
2805
3349
|
this.audioPeaks = options.audioPeaks instanceof VideoAudioPeaksCache ? options.audioPeaks : new VideoAudioPeaksCache(options.audioPeaks);
|
|
2806
|
-
this.luts = options.luts instanceof
|
|
3350
|
+
this.luts = options.luts instanceof _chunk3OSXHLITcjs.VideoLutCache ? options.luts : new (0, _chunk3OSXHLITcjs.VideoLutCache)(options.luts);
|
|
2807
3351
|
this.frameCache = _nullishCoalesce(options.frameCache, () => ( null));
|
|
2808
3352
|
this.effects = new (0, _chunkRUZQMYXHcjs.EffectsSlot)();
|
|
2809
3353
|
if (options.effects) this.effects.set(options.effects);
|
|
2810
|
-
this.effectsEngine = new (0,
|
|
3354
|
+
this.effectsEngine = new (0, _chunk3OSXHLITcjs.VideoEffectsEngine)({ slot: this.effects });
|
|
2811
3355
|
this.project = options.project ? parseVideoProject(options.project) : createVideoProject();
|
|
2812
3356
|
this.issues = checkVideoProject(this.project);
|
|
2813
3357
|
this.readOnly = _nullishCoalesce(options.readOnly, () => ( false));
|
|
2814
3358
|
this.previewScale = checkPreviewScale(_nullishCoalesce(options.previewScale, () => ( "auto")));
|
|
2815
3359
|
this.onChange = options.onChange;
|
|
2816
|
-
this.player = new (0,
|
|
3360
|
+
this.player = new (0, _chunkGTELFJREcjs.AnimationPlayer)({ clock: options.clock, duration: () => _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, this.project), loop: "none" });
|
|
2817
3361
|
this.state = this.computeState();
|
|
2818
3362
|
this.connect();
|
|
2819
3363
|
}
|
|
@@ -2833,8 +3377,8 @@ var VideoEditorController = (_class6 = class {
|
|
|
2833
3377
|
this.effectsEngine.reset();
|
|
2834
3378
|
this.unsubscribers.splice(0).forEach((u) => u());
|
|
2835
3379
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
3380
|
+
__init54() {this.getState = () => this.state}
|
|
3381
|
+
__init55() {this.subscribe = (listener) => {
|
|
2838
3382
|
this.connect();
|
|
2839
3383
|
this.listeners.add(listener);
|
|
2840
3384
|
return () => {
|
|
@@ -2850,12 +3394,12 @@ var VideoEditorController = (_class6 = class {
|
|
|
2850
3394
|
const h = this.history.getState();
|
|
2851
3395
|
return {
|
|
2852
3396
|
project: this.project,
|
|
2853
|
-
duration:
|
|
3397
|
+
duration: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, this.project),
|
|
2854
3398
|
time: p.time,
|
|
2855
3399
|
playing: p.playing,
|
|
2856
3400
|
loop: p.loop,
|
|
2857
3401
|
selection: this.selection,
|
|
2858
|
-
current:
|
|
3402
|
+
current: _chunk3OSXHLITcjs.videoClipsAt.call(void 0, this.project, p.time).map((c) => c.clip.id),
|
|
2859
3403
|
issues: this.issues,
|
|
2860
3404
|
readOnly: this.readOnly,
|
|
2861
3405
|
error: this.error,
|
|
@@ -2865,6 +3409,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
2865
3409
|
redoLabel: h.redoName,
|
|
2866
3410
|
previewScale: this.previewScale,
|
|
2867
3411
|
effectsProvider: this.effects.provider,
|
|
3412
|
+
plans: this.plans,
|
|
2868
3413
|
revision: this.revision
|
|
2869
3414
|
};
|
|
2870
3415
|
}
|
|
@@ -2887,12 +3432,12 @@ var VideoEditorController = (_class6 = class {
|
|
|
2887
3432
|
this.issues = checkVideoProject(project);
|
|
2888
3433
|
const ids = new Set(project.tracks.flatMap((t) => t.clips.map((c) => c.id)));
|
|
2889
3434
|
if (this.selection.some((id) => !ids.has(id))) this.selection = this.selection.filter((id) => ids.has(id));
|
|
2890
|
-
this.player.setDuration(() =>
|
|
3435
|
+
this.player.setDuration(() => _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, this.project));
|
|
2891
3436
|
} finally {
|
|
2892
3437
|
this.applying = false;
|
|
2893
3438
|
}
|
|
2894
3439
|
this.notify();
|
|
2895
|
-
_optionalChain([this, 'access',
|
|
3440
|
+
_optionalChain([this, 'access', _102 => _102.onChange, 'optionalCall', _103 => _103(project)]);
|
|
2896
3441
|
}
|
|
2897
3442
|
commit(label, next, merge) {
|
|
2898
3443
|
const before = this.project;
|
|
@@ -2933,6 +3478,86 @@ var VideoEditorController = (_class6 = class {
|
|
|
2933
3478
|
if (!options.dryRun) this.commit(label, result.project, options.merge);
|
|
2934
3479
|
return result;
|
|
2935
3480
|
}
|
|
3481
|
+
// -----------------------------------------------------------------------------------------
|
|
3482
|
+
// Edit plans: propose, review, apply or discard
|
|
3483
|
+
// -----------------------------------------------------------------------------------------
|
|
3484
|
+
/** Most plans kept open at once; proposing another drops the oldest. */
|
|
3485
|
+
static __initStatic() {this.MAX_PLANS = 20}
|
|
3486
|
+
/**
|
|
3487
|
+
* Works out a batch of edits without applying it and keeps it for review (see `VideoEditPlan`):
|
|
3488
|
+
* the result says what it would do. Allowed while read-only (nothing changes); applying is not.
|
|
3489
|
+
* A plan with the id of an open one replaces it.
|
|
3490
|
+
*/
|
|
3491
|
+
proposePlan(input) {
|
|
3492
|
+
const ids = new Set(this.plans.map((p) => p.id));
|
|
3493
|
+
let id = input.id;
|
|
3494
|
+
let count = this.planCount;
|
|
3495
|
+
if (id === void 0) {
|
|
3496
|
+
do
|
|
3497
|
+
id = `plan${++count}`;
|
|
3498
|
+
while (ids.has(id));
|
|
3499
|
+
}
|
|
3500
|
+
const result = planVideoEdits(this.project, { ...input, id });
|
|
3501
|
+
if (!result.ok) return result;
|
|
3502
|
+
this.planCount = count;
|
|
3503
|
+
const kept = this.plans.filter((p) => p.id !== id);
|
|
3504
|
+
while (kept.length >= _VideoEditorController.MAX_PLANS) this.planBasis.delete(kept.shift().id);
|
|
3505
|
+
this.plans = [...kept, result.plan];
|
|
3506
|
+
this.planBasis.set(id, this.project);
|
|
3507
|
+
this.notify();
|
|
3508
|
+
return { ok: true, plan: result.plan };
|
|
3509
|
+
}
|
|
3510
|
+
/** An open plan, or `undefined`. */
|
|
3511
|
+
getPlan(id) {
|
|
3512
|
+
return this.plans.find((p) => p.id === id);
|
|
3513
|
+
}
|
|
3514
|
+
/** Whether the project changed since a plan was made (its summary may then be out of date). */
|
|
3515
|
+
isPlanStale(id) {
|
|
3516
|
+
const basis = this.planBasis.get(id);
|
|
3517
|
+
return !!basis && basis !== this.project;
|
|
3518
|
+
}
|
|
3519
|
+
/** The project as an open plan would leave it, worked out against the current project; `null` when it no longer applies. */
|
|
3520
|
+
planPreview(id) {
|
|
3521
|
+
const plan = this.getPlan(id);
|
|
3522
|
+
if (!plan) return null;
|
|
3523
|
+
const result = applyVideoEdits(this.project, plan.edits);
|
|
3524
|
+
return result.ok ? result.project : null;
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Applies an open plan as one undo step labelled with its title and closes it. If the project
|
|
3528
|
+
* changed since it was made, its edits are checked again against the project as it is now and
|
|
3529
|
+
* refused (the plan stays open) when they no longer apply.
|
|
3530
|
+
*/
|
|
3531
|
+
applyPlan(id) {
|
|
3532
|
+
const plan = this.getPlan(id);
|
|
3533
|
+
if (!plan) return this.refused(`There is no open plan "${id}"; the open plans are ${this.plans.map((p) => `"${p.id}"`).join(", ") || "none"}.`);
|
|
3534
|
+
const reason = this.readOnlyReason();
|
|
3535
|
+
if (reason) return this.refused(reason);
|
|
3536
|
+
const stale = this.isPlanStale(id);
|
|
3537
|
+
const result = applyVideoEdits(this.project, plan.edits);
|
|
3538
|
+
if (!result.ok) return this.refused(stale ? `The project changed since plan "${id}" was made and it no longer applies (${result.error}); discard it and make a new plan.` : result.error);
|
|
3539
|
+
this.dropPlan(id);
|
|
3540
|
+
this.commit(plan.title, result.project);
|
|
3541
|
+
return { ...result, plan, stale };
|
|
3542
|
+
}
|
|
3543
|
+
/** Closes an open plan without applying it. False when there is no such plan. */
|
|
3544
|
+
discardPlan(id) {
|
|
3545
|
+
if (!this.getPlan(id)) return false;
|
|
3546
|
+
this.dropPlan(id);
|
|
3547
|
+
this.notify();
|
|
3548
|
+
return true;
|
|
3549
|
+
}
|
|
3550
|
+
/** Closes every open plan. */
|
|
3551
|
+
discardPlans() {
|
|
3552
|
+
if (!this.plans.length) return;
|
|
3553
|
+
this.plans = [];
|
|
3554
|
+
this.planBasis.clear();
|
|
3555
|
+
this.notify();
|
|
3556
|
+
}
|
|
3557
|
+
dropPlan(id) {
|
|
3558
|
+
this.plans = this.plans.filter((p) => p.id !== id);
|
|
3559
|
+
this.planBasis.delete(id);
|
|
3560
|
+
}
|
|
2936
3561
|
/** Opens a project (JSON or object). `clearHistory` drops the undo stack (opening a file); otherwise it is one undo step. */
|
|
2937
3562
|
load(project, options = {}) {
|
|
2938
3563
|
let parsed;
|
|
@@ -2969,7 +3594,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
2969
3594
|
}
|
|
2970
3595
|
/** Selects clips (unknown ids are dropped). `add` toggles them into the current selection. */
|
|
2971
3596
|
select(clips, options = {}) {
|
|
2972
|
-
const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess',
|
|
3597
|
+
const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess', _104 => _104.filter, 'call', _105 => _105((id) => _chunk3OSXHLITcjs.findVideoClip.call(void 0, this.project, id))]), () => ( []));
|
|
2973
3598
|
let next;
|
|
2974
3599
|
if (options.add) {
|
|
2975
3600
|
next = [...this.selection];
|
|
@@ -2996,7 +3621,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
2996
3621
|
step(frames) {
|
|
2997
3622
|
this.player.pause();
|
|
2998
3623
|
const fps = this.project.fps;
|
|
2999
|
-
this.player.seek(Math.max(0,
|
|
3624
|
+
this.player.seek(Math.max(0, _chunk3OSXHLITcjs.snapToVideoFrame.call(void 0, this.player.currentTime, fps) + frames * 1e3 / fps));
|
|
3000
3625
|
}
|
|
3001
3626
|
setLoop(loop) {
|
|
3002
3627
|
this.player.setLoop(loop);
|
|
@@ -3013,14 +3638,14 @@ var VideoEditorController = (_class6 = class {
|
|
|
3013
3638
|
*/
|
|
3014
3639
|
snap(time, options = {}) {
|
|
3015
3640
|
const threshold = _nullishCoalesce(options.threshold, () => ( 8 * 1e3 / this.project.fps));
|
|
3016
|
-
const points =
|
|
3017
|
-
const hit =
|
|
3018
|
-
return hit.snapped !== null ? hit : { time: Math.max(0,
|
|
3641
|
+
const points = _chunk3OSXHLITcjs.videoSnapPoints.call(void 0, this.project, { exclude: options.exclude, playhead: options.playhead === false ? void 0 : this.player.currentTime });
|
|
3642
|
+
const hit = _chunk3OSXHLITcjs.snapVideoTime.call(void 0, time, points, threshold);
|
|
3643
|
+
return hit.snapped !== null ? hit : { time: Math.max(0, _chunk3OSXHLITcjs.snapToVideoFrame.call(void 0, time, this.project.fps)), snapped: null };
|
|
3019
3644
|
}
|
|
3020
3645
|
/** Splits the selected clips (or every clip) under the playhead there, as one undo step. */
|
|
3021
3646
|
splitAtPlayhead() {
|
|
3022
|
-
const t =
|
|
3023
|
-
const under =
|
|
3647
|
+
const t = _chunk3OSXHLITcjs.snapToVideoFrame.call(void 0, this.player.currentTime, this.project.fps);
|
|
3648
|
+
const under = _chunk3OSXHLITcjs.videoClipsAt.call(void 0, this.project, t).filter(({ clip }) => t > clip.start && t < _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip));
|
|
3024
3649
|
const chosen = this.selection.length ? under.filter(({ clip }) => this.selection.includes(clip.id)) : under;
|
|
3025
3650
|
if (!chosen.length) return this.refuse("No clip is under the playhead to split.");
|
|
3026
3651
|
return this.apply(chosen.map(({ clip }) => ({ op: "split_clip", clip: clip.id, at: t })), "Split");
|
|
@@ -3048,7 +3673,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3048
3673
|
/** The provider's description of an effect, checked for `place` (rejects without a provider or for an unknown id). */
|
|
3049
3674
|
async describeEffect(effect, place) {
|
|
3050
3675
|
const d = await this.effects.require().describe(effect);
|
|
3051
|
-
if (place)
|
|
3676
|
+
if (place) _chunk3OSXHLITcjs.checkVideoEffectPlace.call(void 0, d, place);
|
|
3052
3677
|
return d;
|
|
3053
3678
|
}
|
|
3054
3679
|
refused(message) {
|
|
@@ -3069,7 +3694,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3069
3694
|
return { ...result, clip: plan.clip, ...plan.effect ? { effect: plan.effect() } : {} };
|
|
3070
3695
|
}
|
|
3071
3696
|
clipOrThrow(id) {
|
|
3072
|
-
const at =
|
|
3697
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, this.project, id);
|
|
3073
3698
|
if (!at) throw new Error(`There is no clip "${id}"; list_applied_effects or get_video_project lists the clips.`);
|
|
3074
3699
|
return at.clip;
|
|
3075
3700
|
}
|
|
@@ -3082,7 +3707,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3082
3707
|
return this.effectEdit(async () => {
|
|
3083
3708
|
const current = this.clipOrThrow(clip);
|
|
3084
3709
|
const d = await this.describeEffect(effect, "clip");
|
|
3085
|
-
const values =
|
|
3710
|
+
const values = _chunk3OSXHLITcjs.checkVideoEffectValues.call(void 0, d, options.values, { keyframes: true });
|
|
3086
3711
|
const stack = [..._nullishCoalesce(current.effects, () => ( []))];
|
|
3087
3712
|
const taken = new Set(stack.map((e) => e.id));
|
|
3088
3713
|
let n = 1;
|
|
@@ -3118,7 +3743,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3118
3743
|
if (!(k in d.controls.properties)) throw new Error(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
|
|
3119
3744
|
base[k] = d.defaults[k];
|
|
3120
3745
|
}
|
|
3121
|
-
next.values =
|
|
3746
|
+
next.values = _chunk3OSXHLITcjs.checkVideoEffectValues.call(void 0, d, given, { base, keyframes: true });
|
|
3122
3747
|
}
|
|
3123
3748
|
if (changes.enabled !== void 0) {
|
|
3124
3749
|
if (changes.enabled) delete next.enabled;
|
|
@@ -3136,7 +3761,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3136
3761
|
removeClipEffect(clip, instance) {
|
|
3137
3762
|
const reason = this.readOnlyReason();
|
|
3138
3763
|
if (reason) return this.refused(reason);
|
|
3139
|
-
const at =
|
|
3764
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, this.project, clip);
|
|
3140
3765
|
if (!at) return this.refused(`There is no clip "${clip}".`);
|
|
3141
3766
|
const stack = _nullishCoalesce(at.clip.effects, () => ( []));
|
|
3142
3767
|
const was = stack.find((e) => e.id === instance);
|
|
@@ -3153,7 +3778,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3153
3778
|
return this.effectEdit(async () => {
|
|
3154
3779
|
this.clipOrThrow(clip);
|
|
3155
3780
|
const d = await this.describeEffect(effect, "transition");
|
|
3156
|
-
const values =
|
|
3781
|
+
const values = _chunk3OSXHLITcjs.checkVideoEffectValues.call(void 0, d, options.values);
|
|
3157
3782
|
const duration = _nullishCoalesce(_nullishCoalesce(options.duration, () => ( d.duration)), () => ( 1e3));
|
|
3158
3783
|
return { edit: { op: "set_transition", clip, transition: { type: "effect", duration, effect: { effect: d.id, provider: this.effects.require().id, name: d.name, values } } }, label: `Transition ${d.name}`, clip };
|
|
3159
3784
|
});
|
|
@@ -3166,7 +3791,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3166
3791
|
addEffectClip(track, effect, options = {}) {
|
|
3167
3792
|
return this.effectEdit(async () => {
|
|
3168
3793
|
const d = await this.describeEffect(effect, "generator");
|
|
3169
|
-
const values =
|
|
3794
|
+
const values = _chunk3OSXHLITcjs.checkVideoEffectValues.call(void 0, d, options.values, { keyframes: true });
|
|
3170
3795
|
const duration = _nullishCoalesce(_nullishCoalesce(options.duration, () => ( d.duration)), () => ( 5e3));
|
|
3171
3796
|
const label = `Add ${d.name}`;
|
|
3172
3797
|
const clipId = _nullishCoalesce(options.id, () => ( ""));
|
|
@@ -3175,7 +3800,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3175
3800
|
label,
|
|
3176
3801
|
clip: clipId
|
|
3177
3802
|
};
|
|
3178
|
-
}).then((r) => r.ok && !r.clip ? { ...r, clip: _nullishCoalesce(_optionalChain([r, 'access',
|
|
3803
|
+
}).then((r) => r.ok && !r.clip ? { ...r, clip: _nullishCoalesce(_optionalChain([r, 'access', _106 => _106.outcomes, 'access', _107 => _107[0], 'optionalAccess', _108 => _108.clip]), () => ( "")) } : r);
|
|
3179
3804
|
}
|
|
3180
3805
|
/**
|
|
3181
3806
|
* Changes the values of an effect clip's picture (`target` its id) or of the effect transition
|
|
@@ -3188,44 +3813,44 @@ var VideoEditorController = (_class6 = class {
|
|
|
3188
3813
|
if (part === "generator") {
|
|
3189
3814
|
if (current.type !== "effect") throw new Error(`Clip "${clip}" is not an effect clip.`);
|
|
3190
3815
|
const d2 = await this.describeEffect(current.effect);
|
|
3191
|
-
return { edit: { op: "update_clip", clip, set: { values:
|
|
3816
|
+
return { edit: { op: "update_clip", clip, set: { values: _chunk3OSXHLITcjs.checkVideoEffectValues.call(void 0, d2, values, { base: _nullishCoalesce(current.values, () => ( {})), keyframes: true }) } }, label: `Change ${_nullishCoalesce(current.name, () => ( d2.name))}`, clip };
|
|
3192
3817
|
}
|
|
3193
3818
|
const t = current.transition;
|
|
3194
|
-
if (_optionalChain([t, 'optionalAccess',
|
|
3819
|
+
if (_optionalChain([t, 'optionalAccess', _109 => _109.type]) !== "effect" || !t.effect) throw new Error(`Clip "${clip}" has no effect transition.`);
|
|
3195
3820
|
const d = await this.describeEffect(t.effect.effect);
|
|
3196
|
-
const merged =
|
|
3821
|
+
const merged = _chunk3OSXHLITcjs.checkVideoEffectValues.call(void 0, d, values, { base: _nullishCoalesce(t.effect.values, () => ( {})) });
|
|
3197
3822
|
return { edit: { op: "set_transition", clip, transition: { ...t, effect: { ...t.effect, values: merged } } }, label: `Change transition ${d.name}`, clip };
|
|
3198
3823
|
}, options.merge);
|
|
3199
3824
|
}
|
|
3200
3825
|
/** Keys for one numeric control of a clip effect (clip-relative ms), or `null` to stop animating it (keeping its value at the clip's start). */
|
|
3201
3826
|
setClipEffectKeyframes(clip, instance, control, keys) {
|
|
3202
|
-
const current = _optionalChain([
|
|
3203
|
-
const was = _optionalChain([current, 'optionalAccess',
|
|
3204
|
-
const value = _optionalChain([was, 'optionalAccess',
|
|
3205
|
-
const plain = keys === null ?
|
|
3827
|
+
const current = _optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, this.project, clip), 'optionalAccess', _110 => _110.clip]);
|
|
3828
|
+
const was = _optionalChain([current, 'optionalAccess', _111 => _111.effects, 'optionalAccess', _112 => _112.find, 'call', _113 => _113((e) => e.id === instance)]);
|
|
3829
|
+
const value = _optionalChain([was, 'optionalAccess', _114 => _114.values, 'optionalAccess', _115 => _115[control]]);
|
|
3830
|
+
const plain = keys === null ? _chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, value) ? _optionalChain([value, 'access', _116 => _116.keys, 'access', _117 => _117[0], 'optionalAccess', _118 => _118.v]) : value : { keys: [...keys].sort((a, b) => a.t - b.t) };
|
|
3206
3831
|
return this.updateClipEffect(clip, instance, { values: { [control]: _nullishCoalesce(plain, () => ( null)) } });
|
|
3207
3832
|
}
|
|
3208
3833
|
/** What plays at `time` (default: the playhead). */
|
|
3209
3834
|
frame(time = this.player.currentTime) {
|
|
3210
|
-
return
|
|
3835
|
+
return _chunk3OSXHLITcjs.videoFrameAt.call(void 0, this.project, time);
|
|
3211
3836
|
}
|
|
3212
3837
|
/** The frame at `time` (default: the playhead) as SVG. */
|
|
3213
3838
|
toSvg(time = this.player.currentTime, options = {}) {
|
|
3214
|
-
return
|
|
3839
|
+
return _chunk3OSXHLITcjs.videoProjectSvg.call(void 0, this.project, time, { lutFor: (src) => this.luts.peek(src), ...options });
|
|
3215
3840
|
}
|
|
3216
3841
|
/** The project as an ffmpeg command. */
|
|
3217
3842
|
toFfmpeg(options = {}) {
|
|
3218
|
-
return
|
|
3843
|
+
return _chunk3OSXHLITcjs.toVideoFfmpeg.call(void 0, this.project, options);
|
|
3219
3844
|
}
|
|
3220
3845
|
/** The ffmpeg command that measures the mix's loudness (see `toVideoLoudnessMeasure`). */
|
|
3221
3846
|
toLoudnessMeasure(options = {}) {
|
|
3222
|
-
return
|
|
3847
|
+
return _chunk3OSXHLITcjs.toVideoLoudnessMeasure.call(void 0, this.project, options);
|
|
3223
3848
|
}
|
|
3224
3849
|
/** One track as a CMX 3600 EDL. */
|
|
3225
3850
|
toEdl(options = {}) {
|
|
3226
|
-
return
|
|
3851
|
+
return _chunk3OSXHLITcjs.toVideoEdl.call(void 0, this.project, options);
|
|
3227
3852
|
}
|
|
3228
|
-
},
|
|
3853
|
+
}, _class7.__initStatic(), _class7);
|
|
3229
3854
|
function checkPreviewScale(scale) {
|
|
3230
3855
|
if (scale === "auto" || scale === "full") return scale;
|
|
3231
3856
|
if (typeof scale !== "number" || !(scale > 0 && scale <= 1)) throw new Error(`A preview scale is "auto", "full" or a fraction above 0 up to 1 (0.5 is half size), not ${JSON.stringify(scale)}.`);
|
|
@@ -3257,7 +3882,7 @@ function planVideoToolPlayback(tool, args) {
|
|
|
3257
3882
|
const clips = clipsOf(args);
|
|
3258
3883
|
const track = str2(args.track) ? args.track : void 0;
|
|
3259
3884
|
const newClip = args.clip && typeof args.clip === "object" ? args.clip : null;
|
|
3260
|
-
const time = [args.start, args.at, args.time, args.to, _optionalChain([newClip, 'optionalAccess',
|
|
3885
|
+
const time = [args.start, args.at, args.time, args.to, _optionalChain([newClip, 'optionalAccess', _119 => _119.start])].find(num2);
|
|
3261
3886
|
const click = (ref) => [{ type: "target", ref }, { type: "press" }, { type: "release" }];
|
|
3262
3887
|
if (clips.length === 1 && (time !== void 0 || track) && tool !== "split_video_clip") {
|
|
3263
3888
|
const clip = clips[0];
|
|
@@ -3291,12 +3916,12 @@ function videoRulerTicks(duration, pxPerSecond, fps) {
|
|
|
3291
3916
|
const ticks = [];
|
|
3292
3917
|
for (let t = 0; t <= duration + 1e-6; t += step) {
|
|
3293
3918
|
const isMajor = Math.abs(t / major - Math.round(t / major)) < 1e-9;
|
|
3294
|
-
ticks.push({ time: t, x: t * pxPerMs, major: isMajor, ...isMajor ? { label:
|
|
3919
|
+
ticks.push({ time: t, x: t * pxPerMs, major: isMajor, ...isMajor ? { label: _chunk3OSXHLITcjs.formatVideoTimecode.call(void 0, t, fps).replace(/^00:/, "") } : {} });
|
|
3295
3920
|
}
|
|
3296
3921
|
return ticks;
|
|
3297
3922
|
}
|
|
3298
3923
|
function videoClipShowsWaveform(clip, asset, mode = "audio") {
|
|
3299
|
-
if (clip.type !== "media" || mode === "none" || !
|
|
3924
|
+
if (clip.type !== "media" || mode === "none" || !_chunk3OSXHLITcjs.videoAssetHasSound.call(void 0, asset)) return false;
|
|
3300
3925
|
return mode === "all" || asset.kind === "audio";
|
|
3301
3926
|
}
|
|
3302
3927
|
var VIDEO_CLIP_ANIMATION_LABELS = {
|
|
@@ -3345,6 +3970,7 @@ function videoClipTint(clip, asset) {
|
|
|
3345
3970
|
if (clip.type === "sequence") return "#0d9488";
|
|
3346
3971
|
if (clip.type === "color") return clip.color;
|
|
3347
3972
|
if (clip.type === "effect") return "#db2777";
|
|
3973
|
+
if (clip.type === "adjustment") return "#64748b";
|
|
3348
3974
|
if (!asset) return "#ef4444";
|
|
3349
3975
|
return asset.kind === "audio" ? "#10b981" : asset.kind === "image" ? "#f59e0b" : "#3b82f6";
|
|
3350
3976
|
}
|
|
@@ -3352,14 +3978,15 @@ function videoClipLabel(clip, asset, sequence) {
|
|
|
3352
3978
|
if (clip.name) return clip.name;
|
|
3353
3979
|
if (clip.type === "text") return clip.text.split("\n")[0];
|
|
3354
3980
|
if (clip.type === "color") return clip.color;
|
|
3355
|
-
if (clip.type === "sequence") return `Compound: ${_nullishCoalesce(_optionalChain([sequence, 'optionalAccess',
|
|
3981
|
+
if (clip.type === "sequence") return `Compound: ${_nullishCoalesce(_optionalChain([sequence, 'optionalAccess', _120 => _120.name]), () => ( clip.sequence))}`;
|
|
3356
3982
|
if (clip.type === "effect") return `Effect: ${clip.effect}`;
|
|
3357
|
-
|
|
3358
|
-
const
|
|
3983
|
+
if (clip.type === "adjustment") return `Adjustment${clip.scope === "all" ? " (all tracks)" : clip.scope === "track" ? ` (${clip.targetTrack})` : ""}`;
|
|
3984
|
+
const base = _nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _121 => _121.name]), () => ( _optionalChain([asset, 'optionalAccess', _122 => _122.src, 'access', _123 => _123.replace, 'call', _124 => _124(/^.*[\\/]/, "")]))), () => ( clip.asset));
|
|
3985
|
+
const speed = _chunk3OSXHLITcjs.videoClipSpeed.call(void 0, clip);
|
|
3359
3986
|
return speed !== 1 ? `${base} (${Math.round(speed * 100)}%)` : base;
|
|
3360
3987
|
}
|
|
3361
3988
|
function planVideoClipDrag(project, input) {
|
|
3362
|
-
const at =
|
|
3989
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project, input.clip);
|
|
3363
3990
|
if (!at) return null;
|
|
3364
3991
|
const { clip, track } = at;
|
|
3365
3992
|
const snap = _nullishCoalesce(input.snap, () => ( ((t) => t)));
|
|
@@ -3376,17 +4003,17 @@ function planVideoClipDrag(project, input) {
|
|
|
3376
4003
|
}
|
|
3377
4004
|
const minimum = 1e3 / project.fps;
|
|
3378
4005
|
if (input.mode === "trim-start") {
|
|
3379
|
-
const to2 = Math.min(snap(Math.max(0, clip.start + input.delta)),
|
|
4006
|
+
const to2 = Math.min(snap(Math.max(0, clip.start + input.delta)), _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip) - minimum);
|
|
3380
4007
|
const changed2 = Math.abs(to2 - clip.start) > 1e-6;
|
|
3381
|
-
const plan = input.ripple ? { start: clip.start, duration: clip.duration - (to2 - clip.start) } : { start: to2, duration:
|
|
4008
|
+
const plan = input.ripple ? { start: clip.start, duration: clip.duration - (to2 - clip.start) } : { start: to2, duration: _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip) - to2 };
|
|
3382
4009
|
return { ...plan, track: track.id, edit: changed2 ? { op: "trim_clip", clip: clip.id, edge: "start", to: to2, ...input.ripple ? { ripple: true } : {} } : null };
|
|
3383
4010
|
}
|
|
3384
|
-
const to = Math.max(snap(
|
|
3385
|
-
const changed = Math.abs(to -
|
|
4011
|
+
const to = Math.max(snap(_chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip) + input.delta), clip.start + minimum);
|
|
4012
|
+
const changed = Math.abs(to - _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip)) > 1e-6;
|
|
3386
4013
|
return { start: clip.start, duration: to - clip.start, track: track.id, edit: changed ? { op: "trim_clip", clip: clip.id, edge: "end", to, ...input.ripple ? { ripple: true } : {} } : null };
|
|
3387
4014
|
}
|
|
3388
4015
|
function videoDropTracks(project, clipId) {
|
|
3389
|
-
const at =
|
|
4016
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project, clipId);
|
|
3390
4017
|
if (!at) return [];
|
|
3391
4018
|
return project.tracks.filter((t) => t.kind === at.track.kind && !t.locked).map((t) => t.id);
|
|
3392
4019
|
}
|
|
@@ -3395,10 +4022,10 @@ function videoDragLabel(edit) {
|
|
|
3395
4022
|
return edit.op === "trim_clip" && edit.ripple ? "Ripple trim" : "Trim clip";
|
|
3396
4023
|
}
|
|
3397
4024
|
function videoAssetDropEdit(project, assetId, options = {}) {
|
|
3398
|
-
const asset =
|
|
4025
|
+
const asset = _chunk3OSXHLITcjs.videoAssetMap.call(void 0, project).get(assetId);
|
|
3399
4026
|
if (!asset) return null;
|
|
3400
4027
|
const kind = asset.kind === "audio" ? "audio" : "video";
|
|
3401
|
-
const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access',
|
|
4028
|
+
const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access', _125 => _125.tracks, 'access', _126 => _126.find, 'call', _127 => _127((t) => t.kind === kind && !t.locked), 'optionalAccess', _128 => _128.id])));
|
|
3402
4029
|
if (!track) return null;
|
|
3403
4030
|
return { op: "add_clip", track, clip: { type: "media", asset: asset.id, ...options.start !== void 0 ? { start: options.start } : {} }, ...options.ripple ? { ripple: true } : {}, ...options.mode && options.mode !== "refuse" ? { mode: options.mode } : {} };
|
|
3404
4031
|
}
|
|
@@ -3447,25 +4074,25 @@ var KEY_LABELS = {
|
|
|
3447
4074
|
scale: { label: "Scale", step: 0.05 },
|
|
3448
4075
|
rotation: { label: "Rotation", step: 1 }
|
|
3449
4076
|
};
|
|
3450
|
-
var animatableOf = (clip, property) =>
|
|
4077
|
+
var animatableOf = (clip, property) => _chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([clip, 'access', _129 => _129.transform, 'optionalAccess', _130 => _130[property]]) : property === "volume" ? clip.type === "media" || clip.type === "sequence" ? clip.volume : void 0 : clip.opacity;
|
|
3451
4078
|
var playheadInClip = (clip, time) => Math.round(Math.min(clip.duration, Math.max(0, time - clip.start)));
|
|
3452
4079
|
function videoKeyframeRows(project, clipId, time) {
|
|
3453
|
-
const at =
|
|
4080
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project, clipId);
|
|
3454
4081
|
if (!at) return [];
|
|
3455
4082
|
const { clip } = at;
|
|
3456
|
-
const asset = clip.type === "media" ?
|
|
3457
|
-
const visual = !(_optionalChain([asset, 'optionalAccess',
|
|
3458
|
-
const audible = clip.type === "media" &&
|
|
3459
|
-
const properties = [...visual ? ["opacity"] : [], ...audible ? ["volume"] : [], ...visual ?
|
|
4083
|
+
const asset = clip.type === "media" ? _chunk3OSXHLITcjs.videoAssetMap.call(void 0, project).get(clip.asset) : void 0;
|
|
4084
|
+
const visual = !(_optionalChain([asset, 'optionalAccess', _131 => _131.kind]) === "audio" || at.track.kind === "audio");
|
|
4085
|
+
const audible = clip.type === "media" && _chunk3OSXHLITcjs.videoAssetHasSound.call(void 0, asset) || clip.type === "sequence";
|
|
4086
|
+
const properties = [...visual ? ["opacity"] : [], ...audible ? ["volume"] : [], ...visual ? _chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES : []];
|
|
3460
4087
|
const clipTime = playheadInClip(clip, time);
|
|
3461
4088
|
return properties.map((property) => {
|
|
3462
4089
|
const prop = animatableOf(clip, property);
|
|
3463
|
-
const keys =
|
|
3464
|
-
const limits =
|
|
4090
|
+
const keys = _chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, prop) ? prop.keys : [];
|
|
4091
|
+
const limits = _chunk3OSXHLITcjs.VIDEO_KEYFRAME_LIMITS[property];
|
|
3465
4092
|
return {
|
|
3466
4093
|
property,
|
|
3467
4094
|
...KEY_LABELS[property],
|
|
3468
|
-
value: Math.round(
|
|
4095
|
+
value: Math.round(_chunk3OSXHLITcjs.videoValueAt.call(void 0, prop, clipTime, limits.default) * 1e3) / 1e3,
|
|
3469
4096
|
keyed: keys.length > 0,
|
|
3470
4097
|
keys: keys.length,
|
|
3471
4098
|
onKey: keys.some((k) => Math.abs(k.t - clipTime) < 0.5),
|
|
@@ -3476,48 +4103,92 @@ function videoKeyframeRows(project, clipId, time) {
|
|
|
3476
4103
|
});
|
|
3477
4104
|
}
|
|
3478
4105
|
function plainValueEdit(clip, property, value) {
|
|
3479
|
-
if (!
|
|
4106
|
+
if (!_chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES.includes(property))
|
|
3480
4107
|
return value === void 0 ? { op: "update_clip", clip: clip.id, remove: [property] } : { op: "update_clip", clip: clip.id, set: { [property]: value } };
|
|
3481
4108
|
const transform = { ...clip.transform, [property]: value };
|
|
3482
4109
|
if (value === void 0) delete transform[property];
|
|
3483
4110
|
return Object.keys(transform).length ? { op: "update_clip", clip: clip.id, set: { transform } } : { op: "update_clip", clip: clip.id, remove: ["transform"] };
|
|
3484
4111
|
}
|
|
3485
4112
|
function videoKeyframeValueEdit(project, clipId, property, time, value) {
|
|
3486
|
-
const at =
|
|
4113
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project, clipId);
|
|
3487
4114
|
if (!at) return null;
|
|
3488
4115
|
const prop = animatableOf(at.clip, property);
|
|
3489
|
-
if (!
|
|
4116
|
+
if (!_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, prop)) return plainValueEdit(at.clip, property, value);
|
|
3490
4117
|
if (value === void 0) return null;
|
|
3491
|
-
return { op: "set_keyframes", clip: clipId, property, keys:
|
|
4118
|
+
return { op: "set_keyframes", clip: clipId, property, keys: _chunk3OSXHLITcjs.setVideoKey.call(void 0, prop, { t: playheadInClip(at.clip, time), v: value }, _chunk3OSXHLITcjs.VIDEO_KEYFRAME_LIMITS[property].default).keys };
|
|
3492
4119
|
}
|
|
3493
4120
|
function videoKeyframeToggleEdit(project, clipId, property, time) {
|
|
3494
|
-
const at =
|
|
4121
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project, clipId);
|
|
3495
4122
|
if (!at) return null;
|
|
3496
4123
|
const prop = animatableOf(at.clip, property);
|
|
3497
4124
|
const t = playheadInClip(at.clip, time);
|
|
3498
|
-
const fallback =
|
|
3499
|
-
if (
|
|
4125
|
+
const fallback = _chunk3OSXHLITcjs.VIDEO_KEYFRAME_LIMITS[property].default;
|
|
4126
|
+
if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, prop)) {
|
|
3500
4127
|
const key = prop.keys.find((k) => Math.abs(k.t - t) < 0.5);
|
|
3501
4128
|
if (key) {
|
|
3502
|
-
const rest =
|
|
3503
|
-
return
|
|
4129
|
+
const rest = _chunk3OSXHLITcjs.removeVideoKey.call(void 0, prop, key.t);
|
|
4130
|
+
return _chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, rest) ? { op: "set_keyframes", clip: clipId, property, keys: rest.keys } : plainValueEdit(at.clip, property, rest === fallback ? void 0 : rest);
|
|
3504
4131
|
}
|
|
3505
4132
|
}
|
|
3506
|
-
const v =
|
|
3507
|
-
return { op: "set_keyframes", clip: clipId, property, keys:
|
|
4133
|
+
const v = _chunk3OSXHLITcjs.videoValueAt.call(void 0, prop, t, fallback);
|
|
4134
|
+
return { op: "set_keyframes", clip: clipId, property, keys: _chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, prop) ? _chunk3OSXHLITcjs.setVideoKey.call(void 0, prop, { t, v }, fallback).keys : [{ t, v }] };
|
|
3508
4135
|
}
|
|
3509
4136
|
function videoLutEdits(project, clips, lut) {
|
|
3510
4137
|
const edits = [];
|
|
3511
4138
|
for (const id of clips) {
|
|
3512
|
-
const at =
|
|
4139
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project, id);
|
|
3513
4140
|
if (!at) continue;
|
|
3514
4141
|
const { lut: current, ...rest } = _nullishCoalesce(at.clip.grade, () => ( {}));
|
|
3515
4142
|
if (!lut && !current) continue;
|
|
3516
|
-
const grade =
|
|
4143
|
+
const grade = _chunk3OSXHLITcjs.normalizeVideoGrade.call(void 0, lut ? { ...rest, lut } : rest);
|
|
3517
4144
|
edits.push(grade ? { op: "update_clip", clip: id, set: { grade } } : { op: "update_clip", clip: id, remove: ["grade"] });
|
|
3518
4145
|
}
|
|
3519
4146
|
return edits;
|
|
3520
4147
|
}
|
|
4148
|
+
var VIDEO_ADJUSTMENT_SCOPE_LABELS = {
|
|
4149
|
+
underneath: "Tracks underneath",
|
|
4150
|
+
all: "Whole frame",
|
|
4151
|
+
track: "One track"
|
|
4152
|
+
};
|
|
4153
|
+
var VIDEO_MASK_SHAPE_LABELS = {
|
|
4154
|
+
rectangle: "Rectangle",
|
|
4155
|
+
ellipse: "Ellipse",
|
|
4156
|
+
linear: "Split",
|
|
4157
|
+
polygon: "Polygon"
|
|
4158
|
+
};
|
|
4159
|
+
var videoClipHeading = (clip) => ({ media: "Media clip", text: "Title", sequence: "Compound clip", color: "Colour clip", effect: "Effect clip", adjustment: "Adjustment clip" })[clip.type];
|
|
4160
|
+
function videoAddAdjustmentEdits(project, time, options = {}) {
|
|
4161
|
+
const duration = _nullishCoalesce(options.duration, () => ( 3e3));
|
|
4162
|
+
const start = Math.max(0, time);
|
|
4163
|
+
const top = project.tracks[0];
|
|
4164
|
+
const free = top && top.kind === "video" && !top.locked && !top.clips.some((c) => c.start < start + duration && _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c) > start);
|
|
4165
|
+
const edits = [];
|
|
4166
|
+
let track = _optionalChain([top, 'optionalAccess', _132 => _132.id]);
|
|
4167
|
+
if (!free) {
|
|
4168
|
+
const ids = new Set(project.tracks.map((t) => t.id));
|
|
4169
|
+
let n = 1;
|
|
4170
|
+
while (ids.has(n === 1 ? "adj" : `adj${n}`)) n++;
|
|
4171
|
+
track = n === 1 ? "adj" : `adj${n}`;
|
|
4172
|
+
edits.push({ op: "add_track", track: { id: track, kind: "video", name: "Adjustments" }, at: 0 });
|
|
4173
|
+
}
|
|
4174
|
+
edits.push({ op: "add_clip", track, clip: { type: "adjustment", ...options.id ? { id: options.id } : {}, start, duration, ...options.grade ? { grade: options.grade } : {} } });
|
|
4175
|
+
return edits;
|
|
4176
|
+
}
|
|
4177
|
+
function videoMaskEdit(clip, mask) {
|
|
4178
|
+
return mask ? { op: "update_clip", clip, set: { mask } } : { op: "update_clip", clip, remove: ["mask"] };
|
|
4179
|
+
}
|
|
4180
|
+
function videoSkinProtectionEdits(project, clips, amount) {
|
|
4181
|
+
const edits = [];
|
|
4182
|
+
for (const id of clips) {
|
|
4183
|
+
const clip = _optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, project, id), 'optionalAccess', _133 => _133.clip]);
|
|
4184
|
+
const lut = _optionalChain([clip, 'optionalAccess', _134 => _134.grade, 'optionalAccess', _135 => _135.lut]);
|
|
4185
|
+
if (!clip || !lut) continue;
|
|
4186
|
+
const { preserveSkinTones: _p, ...rest } = lut;
|
|
4187
|
+
const k = Math.min(1, Math.max(0, amount));
|
|
4188
|
+
edits.push({ op: "update_clip", clip: id, set: { grade: { ...clip.grade, lut: k > 0 ? { ...rest, preserveSkinTones: k } : rest } } });
|
|
4189
|
+
}
|
|
4190
|
+
return edits;
|
|
4191
|
+
}
|
|
3521
4192
|
|
|
3522
4193
|
// src/video-editor/tools.ts
|
|
3523
4194
|
var round = (ms) => Math.round(ms * 10) / 10;
|
|
@@ -3543,7 +4214,7 @@ var RIPPLE = {
|
|
|
3543
4214
|
};
|
|
3544
4215
|
var MODE = {
|
|
3545
4216
|
type: "string",
|
|
3546
|
-
enum: [...
|
|
4217
|
+
enum: [..._chunk3OSXHLITcjs.VIDEO_PLACEMENT_MODES],
|
|
3547
4218
|
description: 'What to do with clips already where it lands: "refuse" (default) fails with the next free place; "overwrite" cuts, trims or removes what is underneath on that track; "insert" cuts everything across the start and pushes later material along by its length on this track and every sync-locked track.'
|
|
3548
4219
|
};
|
|
3549
4220
|
var DELTA = (what) => ({ type: "number", description: `${what}, in milliseconds of timeline time: positive is later, negative earlier. It is clamped to what the media and neighbours allow; the result says how far it went.` });
|
|
@@ -3560,28 +4231,29 @@ var TRANSFORM = object(
|
|
|
3560
4231
|
);
|
|
3561
4232
|
var TRANSITION = object(
|
|
3562
4233
|
{
|
|
3563
|
-
type: { type: "string", enum:
|
|
4234
|
+
type: { type: "string", enum: _chunk3OSXHLITcjs.VIDEO_TRANSITION_TYPES.filter((t) => t !== "effect"), description: "crossfade (this clip fades up over the one before), dip (down into a colour and up out of it), wipe (an edge travels across revealing this clip), push (this clip slides in and pushes the one before out), slide (this clip slides in over the one before) or iris (a circle opens from the centre). For a transition effect from the effects provider use set_video_effect_transition." },
|
|
3564
4235
|
duration: { type: "number", exclusiveMinimum: 0, description: "Length in ms, centred on the cut. Every type but dip needs half of it in media past the cut on both sides (handles); a dip needs none." },
|
|
3565
4236
|
color: COLOR("For dip: the colour passed through (default black)"),
|
|
3566
|
-
direction: { type: "string", enum: [...
|
|
3567
|
-
audioCurve: { type: "string", enum: [...
|
|
4237
|
+
direction: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_WIPE_DIRECTIONS], description: "For wipe, push and slide: the way the edge or picture travels (default right, coming in from the left)." },
|
|
4238
|
+
audioCurve: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_FADE_CURVES], description: "Shape of the sound's fades through it: default equal-power for every type but dip (steady loudness while the two sounds overlap), linear for dip; or s-curve, exponential, logarithmic." }
|
|
3568
4239
|
},
|
|
3569
4240
|
["type", "duration"],
|
|
3570
4241
|
"A transition from the clip that ends where this one starts on the same track."
|
|
3571
4242
|
);
|
|
3572
4243
|
var LUT_SRC = { type: "string", minLength: 1, description: 'A .cube 3D LUT file the host can load and ffmpeg can read: a URL or path, often from the user (e.g. "luts/teal-orange.cube").' };
|
|
3573
4244
|
var LUT_STRENGTH = { type: "number", minimum: 0, maximum: 1, description: "How much of the LUT's change to apply, 0 (none) to 1 (full, the default); 0.3-0.7 for a subtler look." };
|
|
4245
|
+
var SKIN = { type: "number", minimum: 0, maximum: 1, description: "How much of the LUT's change to hold back on skin tones, 0 (none, the default) to 1 (skin keeps its colour); 0.5-0.8 keeps faces natural under a heavy look. Applied in the preview and frame renders; ffmpeg's lut3d cannot, so the export notes it." };
|
|
3574
4246
|
var GRADE = object(
|
|
3575
4247
|
{
|
|
3576
|
-
...Object.fromEntries(
|
|
3577
|
-
lut: object({ src: LUT_SRC, strength: LUT_STRENGTH, name: { type: "string", minLength: 1, description: "A label for the look." } }, ["src"], "A 3D LUT applied after the other settings (set_video_lut checks the file first).")
|
|
4248
|
+
...Object.fromEntries(_chunk3OSXHLITcjs.VIDEO_GRADE_CONTROLS.map((c) => [c.key, { type: "number", minimum: c.min, maximum: c.max, description: c.description }])),
|
|
4249
|
+
lut: object({ src: LUT_SRC, strength: LUT_STRENGTH, name: { type: "string", minLength: 1, description: "A label for the look." }, preserveSkinTones: SKIN }, ["src"], "A 3D LUT applied after the other settings (set_video_lut checks the file first).")
|
|
3578
4250
|
},
|
|
3579
4251
|
[],
|
|
3580
4252
|
"Colour correction of the picture, applied in order: exposure, contrast, saturation, hue, white balance (temperature, tint), then a LUT. Leave a setting out for no change; the object replaces the clip's whole grade."
|
|
3581
4253
|
);
|
|
3582
4254
|
var ANIMATION = (when) => object(
|
|
3583
4255
|
{
|
|
3584
|
-
effect: { type: "string", enum: [...
|
|
4256
|
+
effect: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_CLIP_ANIMATION_EFFECTS], description: "fade; rise (up into place) or drop (down into place); slide-left / slide-right (moving that way); zoom (grows into place); pop (grows past its size and settles). Each also fades; an exit plays the movement backwards." },
|
|
3585
4257
|
duration: { type: "number", exclusiveMinimum: 0, description: "Length in ms (default 500)." }
|
|
3586
4258
|
},
|
|
3587
4259
|
["effect"],
|
|
@@ -3591,11 +4263,11 @@ var CLIP_COMMON = {
|
|
|
3591
4263
|
name: { type: "string", description: "A label for the clip." },
|
|
3592
4264
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0\u20131 (default 1). A number replaces any keyframes; set_video_keyframes animates it." },
|
|
3593
4265
|
transform: TRANSFORM,
|
|
3594
|
-
fit: { type: "string", enum: [...
|
|
4266
|
+
fit: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_FITS], description: "How the picture fits the frame before the transform, as CSS object-fit (default contain for media, fill for colours)." },
|
|
3595
4267
|
grade: GRADE,
|
|
3596
4268
|
animateIn: ANIMATION("How the picture enters, from the clip's start (on top of any fade in). Titles usually rise, pop or fade in."),
|
|
3597
4269
|
animateOut: ANIMATION("How the picture leaves, ending at the clip's end."),
|
|
3598
|
-
blend: { type: "string", enum: [...
|
|
4270
|
+
blend: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_BLEND_MODES], description: "How the picture mixes with what is beneath it, as CSS mix-blend-mode (default normal): multiply darkens (shadows, textures), screen and add lighten (light leaks, flares, glows), overlay and soft-light add contrast, difference and exclusion invert." },
|
|
3599
4271
|
fadeIn: MS("Fade up from nothing (picture and sound) over this long"),
|
|
3600
4272
|
fadeOut: MS("Fade down to nothing over this long")
|
|
3601
4273
|
};
|
|
@@ -3604,8 +4276,8 @@ var MEDIA_FIELDS = {
|
|
|
3604
4276
|
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Playback speed (default 1). Changing it keeps the stretch of source, so the clip gets shorter or longer." },
|
|
3605
4277
|
volume: { type: "number", minimum: 0, maximum: 4, description: "Gain (1 = unchanged, default 1). A number replaces any keyframes; set_video_keyframes animates it." },
|
|
3606
4278
|
muted: { type: "boolean", description: "Silence this clip (default false)." },
|
|
3607
|
-
fadeInCurve: { type: "string", enum: [...
|
|
3608
|
-
fadeOutCurve: { type: "string", enum: [...
|
|
4279
|
+
fadeInCurve: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_FADE_CURVES], description: "Shape of the sound's fade in (default linear; the picture always fades linearly): linear, equal-power (steady loudness, best for crossfades), s-curve (gentle at both ends), exponential (slow start, natural from silence) or logarithmic (fast start)." },
|
|
4280
|
+
fadeOutCurve: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_FADE_CURVES], description: "Shape of the sound's fade out (default linear), the same choices as fadeInCurve; exponential sounds natural fading to silence." }
|
|
3609
4281
|
};
|
|
3610
4282
|
var TEXT_FIELDS = {
|
|
3611
4283
|
text: { type: "string", minLength: 1, description: `The title's text ("\\n" breaks lines).` },
|
|
@@ -3654,7 +4326,7 @@ var ASSET_FIELDS = {
|
|
|
3654
4326
|
};
|
|
3655
4327
|
var KEY_PROPERTY = {
|
|
3656
4328
|
type: "string",
|
|
3657
|
-
enum: [...
|
|
4329
|
+
enum: [..._chunk3OSXHLITcjs.VIDEO_KEYFRAME_PROPERTIES],
|
|
3658
4330
|
description: "Which property: opacity (0\u20131), volume (media only, 0\u20134 gain), x / y (pixels from the frame's centre, right / down), scale (1 = fitted size) or rotation (degrees clockwise)."
|
|
3659
4331
|
};
|
|
3660
4332
|
var KEYS = {
|
|
@@ -3669,7 +4341,7 @@ var KEYS = {
|
|
|
3669
4341
|
ease: {
|
|
3670
4342
|
description: "How the value moves from this key to the next (default linear): linear, hold (stays, then jumps at the next key), ease-in, ease-out, ease-in-out, or CSS cubic-bezier points [x1, y1, x2, y2] with x1 and x2 in 0\u20131.",
|
|
3671
4343
|
anyOf: [
|
|
3672
|
-
{ type: "string", enum: [...
|
|
4344
|
+
{ type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_EASE_NAMES] },
|
|
3673
4345
|
{ type: "array", items: { type: "number" }, minItems: 4, maxItems: 4 }
|
|
3674
4346
|
]
|
|
3675
4347
|
}
|
|
@@ -3698,19 +4370,19 @@ var describeVideoTracks = (tracks) => tracks.map((t, index) => ({
|
|
|
3698
4370
|
...t.locked ? { locked: true } : {},
|
|
3699
4371
|
...t.syncLocked === false ? { syncLocked: false } : {},
|
|
3700
4372
|
...t.volume !== void 0 ? { volume: t.volume } : {},
|
|
3701
|
-
clips:
|
|
4373
|
+
clips: _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, t).map((c) => ({ ...c, end: _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c), ...c.type === "media" || c.type === "sequence" ? { source: _chunk3OSXHLITcjs.videoClipSourceRange.call(void 0, c) } : {} }))
|
|
3702
4374
|
}));
|
|
3703
|
-
var videoSequenceUsers = (project, id) =>
|
|
3704
|
-
({ sequence, track }) => track.clips.flatMap((c) => c.type === "sequence" && c.sequence === id ? [{ clip: c.id, ...sequence ? { inSequence: sequence } : {}, track: track.id, start: c.start, end:
|
|
4375
|
+
var videoSequenceUsers = (project, id) => _chunk3OSXHLITcjs.allVideoTracks.call(void 0, project).flatMap(
|
|
4376
|
+
({ sequence, track }) => track.clips.flatMap((c) => c.type === "sequence" && c.sequence === id ? [{ clip: c.id, ...sequence ? { inSequence: sequence } : {}, track: track.id, start: c.start, end: _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c), source: _chunk3OSXHLITcjs.videoClipSourceRange.call(void 0, c) }] : [])
|
|
3705
4377
|
);
|
|
3706
4378
|
function describeVideoSequence(project, sequence, tracks = true) {
|
|
3707
|
-
const cycle =
|
|
4379
|
+
const cycle = _chunk3OSXHLITcjs.findVideoSequenceCycle.call(void 0, project, sequence.id);
|
|
3708
4380
|
return {
|
|
3709
4381
|
id: sequence.id,
|
|
3710
4382
|
...sequence.name ? { name: sequence.name } : {},
|
|
3711
|
-
duration:
|
|
4383
|
+
duration: _chunk3OSXHLITcjs.videoSequenceDuration.call(void 0, sequence),
|
|
3712
4384
|
clipCount: sequence.tracks.reduce((n, t) => n + t.clips.length, 0),
|
|
3713
|
-
nests:
|
|
4385
|
+
nests: _chunk3OSXHLITcjs.videoSequenceChildren.call(void 0, sequence),
|
|
3714
4386
|
usedBy: videoSequenceUsers(project, sequence.id),
|
|
3715
4387
|
...cycle ? { cycle } : {},
|
|
3716
4388
|
...tracks ? { tracks: describeVideoTracks(sequence.tracks) } : { trackCount: sequence.tracks.length }
|
|
@@ -3722,11 +4394,11 @@ function describeVideoProject(project) {
|
|
|
3722
4394
|
width: project.width,
|
|
3723
4395
|
height: project.height,
|
|
3724
4396
|
fps: project.fps,
|
|
3725
|
-
duration:
|
|
4397
|
+
duration: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, project),
|
|
3726
4398
|
...project.background ? { background: project.background } : {},
|
|
3727
4399
|
assets: project.assets.map(videoAssetOut),
|
|
3728
4400
|
tracks: describeVideoTracks(project.tracks),
|
|
3729
|
-
..._optionalChain([project, 'access',
|
|
4401
|
+
..._optionalChain([project, 'access', _136 => _136.sequences, 'optionalAccess', _137 => _137.length]) ? { sequences: project.sequences.map((q) => describeVideoSequence(project, q, false)) } : {},
|
|
3730
4402
|
markers: _nullishCoalesce(project.markers, () => ( []))
|
|
3731
4403
|
};
|
|
3732
4404
|
}
|
|
@@ -3739,7 +4411,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3739
4411
|
const project = () => controller.getProject();
|
|
3740
4412
|
const outcome = (result, extra = {}) => {
|
|
3741
4413
|
if (!result.ok) throw new Error(result.error);
|
|
3742
|
-
return { ...extra, duration:
|
|
4414
|
+
return { ...extra, duration: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, result.project), issues: issueSummary(result.issues) };
|
|
3743
4415
|
};
|
|
3744
4416
|
const apply = (edits, label, extra) => {
|
|
3745
4417
|
const result = controller.apply(edits, label);
|
|
@@ -3748,19 +4420,19 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3748
4420
|
const clipOut = (p, id) => {
|
|
3749
4421
|
for (const t of p.tracks) {
|
|
3750
4422
|
const c = t.clips.find((x) => x.id === id);
|
|
3751
|
-
if (c) return { track: t.id, ...c, end:
|
|
4423
|
+
if (c) return { track: t.id, ...c, end: _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c), ...c.type === "media" || c.type === "sequence" ? { source: _chunk3OSXHLITcjs.videoClipSourceRange.call(void 0, c) } : {} };
|
|
3752
4424
|
}
|
|
3753
4425
|
return null;
|
|
3754
4426
|
};
|
|
3755
4427
|
const placed = (o) => ({
|
|
3756
|
-
..._optionalChain([o, 'access',
|
|
3757
|
-
..._optionalChain([o, 'access',
|
|
3758
|
-
..._optionalChain([o, 'access',
|
|
4428
|
+
..._optionalChain([o, 'access', _138 => _138.created, 'optionalAccess', _139 => _139.length]) ? { created: o.created } : {},
|
|
4429
|
+
..._optionalChain([o, 'access', _140 => _140.trimmed, 'optionalAccess', _141 => _141.length]) ? { trimmed: o.trimmed } : {},
|
|
4430
|
+
..._optionalChain([o, 'access', _142 => _142.removed, 'optionalAccess', _143 => _143.length]) ? { removed: o.removed } : {},
|
|
3759
4431
|
...o.rippled ? { rippled: o.rippled } : {}
|
|
3760
4432
|
});
|
|
3761
4433
|
const keysOut = (p, clip, property) => {
|
|
3762
4434
|
const c = clipOut(p, clip);
|
|
3763
|
-
const value =
|
|
4435
|
+
const value = _chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess', _144 => _144.transform, 'optionalAccess', _145 => _145[property]]) : _optionalChain([c, 'optionalAccess', _146 => _146[property]]);
|
|
3764
4436
|
return typeof value === "object" ? { keys: value.keys } : { keys: [], value: _nullishCoalesce(value, () => ( null)) };
|
|
3765
4437
|
};
|
|
3766
4438
|
const getProject = {
|
|
@@ -3782,8 +4454,8 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3782
4454
|
inputSchema: object({ time: MS("Time from the start (default: the playhead)") }),
|
|
3783
4455
|
annotations: { title: "What plays at a moment", ...READ },
|
|
3784
4456
|
handler: ({ time }) => {
|
|
3785
|
-
const frame =
|
|
3786
|
-
return { ...frame, timecode:
|
|
4457
|
+
const frame = _chunk3OSXHLITcjs.videoFrameAt.call(void 0, project(), _nullishCoalesce(time, () => ( controller.getState().time)));
|
|
4458
|
+
return { ...frame, timecode: _chunk3OSXHLITcjs.formatVideoTimecode.call(void 0, frame.time, project().fps) };
|
|
3787
4459
|
}
|
|
3788
4460
|
};
|
|
3789
4461
|
const listSequences = {
|
|
@@ -3811,7 +4483,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3811
4483
|
annotations: { title: "Find gaps", ...READ },
|
|
3812
4484
|
handler: ({ track }) => {
|
|
3813
4485
|
if (track !== void 0 && !project().tracks.some((t) => t.id === track)) throw new Error(`There is no track "${track}"; the tracks are ${project().tracks.map((t) => `"${t.id}"`).join(", ")}.`);
|
|
3814
|
-
return { gaps:
|
|
4486
|
+
return { gaps: _chunk3OSXHLITcjs.findVideoGaps.call(void 0, project(), track) };
|
|
3815
4487
|
}
|
|
3816
4488
|
};
|
|
3817
4489
|
const checkProject = {
|
|
@@ -3838,10 +4510,10 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3838
4510
|
annotations: { title: "Render a frame", ...READ },
|
|
3839
4511
|
handler: async ({ time, times, columns, format = "svg" }) => {
|
|
3840
4512
|
const p = project();
|
|
3841
|
-
const lutSrcs = [...new Set(p.tracks.flatMap((t) => t.clips.map((c) => _optionalChain([c, 'access',
|
|
4513
|
+
const lutSrcs = [...new Set(p.tracks.flatMap((t) => t.clips.map((c) => _optionalChain([c, 'access', _147 => _147.grade, 'optionalAccess', _148 => _148.lut, 'optionalAccess', _149 => _149.src])).filter((x) => !!x)))];
|
|
3842
4514
|
const missing = (await Promise.all(lutSrcs.map((src) => controller.luts.load(src).then(() => null, (e) => e.message)))).filter((m) => !!m);
|
|
3843
4515
|
const svgOptions = { lutFor: (src) => controller.luts.peek(src), ...columns ? { columns } : {} };
|
|
3844
|
-
const svg = times ?
|
|
4516
|
+
const svg = times ? _chunk3OSXHLITcjs.videoContactSheetSvg.call(void 0, p, times, svgOptions) : _chunk3OSXHLITcjs.videoProjectSvg.call(void 0, p, _nullishCoalesce(time, () => ( controller.getState().time)), svgOptions);
|
|
3845
4517
|
const notes = [...missing.map((m) => `${m}; that LUT is left out of the picture.`), ...lutSrcs.length > missing.length ? ["LUTs are drawn approximately in SVG (curves and a colour matrix); the preview and export apply them exactly."] : []];
|
|
3846
4518
|
if (format === "png") {
|
|
3847
4519
|
if (!options.rasterize) throw new Error('This host cannot make PNG images; call render_video_frame with format "svg".');
|
|
@@ -3931,7 +4603,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3931
4603
|
if (play === true) controller.play();
|
|
3932
4604
|
if (play === false) controller.pause();
|
|
3933
4605
|
const state = controller.getState();
|
|
3934
|
-
return { time: state.time, timecode:
|
|
4606
|
+
return { time: state.time, timecode: _chunk3OSXHLITcjs.formatVideoTimecode.call(void 0, state.time, p.fps), playing: state.playing, clips: state.current };
|
|
3935
4607
|
}
|
|
3936
4608
|
};
|
|
3937
4609
|
const setSettings = {
|
|
@@ -4053,12 +4725,12 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4053
4725
|
const updateClip = {
|
|
4054
4726
|
name: "update_video_clip",
|
|
4055
4727
|
title: "Change a clip",
|
|
4056
|
-
description: `Changes a clip's properties (one undo step): opacity, transform, fit, blend mode, colour grade, entrance and exit animations, fades, name; for media volume, mute, the sound's fade curves, speed (keeps the stretch of source, so the length changes) or asset; for compound clips in point, volume, mute and fade curves (no speed); for titles text and style; for colours the colour. Fields in "remove" go back to their defaults. Use move / trim for timing. Returns the clip.`,
|
|
4728
|
+
description: `Changes a clip's properties (one undo step): opacity, transform, fit, blend mode, colour grade, entrance and exit animations, fades, name; for media volume, mute, the sound's fade curves, speed (keeps the stretch of source, so the length changes) or asset; for compound clips in point, volume, mute and fade curves (no speed); for titles text and style; for colours the colour; for adjustment clips the scope and target track (masks: set_video_clip_mask). Fields in "remove" go back to their defaults. Use move / trim for timing. Returns the clip.`,
|
|
4057
4729
|
inputSchema: object(
|
|
4058
4730
|
{
|
|
4059
4731
|
clip: CLIP_ID,
|
|
4060
|
-
set: object({ ...CLIP_COMMON, ...MEDIA_FIELDS, asset: { ...ASSET_ID, description: "For media: play another asset (same in point)." }, ...TEXT_FIELDS, color: COLOR("Text colour, or a colour clip's fill") }, [], "Fields to replace (only those that fit the clip's type)."),
|
|
4061
|
-
remove: { type: "array", items: { type: "string", enum: ["name", "opacity", "transform", "fit", "blend", "grade", "animateIn", "animateOut", "fadeIn", "fadeOut", "fadeInCurve", "fadeOutCurve", "in", "speed", "volume", "muted", "fontSize", "fontFamily", "fontWeight", "background"] }, description:
|
|
4732
|
+
set: object({ ...CLIP_COMMON, ...MEDIA_FIELDS, asset: { ...ASSET_ID, description: "For media: play another asset (same in point)." }, ...TEXT_FIELDS, color: COLOR("Text colour, or a colour clip's fill"), scope: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_ADJUSTMENT_SCOPES], description: "For adjustment clips: which pictures it changes, underneath (default), all or track." }, targetTrack: { ...TRACK_ID, description: 'For adjustment clips with scope "track": the track whose pictures it changes.' } }, [], "Fields to replace (only those that fit the clip's type)."),
|
|
4733
|
+
remove: { type: "array", items: { type: "string", enum: ["name", "opacity", "transform", "fit", "blend", "grade", "animateIn", "animateOut", "fadeIn", "fadeOut", "fadeInCurve", "fadeOutCurve", "in", "speed", "volume", "muted", "fontSize", "fontFamily", "fontWeight", "background", "scope", "targetTrack"] }, description: 'Fields to reset to their defaults (an adjustment clip going from scope "track" to another scope must also remove targetTrack).' }
|
|
4062
4734
|
},
|
|
4063
4735
|
["clip"]
|
|
4064
4736
|
),
|
|
@@ -4076,10 +4748,10 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4076
4748
|
const gradeTargets = (ids) => {
|
|
4077
4749
|
const p = project();
|
|
4078
4750
|
for (const id of ids) {
|
|
4079
|
-
const at =
|
|
4751
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, p, id);
|
|
4080
4752
|
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
4081
4753
|
const { clip } = at;
|
|
4082
|
-
if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access',
|
|
4754
|
+
if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access', _150 => _150.assets, 'access', _151 => _151.find, 'call', _152 => _152((a) => a.id === clip.asset), 'optionalAccess', _153 => _153.kind]) === "audio")
|
|
4083
4755
|
throw new Error(`Clip "${id}" is sound only; grades change pictures. Pass clips on video tracks.`);
|
|
4084
4756
|
}
|
|
4085
4757
|
};
|
|
@@ -4090,7 +4762,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4090
4762
|
inputSchema: object(
|
|
4091
4763
|
{
|
|
4092
4764
|
clips: { type: "array", items: CLIP_ID, minItems: 1, description: "Clips on video tracks to grade." },
|
|
4093
|
-
preset: { type: "string", enum: [...
|
|
4765
|
+
preset: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_GRADE_PRESET_NAMES], description: "The look." },
|
|
4094
4766
|
amount: { type: "number", minimum: 0, maximum: 1, description: "Strength, 0 (none) to 1 (full, the default); 0.5 is a subtler version." }
|
|
4095
4767
|
},
|
|
4096
4768
|
["clips", "preset"]
|
|
@@ -4099,10 +4771,10 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4099
4771
|
handler: ({ clips, preset, amount }) => {
|
|
4100
4772
|
gradeTargets(clips);
|
|
4101
4773
|
const edits = clips.map((clip) => {
|
|
4102
|
-
const grade =
|
|
4774
|
+
const grade = _chunk3OSXHLITcjs.videoGradeLook.call(void 0, _chunk3OSXHLITcjs.findVideoClip.call(void 0, project(), clip).clip.grade, preset, _nullishCoalesce(amount, () => ( 1)));
|
|
4103
4775
|
return grade ? { op: "update_clip", clip, set: { grade } } : { op: "update_clip", clip, remove: ["grade"] };
|
|
4104
4776
|
});
|
|
4105
|
-
return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([
|
|
4777
|
+
return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, r.project, id), 'optionalAccess', _154 => _154.clip, 'access', _155 => _155.grade]), () => ( null)) })) }));
|
|
4106
4778
|
}
|
|
4107
4779
|
};
|
|
4108
4780
|
const copyGrade = {
|
|
@@ -4113,7 +4785,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4113
4785
|
annotations: { title: "Copy a colour grade", ...WRITE_IDEMPOTENT },
|
|
4114
4786
|
handler: ({ from, to }) => {
|
|
4115
4787
|
gradeTargets([from, ...to]);
|
|
4116
|
-
const grade =
|
|
4788
|
+
const grade = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project(), from).clip.grade;
|
|
4117
4789
|
const edits = to.map((clip) => grade ? { op: "update_clip", clip, set: { grade: { ...grade } } } : { op: "update_clip", clip, remove: ["grade"] });
|
|
4118
4790
|
return apply(edits, "Copy grade", () => ({ grade: _nullishCoalesce(grade, () => ( null)), clips: to }));
|
|
4119
4791
|
}
|
|
@@ -4127,18 +4799,19 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4127
4799
|
clips: { type: "array", items: CLIP_ID, minItems: 1, uniqueItems: true, description: "Clips on video tracks to grade." },
|
|
4128
4800
|
src: LUT_SRC,
|
|
4129
4801
|
strength: LUT_STRENGTH,
|
|
4130
|
-
name: { type: "string", minLength: 1, description: "A label for the look (default the file's TITLE)." }
|
|
4802
|
+
name: { type: "string", minLength: 1, description: "A label for the look (default the file's TITLE)." },
|
|
4803
|
+
preserve_skin_tones: SKIN
|
|
4131
4804
|
},
|
|
4132
4805
|
["clips", "src"]
|
|
4133
4806
|
),
|
|
4134
4807
|
annotations: { title: "Apply a LUT", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
4135
|
-
handler: async ({ clips, src, strength, name }) => {
|
|
4808
|
+
handler: async ({ clips, src, strength, name, preserve_skin_tones }) => {
|
|
4136
4809
|
if (strength === 0) throw new Error("A strength of 0 changes nothing; pass a strength above 0, or remove the LUT with remove_video_lut.");
|
|
4137
4810
|
gradeTargets(clips);
|
|
4138
4811
|
const lut = await controller.luts.load(src);
|
|
4139
4812
|
const label = _nullishCoalesce(name, () => ( lut.title));
|
|
4140
|
-
const setting = { src, ...strength !== void 0 && strength !== 1 ? { strength } : {}, ...label ? { name: label } : {} };
|
|
4141
|
-
return apply(videoLutEdits(project(), clips, setting), "Apply LUT", (r) => ({ lut: { src, ...lut.title ? { title: lut.title } : {}, size: lut.size }, clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([
|
|
4813
|
+
const setting = { src, ...strength !== void 0 && strength !== 1 ? { strength } : {}, ...label ? { name: label } : {}, ...preserve_skin_tones ? { preserveSkinTones: preserve_skin_tones } : {} };
|
|
4814
|
+
return apply(videoLutEdits(project(), clips, setting), "Apply LUT", (r) => ({ lut: { src, ...lut.title ? { title: lut.title } : {}, size: lut.size }, clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, r.project, id), 'optionalAccess', _156 => _156.clip, 'access', _157 => _157.grade]), () => ( null)) })) }));
|
|
4142
4815
|
}
|
|
4143
4816
|
};
|
|
4144
4817
|
const removeLut = {
|
|
@@ -4150,9 +4823,9 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4150
4823
|
handler: ({ clips }) => {
|
|
4151
4824
|
gradeTargets(clips);
|
|
4152
4825
|
const p = project();
|
|
4153
|
-
const withLut = clips.filter((id) => _optionalChain([
|
|
4826
|
+
const withLut = clips.filter((id) => _optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, p, id), 'access', _158 => _158.clip, 'access', _159 => _159.grade, 'optionalAccess', _160 => _160.lut]));
|
|
4154
4827
|
if (!withLut.length) throw new Error(`None of ${clips.map((c) => `"${c}"`).join(", ")} has a LUT; get_video_project shows each clip's grade.`);
|
|
4155
|
-
return apply(videoLutEdits(p, withLut, null), "Remove LUT", (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([
|
|
4828
|
+
return apply(videoLutEdits(p, withLut, null), "Remove LUT", (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, r.project, id), 'optionalAccess', _161 => _161.clip, 'access', _162 => _162.grade]), () => ( null)) })) }));
|
|
4156
4829
|
}
|
|
4157
4830
|
};
|
|
4158
4831
|
const PROXY_SCALE = { type: "number", exclusiveMinimum: 0, exclusiveMaximum: 1, description: "Proxy size as a fraction of the original: 0.5 (half width and height, the default) or 0.25 for very large footage." };
|
|
@@ -4210,7 +4883,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4210
4883
|
controller.setPreviewScale(scale);
|
|
4211
4884
|
const s = controller.getState().previewScale;
|
|
4212
4885
|
const fixed = s === "full" ? 1 : typeof s === "number" ? s : null;
|
|
4213
|
-
const plays = fixed === null ? void 0 : project().assets.filter((a) => a.kind === "video").map((a) => ({ asset: a.id, src: _nullishCoalesce(_optionalChain([videoAssetProxy, 'call',
|
|
4886
|
+
const plays = fixed === null ? void 0 : project().assets.filter((a) => a.kind === "video").map((a) => ({ asset: a.id, src: _nullishCoalesce(_optionalChain([videoAssetProxy, 'call', _163 => _163(a, fixed), 'optionalAccess', _164 => _164.src]), () => ( a.src)), proxy: !!videoAssetProxy(a, fixed) }));
|
|
4214
4887
|
return { previewScale: s, ...plays ? { plays } : { note: "auto picks the smallest proxy at least as large as the preview canvas shows, so it depends on the preview's size." } };
|
|
4215
4888
|
}
|
|
4216
4889
|
};
|
|
@@ -4261,7 +4934,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4261
4934
|
approximate: parsed.approximate,
|
|
4262
4935
|
first: list[0],
|
|
4263
4936
|
last: list[list.length - 1],
|
|
4264
|
-
...a.duration !== void 0 && list[list.length - 1].end > a.duration +
|
|
4937
|
+
...a.duration !== void 0 && list[list.length - 1].end > a.duration + _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON ? { warning: `The transcript runs to ${list[list.length - 1].end} ms, past the asset's ${a.duration} ms: is it for this asset?` } : {}
|
|
4265
4938
|
}));
|
|
4266
4939
|
}
|
|
4267
4940
|
};
|
|
@@ -4279,7 +4952,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4279
4952
|
handler: ({ track, from, to, limit }) => {
|
|
4280
4953
|
const all = timelineWords(track).filter((w) => w.end > (_nullishCoalesce(from, () => ( 0))) && w.start < (_nullishCoalesce(to, () => ( Infinity))));
|
|
4281
4954
|
const words = all.slice(0, _nullishCoalesce(limit, () => ( 1e3)));
|
|
4282
|
-
return { track: _nullishCoalesce(_optionalChain([words, 'access',
|
|
4955
|
+
return { track: _nullishCoalesce(_optionalChain([words, 'access', _165 => _165[0], 'optionalAccess', _166 => _166.track]), () => ( videoTranscriptTrack(project(), track))), text: words.map((w) => w.word).join(" "), words: words.map((w) => ({ word: w.word, start: round(w.start), end: round(w.end), clip: w.clip })), more: all.length > words.length };
|
|
4283
4956
|
}
|
|
4284
4957
|
};
|
|
4285
4958
|
const findText = {
|
|
@@ -4332,22 +5005,22 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4332
5005
|
const bad = ranges.findIndex((r) => !(r.to > r.from));
|
|
4333
5006
|
if (bad >= 0) throw new Error(`Range ${bad + 1} is empty or backwards (${ranges[bad].from}\u2013${ranges[bad].to} ms); "to" must be after "from".`);
|
|
4334
5007
|
const merged = mergeVideoRanges(ranges);
|
|
4335
|
-
const before =
|
|
4336
|
-
return apply(videoRemoveRangesEdits(merged, tracks), merged.length > 1 ? `Cut ${merged.length} stretches` : "Cut stretch", (r) => ({ removed: merged.length, time: round(before -
|
|
5008
|
+
const before = _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, project());
|
|
5009
|
+
return apply(videoRemoveRangesEdits(merged, tracks), merged.length > 1 ? `Cut ${merged.length} stretches` : "Cut stretch", (r) => ({ removed: merged.length, time: round(before - _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, r.project)), duration: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, r.project) }));
|
|
4337
5010
|
}
|
|
4338
5011
|
};
|
|
4339
5012
|
const rampTarget = (id) => {
|
|
4340
|
-
const at =
|
|
5013
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project(), id);
|
|
4341
5014
|
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
4342
5015
|
const { clip } = at;
|
|
4343
5016
|
if (clip.type === "sequence") throw new Error(`Clip "${id}" is a compound clip, which plays its sequence at normal speed; change the speed of the clips inside the sequence instead.`);
|
|
4344
|
-
if (clip.type !== "media") throw new Error(`Clip "${id}" is a ${
|
|
4345
|
-
if (_optionalChain([project, 'call',
|
|
5017
|
+
if (clip.type !== "media") throw new Error(`Clip "${id}" is a ${_chunk3OSXHLITcjs.videoClipTypeLabel.call(void 0, clip)}, so it has no source to change the speed of; use a video or audio clip.`);
|
|
5018
|
+
if (_optionalChain([project, 'call', _167 => _167(), 'access', _168 => _168.assets, 'access', _169 => _169.find, 'call', _170 => _170((a) => a.id === clip.asset), 'optionalAccess', _171 => _171.kind]) === "image") throw new Error(`Clip "${id}" is a still, so it has no motion to change the speed of.`);
|
|
4346
5019
|
return clip;
|
|
4347
5020
|
};
|
|
4348
5021
|
const rampOut = (r, id) => {
|
|
4349
5022
|
const outcome2 = r.outcomes[r.outcomes.length - 1];
|
|
4350
|
-
return { clip: clipOut(r.project, id), ...outcome2.rippled ? { rippled: outcome2.rippled } : {}, duration:
|
|
5023
|
+
return { clip: clipOut(r.project, id), ...outcome2.rippled ? { rippled: outcome2.rippled } : {}, duration: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, r.project) };
|
|
4351
5024
|
};
|
|
4352
5025
|
const RIPPLE_RAMP = { type: "boolean", description: "Move later material on every sync-locked track by the change in length (default true); false refuses if the clip would overlap the next one." };
|
|
4353
5026
|
const setSpeedRamp = {
|
|
@@ -4378,8 +5051,8 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4378
5051
|
),
|
|
4379
5052
|
annotations: { title: "Ramp the speed", ...WRITE_IDEMPOTENT },
|
|
4380
5053
|
handler: ({ clip, segments, ripple }) => {
|
|
4381
|
-
const edit =
|
|
4382
|
-
return apply(edit, "Speed ramp", (r) => ({ ...rampOut(r, clip), source: Math.round(
|
|
5054
|
+
const edit = _chunk3OSXHLITcjs.videoSpeedRampEdit.call(void 0, rampTarget(clip), segments, _nullishCoalesce(ripple, () => ( true)));
|
|
5055
|
+
return apply(edit, "Speed ramp", (r) => ({ ...rampOut(r, clip), source: Math.round(_chunk3OSXHLITcjs.videoSpeedRampKeys.call(void 0, segments).source) }));
|
|
4383
5056
|
}
|
|
4384
5057
|
};
|
|
4385
5058
|
const addFreeze = {
|
|
@@ -4399,8 +5072,8 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4399
5072
|
handler: ({ clip, at, duration, ripple }) => {
|
|
4400
5073
|
const c = rampTarget(clip);
|
|
4401
5074
|
const t = at - c.start;
|
|
4402
|
-
if (t < 0 || t > c.duration) throw new Error(`${
|
|
4403
|
-
return apply(
|
|
5075
|
+
if (t < 0 || t > c.duration) throw new Error(`${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, at)} is outside clip "${clip}" (${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, c.start)}\u2013${_chunk3OSXHLITcjs.formatVideoSeconds.call(void 0, _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c))}); pick a time inside it.`);
|
|
5076
|
+
return apply(_chunk3OSXHLITcjs.videoFreezeFrameEdit.call(void 0, c, t, duration, _nullishCoalesce(ripple, () => ( true))), "Freeze frame", (r) => rampOut(r, clip));
|
|
4404
5077
|
}
|
|
4405
5078
|
};
|
|
4406
5079
|
const removeRamp = {
|
|
@@ -4573,7 +5246,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4573
5246
|
let from;
|
|
4574
5247
|
let to;
|
|
4575
5248
|
if (args.clip !== void 0) {
|
|
4576
|
-
const found =
|
|
5249
|
+
const found = _chunk3OSXHLITcjs.findVideoClip.call(void 0, p, args.clip);
|
|
4577
5250
|
if (!found) throw new Error(`There is no clip "${args.clip}"; get_video_project lists the clips.`);
|
|
4578
5251
|
const c = found.clip;
|
|
4579
5252
|
if (c.type === "sequence") throw new Error(`Clip "${c.id}" is a compound clip; its sound is the mix of its sequence "${c.sequence}", so read the levels of the media in it (list_video_sequences lists its clips and assets).`);
|
|
@@ -4583,7 +5256,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4583
5256
|
toSource = (t) => (_nullishCoalesce(c.in, () => ( 0))) + (t - c.start) * speed;
|
|
4584
5257
|
toOut = (s) => c.start + (s - (_nullishCoalesce(c.in, () => ( 0)))) / speed;
|
|
4585
5258
|
from = Math.max(c.start, _nullishCoalesce(args.from, () => ( c.start)));
|
|
4586
|
-
to = Math.min(
|
|
5259
|
+
to = Math.min(_chunk3OSXHLITcjs.videoClipEnd.call(void 0, c), _nullishCoalesce(args.to, () => ( _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c))));
|
|
4587
5260
|
} else {
|
|
4588
5261
|
asset = p.assets.find((a) => a.id === args.asset);
|
|
4589
5262
|
if (!asset) throw new Error(`There is no asset "${args.asset}"; the assets are ${p.assets.map((a) => `"${a.id}"`).join(", ") || "none"}.`);
|
|
@@ -4595,7 +5268,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4595
5268
|
if (!(to > from)) throw new Error(`The stretch to read is empty (${from}\u2013${to} ms); "to" must be after "from"${args.clip !== void 0 ? " and both inside the clip" : asset.duration === void 0 ? ', and the asset has no duration, so pass "to"' : ""}.`);
|
|
4596
5269
|
return { asset, from, to, toSource, toOut };
|
|
4597
5270
|
};
|
|
4598
|
-
const
|
|
5271
|
+
const SOUND_TARGET2 = {
|
|
4599
5272
|
clip: { ...CLIP_ID, description: "A media clip: read the stretch of source it plays, with times on the timeline. Pass this or asset." },
|
|
4600
5273
|
asset: { ...ASSET_ID, description: "A video or audio asset: read its source, with times in the source. Pass this or clip." },
|
|
4601
5274
|
from: MS("Start of the stretch (default: the clip's start, or 0 for an asset)"),
|
|
@@ -4606,7 +5279,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4606
5279
|
name: "get_video_audio_levels",
|
|
4607
5280
|
title: "Read sound levels",
|
|
4608
5281
|
description: 'Measures how loud the source sound is over a clip or an asset, without changing anything: the peak level in dBFS (0 = full scale, -100 = silence) for each of "buckets" equal stretches, plus the loudest peak. Levels are the recording as it is, before clip volume and fades. Use it to find where speech or a beat starts, or to judge whether a clip is too loud. The first call for an asset decodes its file, which can take a moment.',
|
|
4609
|
-
inputSchema: object({ ...
|
|
5282
|
+
inputSchema: object({ ...SOUND_TARGET2, buckets: { type: "integer", minimum: 1, maximum: 500, description: "How many equal stretches to measure (default 50)." } }),
|
|
4610
5283
|
annotations: { title: "Read sound levels", ...SOUND_ANNOTATIONS },
|
|
4611
5284
|
handler: async (args) => {
|
|
4612
5285
|
const r = soundRange(args);
|
|
@@ -4620,7 +5293,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4620
5293
|
title: "Find silences",
|
|
4621
5294
|
description: "Lists the stretches of a clip or an asset whose source sound stays below a level for at least a minimum length, as { start, end } ms (timeline times for a clip, so they can go straight to extract_video_range or split_video_at; source times for an asset). Use it to cut pauses, dead air or the gaps between takes. The first call for an asset decodes its file.",
|
|
4622
5295
|
inputSchema: object({
|
|
4623
|
-
...
|
|
5296
|
+
...SOUND_TARGET2,
|
|
4624
5297
|
thresholdDb: { type: "number", minimum: -100, maximum: 0, description: "Quieter than this peak level counts as silence, in dBFS (default -40; -50 for a quiet room, -30 over background noise)." },
|
|
4625
5298
|
minDuration: { type: "number", exclusiveMinimum: 0, description: "Shortest silence to report, in ms (default 300)." }
|
|
4626
5299
|
}),
|
|
@@ -4664,15 +5337,174 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4664
5337
|
annotations: { title: "Measure a frame with scopes", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
4665
5338
|
handler: async ({ time, bins, columns }) => {
|
|
4666
5339
|
const p = project();
|
|
4667
|
-
const end =
|
|
5340
|
+
const end = _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, p);
|
|
4668
5341
|
const t = _nullishCoalesce(time, () => ( controller.getState().time));
|
|
4669
5342
|
if (time !== void 0 && time > end) throw new Error(`Time ${time} ms is past the end of the project (${end} ms).`);
|
|
4670
5343
|
const pixels = await pixelsAt(t);
|
|
4671
|
-
return { time: t, timecode:
|
|
5344
|
+
return { time: t, timecode: _chunk3OSXHLITcjs.formatVideoTimecode.call(void 0, t, p.fps), width: pixels.width, height: pixels.height, ...summarizeVideoScopes(pixels, { bins, columns }) };
|
|
5345
|
+
}
|
|
5346
|
+
};
|
|
5347
|
+
const MASK_FIELDS = {
|
|
5348
|
+
shape: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_MASK_SHAPES], description: "rectangle (with corner_radius), ellipse, linear (a straight split: the side above the line at rotation 0 shows) or polygon (points)." },
|
|
5349
|
+
x: { type: "number", description: "Centre across the clip's picture, 0 (left edge) to 1 (right edge); default 0.5." },
|
|
5350
|
+
y: { type: "number", description: "Centre down the clip's picture, 0 (top) to 1 (bottom); default 0.5." },
|
|
5351
|
+
width: { type: "number", minimum: 0, description: "Width as a fraction of the picture's width (default 0.5). Unused by linear." },
|
|
5352
|
+
height: { type: "number", minimum: 0, description: "Height as a fraction of the picture's height (default 0.5). Unused by linear." },
|
|
5353
|
+
rotation: { type: "number", description: "Degrees clockwise (default 0)." },
|
|
5354
|
+
feather: { type: "number", minimum: 0, description: "Soft edge width in frame pixels (default 0 = hard edge)." },
|
|
5355
|
+
corner_radius: { type: "number", minimum: 0, maximum: 1, description: "For rectangle: corner rounding as a fraction of half the shorter side (default 0)." },
|
|
5356
|
+
inverted: { type: "boolean", description: "Show the outside of the shape instead of the inside (default false)." },
|
|
5357
|
+
points: { type: "array", minItems: 3, maxItems: 200, items: object({ x: { type: "number", description: "Across the picture, 0\u20131." }, y: { type: "number", description: "Down the picture, 0\u20131." } }, ["x", "y"], "A corner of the polygon."), description: "For polygon (required): its corners in order, as fractions of the clip's picture." }
|
|
5358
|
+
};
|
|
5359
|
+
const addAdjustment = {
|
|
5360
|
+
name: "add_video_adjustment_clip",
|
|
5361
|
+
title: "Add an adjustment clip",
|
|
5362
|
+
description: 'Adds an adjustment clip to a video track (one undo step): it has no picture, but while it plays its grade (and any effects added later with apply_effect, and a mask from set_video_clip_mask) change the composite of the pictures in its scope: "underneath" (every video track below its own, the default), "all" (the whole frame) or "track" (only target_track). Put it on a track above what it should change, usually a new top track. Give the look as "grade" and/or "preset" (a named look at "amount"). Returns the clip (id, start, duration, end, scope).',
|
|
5363
|
+
inputSchema: object(
|
|
5364
|
+
{
|
|
5365
|
+
track: { ...TRACK_ID, description: 'A video track to put it on; tracks below it are "underneath".' },
|
|
5366
|
+
start: MS("Where it starts on the timeline (default: the end of the track)"),
|
|
5367
|
+
duration: { type: "number", exclusiveMinimum: 0, description: "How long it lasts, in ms (default 3000)." },
|
|
5368
|
+
scope: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_ADJUSTMENT_SCOPES], description: "Which pictures it changes: underneath (default), all or track." },
|
|
5369
|
+
target_track: { ...TRACK_ID, description: 'For scope "track" (required then): the track whose pictures it changes.' },
|
|
5370
|
+
grade: GRADE,
|
|
5371
|
+
preset: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_GRADE_PRESET_NAMES], description: "A named look to start from (grade settings given override it)." },
|
|
5372
|
+
amount: { type: "number", minimum: 0, maximum: 1, description: "Strength of the preset, 0\u20131 (default 1)." },
|
|
5373
|
+
name: { type: "string", minLength: 1, description: 'A label for the clip (e.g. "Night look").' },
|
|
5374
|
+
id: { type: "string", minLength: 1, description: 'Your own id for the clip (default: a fresh one such as "c7").' },
|
|
5375
|
+
mode: MODE
|
|
5376
|
+
},
|
|
5377
|
+
["track"]
|
|
5378
|
+
),
|
|
5379
|
+
annotations: { title: "Add an adjustment clip", ...WRITE },
|
|
5380
|
+
handler: ({ track, start, duration, scope, target_track, grade, preset, amount, name, id, mode }) => {
|
|
5381
|
+
const t = project().tracks.find((x) => x.id === track);
|
|
5382
|
+
if (t && t.kind !== "video") throw new Error(`Track "${track}" is an audio track; adjustment clips change pictures, so put it on a video track.`);
|
|
5383
|
+
if (scope === "track" && !target_track) throw new Error('Scope "track" needs target_track: the id of the track whose pictures it changes.');
|
|
5384
|
+
if (target_track !== void 0 && scope !== "track") throw new Error('target_track is only for scope "track"; leave it out or set scope to "track".');
|
|
5385
|
+
const look = { ...preset ? _chunk3OSXHLITcjs.videoGradePreset.call(void 0, preset, _nullishCoalesce(amount, () => ( 1))) : {}, ..._nullishCoalesce(grade, () => ( {})) };
|
|
5386
|
+
const clip = { type: "adjustment", ...id ? { id } : {}, ...name ? { name } : {}, ...start !== void 0 ? { start } : {}, duration: _nullishCoalesce(duration, () => ( 3e3)), ...scope ? { scope } : {}, ...target_track ? { targetTrack: target_track } : {}, ...Object.keys(look).length ? { grade: look } : {} };
|
|
5387
|
+
return apply({ op: "add_clip", track, clip, mode }, "Add adjustment clip", (r) => ({ clip: clipOut(r.project, r.outcomes[0].clip), ...placed(r.outcomes[0]), ...Object.keys(look).length ? {} : { hint: "It changes nothing yet: give it a grade (update_video_clip), effects (apply_effect) or a mask." } }));
|
|
5388
|
+
}
|
|
5389
|
+
};
|
|
5390
|
+
const maskTarget = (id) => {
|
|
5391
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project(), id);
|
|
5392
|
+
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
5393
|
+
if (at.track.kind !== "video") throw new Error(`Clip "${id}" is on audio track "${at.track.id}"; masks cut pictures, so pass a clip on a video track.`);
|
|
5394
|
+
return at;
|
|
5395
|
+
};
|
|
5396
|
+
const setMask = {
|
|
5397
|
+
name: "set_video_clip_mask",
|
|
5398
|
+
title: "Mask a clip",
|
|
5399
|
+
description: `Cuts a clip's picture to a shape (one undo step): only the inside shows, or the outside when inverted, with a soft edge of "feather" pixels. Positions are fractions of the clip's placed picture, so the mask follows the clip's transform. On an adjustment clip it limits the adjustment to the shape (e.g. brighten a face). Replaces any mask the clip had. Returns the clip with its mask. ffmpeg exports need rendered frames for masks.`,
|
|
5400
|
+
inputSchema: object({ clip: CLIP_ID, ...MASK_FIELDS }, ["clip", "shape"]),
|
|
5401
|
+
annotations: { title: "Mask a clip", ...WRITE_IDEMPOTENT },
|
|
5402
|
+
handler: ({ clip, corner_radius, ...fields }) => {
|
|
5403
|
+
maskTarget(clip);
|
|
5404
|
+
if (fields.shape === "polygon" && !fields.points) throw new Error('A polygon mask needs "points": at least 3 { x, y } corners as fractions of the picture.');
|
|
5405
|
+
if (fields.shape !== "polygon" && fields.points) throw new Error('"points" is only for shape "polygon".');
|
|
5406
|
+
if (corner_radius !== void 0 && fields.shape !== "rectangle") throw new Error('"corner_radius" is only for shape "rectangle".');
|
|
5407
|
+
const mask = { ...fields, ...corner_radius !== void 0 ? { cornerRadius: corner_radius } : {} };
|
|
5408
|
+
return apply({ op: "update_clip", clip, set: { mask } }, "Mask clip", (r) => ({ clip: clipOut(r.project, clip) }));
|
|
5409
|
+
}
|
|
5410
|
+
};
|
|
5411
|
+
const removeMask = {
|
|
5412
|
+
name: "remove_video_clip_mask",
|
|
5413
|
+
title: "Remove a clip's mask",
|
|
5414
|
+
description: "Takes a clip's mask off, so its whole picture shows again (one undo step). Fails if it has none. Returns the clip.",
|
|
5415
|
+
inputSchema: object({ clip: CLIP_ID }, ["clip"]),
|
|
5416
|
+
annotations: { title: "Remove a clip's mask", ...DESTRUCTIVE },
|
|
5417
|
+
handler: ({ clip }) => {
|
|
5418
|
+
const at = maskTarget(clip);
|
|
5419
|
+
if (!at.clip.mask) throw new Error(`Clip "${clip}" has no mask.`);
|
|
5420
|
+
return apply({ op: "update_clip", clip, remove: ["mask"] }, "Remove mask", (r) => ({ clip: clipOut(r.project, clip) }));
|
|
5421
|
+
}
|
|
5422
|
+
};
|
|
5423
|
+
const setSkin = {
|
|
5424
|
+
name: "set_video_skin_protection",
|
|
5425
|
+
title: "Protect skin tones from a LUT",
|
|
5426
|
+
description: `Holds a LUT back on skin tones for clips that already have one (one undo step): at "amount" 1 skin keeps its colour while the rest of the picture takes the full look; 0 turns the protection off. Skin is found by colour, so warm orange or beige objects are protected too. Fails for clips without a LUT (add one with set_video_lut first). Returns each clip's LUT setting.`,
|
|
5427
|
+
inputSchema: object({ clips: { type: "array", items: CLIP_ID, minItems: 1, uniqueItems: true, description: "Clips with a LUT." }, amount: { ...SKIN, description: "How much of the LUT to hold back on skin, 0 (off) to 1 (all of it)." } }, ["clips", "amount"]),
|
|
5428
|
+
annotations: { title: "Protect skin tones from a LUT", ...WRITE_IDEMPOTENT },
|
|
5429
|
+
handler: ({ clips, amount }) => {
|
|
5430
|
+
gradeTargets(clips);
|
|
5431
|
+
const edits = [];
|
|
5432
|
+
for (const id of clips) {
|
|
5433
|
+
const clip = _chunk3OSXHLITcjs.findVideoClip.call(void 0, project(), id).clip;
|
|
5434
|
+
const lut = _optionalChain([clip, 'access', _172 => _172.grade, 'optionalAccess', _173 => _173.lut]);
|
|
5435
|
+
if (!lut) throw new Error(`Clip "${id}" has no LUT to protect skin from; add one with set_video_lut first.`);
|
|
5436
|
+
const { preserveSkinTones: _p, ...rest } = lut;
|
|
5437
|
+
edits.push({ op: "update_clip", clip: id, set: { grade: { ...clip.grade, lut: amount > 0 ? { ...rest, preserveSkinTones: amount } : rest } } });
|
|
5438
|
+
}
|
|
5439
|
+
return apply(edits, amount > 0 ? "Protect skin tones" : "Stop protecting skin tones", (r) => ({ clips: clips.map((id) => ({ id, lut: _nullishCoalesce(_optionalChain([_chunk3OSXHLITcjs.findVideoClip.call(void 0, r.project, id), 'optionalAccess', _174 => _174.clip, 'access', _175 => _175.grade, 'optionalAccess', _176 => _176.lut]), () => ( null)) })) }));
|
|
5440
|
+
}
|
|
5441
|
+
};
|
|
5442
|
+
const planOut = (plan) => ({
|
|
5443
|
+
id: plan.id,
|
|
5444
|
+
title: plan.title,
|
|
5445
|
+
...plan.description ? { description: plan.description } : {},
|
|
5446
|
+
...plan.source ? { source: plan.source } : {},
|
|
5447
|
+
summary: { ...plan.summary, newIssues: issueSummary(plan.summary.newIssues) },
|
|
5448
|
+
...plan.ranges ? { ranges: plan.ranges.map((r) => ({ ...r, from: round(r.from), to: round(r.to) })) } : {},
|
|
5449
|
+
edits: plan.edits.length,
|
|
5450
|
+
stale: controller.isPlanStale(plan.id),
|
|
5451
|
+
text: describeVideoEditPlan(plan)
|
|
5452
|
+
});
|
|
5453
|
+
const PLAN_ID = { type: "string", minLength: 1, description: 'A plan id from plan_video_edits, a plan_* tool or list_video_edit_plans (e.g. "plan1").' };
|
|
5454
|
+
const planEdits = {
|
|
5455
|
+
name: "plan_video_edits",
|
|
5456
|
+
title: "Propose edits for review",
|
|
5457
|
+
description: "Works out a batch of edits without applying them and keeps it as a plan for review: the project does not change. The plan says what it would do (length before and after, clips added, removed and changed, problems it would add) and shows in the editor for the user to accept or discard. Use it for anything the user should see before it happens (a rough cut, a cleanup, a re-time). Fails, keeping nothing, if the edits do not apply. Apply it with apply_video_edit_plan. Returns the plan.",
|
|
5458
|
+
inputSchema: object(
|
|
5459
|
+
{
|
|
5460
|
+
title: { type: "string", minLength: 1, maxLength: 120, description: 'What the plan does, shown to the user and used as the undo label (e.g. "Tighten the intro").' },
|
|
5461
|
+
description: { type: "string", maxLength: 2e3, description: "Why, and anything the reviewer should check." },
|
|
5462
|
+
ops: { type: "array", items: EDIT_OP, minItems: 1, maxItems: 500, description: "The edits, in order, as for edit_video." },
|
|
5463
|
+
ranges: { type: "array", maxItems: 500, items: object({ from: MS("Start, on the timeline"), to: MS("End, on the timeline"), text: { type: "string", description: "What is said there." }, reason: { type: "string", description: 'Why it is in the plan (e.g. "filler").' } }, ["from", "to"], "A stretch the plan cuts or changes."), description: "The stretches it cuts or changes, for the review view (optional)." }
|
|
5464
|
+
},
|
|
5465
|
+
["title", "ops"]
|
|
5466
|
+
),
|
|
5467
|
+
annotations: { title: "Propose edits for review", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
5468
|
+
handler: ({ title, description, ops, ranges }) => {
|
|
5469
|
+
if (ops.some((o) => o.op === "replace_project")) throw new Error("A plan cannot replace the whole project; use the individual ops.");
|
|
5470
|
+
const r = controller.proposePlan({ title, description, edits: ops, ranges, source: "agent" });
|
|
5471
|
+
if (!r.ok) throw new Error(r.error);
|
|
5472
|
+
return { plan: planOut(r.plan), hint: `Nothing changed yet. Ask the user to review it, then call apply_video_edit_plan with plan "${r.plan.id}" (or discard_video_edit_plan).` };
|
|
5473
|
+
}
|
|
5474
|
+
};
|
|
5475
|
+
const listPlans = {
|
|
5476
|
+
name: "list_video_edit_plans",
|
|
5477
|
+
title: "List proposed edit plans",
|
|
5478
|
+
description: "Lists the plans waiting for review, oldest first: id, title, summary (length change, clips added, removed and changed, new problems), the stretches they cut, and whether the project changed since each was made (stale: it is checked again when applied).",
|
|
5479
|
+
inputSchema: object({}),
|
|
5480
|
+
annotations: { title: "List proposed edit plans", ...READ },
|
|
5481
|
+
handler: () => ({ plans: controller.getState().plans.map(planOut) })
|
|
5482
|
+
};
|
|
5483
|
+
const applyPlan = {
|
|
5484
|
+
name: "apply_video_edit_plan",
|
|
5485
|
+
title: "Apply an edit plan",
|
|
5486
|
+
description: "Applies a proposed plan as one undo step (labelled with its title) and closes it. A plan made before the project changed is checked again first and refused, changing nothing, if its edits no longer apply. Returns what it did (each op's outcome), the new length and issues, and whether it was stale.",
|
|
5487
|
+
inputSchema: object({ plan: PLAN_ID }, ["plan"]),
|
|
5488
|
+
annotations: { title: "Apply an edit plan", ...DESTRUCTIVE },
|
|
5489
|
+
handler: ({ plan }) => {
|
|
5490
|
+
const r = controller.applyPlan(plan);
|
|
5491
|
+
if (!r.ok) throw new Error(r.error);
|
|
5492
|
+
return { plan: r.plan.id, title: r.plan.title, stale: r.stale, outcomes: r.outcomes, duration: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, r.project), issues: issueSummary(r.issues) };
|
|
5493
|
+
}
|
|
5494
|
+
};
|
|
5495
|
+
const discardPlan = {
|
|
5496
|
+
name: "discard_video_edit_plan",
|
|
5497
|
+
title: "Discard an edit plan",
|
|
5498
|
+
description: "Closes a proposed plan without applying it; the project is unchanged. Returns the plans still open.",
|
|
5499
|
+
inputSchema: object({ plan: PLAN_ID }, ["plan"]),
|
|
5500
|
+
annotations: { title: "Discard an edit plan", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
5501
|
+
handler: ({ plan }) => {
|
|
5502
|
+
if (!controller.discardPlan(plan)) throw new Error(`There is no open plan "${plan}"; the open plans are ${controller.getState().plans.map((p) => `"${p.id}"`).join(", ") || "none"}.`);
|
|
5503
|
+
return { discarded: plan, open: controller.getState().plans.map((p) => p.id) };
|
|
4672
5504
|
}
|
|
4673
5505
|
};
|
|
4674
|
-
const reads = [getProject, listSequences, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, previewCache, planProxies, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek, setPreviewScale];
|
|
4675
|
-
const writes = [setSettings, addAsset, updateAsset, removeAsset, setProxies, addTrack, updateTrack, removeTrack, moveTrack, addClip, moveClip, trimClip, splitClip, deleteClips, updateClip, setTransition, setSpeedRamp, addFreeze, removeRamp, setTranscript, removeRanges, applyGradePreset, copyGrade, setLut, removeLut, setKeyframes, removeKeyframes, slipClip, rollEdit, slideClip, liftRange, extractRange, splitAt, closeGaps, addMarker, removeMarker, editVideo, undo, redo];
|
|
5506
|
+
const reads = [getProject, listPlans, listSequences, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, previewCache, planProxies, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek, setPreviewScale];
|
|
5507
|
+
const writes = [setSettings, addAsset, updateAsset, removeAsset, setProxies, addTrack, updateTrack, removeTrack, moveTrack, addClip, moveClip, trimClip, splitClip, deleteClips, updateClip, setTransition, setSpeedRamp, addFreeze, removeRamp, setTranscript, removeRanges, applyGradePreset, copyGrade, setLut, removeLut, setSkin, addAdjustment, setMask, removeMask, planEdits, applyPlan, discardPlan, setKeyframes, removeKeyframes, slipClip, rollEdit, slideClip, liftRange, extractRange, splitAt, closeGaps, addMarker, removeMarker, editVideo, undo, redo];
|
|
4676
5508
|
return withVideoPlayback({
|
|
4677
5509
|
id: _nullishCoalesce(options.id, () => ( "video")),
|
|
4678
5510
|
namespace: options.namespace,
|
|
@@ -4698,7 +5530,7 @@ function videoClipAppliedEffects(clip) {
|
|
|
4698
5530
|
const out = [];
|
|
4699
5531
|
const ref = (r) => ({ effect: r.effect, ...r.name ? { name: r.name } : {}, ...r.provider ? { provider: r.provider } : {} });
|
|
4700
5532
|
if (clip.type === "effect") out.push({ id: "generator", ...ref(clip), role: "effect clip", enabled: true, values: _nullishCoalesce(clip.values, () => ( {})) });
|
|
4701
|
-
if (_optionalChain([clip, 'access',
|
|
5533
|
+
if (_optionalChain([clip, 'access', _177 => _177.transition, 'optionalAccess', _178 => _178.type]) === "effect" && clip.transition.effect) out.push({ id: "transition", ...ref(clip.transition.effect), role: "transition", enabled: true, values: { ..._nullishCoalesce(clip.transition.effect.values, () => ( {})) }, duration: clip.transition.duration });
|
|
4702
5534
|
(_nullishCoalesce(clip.effects, () => ( []))).forEach((e, index) => out.push({ id: e.id, ...ref(e), role: "clip effect", enabled: e.enabled !== false, ...e.opacity !== void 0 ? { opacity: e.opacity } : {}, values: _nullishCoalesce(e.values, () => ( {})), index }));
|
|
4703
5535
|
return out;
|
|
4704
5536
|
}
|
|
@@ -4711,7 +5543,7 @@ var KEYS2 = {
|
|
|
4711
5543
|
properties: {
|
|
4712
5544
|
t: { type: "number", description: "Clip-relative time in ms (0 = the clip's start)." },
|
|
4713
5545
|
v: { type: "number", description: "The control's value at t, within its range from describe_effect." },
|
|
4714
|
-
ease: { anyOf: [{ type: "string", enum: [...
|
|
5546
|
+
ease: { anyOf: [{ type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_EASE_NAMES] }, { type: "array", items: { type: "number" }, minItems: 4, maxItems: 4 }], description: "How the value moves to the next key: linear (default), hold, ease-in, ease-out, ease-in-out, or CSS cubic-bezier [x1, y1, x2, y2]." }
|
|
4715
5547
|
},
|
|
4716
5548
|
required: ["t", "v"],
|
|
4717
5549
|
additionalProperties: false
|
|
@@ -4725,7 +5557,7 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
4725
5557
|
return controller.readOnlyReason();
|
|
4726
5558
|
};
|
|
4727
5559
|
const clipOf = (id) => {
|
|
4728
|
-
const at =
|
|
5560
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, controller.getProject(), id);
|
|
4729
5561
|
if (!at) {
|
|
4730
5562
|
const ids = controller.getProject().tracks.flatMap((t) => t.clips.map((c) => c.id));
|
|
4731
5563
|
throw new Error(`There is no clip "${id}"; the clips are ${ids.map((c) => `"${c}"`).join(", ") || "none"} (get_video_project lists them).`);
|
|
@@ -4735,7 +5567,7 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
4735
5567
|
const result = (r, target, instance, extra = {}) => {
|
|
4736
5568
|
if (!r.ok) throw new Error(r.error);
|
|
4737
5569
|
const effects = videoClipAppliedEffects(clipOf(target));
|
|
4738
|
-
const effect = _nullishCoalesce(effects.find((e) => e.id === (_nullishCoalesce(instance, () => ( _optionalChain([r, 'access',
|
|
5570
|
+
const effect = _nullishCoalesce(effects.find((e) => e.id === (_nullishCoalesce(instance, () => ( _optionalChain([r, 'access', _179 => _179.effect, 'optionalAccess', _180 => _180.id]))))), () => ( (r.effect ? { id: r.effect.id, effect: r.effect.effect, role: "clip effect", enabled: r.effect.enabled !== false, values: _nullishCoalesce(r.effect.values, () => ( {})) } : effects[0])));
|
|
4739
5571
|
return { target, effect, effects, ...extra };
|
|
4740
5572
|
};
|
|
4741
5573
|
return withVideoPlayback(_chunkRUZQMYXHcjs.createEffectToolProvider.call(void 0, {
|
|
@@ -4796,7 +5628,7 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
4796
5628
|
duration: { type: "number", exclusiveMinimum: 0, description: "Length in ms (default: the effect's own length, else 5000)." },
|
|
4797
5629
|
values: ctx.valuesSchema("Control values over the effect's defaults", true),
|
|
4798
5630
|
name: { type: "string", minLength: 1, description: "A label for the clip (default: the effect's name)." },
|
|
4799
|
-
mode: { type: "string", enum: [...
|
|
5631
|
+
mode: { type: "string", enum: [..._chunk3OSXHLITcjs.VIDEO_PLACEMENT_MODES], description: "What to do with clips already there: refuse (default), overwrite or insert." }
|
|
4800
5632
|
},
|
|
4801
5633
|
required: ["track", "effect"],
|
|
4802
5634
|
additionalProperties: false
|
|
@@ -4855,8 +5687,8 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
4855
5687
|
if (args.effect === "generator") {
|
|
4856
5688
|
const clip = clipOf(args.clip);
|
|
4857
5689
|
if (clip.type !== "effect") throw new Error(`Clip "${args.clip}" is not an effect clip; give the id of one of its effects (list_applied_effects).`);
|
|
4858
|
-
const was = _optionalChain([clip, 'access',
|
|
4859
|
-
const plain = keys === null ?
|
|
5690
|
+
const was = _optionalChain([clip, 'access', _181 => _181.values, 'optionalAccess', _182 => _182[args.control]]);
|
|
5691
|
+
const plain = keys === null ? _chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, was) ? _optionalChain([was, 'access', _183 => _183.keys, 'access', _184 => _184[0], 'optionalAccess', _185 => _185.v]) : was : { keys: [...keys].sort((a, b) => a.t - b.t) };
|
|
4860
5692
|
if (plain === void 0) throw new Error(`"${args.control}" has no value to keep; give keys instead.`);
|
|
4861
5693
|
r = await controller.updateEffectValues(args.clip, "generator", { [args.control]: plain });
|
|
4862
5694
|
} else r = await controller.setClipEffectKeyframes(args.clip, args.effect, args.control, keys);
|
|
@@ -4868,6 +5700,456 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
4868
5700
|
}), options.playbackSurface);
|
|
4869
5701
|
}
|
|
4870
5702
|
|
|
5703
|
+
// src/video-editor/dialogue-tools.ts
|
|
5704
|
+
var round2 = (ms) => Math.round(ms * 10) / 10;
|
|
5705
|
+
var object2 = (properties, required = []) => ({ type: "object", properties, ...required.length ? { required } : {}, additionalProperties: false });
|
|
5706
|
+
var FIND_FIELDS = {
|
|
5707
|
+
track: { type: "string", minLength: 1, description: "Track whose words to read (default: the first audio track with a transcribed clip, else the first video track with one)." },
|
|
5708
|
+
kinds: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", enum: [...VIDEO_DISFLUENCY_KINDS] }, description: 'What to look for: filler (um, uh, you know\u2026), pause (long gaps between words), repetition (stutters such as "I I think"). Default all three.' },
|
|
5709
|
+
fillers: { type: "array", minItems: 1, items: { type: "string", minLength: 1 }, description: `Words or phrases counted as fillers instead of the defaults (${VIDEO_DEFAULT_FILLERS.join(", ")}).` },
|
|
5710
|
+
min_pause: { type: "number", exclusiveMinimum: 0, description: "Shortest gap between words that counts as a pause, in ms (default 700; 600 for brisk talking heads)." },
|
|
5711
|
+
keep: { type: "number", minimum: 0, maximum: 1e3, description: "Breath kept either side of a pause cut, in ms (default 150; 150\u2013250 sounds natural)." },
|
|
5712
|
+
max_phrase: { type: "integer", minimum: 1, maximum: 6, description: "Longest phrase checked for a repeat, in words (default 3)." }
|
|
5713
|
+
};
|
|
5714
|
+
function createVideoDialogueToolProvider(controller, options = {}) {
|
|
5715
|
+
const readOnlyReason = () => {
|
|
5716
|
+
const r = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
|
|
5717
|
+
if (r) return typeof r === "string" ? r : "This video project is read-only.";
|
|
5718
|
+
return controller.readOnlyReason();
|
|
5719
|
+
};
|
|
5720
|
+
const find = (args) => {
|
|
5721
|
+
const project = controller.getProject();
|
|
5722
|
+
const words = videoTimelineTranscript(project, { track: args.track });
|
|
5723
|
+
if (!words.length) throw new Error(args.track ? `No clip on track "${args.track}" plays a transcribed asset; attach one with set_video_transcript.` : "No clip plays a transcribed asset; attach a transcript to the media with set_video_transcript first.");
|
|
5724
|
+
return detectVideoDisfluencies(words, {
|
|
5725
|
+
kinds: args.kinds,
|
|
5726
|
+
fillers: args.fillers,
|
|
5727
|
+
pauses: { fps: project.fps, ...args.min_pause !== void 0 ? { min: args.min_pause } : {}, ...args.keep !== void 0 ? { keep: args.keep } : {} },
|
|
5728
|
+
repetitions: args.max_phrase !== void 0 ? { maxPhrase: args.max_phrase } : void 0
|
|
5729
|
+
});
|
|
5730
|
+
};
|
|
5731
|
+
const out = (d) => ({ kind: d.kind, from: round2(d.from), to: round2(d.to), text: d.text, words: d.words });
|
|
5732
|
+
const findDisfluencies = {
|
|
5733
|
+
name: "find_video_disfluencies",
|
|
5734
|
+
title: "Find fillers, pauses and stutters",
|
|
5735
|
+
description: `Reads the timeline transcript and lists, in time order, every filler word, long pause and repeated word or phrase as { kind, from, to, text, words } (timeline ms; "words" are the first and last word indexes in get_video_transcript's list), with a count and total per kind. Changes nothing. Review the list (some repeats and pauses are meant), then call plan_video_dialogue_cleanup or remove_video_ranges with the ones to cut.`,
|
|
5736
|
+
inputSchema: object2(FIND_FIELDS),
|
|
5737
|
+
annotations: { title: "Find fillers, pauses and stutters", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
5738
|
+
handler: (args) => {
|
|
5739
|
+
const found = find(args);
|
|
5740
|
+
const totals = Object.fromEntries(VIDEO_DISFLUENCY_KINDS.map((k) => [k, { count: found.filter((d) => d.kind === k).length, time: round2(found.filter((d) => d.kind === k).reduce((n, d) => n + d.to - d.from, 0)) }]));
|
|
5741
|
+
const merged = mergeVideoRanges(found);
|
|
5742
|
+
return { disfluencies: found.map(out), totals, removable: round2(merged.reduce((n, r) => n + r.to - r.from, 0)) };
|
|
5743
|
+
}
|
|
5744
|
+
};
|
|
5745
|
+
const planCleanup = {
|
|
5746
|
+
name: "plan_video_dialogue_cleanup",
|
|
5747
|
+
title: "Propose a dialogue cleanup",
|
|
5748
|
+
description: 'Finds fillers, long pauses and stutters (as find_video_disfluencies) and proposes cutting them as one plan for review: nothing changes until apply_video_edit_plan. Pass "exclude" to keep finds the user wants (by their from time), and "margin" to keep a little breath around filler and stutter cuts. Every sync-locked track is cut with the dialogue. Returns the plan (id, title, length change, the stretches it cuts) for the user to review.',
|
|
5749
|
+
inputSchema: object2(
|
|
5750
|
+
{
|
|
5751
|
+
...FIND_FIELDS,
|
|
5752
|
+
exclude: { type: "array", maxItems: 500, items: { type: "number", minimum: 0 }, description: 'The "from" times (ms) of finds to keep, from find_video_disfluencies.' },
|
|
5753
|
+
margin: { type: "number", minimum: 0, maximum: 200, description: "Ms left uncut at each end of a filler or stutter cut, so the words around it are not clipped (default 0)." },
|
|
5754
|
+
title: { type: "string", minLength: 1, maxLength: 120, description: `The plan's title (default: what it cleans up, e.g. "Clean up 12 fillers, 3 pauses").` }
|
|
5755
|
+
}
|
|
5756
|
+
),
|
|
5757
|
+
annotations: { title: "Propose a dialogue cleanup", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
5758
|
+
handler: (args) => {
|
|
5759
|
+
const skip = new Set((_nullishCoalesce(args.exclude, () => ( []))).map((t) => Math.round(t)));
|
|
5760
|
+
const found = find(args).filter((d) => !skip.has(Math.round(d.from)));
|
|
5761
|
+
if (!found.length) throw new Error("Nothing to clean up: no fillers, long pauses or repetitions were found (or all were excluded).");
|
|
5762
|
+
const margin = _nullishCoalesce(args.margin, () => ( 0));
|
|
5763
|
+
const cuts = found.flatMap((d) => d.kind === "pause" || !margin ? [d] : videoCutsWithMargin([d], margin));
|
|
5764
|
+
const counts = VIDEO_DISFLUENCY_KINDS.map((k) => [k, found.filter((d) => d.kind === k).length]).filter(([, n]) => n);
|
|
5765
|
+
const title = _nullishCoalesce(args.title, () => ( `Clean up ${counts.map(([k, n]) => `${n} ${k}${n > 1 ? "s" : ""}`).join(", ")}`));
|
|
5766
|
+
const r = controller.proposePlan({ title, description: "Cuts fillers, long pauses and stutters found in the transcript, closing up every sync-locked track.", edits: videoRemoveRangesEdits(cuts), ranges: cuts.map((d) => ({ from: d.from, to: d.to, text: d.text, reason: d.kind })), source: "plan_video_dialogue_cleanup" });
|
|
5767
|
+
if (!r.ok) throw new Error(r.error);
|
|
5768
|
+
const p = r.plan;
|
|
5769
|
+
return { plan: { id: p.id, title: p.title, summary: p.summary, text: describeVideoEditPlan(p), cuts: cuts.length }, durationNow: _chunk3OSXHLITcjs.videoProjectDuration.call(void 0, controller.getProject()), hint: `Nothing changed yet. Show the user the plan, then call apply_video_edit_plan with plan "${p.id}" (or discard_video_edit_plan).` };
|
|
5770
|
+
}
|
|
5771
|
+
};
|
|
5772
|
+
return withVideoPlayback(
|
|
5773
|
+
{
|
|
5774
|
+
id: _nullishCoalesce(options.id, () => ( "video-dialogue")),
|
|
5775
|
+
namespace: options.namespace,
|
|
5776
|
+
title: "Video dialogue cleanup",
|
|
5777
|
+
description: "Talking-head cleanup by transcript: find fillers, long pauses and stutters, and propose cutting them as one plan for review.",
|
|
5778
|
+
skill: "pixerate-talking-head-edit",
|
|
5779
|
+
tools: () => readOnlyReason() ? [findDisfluencies] : [findDisfluencies, planCleanup],
|
|
5780
|
+
subscribe: (notify) => {
|
|
5781
|
+
let last = readOnlyReason();
|
|
5782
|
+
return controller.subscribe(() => {
|
|
5783
|
+
const now = readOnlyReason();
|
|
5784
|
+
if (now !== last) {
|
|
5785
|
+
last = now;
|
|
5786
|
+
notify();
|
|
5787
|
+
}
|
|
5788
|
+
});
|
|
5789
|
+
}
|
|
5790
|
+
},
|
|
5791
|
+
options.playbackSurface
|
|
5792
|
+
);
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5795
|
+
// src/video-editor/music-tools.ts
|
|
5796
|
+
var round4 = (ms) => Math.round(ms * 10) / 10;
|
|
5797
|
+
var object3 = (properties, required = [], description) => ({ type: "object", ...description ? { description } : {}, properties, ...required.length ? { required } : {}, additionalProperties: false });
|
|
5798
|
+
var MS3 = (what) => ({ type: "number", minimum: 0, description: `${what}, in milliseconds.` });
|
|
5799
|
+
var CLIP_ID2 = { type: "string", minLength: 1, description: 'A clip id from get_video_project (e.g. "c1").' };
|
|
5800
|
+
var TRACK_ID2 = { type: "string", minLength: 1, description: 'A track id from get_video_project (e.g. "v1" or "a1").' };
|
|
5801
|
+
var SOUND_TARGET = {
|
|
5802
|
+
clip: { ...CLIP_ID2, description: "A media clip with sound: read the stretch of source it plays, times on the timeline. Pass this or asset." },
|
|
5803
|
+
asset: { type: "string", minLength: 1, description: "A video or audio asset: read its source, times in the source. Pass this or clip." },
|
|
5804
|
+
from: MS3("Start of the stretch (default: the clip's start, or 0 for an asset)"),
|
|
5805
|
+
to: MS3("End of the stretch (default: the clip's end, or the asset's duration)")
|
|
5806
|
+
};
|
|
5807
|
+
var SOUND = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
5808
|
+
function createVideoMusicToolProvider(controller, options = {}) {
|
|
5809
|
+
const readOnlyReason = () => {
|
|
5810
|
+
const r = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
|
|
5811
|
+
if (r) return typeof r === "string" ? r : "This video project is read-only.";
|
|
5812
|
+
return controller.readOnlyReason();
|
|
5813
|
+
};
|
|
5814
|
+
const project = () => controller.getProject();
|
|
5815
|
+
const soundClip = (p, id) => {
|
|
5816
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, p, id);
|
|
5817
|
+
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
5818
|
+
const c = at.clip;
|
|
5819
|
+
if (c.type !== "media") throw new Error(`Clip "${id}" is a ${c.type} clip and has no sound of its own; pass a video or audio clip.`);
|
|
5820
|
+
const asset = p.assets.find((a) => a.id === c.asset);
|
|
5821
|
+
if (!asset) throw new Error(`Clip "${id}" plays a missing asset "${c.asset}".`);
|
|
5822
|
+
if (asset.kind === "image" || asset.hasAudio === false) throw new Error(`Clip "${id}" has no sound (asset "${asset.id}").`);
|
|
5823
|
+
return { clip: c, asset, track: at.track.id };
|
|
5824
|
+
};
|
|
5825
|
+
const stretch = (args) => {
|
|
5826
|
+
const p = project();
|
|
5827
|
+
if (args.clip === void 0 === (args.asset === void 0)) throw new Error('Pass either "clip" (a media clip, times on the timeline) or "asset" (times in the source), not both.');
|
|
5828
|
+
if (args.clip !== void 0) {
|
|
5829
|
+
const { clip, asset: asset2 } = soundClip(p, args.clip);
|
|
5830
|
+
const speed = _nullishCoalesce(clip.speed, () => ( 1));
|
|
5831
|
+
const from2 = Math.max(clip.start, _nullishCoalesce(args.from, () => ( clip.start)));
|
|
5832
|
+
const to2 = Math.min(_chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip), _nullishCoalesce(args.to, () => ( _chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip))));
|
|
5833
|
+
if (!(to2 > from2)) throw new Error(`The stretch is empty (${from2}\u2013${to2} ms); "from" and "to" must be inside the clip (${clip.start}\u2013${_chunk3OSXHLITcjs.videoClipEnd.call(void 0, clip)} ms) with "to" after "from".`);
|
|
5834
|
+
return { asset: asset2, from: (_nullishCoalesce(clip.in, () => ( 0))) + (from2 - clip.start) * speed, to: (_nullishCoalesce(clip.in, () => ( 0))) + (to2 - clip.start) * speed, out: (s) => clip.start + (s - (_nullishCoalesce(clip.in, () => ( 0)))) / speed };
|
|
5835
|
+
}
|
|
5836
|
+
const asset = p.assets.find((a) => a.id === args.asset);
|
|
5837
|
+
if (!asset) throw new Error(`There is no asset "${args.asset}"; the assets are ${p.assets.map((a) => `"${a.id}"`).join(", ") || "none"}.`);
|
|
5838
|
+
if (asset.kind === "image" || asset.hasAudio === false) throw new Error(`Asset "${asset.id}" has no sound.`);
|
|
5839
|
+
const from = _nullishCoalesce(args.from, () => ( 0));
|
|
5840
|
+
const to = _nullishCoalesce(_nullishCoalesce(args.to, () => ( asset.duration)), () => ( 0));
|
|
5841
|
+
if (!(to > from)) throw new Error(`The stretch is empty (${from}\u2013${to} ms)${asset.duration === void 0 ? '; the asset has no duration, so pass "to"' : ""}.`);
|
|
5842
|
+
return { asset, from, to, out: (s) => s };
|
|
5843
|
+
};
|
|
5844
|
+
const measureLoudness = {
|
|
5845
|
+
name: "measure_video_loudness",
|
|
5846
|
+
title: "Measure loudness",
|
|
5847
|
+
description: "Measures how loud the source sound of a clip or an asset is, as streaming services and broadcasters do (ITU-R BS.1770, gated): integrated loudness in LUFS, the sample peak in dBFS, and the gain that would bring it to -14 LUFS (streaming) without the peak passing -1 dBFS. Before clip volume and fades. Changes nothing. The first call for an asset decodes its file. Use normalize_video_loudness to apply the gain.",
|
|
5848
|
+
inputSchema: object3(SOUND_TARGET),
|
|
5849
|
+
annotations: { title: "Measure loudness", ...SOUND },
|
|
5850
|
+
handler: async (args) => {
|
|
5851
|
+
const r = stretch(args);
|
|
5852
|
+
const peaks = await controller.audioPeaks.load(r.asset);
|
|
5853
|
+
const lufs2 = videoAudioLoudness(peaks, r.from, r.to);
|
|
5854
|
+
const peakDb = Math.max(...videoAudioLevels(peaks, r.from, r.to, 50).map((l) => l.peakDb));
|
|
5855
|
+
return { asset: r.asset.id, lufs: lufs2, peakDb, ...lufs2 === null ? { note: "The stretch is silent (everything is below the -70 LUFS gate)." } : { toStreaming: videoLoudnessGain(lufs2, -14, { peakDb }) } };
|
|
5856
|
+
}
|
|
5857
|
+
};
|
|
5858
|
+
const normalizeLoudness = {
|
|
5859
|
+
name: "normalize_video_loudness",
|
|
5860
|
+
title: "Normalise loudness",
|
|
5861
|
+
description: "Sets each clip's volume so its sound plays at a target loudness (default -14 LUFS, streaming; -16 podcasts, -23 EBU R128 broadcast), keeping its peak under the ceiling and the gain at most 4\xD7 (one undo step). Measures each clip's own stretch of source first; silent clips are skipped. Refuses clips with volume keyframes (a constant gain would replace them) unless replace is true. Returns each clip's measured loudness, the gain set (linear and dB) and whether a cap held it short of the target.",
|
|
5862
|
+
inputSchema: object3(
|
|
5863
|
+
{
|
|
5864
|
+
clips: { type: "array", items: CLIP_ID2, minItems: 1, maxItems: 200, uniqueItems: true, description: "Media clips with sound." },
|
|
5865
|
+
target: { type: "number", minimum: -40, maximum: -5, description: "Loudness to reach, in LUFS (default -14)." },
|
|
5866
|
+
ceiling: { type: "number", minimum: -20, maximum: 0, description: "Highest sample peak allowed after the gain, in dBFS (default -1)." },
|
|
5867
|
+
replace: { type: "boolean", description: "Replace volume keyframes with the constant gain (default false: refuse)." }
|
|
5868
|
+
},
|
|
5869
|
+
["clips"]
|
|
5870
|
+
),
|
|
5871
|
+
annotations: { title: "Normalise loudness", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
5872
|
+
handler: async ({ clips, target, ceiling, replace }) => {
|
|
5873
|
+
const p = project();
|
|
5874
|
+
const targets = clips.map((id) => soundClip(p, id));
|
|
5875
|
+
for (const t of targets) if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, t.clip.volume) && !replace) throw new Error(`Clip "${t.clip.id}" has volume keyframes, which a constant gain would replace; pass replace: true, or remove them first.`);
|
|
5876
|
+
const results = [];
|
|
5877
|
+
const edits = [];
|
|
5878
|
+
for (const { clip, asset } of targets) {
|
|
5879
|
+
const peaks = await controller.audioPeaks.load(asset);
|
|
5880
|
+
const speed = _nullishCoalesce(clip.speed, () => ( 1));
|
|
5881
|
+
const from = _nullishCoalesce(clip.in, () => ( 0));
|
|
5882
|
+
const to = from + clip.duration * speed;
|
|
5883
|
+
const lufs2 = videoAudioLoudness(peaks, from, to);
|
|
5884
|
+
if (lufs2 === null) {
|
|
5885
|
+
results.push({ clip: clip.id, skipped: "silent" });
|
|
5886
|
+
continue;
|
|
5887
|
+
}
|
|
5888
|
+
const peakDb = Math.max(...videoAudioLevels(peaks, from, to, 50).map((l) => l.peakDb));
|
|
5889
|
+
const g = videoLoudnessGain(lufs2, _nullishCoalesce(target, () => ( -14)), { peakDb, ceiling });
|
|
5890
|
+
edits.push({ op: "update_clip", clip: clip.id, set: { volume: g.gain } });
|
|
5891
|
+
results.push({ clip: clip.id, lufs: lufs2, gain: g.gain, gainDb: g.gainDb, ...g.limitedBy ? { limitedBy: g.limitedBy } : {} });
|
|
5892
|
+
}
|
|
5893
|
+
if (!edits.length) throw new Error("Every clip is silent, so there is nothing to normalise.");
|
|
5894
|
+
const r = controller.apply(edits, "Normalise loudness");
|
|
5895
|
+
if (!r.ok) throw new Error(r.error);
|
|
5896
|
+
return { target: _nullishCoalesce(target, () => ( -14)), clips: results };
|
|
5897
|
+
}
|
|
5898
|
+
};
|
|
5899
|
+
const duckMusic = {
|
|
5900
|
+
name: "duck_video_music",
|
|
5901
|
+
title: "Duck music under speech",
|
|
5902
|
+
description: `Dips music clips under speech with volume keyframes (one undo step): before each stretch of speech the level eases down by duck_db over "attack" ms, holds, and eases back over "release" ms after it; gaps shorter than merge_gap stay ducked. Speech comes from the transcript of the dialogue track ("transcript", the default when it has one) or from where its sound is above -40 dBFS ("audio", which decodes it). Refuses music clips that already have volume keyframes unless replace is true. Returns each music clip's keys and the speech stretches used.`,
|
|
5903
|
+
inputSchema: object3(
|
|
5904
|
+
{
|
|
5905
|
+
music: { type: "array", items: CLIP_ID2, minItems: 1, maxItems: 100, uniqueItems: true, description: "The music clips to duck (media clips with sound)." },
|
|
5906
|
+
dialogue_track: { ...TRACK_ID2, description: 'Track the speech is on (default: the transcript track for "transcript"; required for "audio").' },
|
|
5907
|
+
speech: { type: "string", enum: ["transcript", "audio"], description: "Where speech is found: transcript words (default when transcribed) or the dialogue track's sound levels." },
|
|
5908
|
+
duck_db: { type: "number", minimum: -40, maximum: -1, description: "How far to dip, in dB (default -14; -12 to -18 is usual)." },
|
|
5909
|
+
attack: { type: "number", minimum: 0, maximum: 2e3, description: "Ms the dip takes to come in before speech (default 200)." },
|
|
5910
|
+
release: { type: "number", minimum: 0, maximum: 4e3, description: "Ms the music takes to come back after speech (default 400)." },
|
|
5911
|
+
merge_gap: { type: "number", minimum: 0, maximum: 1e4, description: "Speech gaps shorter than this stay ducked, in ms (default 600)." },
|
|
5912
|
+
replace: { type: "boolean", description: "Replace existing volume keyframes (default false: refuse)." }
|
|
5913
|
+
},
|
|
5914
|
+
["music"]
|
|
5915
|
+
),
|
|
5916
|
+
annotations: { title: "Duck music under speech", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
5917
|
+
handler: async (args) => {
|
|
5918
|
+
const p = project();
|
|
5919
|
+
const music = args.music.map((id) => soundClip(p, id));
|
|
5920
|
+
for (const m of music) if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, m.clip.volume) && !args.replace) throw new Error(`Music clip "${m.clip.id}" already has volume keyframes; pass replace: true to replace them, or remove them first.`);
|
|
5921
|
+
const transcriptTrack = videoTranscriptTrack(p, args.dialogue_track);
|
|
5922
|
+
const mode = _nullishCoalesce(args.speech, () => ( (transcriptTrack && videoTimelineTranscript(p, { track: transcriptTrack }).length ? "transcript" : "audio")));
|
|
5923
|
+
let ranges = [];
|
|
5924
|
+
if (mode === "transcript") {
|
|
5925
|
+
const words = videoTimelineTranscript(p, { track: args.dialogue_track });
|
|
5926
|
+
if (!words.length) throw new Error(`No transcribed speech${args.dialogue_track ? ` on track "${args.dialogue_track}"` : ""}; attach a transcript with set_video_transcript, or pass speech: "audio" with dialogue_track.`);
|
|
5927
|
+
ranges = videoSpeechRanges(words, 250);
|
|
5928
|
+
} else {
|
|
5929
|
+
if (!args.dialogue_track) throw new Error('speech "audio" needs dialogue_track: the track whose sound is the speech.');
|
|
5930
|
+
const track = p.tracks.find((t) => t.id === args.dialogue_track);
|
|
5931
|
+
if (!track) throw new Error(`There is no track "${args.dialogue_track}".`);
|
|
5932
|
+
const musicIds = new Set(music.map((m) => m.clip.id));
|
|
5933
|
+
for (const c of _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, track)) {
|
|
5934
|
+
if (c.type !== "media" || musicIds.has(c.id)) continue;
|
|
5935
|
+
const asset = p.assets.find((a) => a.id === c.asset);
|
|
5936
|
+
if (!asset || asset.kind === "image" || asset.hasAudio === false) continue;
|
|
5937
|
+
const peaks = await controller.audioPeaks.load(asset);
|
|
5938
|
+
const speed = _nullishCoalesce(c.speed, () => ( 1));
|
|
5939
|
+
const src0 = _nullishCoalesce(c.in, () => ( 0));
|
|
5940
|
+
const silences = findVideoAudioSilences(peaks, src0, src0 + c.duration * speed, { thresholdDb: -40, minDuration: 250 });
|
|
5941
|
+
let at = c.start;
|
|
5942
|
+
for (const s of silences) {
|
|
5943
|
+
const a = c.start + (s.start - src0) / speed;
|
|
5944
|
+
if (a - at > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) ranges.push({ from: at, to: a });
|
|
5945
|
+
at = c.start + (s.end - src0) / speed;
|
|
5946
|
+
}
|
|
5947
|
+
if (_chunk3OSXHLITcjs.videoClipEnd.call(void 0, c) - at > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) ranges.push({ from: at, to: _chunk3OSXHLITcjs.videoClipEnd.call(void 0, c) });
|
|
5948
|
+
}
|
|
5949
|
+
if (!ranges.length) throw new Error(`Track "${args.dialogue_track}" has no sound above -40 dBFS, so there is no speech to duck under.`);
|
|
5950
|
+
}
|
|
5951
|
+
const edits = [];
|
|
5952
|
+
const out = [];
|
|
5953
|
+
for (const { clip } of music) {
|
|
5954
|
+
const base = typeof clip.volume === "number" ? clip.volume : 1;
|
|
5955
|
+
const keys = videoDuckingKeys(clip, ranges, { duckDb: args.duck_db, attack: args.attack, release: args.release, mergeGap: args.merge_gap, base });
|
|
5956
|
+
out.push({ clip: clip.id, keys: keys.length, ...keys.length ? { keyframes: keys } : { note: "No speech overlaps it." } });
|
|
5957
|
+
if (keys.length) edits.push({ op: "set_keyframes", clip: clip.id, property: "volume", keys });
|
|
5958
|
+
}
|
|
5959
|
+
if (!edits.length) throw new Error("No speech overlaps the music clips, so there is nothing to duck.");
|
|
5960
|
+
const r = controller.apply(edits, "Duck music");
|
|
5961
|
+
if (!r.ok) throw new Error(r.error);
|
|
5962
|
+
return { speech: mode, speechRanges: ranges.map((x) => ({ from: round4(x.from), to: round4(x.to) })), music: out };
|
|
5963
|
+
}
|
|
5964
|
+
};
|
|
5965
|
+
const BEAT_FIELDS = {
|
|
5966
|
+
sensitivity: { type: "number", minimum: 0.5, maximum: 4, description: "How far above the local average a hit must rise, in standard deviations (default 1.5; lower finds more)." },
|
|
5967
|
+
min_bpm: { type: "number", minimum: 30, maximum: 300, description: "Slowest tempo to consider (default 60)." },
|
|
5968
|
+
max_bpm: { type: "number", minimum: 30, maximum: 300, description: "Fastest tempo to consider (default 200)." }
|
|
5969
|
+
};
|
|
5970
|
+
const beatsOf = async (args) => {
|
|
5971
|
+
const r = stretch(args);
|
|
5972
|
+
const peaks = await controller.audioPeaks.load(r.asset);
|
|
5973
|
+
const found = findVideoAudioBeats(peaks, r.from, r.to, { sensitivity: args.sensitivity, minBpm: args.min_bpm, maxBpm: args.max_bpm });
|
|
5974
|
+
return { asset: r.asset.id, bpm: found.bpm, beats: found.beats.map((t) => round4(r.out(t))), onsets: found.onsets.map((t) => round4(r.out(t))) };
|
|
5975
|
+
};
|
|
5976
|
+
const findBeats = {
|
|
5977
|
+
name: "find_video_beats",
|
|
5978
|
+
title: "Find the beats",
|
|
5979
|
+
description: "Finds the beat of a music clip or asset: the tempo (BPM, or null when the hits do not repeat), a steady beat grid at that tempo, and the onsets (each hit) \u2014 timeline ms for a clip, source ms for an asset. Changes nothing. Use the beats to cut on the music (plan_video_beat_snap), place markers (add_video_marker) or time titles. Works best on percussive music. The first call for an asset decodes its file.",
|
|
5980
|
+
inputSchema: object3({ ...SOUND_TARGET, ...BEAT_FIELDS }),
|
|
5981
|
+
annotations: { title: "Find the beats", ...SOUND },
|
|
5982
|
+
handler: (args) => beatsOf(args)
|
|
5983
|
+
};
|
|
5984
|
+
const planBeatSnap = {
|
|
5985
|
+
name: "plan_video_beat_snap",
|
|
5986
|
+
title: "Propose cuts on the beat",
|
|
5987
|
+
description: 'Proposes, as a plan for review, moving each cut on a track to the nearest beat within "tolerance" ms with roll edits (the outgoing clip gets longer as the incoming one gets shorter, so nothing else moves). Beats come from a music clip (found as find_video_beats does) or are given. Cuts already on a beat, with none in reach, or without enough media to roll are left (listed in "skipped"). Nothing changes until apply_video_edit_plan. Returns the plan and what was skipped.',
|
|
5988
|
+
inputSchema: object3(
|
|
5989
|
+
{
|
|
5990
|
+
track: { ...TRACK_ID2, description: "The track whose cuts to move (default the first video track)." },
|
|
5991
|
+
music: { ...CLIP_ID2, description: "A music clip to take the beats from. Pass this or beats." },
|
|
5992
|
+
beats: { type: "array", minItems: 1, maxItems: 5e3, items: { type: "number", minimum: 0 }, description: "Beat times on the timeline, in ms. Pass this or music." },
|
|
5993
|
+
tolerance: { type: "number", exclusiveMinimum: 0, maximum: 2e3, description: "Furthest a cut may move, in ms (default 150)." },
|
|
5994
|
+
...BEAT_FIELDS
|
|
5995
|
+
}
|
|
5996
|
+
),
|
|
5997
|
+
annotations: { title: "Propose cuts on the beat", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
5998
|
+
handler: async (args) => {
|
|
5999
|
+
const p = project();
|
|
6000
|
+
if (args.music === void 0 === (args.beats === void 0)) throw new Error('Pass either "music" (a music clip to find the beats in) or "beats" (times in ms), not both.');
|
|
6001
|
+
const track = args.track !== void 0 ? p.tracks.find((t) => t.id === args.track) : p.tracks.find((t) => t.kind === "video");
|
|
6002
|
+
if (!track) throw new Error(args.track !== void 0 ? `There is no track "${args.track}".` : "The project has no video track.");
|
|
6003
|
+
let beats;
|
|
6004
|
+
if (args.music !== void 0) {
|
|
6005
|
+
const found = await beatsOf({ clip: args.music, sensitivity: args.sensitivity, min_bpm: args.min_bpm, max_bpm: args.max_bpm });
|
|
6006
|
+
beats = found.beats.length ? found.beats : found.onsets;
|
|
6007
|
+
if (!beats.length) throw new Error(`No beats were found in "${args.music}"; try a lower sensitivity, or pass beats.`);
|
|
6008
|
+
} else beats = [...args.beats].sort((a, b) => a - b);
|
|
6009
|
+
const tolerance = _nullishCoalesce(args.tolerance, () => ( 150));
|
|
6010
|
+
const clips = _chunk3OSXHLITcjs.sortedVideoClips.call(void 0, track);
|
|
6011
|
+
const edits = [];
|
|
6012
|
+
const ranges = [];
|
|
6013
|
+
const skipped = [];
|
|
6014
|
+
let working = p;
|
|
6015
|
+
for (let i = 0; i + 1 < clips.length; i++) {
|
|
6016
|
+
const left = clips[i];
|
|
6017
|
+
const right = clips[i + 1];
|
|
6018
|
+
const cut = _chunk3OSXHLITcjs.videoClipEnd.call(void 0, left);
|
|
6019
|
+
if (Math.abs(right.start - cut) > _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON) continue;
|
|
6020
|
+
const beat = nearestVideoBeat(beats, cut, tolerance);
|
|
6021
|
+
if (beat === null) {
|
|
6022
|
+
skipped.push({ cut: round4(cut), reason: `no beat within ${tolerance} ms` });
|
|
6023
|
+
continue;
|
|
6024
|
+
}
|
|
6025
|
+
const delta = Math.round((beat - cut) * 10) / 10;
|
|
6026
|
+
if (Math.abs(delta) < 1) {
|
|
6027
|
+
skipped.push({ cut: round4(cut), reason: "already on the beat" });
|
|
6028
|
+
continue;
|
|
6029
|
+
}
|
|
6030
|
+
const edit = { op: "roll_edit", left: left.id, right: right.id, delta };
|
|
6031
|
+
const tried = applyVideoEdits(working, edit);
|
|
6032
|
+
if (!tried.ok) {
|
|
6033
|
+
skipped.push({ cut: round4(cut), reason: tried.error });
|
|
6034
|
+
continue;
|
|
6035
|
+
}
|
|
6036
|
+
working = tried.project;
|
|
6037
|
+
edits.push(edit);
|
|
6038
|
+
ranges.push({ from: Math.min(cut, beat), to: Math.max(cut, beat), reason: "beat" });
|
|
6039
|
+
}
|
|
6040
|
+
if (!edits.length) throw new Error(`No cut on track "${track.id}" can move to a beat${skipped.length ? `: ${skipped.slice(0, 5).map((s) => `${s.cut} ms (${s.reason})`).join("; ")}` : " (it has no cuts between touching clips)"}.`);
|
|
6041
|
+
const r = controller.proposePlan({ title: `Snap ${edits.length} cut${edits.length > 1 ? "s" : ""} to the beat`, description: `Rolls cuts on track "${track.id}" to the nearest beat within ${tolerance} ms.`, edits, ranges, source: "plan_video_beat_snap" });
|
|
6042
|
+
if (!r.ok) throw new Error(r.error);
|
|
6043
|
+
return { plan: { id: r.plan.id, title: r.plan.title, summary: r.plan.summary, text: describeVideoEditPlan(r.plan) }, moved: edits.length, skipped, hint: `Nothing changed yet. Call apply_video_edit_plan with plan "${r.plan.id}" once the user agrees.` };
|
|
6044
|
+
}
|
|
6045
|
+
};
|
|
6046
|
+
const reads = [measureLoudness, findBeats];
|
|
6047
|
+
const writes = [normalizeLoudness, duckMusic, planBeatSnap];
|
|
6048
|
+
return withVideoPlayback(
|
|
6049
|
+
{
|
|
6050
|
+
id: _nullishCoalesce(options.id, () => ( "video-music")),
|
|
6051
|
+
namespace: options.namespace,
|
|
6052
|
+
title: "Video music and mix",
|
|
6053
|
+
description: "Music and the mix: beats and cuts on the beat, ducking music under speech, and loudness in LUFS.",
|
|
6054
|
+
skill: "pixerate-bgm-alignment",
|
|
6055
|
+
tools: () => readOnlyReason() ? reads : [...reads, ...writes],
|
|
6056
|
+
subscribe: (notify) => {
|
|
6057
|
+
let last = readOnlyReason();
|
|
6058
|
+
return controller.subscribe(() => {
|
|
6059
|
+
const now = readOnlyReason();
|
|
6060
|
+
if (now !== last) {
|
|
6061
|
+
last = now;
|
|
6062
|
+
notify();
|
|
6063
|
+
}
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6066
|
+
},
|
|
6067
|
+
options.playbackSurface
|
|
6068
|
+
);
|
|
6069
|
+
}
|
|
6070
|
+
|
|
6071
|
+
// src/video-editor/effect-authoring.ts
|
|
6072
|
+
function videoEaseToAnimationEasing(ease) {
|
|
6073
|
+
if (ease === void 0 || ease === "linear") return "linear";
|
|
6074
|
+
if (Array.isArray(ease)) return { type: "cubic-bezier", x1: ease[0], y1: ease[1], x2: ease[2], y2: ease[3] };
|
|
6075
|
+
return ease;
|
|
6076
|
+
}
|
|
6077
|
+
function animationEasingToVideoEase(easing) {
|
|
6078
|
+
if (easing === void 0 || easing === "linear") return "linear";
|
|
6079
|
+
if (easing === "hold" || easing === "ease-in" || easing === "ease-out" || easing === "ease-in-out") return easing;
|
|
6080
|
+
if (typeof easing === "string") {
|
|
6081
|
+
const p = _chunkDWJJI6RTcjs.CUBIC_BEZIER_PRESETS[easing];
|
|
6082
|
+
return p ? [p[0], p[1], p[2], p[3]] : null;
|
|
6083
|
+
}
|
|
6084
|
+
if (easing.type === "cubic-bezier") return [easing.x1, easing.y1, easing.x2, easing.y2];
|
|
6085
|
+
return null;
|
|
6086
|
+
}
|
|
6087
|
+
var SAMPLES = 12;
|
|
6088
|
+
function videoClipEffectDocument(clip, instance, providerId) {
|
|
6089
|
+
const effect = (_nullishCoalesce(clip.effects, () => ( []))).find((e) => e.id === instance);
|
|
6090
|
+
if (!effect) throw new Error(`Clip "${clip.id}" has no effect "${instance}"; its effects are ${(_nullishCoalesce(clip.effects, () => ( []))).map((e) => `"${e.id}" (${_nullishCoalesce(e.name, () => ( e.effect))})`).join(", ") || "none"}.`);
|
|
6091
|
+
const values = {};
|
|
6092
|
+
const tracks = {};
|
|
6093
|
+
for (const [control, v] of Object.entries(_nullishCoalesce(effect.values, () => ( {})))) {
|
|
6094
|
+
if (_chunk3OSXHLITcjs.isVideoKeyframes.call(void 0, v)) {
|
|
6095
|
+
values[control] = v.keys[0].v;
|
|
6096
|
+
tracks[control] = v.keys.map((k) => ({ time: k.t, value: k.v, ...k.ease && k.ease !== "linear" ? { easing: videoEaseToAnimationEasing(k.ease) } : {} }));
|
|
6097
|
+
} else values[control] = v;
|
|
6098
|
+
}
|
|
6099
|
+
return { version: 1, providerId, effectId: effect.effect, name: _nullishCoalesce(effect.name, () => ( effect.effect)), values, tracks, presets: [], duration: clip.duration, loop: false };
|
|
6100
|
+
}
|
|
6101
|
+
function videoEffectValuesFromDocument(doc) {
|
|
6102
|
+
const values = { ...doc.values };
|
|
6103
|
+
const notes = [];
|
|
6104
|
+
for (const [control, track] of Object.entries(_nullishCoalesce(doc.tracks, () => ( {})))) {
|
|
6105
|
+
const kfs = [...track].sort((a, b) => a.time - b.time);
|
|
6106
|
+
if (!kfs.length) continue;
|
|
6107
|
+
if (!kfs.every((k) => typeof k.value === "number")) {
|
|
6108
|
+
values[control] = kfs[0].value;
|
|
6109
|
+
notes.push(`"${control}" is keyframed with non-numeric values, which clip effects cannot animate; it keeps its first value.`);
|
|
6110
|
+
continue;
|
|
6111
|
+
}
|
|
6112
|
+
const keys = [];
|
|
6113
|
+
let sampled = false;
|
|
6114
|
+
kfs.forEach((k, i) => {
|
|
6115
|
+
const next = kfs[i + 1];
|
|
6116
|
+
const ease = animationEasingToVideoEase(k.easing);
|
|
6117
|
+
if (!next || ease) {
|
|
6118
|
+
keys.push({ t: k.time, v: k.value, ...next && ease && ease !== "linear" ? { ease } : {} });
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
sampled = true;
|
|
6122
|
+
const a = k.value;
|
|
6123
|
+
const b = next.value;
|
|
6124
|
+
const span = next.time - k.time;
|
|
6125
|
+
for (let s = 0; s < SAMPLES; s++) keys.push({ t: Math.round((k.time + span * s / SAMPLES) * 1e3) / 1e3, v: a + (b - a) * _chunkDWJJI6RTcjs.evaluateEasing.call(void 0, k.easing, s / SAMPLES, span) });
|
|
6126
|
+
});
|
|
6127
|
+
values[control] = { keys: keys.filter((k, i) => i === 0 || k.t > keys[i - 1].t) };
|
|
6128
|
+
if (sampled) notes.push(`"${control}" uses a spring or elastic easing, sampled into ${SAMPLES} linear steps per segment.`);
|
|
6129
|
+
}
|
|
6130
|
+
return { values, notes };
|
|
6131
|
+
}
|
|
6132
|
+
async function openVideoClipEffectAuthoring(controller, clip, instance, options = {}) {
|
|
6133
|
+
const provider = controller.effectsProvider;
|
|
6134
|
+
if (!provider) throw new Error("No effects provider is registered, so the effect cannot be opened for authoring; register one with setEffectsProvider.");
|
|
6135
|
+
const at = _chunk3OSXHLITcjs.findVideoClip.call(void 0, controller.getProject(), clip);
|
|
6136
|
+
if (!at) throw new Error(`There is no clip "${clip}".`);
|
|
6137
|
+
const doc = videoClipEffectDocument(at.clip, instance, provider.id);
|
|
6138
|
+
const session = await _chunk6DM3PUQDcjs.createEffectAuthoringSession.call(void 0, { document: doc }, { ...options, provider });
|
|
6139
|
+
return {
|
|
6140
|
+
clip,
|
|
6141
|
+
instance,
|
|
6142
|
+
session,
|
|
6143
|
+
async commit() {
|
|
6144
|
+
const { values, notes } = videoEffectValuesFromDocument(session.exportDocument());
|
|
6145
|
+
const result = await controller.updateClipEffect(clip, instance, { values });
|
|
6146
|
+
return result.ok && notes.length ? { ...result, notes } : result;
|
|
6147
|
+
},
|
|
6148
|
+
dispose: () => session.dispose()
|
|
6149
|
+
};
|
|
6150
|
+
}
|
|
6151
|
+
var videoAuthorableEffects = (clip) => _nullishCoalesce(clip.effects, () => ( []));
|
|
6152
|
+
|
|
4871
6153
|
// src/video-editor/editorDocument.ts
|
|
4872
6154
|
function createVideoEditorDocument(controller) {
|
|
4873
6155
|
return _chunkM4MIZPQIcjs.defineEditorDocument.call(void 0, {
|
|
@@ -5150,4 +6432,48 @@ function createVideoEditorDocument(controller) {
|
|
|
5150
6432
|
|
|
5151
6433
|
|
|
5152
6434
|
|
|
5153
|
-
exports.VIDEO_BLEND_MODES = _chunkVKF3PWKRcjs.VIDEO_BLEND_MODES; exports.VIDEO_BLEND_MODE_LABELS = VIDEO_BLEND_MODE_LABELS; exports.VIDEO_CLIP_ANIMATION_DURATION = _chunkVKF3PWKRcjs.VIDEO_CLIP_ANIMATION_DURATION; exports.VIDEO_CLIP_ANIMATION_EFFECTS = _chunkVKF3PWKRcjs.VIDEO_CLIP_ANIMATION_EFFECTS; exports.VIDEO_CLIP_ANIMATION_LABELS = VIDEO_CLIP_ANIMATION_LABELS; exports.VIDEO_CLIP_TYPES = _chunkVKF3PWKRcjs.VIDEO_CLIP_TYPES; exports.VIDEO_DEFAULT_FILLERS = VIDEO_DEFAULT_FILLERS; exports.VIDEO_DEFAULT_STILL_DURATION = VIDEO_DEFAULT_STILL_DURATION; exports.VIDEO_DEFAULT_TITLE_DURATION = VIDEO_DEFAULT_TITLE_DURATION; exports.VIDEO_EASE_NAMES = _chunkVKF3PWKRcjs.VIDEO_EASE_NAMES; exports.VIDEO_EDIT_OPS = VIDEO_EDIT_OPS; exports.VIDEO_EFFECT_PLACES = _chunkVKF3PWKRcjs.VIDEO_EFFECT_PLACES; exports.VIDEO_FADE_CURVES = _chunkVKF3PWKRcjs.VIDEO_FADE_CURVES; exports.VIDEO_FADE_CURVE_LABELS = VIDEO_FADE_CURVE_LABELS; exports.VIDEO_FITS = _chunkVKF3PWKRcjs.VIDEO_FITS; exports.VIDEO_GRADE_CONTROLS = _chunkVKF3PWKRcjs.VIDEO_GRADE_CONTROLS; exports.VIDEO_GRADE_PRESETS = _chunkVKF3PWKRcjs.VIDEO_GRADE_PRESETS; exports.VIDEO_GRADE_PRESET_NAMES = _chunkVKF3PWKRcjs.VIDEO_GRADE_PRESET_NAMES; exports.VIDEO_ISSUE_CODES = VIDEO_ISSUE_CODES; exports.VIDEO_KEYFRAME_LIMITS = _chunkVKF3PWKRcjs.VIDEO_KEYFRAME_LIMITS; exports.VIDEO_KEYFRAME_PROPERTIES = _chunkVKF3PWKRcjs.VIDEO_KEYFRAME_PROPERTIES; exports.VIDEO_LUT_MAX_SIZE = _chunkVKF3PWKRcjs.VIDEO_LUT_MAX_SIZE; exports.VIDEO_PEAK_BLOCK = VIDEO_PEAK_BLOCK; exports.VIDEO_PLACEMENT_MODES = _chunkVKF3PWKRcjs.VIDEO_PLACEMENT_MODES; exports.VIDEO_PREVIEW_SCALE_OPTIONS = VIDEO_PREVIEW_SCALE_OPTIONS; exports.VIDEO_PROXY_SCALES = VIDEO_PROXY_SCALES; exports.VIDEO_SCOPE_KINDS = VIDEO_SCOPE_KINDS; exports.VIDEO_TIME_EPSILON = _chunkVKF3PWKRcjs.VIDEO_TIME_EPSILON; exports.VIDEO_TRANSFORM_PROPERTIES = _chunkVKF3PWKRcjs.VIDEO_TRANSFORM_PROPERTIES; exports.VIDEO_TRANSITION_LABELS = VIDEO_TRANSITION_LABELS; exports.VIDEO_TRANSITION_TYPES = _chunkVKF3PWKRcjs.VIDEO_TRANSITION_TYPES; exports.VIDEO_VECTORSCOPE_TARGETS = VIDEO_VECTORSCOPE_TARGETS; exports.VIDEO_WIPE_DIRECTIONS = _chunkVKF3PWKRcjs.VIDEO_WIPE_DIRECTIONS; exports.VideoAudioPeaksCache = VideoAudioPeaksCache; exports.VideoEditorController = VideoEditorController; exports.VideoEffectsEngine = _chunkVKF3PWKRcjs.VideoEffectsEngine; exports.VideoFrameCache = VideoFrameCache; exports.VideoLutCache = _chunkVKF3PWKRcjs.VideoLutCache; exports.VideoPreviewEngine = VideoPreviewEngine; exports.VideoPreviewPixels = VideoPreviewPixels; exports.allVideoClipIds = _chunkVKF3PWKRcjs.allVideoClipIds; exports.allVideoTracks = _chunkVKF3PWKRcjs.allVideoTracks; exports.applyVideoEdits = applyVideoEdits; exports.applyVideoGrade = _chunkVKF3PWKRcjs.applyVideoGrade; exports.applyVideoLut = _chunkVKF3PWKRcjs.applyVideoLut; exports.applyVideoLutToPixels = _chunkVKF3PWKRcjs.applyVideoLutToPixels; exports.buildVideoAudioPeaks = buildVideoAudioPeaks; exports.checkVideoEffectPlace = _chunkVKF3PWKRcjs.checkVideoEffectPlace; exports.checkVideoEffectValues = _chunkVKF3PWKRcjs.checkVideoEffectValues; exports.checkVideoProject = checkVideoProject; exports.checkVideoTimeRemap = _chunkVKF3PWKRcjs.checkVideoTimeRemap; exports.createVideoEditorDocument = createVideoEditorDocument; exports.createVideoEditorToolProvider = createVideoEditorToolProvider; exports.createVideoEffectsToolProvider = createVideoEffectsToolProvider; exports.createVideoElementDecoder = createVideoElementDecoder; exports.createVideoProject = createVideoProject; exports.decodeVideoAudioPeaks = decodeVideoAudioPeaks; exports.describeVideoProject = describeVideoProject; exports.describeVideoSequence = describeVideoSequence; exports.drawVideoFrame = _chunkVKF3PWKRcjs.drawVideoFrame; exports.ffmpegFilterValue = _chunkVKF3PWKRcjs.ffmpegFilterValue; exports.findVideoAudioSilences = findVideoAudioSilences; exports.findVideoClip = _chunkVKF3PWKRcjs.findVideoClip; exports.findVideoFillers = findVideoFillers; exports.findVideoGaps = _chunkVKF3PWKRcjs.findVideoGaps; exports.findVideoSequenceClip = _chunkVKF3PWKRcjs.findVideoSequenceClip; exports.findVideoSequenceCycle = _chunkVKF3PWKRcjs.findVideoSequenceCycle; exports.findVideoTranscriptPauses = findVideoTranscriptPauses; exports.findVideoTranscriptText = findVideoTranscriptText; exports.formatVideoSeconds = _chunkVKF3PWKRcjs.formatVideoSeconds; exports.formatVideoTimecode = _chunkVKF3PWKRcjs.formatVideoTimecode; exports.formatVideoTimecodeFrames = _chunkVKF3PWKRcjs.formatVideoTimecodeFrames; exports.isVideoDropFrameRate = _chunkVKF3PWKRcjs.isVideoDropFrameRate; exports.isVideoKeyframes = _chunkVKF3PWKRcjs.isVideoKeyframes; exports.mergeVideoRanges = mergeVideoRanges; exports.mixEffectPixels = _chunkVKF3PWKRcjs.mixEffectPixels; exports.nextFreeVideoSlot = _chunkVKF3PWKRcjs.nextFreeVideoSlot; exports.normalizeVideoGrade = _chunkVKF3PWKRcjs.normalizeVideoGrade; exports.normalizeVideoGradeLut = _chunkVKF3PWKRcjs.normalizeVideoGradeLut; exports.normalizeVideoWord = normalizeVideoWord; exports.overEffectPixels = _chunkVKF3PWKRcjs.overEffectPixels; exports.parseVideoAnimatable = parseVideoAnimatable; exports.parseVideoAsset = parseVideoAsset; exports.parseVideoAssetProxies = parseVideoAssetProxies; exports.parseVideoClip = parseVideoClip; exports.parseVideoClipAnimation = parseVideoClipAnimation; exports.parseVideoClipEffects = parseVideoClipEffects; exports.parseVideoCubeLut = _chunkVKF3PWKRcjs.parseVideoCubeLut; exports.parseVideoEffectRef = parseVideoEffectRef; exports.parseVideoEffectValues = parseVideoEffectValues; exports.parseVideoGrade = parseVideoGrade; exports.parseVideoGradeLut = parseVideoGradeLut; exports.parseVideoKeyframes = parseVideoKeyframes; exports.parseVideoLoudnessMeasurement = _chunkVKF3PWKRcjs.parseVideoLoudnessMeasurement; exports.parseVideoMarker = parseVideoMarker; exports.parseVideoPreviewScale = parseVideoPreviewScale; exports.parseVideoProject = parseVideoProject; exports.parseVideoSequence = parseVideoSequence; exports.parseVideoTimeRef = parseVideoTimeRef; exports.parseVideoTimeRemap = parseVideoTimeRemap; exports.parseVideoTrack = parseVideoTrack; exports.parseVideoTranscript = parseVideoTranscript; exports.parseVideoTranscriptWords = parseVideoTranscriptWords; exports.parseVideoTransform = parseVideoTransform; exports.parseVideoTransition = parseVideoTransition; exports.placeVideoRect = _chunkVKF3PWKRcjs.placeVideoRect; exports.planVideoClipDrag = planVideoClipDrag; exports.planVideoPrefetch = planVideoPrefetch; exports.planVideoToolPlayback = planVideoToolPlayback; exports.probeVideoMedia = probeVideoMedia; exports.readVideoScopesPanel = readVideoScopesPanel; exports.removeVideoKey = _chunkVKF3PWKRcjs.removeVideoKey; exports.renderVideoFrameExact = _chunkVKF3PWKRcjs.renderVideoFrameExact; exports.resolveVideoPreviewScale = resolveVideoPreviewScale; exports.retimeVideoAnimatable = _chunkVKF3PWKRcjs.retimeVideoAnimatable; exports.setVideoKey = _chunkVKF3PWKRcjs.setVideoKey; exports.snapToVideoFrame = _chunkVKF3PWKRcjs.snapToVideoFrame; exports.snapVideoTime = _chunkVKF3PWKRcjs.snapVideoTime; exports.sortVideoKeys = _chunkVKF3PWKRcjs.sortVideoKeys; exports.sortedVideoClips = _chunkVKF3PWKRcjs.sortedVideoClips; exports.splitVideoAnimatable = _chunkVKF3PWKRcjs.splitVideoAnimatable; exports.summarizeVideoScopes = summarizeVideoScopes; exports.toVideoEdl = _chunkVKF3PWKRcjs.toVideoEdl; exports.toVideoFfmpeg = _chunkVKF3PWKRcjs.toVideoFfmpeg; exports.toVideoLoudnessMeasure = _chunkVKF3PWKRcjs.toVideoLoudnessMeasure; exports.videoAssetDropEdit = videoAssetDropEdit; exports.videoAssetHasSound = _chunkVKF3PWKRcjs.videoAssetHasSound; exports.videoAssetKindOf = videoAssetKindOf; exports.videoAssetMap = _chunkVKF3PWKRcjs.videoAssetMap; exports.videoAssetProxy = videoAssetProxy; exports.videoAudioLevels = videoAudioLevels; exports.videoAudioPeakRange = videoAudioPeakRange; exports.videoAudioPeakSpans = videoAudioPeakSpans; exports.videoAudioPeaksDuration = videoAudioPeaksDuration; exports.videoCacheEvictions = videoCacheEvictions; exports.videoChroma = videoChroma; exports.videoClipAfter = _chunkVKF3PWKRcjs.videoClipAfter; exports.videoClipAnimated = _chunkVKF3PWKRcjs.videoClipAnimated; exports.videoClipAnimationProperties = _chunkVKF3PWKRcjs.videoClipAnimationProperties; exports.videoClipAnimationSpans = _chunkVKF3PWKRcjs.videoClipAnimationSpans; exports.videoClipAppliedEffects = videoClipAppliedEffects; exports.videoClipBefore = _chunkVKF3PWKRcjs.videoClipBefore; exports.videoClipEffectUses = _chunkVKF3PWKRcjs.videoClipEffectUses; exports.videoClipEnd = _chunkVKF3PWKRcjs.videoClipEnd; exports.videoClipHandles = _chunkVKF3PWKRcjs.videoClipHandles; exports.videoClipKeyframes = videoClipKeyframes; exports.videoClipLabel = videoClipLabel; exports.videoClipOverhang = _chunkVKF3PWKRcjs.videoClipOverhang; exports.videoClipShowsWaveform = videoClipShowsWaveform; exports.videoClipSoundGain = _chunkVKF3PWKRcjs.videoClipSoundGain; exports.videoClipSourceRange = _chunkVKF3PWKRcjs.videoClipSourceRange; exports.videoClipSourceTime = _chunkVKF3PWKRcjs.videoClipSourceTime; exports.videoClipSpeed = _chunkVKF3PWKRcjs.videoClipSpeed; exports.videoClipSpeedAt = _chunkVKF3PWKRcjs.videoClipSpeedAt; exports.videoClipTint = videoClipTint; exports.videoClipWaveform = videoClipWaveform; exports.videoClipWaveformPath = videoClipWaveformPath; exports.videoClipsAt = _chunkVKF3PWKRcjs.videoClipsAt; exports.videoContactSheetSvg = _chunkVKF3PWKRcjs.videoContactSheetSvg; exports.videoDragLabel = videoDragLabel; exports.videoDropTracks = videoDropTracks; exports.videoEaseBezier = _chunkVKF3PWKRcjs.videoEaseBezier; exports.videoEaseProgress = _chunkVKF3PWKRcjs.videoEaseProgress; exports.videoEffectFrameTime = _chunkVKF3PWKRcjs.videoEffectFrameTime; exports.videoEffectLayerKey = _chunkVKF3PWKRcjs.videoEffectLayerKey; exports.videoEffectValuesAt = _chunkVKF3PWKRcjs.videoEffectValuesAt; exports.videoExposureNotes = videoExposureNotes; exports.videoExposureStats = videoExposureStats; exports.videoFadeCurveGain = _chunkVKF3PWKRcjs.videoFadeCurveGain; exports.videoFfmpegUnsupportedEffects = _chunkVKF3PWKRcjs.videoFfmpegUnsupportedEffects; exports.videoFrameAt = _chunkVKF3PWKRcjs.videoFrameAt; exports.videoFrameCacheKey = videoFrameCacheKey; exports.videoFrameDuration = _chunkVKF3PWKRcjs.videoFrameDuration; exports.videoFrameLeafLayers = _chunkVKF3PWKRcjs.videoFrameLeafLayers; exports.videoFrameRequestsAt = videoFrameRequestsAt; exports.videoFrameToSvg = _chunkVKF3PWKRcjs.videoFrameToSvg; exports.videoFrames = _chunkVKF3PWKRcjs.videoFrames; exports.videoFreezeFrameEdit = _chunkVKF3PWKRcjs.videoFreezeFrameEdit; exports.videoGradeCssFilter = _chunkVKF3PWKRcjs.videoGradeCssFilter; exports.videoGradeFfmpegFilters = _chunkVKF3PWKRcjs.videoGradeFfmpegFilters; exports.videoGradeGainsFilterId = _chunkVKF3PWKRcjs.videoGradeGainsFilterId; exports.videoGradeLook = _chunkVKF3PWKRcjs.videoGradeLook; exports.videoGradePreset = _chunkVKF3PWKRcjs.videoGradePreset; exports.videoGradeStepMatrix = _chunkVKF3PWKRcjs.videoGradeStepMatrix; exports.videoGradeStepSvgValues = _chunkVKF3PWKRcjs.videoGradeStepSvgValues; exports.videoGradeSteps = _chunkVKF3PWKRcjs.videoGradeSteps; exports.videoGradeSvgDefs = _chunkVKF3PWKRcjs.videoGradeSvgDefs; exports.videoGradeSvgFilter = _chunkVKF3PWKRcjs.videoGradeSvgFilter; exports.videoHistogram = videoHistogram; exports.videoHistogramImage = videoHistogramImage; exports.videoHourFrames = _chunkVKF3PWKRcjs.videoHourFrames; exports.videoIrisRadius = _chunkVKF3PWKRcjs.videoIrisRadius; exports.videoIssueKey = videoIssueKey; exports.videoKeyframeBreakpoints = _chunkVKF3PWKRcjs.videoKeyframeBreakpoints; exports.videoKeyframeFfmpegExpr = _chunkVKF3PWKRcjs.videoKeyframeFfmpegExpr; exports.videoKeyframeRows = videoKeyframeRows; exports.videoKeyframeToggleEdit = videoKeyframeToggleEdit; exports.videoKeyframeValueEdit = videoKeyframeValueEdit; exports.videoKeysOf = _chunkVKF3PWKRcjs.videoKeysOf; exports.videoLayersUseEffects = _chunkVKF3PWKRcjs.videoLayersUseEffects; exports.videoLayersWithoutTransitionEffects = _chunkVKF3PWKRcjs.videoLayersWithoutTransitionEffects; exports.videoLuma = videoLuma; exports.videoLumaWaveform = videoLumaWaveform; exports.videoLutEdits = videoLutEdits; exports.videoLutFromFunction = _chunkVKF3PWKRcjs.videoLutFromFunction; exports.videoLutSvgApproximation = _chunkVKF3PWKRcjs.videoLutSvgApproximation; exports.videoLutSvgPrimitives = _chunkVKF3PWKRcjs.videoLutSvgPrimitives; exports.videoLutToCube = _chunkVKF3PWKRcjs.videoLutToCube; exports.videoOverlap = _chunkVKF3PWKRcjs.videoOverlap; exports.videoPeakDb = videoPeakDb; exports.videoPreviewSrc = videoPreviewSrc; exports.videoProjectDuration = _chunkVKF3PWKRcjs.videoProjectDuration; exports.videoProjectEffects = _chunkVKF3PWKRcjs.videoProjectEffects; exports.videoProjectHasProxies = videoProjectHasProxies; exports.videoProjectSvg = _chunkVKF3PWKRcjs.videoProjectSvg; exports.videoProxyFfmpeg = videoProxyFfmpeg; exports.videoProxyName = videoProxyName; exports.videoProxyPlan = videoProxyPlan; exports.videoRemoveRangesEdits = videoRemoveRangesEdits; exports.videoRulerTicks = videoRulerTicks; exports.videoScopeImage = videoScopeImage; exports.videoSequenceChildren = _chunkVKF3PWKRcjs.videoSequenceChildren; exports.videoSequenceDuration = _chunkVKF3PWKRcjs.videoSequenceDuration; exports.videoSequenceMap = _chunkVKF3PWKRcjs.videoSequenceMap; exports.videoShellQuote = _chunkVKF3PWKRcjs.videoShellQuote; exports.videoSlowMotionRamp = _chunkVKF3PWKRcjs.videoSlowMotionRamp; exports.videoSnapPoints = _chunkVKF3PWKRcjs.videoSnapPoints; exports.videoSourceFrame = videoSourceFrame; exports.videoSourceFrameTime = videoSourceFrameTime; exports.videoSpeedRampEdit = _chunkVKF3PWKRcjs.videoSpeedRampEdit; exports.videoSpeedRampKeys = _chunkVKF3PWKRcjs.videoSpeedRampKeys; exports.videoTextStyle = _chunkVKF3PWKRcjs.videoTextStyle; exports.videoTimeRef = videoTimeRef; exports.videoTimeRemapBreakpoints = _chunkVKF3PWKRcjs.videoTimeRemapBreakpoints; exports.videoTimeRemapSpan = _chunkVKF3PWKRcjs.videoTimeRemapSpan; exports.videoTimecodeFrames = _chunkVKF3PWKRcjs.videoTimecodeFrames; exports.videoTimelineTranscript = videoTimelineTranscript; exports.videoTitleFrame = _chunkVKF3PWKRcjs.videoTitleFrame; exports.videoTrackSyncLocked = _chunkVKF3PWKRcjs.videoTrackSyncLocked; exports.videoTranscriptEdit = videoTranscriptEdit; exports.videoTranscriptTrack = videoTranscriptTrack; exports.videoTransformAt = _chunkVKF3PWKRcjs.videoTransformAt; exports.videoTransitionAudioCurve = _chunkVKF3PWKRcjs.videoTransitionAudioCurve; exports.videoTransitionEase = _chunkVKF3PWKRcjs.videoTransitionEase; exports.videoTransitionHasDirection = videoTransitionHasDirection; exports.videoValueAt = _chunkVKF3PWKRcjs.videoValueAt; exports.videoVectorscope = videoVectorscope; exports.videoVectorscopeAngle = videoVectorscopeAngle; exports.videoVectorscopeImage = videoVectorscopeImage; exports.videoVectorscopePoint = videoVectorscopePoint; exports.videoWaveformImage = videoWaveformImage; exports.videoWaveformPath = videoWaveformPath; exports.videoWipeRect = _chunkVKF3PWKRcjs.videoWipeRect; exports.videoWordAt = videoWordAt; exports.videoWordsRange = videoWordsRange; exports.withVideoPlayback = withVideoPlayback;
|
|
6435
|
+
|
|
6436
|
+
|
|
6437
|
+
|
|
6438
|
+
|
|
6439
|
+
|
|
6440
|
+
|
|
6441
|
+
|
|
6442
|
+
|
|
6443
|
+
|
|
6444
|
+
|
|
6445
|
+
|
|
6446
|
+
|
|
6447
|
+
|
|
6448
|
+
|
|
6449
|
+
|
|
6450
|
+
|
|
6451
|
+
|
|
6452
|
+
|
|
6453
|
+
|
|
6454
|
+
|
|
6455
|
+
|
|
6456
|
+
|
|
6457
|
+
|
|
6458
|
+
|
|
6459
|
+
|
|
6460
|
+
|
|
6461
|
+
|
|
6462
|
+
|
|
6463
|
+
|
|
6464
|
+
|
|
6465
|
+
|
|
6466
|
+
|
|
6467
|
+
|
|
6468
|
+
|
|
6469
|
+
|
|
6470
|
+
|
|
6471
|
+
|
|
6472
|
+
|
|
6473
|
+
|
|
6474
|
+
|
|
6475
|
+
|
|
6476
|
+
|
|
6477
|
+
|
|
6478
|
+
|
|
6479
|
+
exports.TranscriptVideoController = TranscriptVideoController; exports.VIDEO_ADJUSTMENT_SCOPES = _chunk3OSXHLITcjs.VIDEO_ADJUSTMENT_SCOPES; exports.VIDEO_ADJUSTMENT_SCOPE_LABELS = VIDEO_ADJUSTMENT_SCOPE_LABELS; exports.VIDEO_BLEND_MODES = _chunk3OSXHLITcjs.VIDEO_BLEND_MODES; exports.VIDEO_BLEND_MODE_LABELS = VIDEO_BLEND_MODE_LABELS; exports.VIDEO_CLIP_ANIMATION_DURATION = _chunk3OSXHLITcjs.VIDEO_CLIP_ANIMATION_DURATION; exports.VIDEO_CLIP_ANIMATION_EFFECTS = _chunk3OSXHLITcjs.VIDEO_CLIP_ANIMATION_EFFECTS; exports.VIDEO_CLIP_ANIMATION_LABELS = VIDEO_CLIP_ANIMATION_LABELS; exports.VIDEO_CLIP_TYPES = _chunk3OSXHLITcjs.VIDEO_CLIP_TYPES; exports.VIDEO_DEFAULT_FILLERS = VIDEO_DEFAULT_FILLERS; exports.VIDEO_DEFAULT_STILL_DURATION = VIDEO_DEFAULT_STILL_DURATION; exports.VIDEO_DEFAULT_TITLE_DURATION = VIDEO_DEFAULT_TITLE_DURATION; exports.VIDEO_DISFLUENCY_KINDS = VIDEO_DISFLUENCY_KINDS; exports.VIDEO_EASE_NAMES = _chunk3OSXHLITcjs.VIDEO_EASE_NAMES; exports.VIDEO_EDIT_OPS = VIDEO_EDIT_OPS; exports.VIDEO_EFFECT_PLACES = _chunk3OSXHLITcjs.VIDEO_EFFECT_PLACES; exports.VIDEO_FADE_CURVES = _chunk3OSXHLITcjs.VIDEO_FADE_CURVES; exports.VIDEO_FADE_CURVE_LABELS = VIDEO_FADE_CURVE_LABELS; exports.VIDEO_FITS = _chunk3OSXHLITcjs.VIDEO_FITS; exports.VIDEO_GRADE_CONTROLS = _chunk3OSXHLITcjs.VIDEO_GRADE_CONTROLS; exports.VIDEO_GRADE_PRESETS = _chunk3OSXHLITcjs.VIDEO_GRADE_PRESETS; exports.VIDEO_GRADE_PRESET_NAMES = _chunk3OSXHLITcjs.VIDEO_GRADE_PRESET_NAMES; exports.VIDEO_ISSUE_CODES = VIDEO_ISSUE_CODES; exports.VIDEO_KEYFRAME_LIMITS = _chunk3OSXHLITcjs.VIDEO_KEYFRAME_LIMITS; exports.VIDEO_KEYFRAME_PROPERTIES = _chunk3OSXHLITcjs.VIDEO_KEYFRAME_PROPERTIES; exports.VIDEO_LOUDNESS_HOP = VIDEO_LOUDNESS_HOP; exports.VIDEO_LUT_MAX_SIZE = _chunk3OSXHLITcjs.VIDEO_LUT_MAX_SIZE; exports.VIDEO_MASK_DEFAULTS = _chunk3OSXHLITcjs.VIDEO_MASK_DEFAULTS; exports.VIDEO_MASK_SHAPES = _chunk3OSXHLITcjs.VIDEO_MASK_SHAPES; exports.VIDEO_MASK_SHAPE_LABELS = VIDEO_MASK_SHAPE_LABELS; exports.VIDEO_PEAK_BLOCK = VIDEO_PEAK_BLOCK; exports.VIDEO_PLACEMENT_MODES = _chunk3OSXHLITcjs.VIDEO_PLACEMENT_MODES; exports.VIDEO_PREVIEW_SCALE_OPTIONS = VIDEO_PREVIEW_SCALE_OPTIONS; exports.VIDEO_PROXY_SCALES = VIDEO_PROXY_SCALES; exports.VIDEO_SCOPE_KINDS = VIDEO_SCOPE_KINDS; exports.VIDEO_TIME_EPSILON = _chunk3OSXHLITcjs.VIDEO_TIME_EPSILON; exports.VIDEO_TRANSFORM_PROPERTIES = _chunk3OSXHLITcjs.VIDEO_TRANSFORM_PROPERTIES; exports.VIDEO_TRANSITION_LABELS = VIDEO_TRANSITION_LABELS; exports.VIDEO_TRANSITION_TYPES = _chunk3OSXHLITcjs.VIDEO_TRANSITION_TYPES; exports.VIDEO_VECTORSCOPE_TARGETS = VIDEO_VECTORSCOPE_TARGETS; exports.VIDEO_WIPE_DIRECTIONS = _chunk3OSXHLITcjs.VIDEO_WIPE_DIRECTIONS; exports.VideoAudioPeaksCache = VideoAudioPeaksCache; exports.VideoEditorController = VideoEditorController; exports.VideoEffectsEngine = _chunk3OSXHLITcjs.VideoEffectsEngine; exports.VideoFrameCache = VideoFrameCache; exports.VideoLutCache = _chunk3OSXHLITcjs.VideoLutCache; exports.VideoPreviewEngine = VideoPreviewEngine; exports.VideoPreviewPixels = VideoPreviewPixels; exports.allVideoClipIds = _chunk3OSXHLITcjs.allVideoClipIds; exports.allVideoTracks = _chunk3OSXHLITcjs.allVideoTracks; exports.animationEasingToVideoEase = animationEasingToVideoEase; exports.applyVideoEdits = applyVideoEdits; exports.applyVideoGrade = _chunk3OSXHLITcjs.applyVideoGrade; exports.applyVideoLut = _chunk3OSXHLITcjs.applyVideoLut; exports.applyVideoLutToPixels = _chunk3OSXHLITcjs.applyVideoLutToPixels; exports.buildVideoAudioPeaks = buildVideoAudioPeaks; exports.checkVideoEffectPlace = _chunk3OSXHLITcjs.checkVideoEffectPlace; exports.checkVideoEffectValues = _chunk3OSXHLITcjs.checkVideoEffectValues; exports.checkVideoProject = checkVideoProject; exports.checkVideoTimeRemap = _chunk3OSXHLITcjs.checkVideoTimeRemap; exports.createVideoDialogueToolProvider = createVideoDialogueToolProvider; exports.createVideoEditorDocument = createVideoEditorDocument; exports.createVideoEditorToolProvider = createVideoEditorToolProvider; exports.createVideoEffectsToolProvider = createVideoEffectsToolProvider; exports.createVideoElementDecoder = createVideoElementDecoder; exports.createVideoMusicToolProvider = createVideoMusicToolProvider; exports.createVideoProject = createVideoProject; exports.decodeVideoAudioPeaks = decodeVideoAudioPeaks; exports.describeVideoEditPlan = describeVideoEditPlan; exports.describeVideoProject = describeVideoProject; exports.describeVideoSequence = describeVideoSequence; exports.detectVideoDisfluencies = detectVideoDisfluencies; exports.diffVideoProjects = diffVideoProjects; exports.drawVideoFrame = _chunk3OSXHLITcjs.drawVideoFrame; exports.ffmpegFilterValue = _chunk3OSXHLITcjs.ffmpegFilterValue; exports.findVideoAudioBeats = findVideoAudioBeats; exports.findVideoAudioSilences = findVideoAudioSilences; exports.findVideoClip = _chunk3OSXHLITcjs.findVideoClip; exports.findVideoFillers = findVideoFillers; exports.findVideoGaps = _chunk3OSXHLITcjs.findVideoGaps; exports.findVideoRepetitions = findVideoRepetitions; exports.findVideoSequenceClip = _chunk3OSXHLITcjs.findVideoSequenceClip; exports.findVideoSequenceCycle = _chunk3OSXHLITcjs.findVideoSequenceCycle; exports.findVideoTranscriptPauses = findVideoTranscriptPauses; exports.findVideoTranscriptText = findVideoTranscriptText; exports.formatVideoSeconds = _chunk3OSXHLITcjs.formatVideoSeconds; exports.formatVideoTimecode = _chunk3OSXHLITcjs.formatVideoTimecode; exports.formatVideoTimecodeFrames = _chunk3OSXHLITcjs.formatVideoTimecodeFrames; exports.isVideoDropFrameRate = _chunk3OSXHLITcjs.isVideoDropFrameRate; exports.isVideoKeyframes = _chunk3OSXHLITcjs.isVideoKeyframes; exports.measureVideoLoudness = measureVideoLoudness; exports.mergeVideoRanges = mergeVideoRanges; exports.mixEffectPixels = _chunk3OSXHLITcjs.mixEffectPixels; exports.nearestVideoBeat = nearestVideoBeat; exports.nextFreeVideoSlot = _chunk3OSXHLITcjs.nextFreeVideoSlot; exports.normalizeVideoClipMask = _chunk3OSXHLITcjs.normalizeVideoClipMask; exports.normalizeVideoGrade = _chunk3OSXHLITcjs.normalizeVideoGrade; exports.normalizeVideoGradeLut = _chunk3OSXHLITcjs.normalizeVideoGradeLut; exports.normalizeVideoWord = normalizeVideoWord; exports.openVideoClipEffectAuthoring = openVideoClipEffectAuthoring; exports.overEffectPixels = _chunk3OSXHLITcjs.overEffectPixels; exports.parseVideoAnimatable = parseVideoAnimatable; exports.parseVideoAsset = parseVideoAsset; exports.parseVideoAssetProxies = parseVideoAssetProxies; exports.parseVideoClip = parseVideoClip; exports.parseVideoClipAnimation = parseVideoClipAnimation; exports.parseVideoClipEffects = parseVideoClipEffects; exports.parseVideoClipMask = parseVideoClipMask; exports.parseVideoCubeLut = _chunk3OSXHLITcjs.parseVideoCubeLut; exports.parseVideoEffectRef = parseVideoEffectRef; exports.parseVideoEffectValues = parseVideoEffectValues; exports.parseVideoGrade = parseVideoGrade; exports.parseVideoGradeLut = parseVideoGradeLut; exports.parseVideoKeyframes = parseVideoKeyframes; exports.parseVideoLoudnessMeasurement = _chunk3OSXHLITcjs.parseVideoLoudnessMeasurement; exports.parseVideoMarker = parseVideoMarker; exports.parseVideoPreviewScale = parseVideoPreviewScale; exports.parseVideoProject = parseVideoProject; exports.parseVideoSequence = parseVideoSequence; exports.parseVideoTimeRef = parseVideoTimeRef; exports.parseVideoTimeRemap = parseVideoTimeRemap; exports.parseVideoTrack = parseVideoTrack; exports.parseVideoTranscript = parseVideoTranscript; exports.parseVideoTranscriptWords = parseVideoTranscriptWords; exports.parseVideoTransform = parseVideoTransform; exports.parseVideoTransition = parseVideoTransition; exports.placeVideoRect = _chunk3OSXHLITcjs.placeVideoRect; exports.planVideoClipDrag = planVideoClipDrag; exports.planVideoEdits = planVideoEdits; exports.planVideoPrefetch = planVideoPrefetch; exports.planVideoToolPlayback = planVideoToolPlayback; exports.probeVideoMedia = probeVideoMedia; exports.readVideoScopesPanel = readVideoScopesPanel; exports.removeVideoKey = _chunk3OSXHLITcjs.removeVideoKey; exports.renderVideoFrameExact = _chunk3OSXHLITcjs.renderVideoFrameExact; exports.resolveVideoPreviewScale = resolveVideoPreviewScale; exports.retimeVideoAnimatable = _chunk3OSXHLITcjs.retimeVideoAnimatable; exports.setVideoKey = _chunk3OSXHLITcjs.setVideoKey; exports.snapToVideoFrame = _chunk3OSXHLITcjs.snapToVideoFrame; exports.snapVideoTime = _chunk3OSXHLITcjs.snapVideoTime; exports.sortVideoKeys = _chunk3OSXHLITcjs.sortVideoKeys; exports.sortedVideoClips = _chunk3OSXHLITcjs.sortedVideoClips; exports.splitVideoAnimatable = _chunk3OSXHLITcjs.splitVideoAnimatable; exports.summarizeVideoScopes = summarizeVideoScopes; exports.toVideoEdl = _chunk3OSXHLITcjs.toVideoEdl; exports.toVideoFfmpeg = _chunk3OSXHLITcjs.toVideoFfmpeg; exports.toVideoLoudnessMeasure = _chunk3OSXHLITcjs.toVideoLoudnessMeasure; exports.videoAddAdjustmentEdits = videoAddAdjustmentEdits; exports.videoAssetDropEdit = videoAssetDropEdit; exports.videoAssetHasSound = _chunk3OSXHLITcjs.videoAssetHasSound; exports.videoAssetKindOf = videoAssetKindOf; exports.videoAssetMap = _chunk3OSXHLITcjs.videoAssetMap; exports.videoAssetProxy = videoAssetProxy; exports.videoAudioLevels = videoAudioLevels; exports.videoAudioLoudness = videoAudioLoudness; exports.videoAudioPeakRange = videoAudioPeakRange; exports.videoAudioPeakSpans = videoAudioPeakSpans; exports.videoAudioPeaksDuration = videoAudioPeaksDuration; exports.videoAuthorableEffects = videoAuthorableEffects; exports.videoCacheEvictions = videoCacheEvictions; exports.videoChroma = videoChroma; exports.videoClipAfter = _chunk3OSXHLITcjs.videoClipAfter; exports.videoClipAnimated = _chunk3OSXHLITcjs.videoClipAnimated; exports.videoClipAnimationProperties = _chunk3OSXHLITcjs.videoClipAnimationProperties; exports.videoClipAnimationSpans = _chunk3OSXHLITcjs.videoClipAnimationSpans; exports.videoClipAppliedEffects = videoClipAppliedEffects; exports.videoClipBefore = _chunk3OSXHLITcjs.videoClipBefore; exports.videoClipEffectDocument = videoClipEffectDocument; exports.videoClipEffectUses = _chunk3OSXHLITcjs.videoClipEffectUses; exports.videoClipEnd = _chunk3OSXHLITcjs.videoClipEnd; exports.videoClipHandles = _chunk3OSXHLITcjs.videoClipHandles; exports.videoClipHeading = videoClipHeading; exports.videoClipKeyframes = videoClipKeyframes; exports.videoClipLabel = videoClipLabel; exports.videoClipOverhang = _chunk3OSXHLITcjs.videoClipOverhang; exports.videoClipShowsWaveform = videoClipShowsWaveform; exports.videoClipSoundGain = _chunk3OSXHLITcjs.videoClipSoundGain; exports.videoClipSourceRange = _chunk3OSXHLITcjs.videoClipSourceRange; exports.videoClipSourceTime = _chunk3OSXHLITcjs.videoClipSourceTime; exports.videoClipSpeed = _chunk3OSXHLITcjs.videoClipSpeed; exports.videoClipSpeedAt = _chunk3OSXHLITcjs.videoClipSpeedAt; exports.videoClipTint = videoClipTint; exports.videoClipTypeLabel = _chunk3OSXHLITcjs.videoClipTypeLabel; exports.videoClipWaveform = videoClipWaveform; exports.videoClipWaveformPath = videoClipWaveformPath; exports.videoClipsAt = _chunk3OSXHLITcjs.videoClipsAt; exports.videoContactSheetSvg = _chunk3OSXHLITcjs.videoContactSheetSvg; exports.videoCutsWithMargin = videoCutsWithMargin; exports.videoDragLabel = videoDragLabel; exports.videoDropTracks = videoDropTracks; exports.videoDuckingKeys = videoDuckingKeys; exports.videoEaseBezier = _chunk3OSXHLITcjs.videoEaseBezier; exports.videoEaseProgress = _chunk3OSXHLITcjs.videoEaseProgress; exports.videoEaseToAnimationEasing = videoEaseToAnimationEasing; exports.videoEffectFrameTime = _chunk3OSXHLITcjs.videoEffectFrameTime; exports.videoEffectLayerKey = _chunk3OSXHLITcjs.videoEffectLayerKey; exports.videoEffectValuesAt = _chunk3OSXHLITcjs.videoEffectValuesAt; exports.videoEffectValuesFromDocument = videoEffectValuesFromDocument; exports.videoExposureNotes = videoExposureNotes; exports.videoExposureStats = videoExposureStats; exports.videoFadeCurveGain = _chunk3OSXHLITcjs.videoFadeCurveGain; exports.videoFfmpegUnsupportedEffects = _chunk3OSXHLITcjs.videoFfmpegUnsupportedEffects; exports.videoFfmpegUnsupportedFeatures = _chunk3OSXHLITcjs.videoFfmpegUnsupportedFeatures; exports.videoFrameAt = _chunk3OSXHLITcjs.videoFrameAt; exports.videoFrameCacheKey = videoFrameCacheKey; exports.videoFrameDuration = _chunk3OSXHLITcjs.videoFrameDuration; exports.videoFrameLeafLayers = _chunk3OSXHLITcjs.videoFrameLeafLayers; exports.videoFrameRequestsAt = videoFrameRequestsAt; exports.videoFrameToSvg = _chunk3OSXHLITcjs.videoFrameToSvg; exports.videoFrames = _chunk3OSXHLITcjs.videoFrames; exports.videoFreezeFrameEdit = _chunk3OSXHLITcjs.videoFreezeFrameEdit; exports.videoGradeCssFilter = _chunk3OSXHLITcjs.videoGradeCssFilter; exports.videoGradeFfmpegFilters = _chunk3OSXHLITcjs.videoGradeFfmpegFilters; exports.videoGradeGainsFilterId = _chunk3OSXHLITcjs.videoGradeGainsFilterId; exports.videoGradeLook = _chunk3OSXHLITcjs.videoGradeLook; exports.videoGradePreset = _chunk3OSXHLITcjs.videoGradePreset; exports.videoGradeStepMatrix = _chunk3OSXHLITcjs.videoGradeStepMatrix; exports.videoGradeStepSvgValues = _chunk3OSXHLITcjs.videoGradeStepSvgValues; exports.videoGradeSteps = _chunk3OSXHLITcjs.videoGradeSteps; exports.videoGradeSvgDefs = _chunk3OSXHLITcjs.videoGradeSvgDefs; exports.videoGradeSvgFilter = _chunk3OSXHLITcjs.videoGradeSvgFilter; exports.videoHistogram = videoHistogram; exports.videoHistogramImage = videoHistogramImage; exports.videoHourFrames = _chunk3OSXHLITcjs.videoHourFrames; exports.videoIrisRadius = _chunk3OSXHLITcjs.videoIrisRadius; exports.videoIssueKey = videoIssueKey; exports.videoKeyframeBreakpoints = _chunk3OSXHLITcjs.videoKeyframeBreakpoints; exports.videoKeyframeFfmpegExpr = _chunk3OSXHLITcjs.videoKeyframeFfmpegExpr; exports.videoKeyframeRows = videoKeyframeRows; exports.videoKeyframeToggleEdit = videoKeyframeToggleEdit; exports.videoKeyframeValueEdit = videoKeyframeValueEdit; exports.videoKeysOf = _chunk3OSXHLITcjs.videoKeysOf; exports.videoLayerMask = _chunk3OSXHLITcjs.videoLayerMask; exports.videoLayersUseEffects = _chunk3OSXHLITcjs.videoLayersUseEffects; exports.videoLayersWithoutTransitionEffects = _chunk3OSXHLITcjs.videoLayersWithoutTransitionEffects; exports.videoLoudnessEnergy = videoLoudnessEnergy; exports.videoLoudnessGain = videoLoudnessGain; exports.videoLoudnessOf = videoLoudnessOf; exports.videoLuma = videoLuma; exports.videoLumaWaveform = videoLumaWaveform; exports.videoLutEdits = videoLutEdits; exports.videoLutFromFunction = _chunk3OSXHLITcjs.videoLutFromFunction; exports.videoLutSvgApproximation = _chunk3OSXHLITcjs.videoLutSvgApproximation; exports.videoLutSvgPrimitives = _chunk3OSXHLITcjs.videoLutSvgPrimitives; exports.videoLutToCube = _chunk3OSXHLITcjs.videoLutToCube; exports.videoMaskContains = _chunk3OSXHLITcjs.videoMaskContains; exports.videoMaskEdit = videoMaskEdit; exports.videoMaskPolygon = _chunk3OSXHLITcjs.videoMaskPolygon; exports.videoMaskSvgPath = _chunk3OSXHLITcjs.videoMaskSvgPath; exports.videoOverlap = _chunk3OSXHLITcjs.videoOverlap; exports.videoPeakDb = videoPeakDb; exports.videoPreviewSrc = videoPreviewSrc; exports.videoProjectDuration = _chunk3OSXHLITcjs.videoProjectDuration; exports.videoProjectEffects = _chunk3OSXHLITcjs.videoProjectEffects; exports.videoProjectHasProxies = videoProjectHasProxies; exports.videoProjectSvg = _chunk3OSXHLITcjs.videoProjectSvg; exports.videoProxyFfmpeg = videoProxyFfmpeg; exports.videoProxyName = videoProxyName; exports.videoProxyPlan = videoProxyPlan; exports.videoRemoveRangesEdits = videoRemoveRangesEdits; exports.videoRulerTicks = videoRulerTicks; exports.videoScopeImage = videoScopeImage; exports.videoSequenceChildren = _chunk3OSXHLITcjs.videoSequenceChildren; exports.videoSequenceDuration = _chunk3OSXHLITcjs.videoSequenceDuration; exports.videoSequenceMap = _chunk3OSXHLITcjs.videoSequenceMap; exports.videoShellQuote = _chunk3OSXHLITcjs.videoShellQuote; exports.videoSkinProtectionEdits = videoSkinProtectionEdits; exports.videoSkinToneWeight = _chunk3OSXHLITcjs.videoSkinToneWeight; exports.videoSlowMotionRamp = _chunk3OSXHLITcjs.videoSlowMotionRamp; exports.videoSnapPoints = _chunk3OSXHLITcjs.videoSnapPoints; exports.videoSourceFrame = videoSourceFrame; exports.videoSourceFrameTime = videoSourceFrameTime; exports.videoSpeechRanges = videoSpeechRanges; exports.videoSpeedRampEdit = _chunk3OSXHLITcjs.videoSpeedRampEdit; exports.videoSpeedRampKeys = _chunk3OSXHLITcjs.videoSpeedRampKeys; exports.videoTextStyle = _chunk3OSXHLITcjs.videoTextStyle; exports.videoTimeRef = videoTimeRef; exports.videoTimeRemapBreakpoints = _chunk3OSXHLITcjs.videoTimeRemapBreakpoints; exports.videoTimeRemapSpan = _chunk3OSXHLITcjs.videoTimeRemapSpan; exports.videoTimecodeFrames = _chunk3OSXHLITcjs.videoTimecodeFrames; exports.videoTimelineTranscript = videoTimelineTranscript; exports.videoTitleFrame = _chunk3OSXHLITcjs.videoTitleFrame; exports.videoTrackSyncLocked = _chunk3OSXHLITcjs.videoTrackSyncLocked; exports.videoTranscriptEdit = videoTranscriptEdit; exports.videoTranscriptTrack = videoTranscriptTrack; exports.videoTransformAt = _chunk3OSXHLITcjs.videoTransformAt; exports.videoTransitionAudioCurve = _chunk3OSXHLITcjs.videoTransitionAudioCurve; exports.videoTransitionEase = _chunk3OSXHLITcjs.videoTransitionEase; exports.videoTransitionHasDirection = videoTransitionHasDirection; exports.videoValueAt = _chunk3OSXHLITcjs.videoValueAt; exports.videoVectorscope = videoVectorscope; exports.videoVectorscopeAngle = videoVectorscopeAngle; exports.videoVectorscopeImage = videoVectorscopeImage; exports.videoVectorscopePoint = videoVectorscopePoint; exports.videoWaveformImage = videoWaveformImage; exports.videoWaveformPath = videoWaveformPath; exports.videoWipeRect = _chunk3OSXHLITcjs.videoWipeRect; exports.videoWordAt = videoWordAt; exports.videoWordsRange = videoWordsRange; exports.withVideoPlayback = withVideoPlayback;
|