@pixerate/editor 0.123.0 → 0.124.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 +2 -2
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +3 -3
- package/dist/animation/index.d.cts +2 -1
- package/dist/animation/index.d.ts +2 -1
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +11 -733
- package/dist/animation-editor/index.d.ts +11 -733
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{bezier-C-W9LFyD.d.cts → bezier-CBzKKDjl.d.cts} +2 -66
- package/dist/{bezier-DQZE5dxA.d.ts → bezier-CWv6LQal.d.ts} +2 -66
- package/dist/{brushstrokeRender-M4S65DHT.cjs → brushstrokeRender-EELTV7U4.cjs} +8 -8
- package/dist/{brushstrokeRender-M4S65DHT.cjs.map → brushstrokeRender-EELTV7U4.cjs.map} +1 -1
- package/dist/{brushstrokeRender-CDDRSV2W.js → brushstrokeRender-LRV7GXGD.js} +3 -3
- package/dist/canvas/index.cjs +6 -6
- package/dist/canvas/index.d.cts +10 -6
- package/dist/canvas/index.d.ts +10 -6
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-TVXFHE4T.js → chunk-64PYNEHJ.js} +45 -12
- package/dist/chunk-64PYNEHJ.js.map +1 -0
- package/dist/{chunk-2IW3BWRT.cjs → chunk-7LQO6OHH.cjs} +2 -2
- package/dist/{chunk-2IW3BWRT.cjs.map → chunk-7LQO6OHH.cjs.map} +1 -1
- package/dist/chunk-IOXKXQNW.cjs +178 -0
- package/dist/chunk-IOXKXQNW.cjs.map +1 -0
- package/dist/{chunk-SMJGE7HM.cjs → chunk-IWSMDQE5.cjs} +523 -39
- package/dist/chunk-IWSMDQE5.cjs.map +1 -0
- package/dist/{chunk-KMBC4HNV.js → chunk-J67OLQUQ.js} +2 -2
- package/dist/{chunk-6CJRDJCU.cjs → chunk-PEHYWUVV.cjs} +176 -111
- package/dist/chunk-PEHYWUVV.cjs.map +1 -0
- package/dist/chunk-TGKT5QAB.js +178 -0
- package/dist/chunk-TGKT5QAB.js.map +1 -0
- package/dist/chunk-TK53AB4Q.cjs +215 -0
- package/dist/chunk-TK53AB4Q.cjs.map +1 -0
- package/dist/chunk-VFAKN46Z.js +215 -0
- package/dist/chunk-VFAKN46Z.js.map +1 -0
- package/dist/{chunk-6ZBR4J62.js → chunk-WATAQ2Z2.js} +79 -14
- package/dist/chunk-WATAQ2Z2.js.map +1 -0
- package/dist/{chunk-IPFRWILU.js → chunk-YVVSTD3B.js} +499 -15
- package/dist/chunk-YVVSTD3B.js.map +1 -0
- package/dist/{chunk-RLRUAPBJ.cjs → chunk-Z6OUCS2K.cjs} +47 -14
- package/dist/chunk-Z6OUCS2K.cjs.map +1 -0
- package/dist/effects/index.cjs +588 -0
- package/dist/effects/index.cjs.map +1 -0
- package/dist/effects/index.d.cts +131 -0
- package/dist/effects/index.d.ts +131 -0
- package/dist/effects/index.js +588 -0
- package/dist/effects/index.js.map +1 -0
- package/dist/evaluate-2S3vSPLy.d.cts +55 -0
- package/dist/evaluate-CfmT6xA3.d.ts +55 -0
- package/dist/{export-DfjzHAJI.d.ts → export-8RnlngAv.d.ts} +534 -128
- package/dist/{export-Dv0-ZVrV.d.ts → export-BzSRXyH6.d.cts} +390 -5
- package/dist/{export-Dv0-ZVrV.d.cts → export-DM3h7QEI.d.ts} +390 -5
- package/dist/{export-lU5qSpHE.d.cts → export-TqXMOlDt.d.cts} +534 -128
- package/dist/flair-CxB3fOcU.d.ts +739 -0
- package/dist/flair-D7BxyBwL.d.cts +739 -0
- package/dist/gradient-KDfCvhuj.d.cts +66 -0
- package/dist/gradient-KDfCvhuj.d.ts +66 -0
- package/dist/helpers-777mC6dc.d.cts +253 -0
- package/dist/helpers-DX2_7L5t.d.ts +253 -0
- package/dist/image-editor/index.cjs +1307 -805
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +39 -182
- package/dist/image-editor/index.d.ts +39 -182
- package/dist/image-editor/index.js +508 -6
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{opentype-p8Xxf-Wh.d.ts → opentype-14PDI5D2.d.ts} +1 -1
- package/dist/{opentype-BV7wWatY.d.cts → opentype-D80AVmMu.d.cts} +1 -1
- package/dist/paint/index.cjs +2 -2
- package/dist/paint/index.d.cts +32 -10
- package/dist/paint/index.d.ts +32 -10
- package/dist/paint/index.js +1 -1
- package/dist/paint-editor/index.cjs +10 -8
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +11 -5
- package/dist/paint-editor/index.d.ts +11 -5
- package/dist/paint-editor/index.js +4 -2
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/parameters/index.d.cts +4 -184
- package/dist/parameters/index.d.ts +4 -184
- package/dist/{pathfinder-IxcyhO00.d.cts → pathfinder-C0UoFypx.d.cts} +1 -1
- package/dist/{pathfinder-StVChJOT.d.ts → pathfinder-DjISxkUv.d.ts} +1 -1
- package/dist/{player-DrcP2xqj.d.cts → player-CBtEoU2x.d.cts} +2 -54
- package/dist/{player-DV4Jujdq.d.ts → player-cGLrngL_.d.ts} +2 -54
- package/dist/{svg-BtND4TJu.d.cts → svg-BDrjRnL1.d.cts} +3 -2
- package/dist/{svg-BfVXG7hL.d.ts → svg-C-vZ3q50.d.ts} +3 -2
- package/dist/tools/index.cjs +2 -2
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools-C7JtLYzo.d.ts +126 -0
- package/dist/tools-DHKGHiSK.d.cts +126 -0
- package/dist/{types-fuXEme9j.d.ts → types-BcX7sCsn.d.ts} +2 -2
- package/dist/{types-DJF3Ghn5.d.cts → types-CznabG7m.d.cts} +2 -2
- package/dist/types-D3-J3TFf.d.ts +185 -0
- package/dist/types-DMcf_z4J.d.cts +185 -0
- package/dist/vector/index.d.cts +10 -10
- package/dist/vector/index.d.ts +10 -10
- package/dist/video-editor/index.cjs +749 -277
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +265 -138
- package/dist/video-editor/index.d.ts +265 -138
- package/dist/video-editor/index.js +490 -18
- package/dist/video-editor/index.js.map +1 -1
- package/dist/{warp-BjmAgQwq.d.cts → warp-CH9MM435.d.cts} +1 -1
- package/dist/{warp-Ca5fedzS.d.ts → warp-k_PdtXOG.d.ts} +1 -1
- package/package.json +6 -1
- package/skills/pixerate-image-editor/SKILL.md +7 -0
- package/skills/pixerate-image-editor/references/provider-effects.md +35 -0
- package/skills/pixerate-video-editor/SKILL.md +39 -1
- package/dist/chunk-6CJRDJCU.cjs.map +0 -1
- package/dist/chunk-6ZBR4J62.js.map +0 -1
- package/dist/chunk-IPFRWILU.js.map +0 -1
- package/dist/chunk-RLRUAPBJ.cjs.map +0 -1
- package/dist/chunk-SMJGE7HM.cjs.map +0 -1
- package/dist/chunk-TVXFHE4T.js.map +0 -1
- /package/dist/{brushstrokeRender-CDDRSV2W.js.map → brushstrokeRender-LRV7GXGD.js.map} +0 -0
- /package/dist/{chunk-KMBC4HNV.js.map → chunk-J67OLQUQ.js.map} +0 -0
|
@@ -1,13 +1,24 @@
|
|
|
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
|
|
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;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkIOXKXQNWcjs = require('./chunk-IOXKXQNW.cjs');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
var _chunkRIWPYSKUcjs = require('./chunk-RIWPYSKU.cjs');
|
|
11
|
+
|
|
12
|
+
// src/video-editor/types.ts
|
|
2
13
|
var VIDEO_FITS = ["contain", "cover", "fill", "none"];
|
|
3
|
-
var VIDEO_TRANSITION_TYPES = ["crossfade", "dip", "wipe", "push", "slide", "iris"];
|
|
14
|
+
var VIDEO_TRANSITION_TYPES = ["crossfade", "dip", "wipe", "push", "slide", "iris", "effect"];
|
|
4
15
|
var VIDEO_WIPE_DIRECTIONS = ["left", "right", "up", "down"];
|
|
5
16
|
var videoTransitionAudioCurve = (t) => _nullishCoalesce(t.audioCurve, () => ( (t.type === "dip" ? "linear" : "equal-power")));
|
|
6
17
|
var VIDEO_BLEND_MODES = ["normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "add"];
|
|
7
18
|
var VIDEO_CLIP_ANIMATION_EFFECTS = ["fade", "rise", "drop", "slide-left", "slide-right", "zoom", "pop"];
|
|
8
19
|
var VIDEO_FADE_CURVES = ["linear", "equal-power", "s-curve", "exponential", "logarithmic"];
|
|
9
20
|
var VIDEO_PLACEMENT_MODES = ["refuse", "overwrite", "insert"];
|
|
10
|
-
var VIDEO_CLIP_TYPES = ["media", "text", "color", "sequence"];
|
|
21
|
+
var VIDEO_CLIP_TYPES = ["media", "text", "color", "sequence", "effect"];
|
|
11
22
|
|
|
12
23
|
// src/video-editor/keyframes.ts
|
|
13
24
|
var VIDEO_EASE_NAMES = ["linear", "hold", "ease-in", "ease-out", "ease-in-out"];
|
|
@@ -953,6 +964,11 @@ function videoClipAnimated(clip, project) {
|
|
|
953
964
|
}
|
|
954
965
|
|
|
955
966
|
// src/video-editor/evaluate.ts
|
|
967
|
+
function videoEffectValuesAt(values, clipTime) {
|
|
968
|
+
const out = {};
|
|
969
|
+
for (const [k, v] of Object.entries(_nullishCoalesce(values, () => ( {})))) out[k] = isVideoKeyframes(v) ? videoValueAt(v, clipTime, 0) : v;
|
|
970
|
+
return out;
|
|
971
|
+
}
|
|
956
972
|
function videoTransformAt(transform, clipTime = 0) {
|
|
957
973
|
return { x: videoValueAt(_optionalChain([transform, 'optionalAccess', _14 => _14.x]), clipTime, 0), y: videoValueAt(_optionalChain([transform, 'optionalAccess', _15 => _15.y]), clipTime, 0), scale: videoValueAt(_optionalChain([transform, 'optionalAccess', _16 => _16.scale]), clipTime, 1), rotation: videoValueAt(_optionalChain([transform, 'optionalAccess', _17 => _17.rotation]), clipTime, 0) };
|
|
958
974
|
}
|
|
@@ -1068,12 +1084,14 @@ function tracksAt(project, tracks, time, scope) {
|
|
|
1068
1084
|
let wipe;
|
|
1069
1085
|
let dip = null;
|
|
1070
1086
|
let iris;
|
|
1087
|
+
let effectIn;
|
|
1071
1088
|
let shift = 0;
|
|
1072
1089
|
let shiftDirection = "right";
|
|
1073
1090
|
if (incoming) {
|
|
1074
1091
|
if (incoming.type === "crossfade") opacity *= incoming.progress;
|
|
1075
1092
|
else if (incoming.type === "wipe") wipe = { direction: _nullishCoalesce(incoming.transition.direction, () => ( "right")), progress: incoming.progress };
|
|
1076
1093
|
else if (incoming.type === "iris") iris = { progress: incoming.progress };
|
|
1094
|
+
else if (incoming.type === "effect") effectIn = incoming;
|
|
1077
1095
|
else if (incoming.type === "push" || incoming.type === "slide") {
|
|
1078
1096
|
shiftDirection = _nullishCoalesce(incoming.transition.direction, () => ( "right"));
|
|
1079
1097
|
shift = videoTransitionEase(incoming.progress) - 1;
|
|
@@ -1100,14 +1118,291 @@ function tracksAt(project, tracks, time, scope) {
|
|
|
1100
1118
|
if (clip.type === "sequence") Object.assign(layer, { sequence: clip.sequence, sourceTime, layers: nested.layers });
|
|
1101
1119
|
if (clip.type === "text") layer.text = videoTextStyle(clip);
|
|
1102
1120
|
if (clip.type === "color") layer.color = clip.color;
|
|
1121
|
+
if (clip.type === "effect") layer.generator = { effect: clip.effect, ...clip.name ? { name: clip.name } : {}, values: videoEffectValuesAt(clip.values, clipTime), time: Math.max(0, clipTime) };
|
|
1122
|
+
const effects = (_nullishCoalesce(clip.effects, () => ( []))).filter((e) => e.enabled !== false).map((e) => ({ id: e.id, effect: e.effect, ...e.name ? { name: e.name } : {}, opacity: clamp012(videoValueAt(e.opacity, clipTime, 1)), values: videoEffectValuesAt(e.values, clipTime), time: clipTime })).filter((e) => e.opacity > 0);
|
|
1123
|
+
if (effects.length) layer.effects = effects;
|
|
1124
|
+
if (effectIn) {
|
|
1125
|
+
const before = videoClipBefore(track, clip);
|
|
1126
|
+
const i = before ? layers.findIndex((l) => l.clip === prefix + before.id && l.track === track.id) : -1;
|
|
1127
|
+
const from = i >= 0 ? layers.splice(i, 1)[0] : null;
|
|
1128
|
+
const t = effectIn.transition;
|
|
1129
|
+
layer.transitionEffect = { effect: t.effect.effect, ...t.effect.name ? { name: t.effect.name } : {}, values: videoEffectValuesAt(t.effect.values, 0), progress: effectIn.progress, time: effectIn.progress * t.duration, from };
|
|
1130
|
+
}
|
|
1103
1131
|
if (layer.opacity > 0) layers.push(layer);
|
|
1104
1132
|
if (dip && dip.opacity > 0) layers.push({ clip: `transition:${prefix}${incoming ? clip.id : outgoing.next.id}`, track: track.id, type: "color", color: dip.color, opacity: clamp012(dip.opacity), rect: { x: 0, y: 0, width: project.width, height: project.height }, rotation: 0 });
|
|
1105
1133
|
}
|
|
1106
1134
|
}
|
|
1107
1135
|
return { layers, audio };
|
|
1108
1136
|
}
|
|
1137
|
+
function videoLayersWithoutTransitionEffects(layers) {
|
|
1138
|
+
return layers.flatMap((l) => {
|
|
1139
|
+
const t = l.transitionEffect;
|
|
1140
|
+
if (!t) return [l];
|
|
1141
|
+
const { transitionEffect: _t, ...rest } = l;
|
|
1142
|
+
const self = { ...rest, opacity: rest.opacity * t.progress };
|
|
1143
|
+
return t.from ? [t.from, self] : [self];
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1109
1146
|
function videoFrameLeafLayers(layers) {
|
|
1110
|
-
return layers.flatMap((l) => l.layers ? videoFrameLeafLayers(l.layers) : [l]);
|
|
1147
|
+
return layers.flatMap((l) => [..._optionalChain([l, 'access', _27 => _27.transitionEffect, 'optionalAccess', _28 => _28.from]) ? videoFrameLeafLayers([l.transitionEffect.from]) : [], ...l.layers ? videoFrameLeafLayers(l.layers) : [l]]);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/video-editor/effects.ts
|
|
1151
|
+
function videoProjectEffects(project) {
|
|
1152
|
+
const out = [];
|
|
1153
|
+
for (const { track } of allVideoTracks(project)) for (const clip of track.clips) out.push(...videoClipEffectUses(clip));
|
|
1154
|
+
return out;
|
|
1155
|
+
}
|
|
1156
|
+
var refFields = (r) => ({ effect: r.effect, ...r.name ? { name: r.name } : {}, ...r.provider ? { provider: r.provider } : {} });
|
|
1157
|
+
function videoClipEffectUses(clip) {
|
|
1158
|
+
const out = [];
|
|
1159
|
+
if (clip.type === "effect") out.push({ clip: clip.id, kind: "generator", ...refFields(clip), enabled: true });
|
|
1160
|
+
if (_optionalChain([clip, 'access', _29 => _29.transition, 'optionalAccess', _30 => _30.type]) === "effect" && clip.transition.effect) out.push({ clip: clip.id, kind: "transition", ...refFields(clip.transition.effect), enabled: true });
|
|
1161
|
+
for (const e of _nullishCoalesce(clip.effects, () => ( []))) out.push({ clip: clip.id, kind: "clip", instance: e.id, ...refFields(e), enabled: e.enabled !== false });
|
|
1162
|
+
return out;
|
|
1163
|
+
}
|
|
1164
|
+
var videoLayersUseEffects = (layers) => layers.some((l) => !!(_optionalChain([l, 'access', _31 => _31.effects, 'optionalAccess', _32 => _32.length]) || l.generator || l.transitionEffect || l.layers && videoLayersUseEffects(l.layers)));
|
|
1165
|
+
var videoEffectFrameTime = (time, fps) => Math.floor(time * fps / 1e3 + 1e-6) * 1e3 / fps;
|
|
1166
|
+
var PLACEMENT = /* @__PURE__ */ new Set(["opacity", "rect", "rotation", "blend", "wipe", "iris", "grade", "track"]);
|
|
1167
|
+
var round = (_k, v) => typeof v === "number" ? Math.round(v * 100) / 100 : v;
|
|
1168
|
+
function videoEffectLayerKey(layer, width, height) {
|
|
1169
|
+
const content = layer.transitionEffect ? layer : Object.fromEntries(Object.entries(layer).filter(([k]) => !PLACEMENT.has(k)));
|
|
1170
|
+
return `${width}x${height}:${JSON.stringify(content, round)}`;
|
|
1171
|
+
}
|
|
1172
|
+
function mixEffectPixels(a, b, k) {
|
|
1173
|
+
if (k >= 1) return b;
|
|
1174
|
+
const out = _chunkIOXKXQNWcjs.createEffectPixels.call(void 0, a.width, a.height);
|
|
1175
|
+
for (let i = 0; i < out.data.length; i++) out.data[i] = a.data[i] + (b.data[i] - a.data[i]) * k;
|
|
1176
|
+
return out;
|
|
1177
|
+
}
|
|
1178
|
+
function overEffectPixels(base, top, opacity = 1) {
|
|
1179
|
+
const out = _chunkIOXKXQNWcjs.createEffectPixels.call(void 0, base.width, base.height);
|
|
1180
|
+
const o = out.data;
|
|
1181
|
+
const b = base.data;
|
|
1182
|
+
const t = top.data;
|
|
1183
|
+
for (let i = 0; i < o.length; i += 4) {
|
|
1184
|
+
const ta = t[i + 3] / 255 * opacity;
|
|
1185
|
+
const ba = b[i + 3] / 255;
|
|
1186
|
+
const a = ta + ba * (1 - ta);
|
|
1187
|
+
o[i + 3] = Math.round(a * 255);
|
|
1188
|
+
if (a <= 0) continue;
|
|
1189
|
+
for (let c = 0; c < 3; c++) o[i + c] = (t[i + c] * ta + b[i + c] * ba * (1 - ta)) / a;
|
|
1190
|
+
}
|
|
1191
|
+
return out;
|
|
1192
|
+
}
|
|
1193
|
+
function defaultToImage(p) {
|
|
1194
|
+
const data = new Uint8ClampedArray(p.data);
|
|
1195
|
+
if (typeof ImageData === "undefined") return null;
|
|
1196
|
+
const image = new ImageData(data, p.width, p.height);
|
|
1197
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
1198
|
+
const c = new OffscreenCanvas(p.width, p.height);
|
|
1199
|
+
_optionalChain([c, 'access', _33 => _33.getContext, 'call', _34 => _34("2d"), 'optionalAccess', _35 => _35.putImageData, 'call', _36 => _36(image, 0, 0)]);
|
|
1200
|
+
return c;
|
|
1201
|
+
}
|
|
1202
|
+
if (typeof document !== "undefined") {
|
|
1203
|
+
const c = document.createElement("canvas");
|
|
1204
|
+
c.width = p.width;
|
|
1205
|
+
c.height = p.height;
|
|
1206
|
+
_optionalChain([c, 'access', _37 => _37.getContext, 'call', _38 => _38("2d"), 'optionalAccess', _39 => _39.putImageData, 'call', _40 => _40(image, 0, 0)]);
|
|
1207
|
+
return c;
|
|
1208
|
+
}
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
var VideoEffectsEngine = (_class2 = class {
|
|
1212
|
+
|
|
1213
|
+
__init5() {this.cache = /* @__PURE__ */ new Map()}
|
|
1214
|
+
__init6() {this.pending = /* @__PURE__ */ new Map()}
|
|
1215
|
+
__init7() {this.failures = /* @__PURE__ */ new Map()}
|
|
1216
|
+
__init8() {this.renderers = /* @__PURE__ */ new Map()}
|
|
1217
|
+
__init9() {this.descriptions = /* @__PURE__ */ new Map()}
|
|
1218
|
+
__init10() {this.listeners = /* @__PURE__ */ new Set()}
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
__init11() {this.generation = 0}
|
|
1222
|
+
|
|
1223
|
+
constructor(options = {}) {;_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);
|
|
1224
|
+
this.slot = _nullishCoalesce(options.slot, () => ( new (0, _chunkIOXKXQNWcjs.EffectsSlot)()));
|
|
1225
|
+
this.maxEntries = _nullishCoalesce(options.maxEntries, () => ( 120));
|
|
1226
|
+
this.toImage = _nullishCoalesce(options.toImage, () => ( defaultToImage));
|
|
1227
|
+
this.detach = this.slot.subscribe(() => {
|
|
1228
|
+
this.reset();
|
|
1229
|
+
this.notify();
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
get provider() {
|
|
1233
|
+
return this.slot.provider;
|
|
1234
|
+
}
|
|
1235
|
+
/** True when a provider is registered, so effects can be drawn. */
|
|
1236
|
+
get available() {
|
|
1237
|
+
return !!this.slot.provider;
|
|
1238
|
+
}
|
|
1239
|
+
/** The drawable picture for `key`, or `undefined` when it is not rendered (yet). */
|
|
1240
|
+
get(key) {
|
|
1241
|
+
const e = this.cache.get(key);
|
|
1242
|
+
if (!e) return void 0;
|
|
1243
|
+
this.cache.delete(key);
|
|
1244
|
+
this.cache.set(key, e);
|
|
1245
|
+
if (e.image === void 0) e.image = this.toImage(e.pixels);
|
|
1246
|
+
return e.image;
|
|
1247
|
+
}
|
|
1248
|
+
/** The rendered pixels for `key`. */
|
|
1249
|
+
pixels(key) {
|
|
1250
|
+
return _optionalChain([this, 'access', _41 => _41.cache, 'access', _42 => _42.get, 'call', _43 => _43(key), 'optionalAccess', _44 => _44.pixels]);
|
|
1251
|
+
}
|
|
1252
|
+
/** Why the render for `key` failed, if it did (it is not retried until the cache is cleared). */
|
|
1253
|
+
failure(key) {
|
|
1254
|
+
return this.failures.get(key);
|
|
1255
|
+
}
|
|
1256
|
+
/** Every failure so far, newest last. */
|
|
1257
|
+
failuresList() {
|
|
1258
|
+
return [...this.failures].map(([key, error]) => ({ key, error }));
|
|
1259
|
+
}
|
|
1260
|
+
/** Whether `key` is being rendered. */
|
|
1261
|
+
isPending(key) {
|
|
1262
|
+
return this.pending.has(key);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Starts rendering `job` into `key` unless it is cached, pending or failed. Returns true while
|
|
1266
|
+
* the picture is on its way (so the frame is waiting for it).
|
|
1267
|
+
*/
|
|
1268
|
+
request(key, job) {
|
|
1269
|
+
if (this.cache.has(key)) return false;
|
|
1270
|
+
if (this.pending.has(key)) return true;
|
|
1271
|
+
if (this.failures.has(key) || !this.slot.provider) return false;
|
|
1272
|
+
const generation = this.generation;
|
|
1273
|
+
const run = this.run(job).then((pixels) => {
|
|
1274
|
+
if (generation !== this.generation) return;
|
|
1275
|
+
this.cache.set(key, { pixels });
|
|
1276
|
+
while (this.cache.size > this.maxEntries) this.cache.delete(this.cache.keys().next().value);
|
|
1277
|
+
}).catch((error) => {
|
|
1278
|
+
if (generation !== this.generation) return;
|
|
1279
|
+
this.failures.set(key, error instanceof Error ? error : new Error(String(error)));
|
|
1280
|
+
}).finally(() => {
|
|
1281
|
+
if (this.pending.get(key) === run) this.pending.delete(key);
|
|
1282
|
+
if (generation === this.generation) this.notify();
|
|
1283
|
+
});
|
|
1284
|
+
this.pending.set(key, run);
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
/** Resolves when nothing is being rendered. */
|
|
1288
|
+
async idle() {
|
|
1289
|
+
while (this.pending.size) await Promise.all([...this.pending.values()]);
|
|
1290
|
+
}
|
|
1291
|
+
/** Renders a job now, without the cache. Rejects with the provider's error. */
|
|
1292
|
+
async run(job) {
|
|
1293
|
+
const provider = this.slot.require();
|
|
1294
|
+
if (job.kind === "transition") {
|
|
1295
|
+
const out = await this.render(provider, job.effect, job.from.width, job.from.height, { time: job.time, progress: job.progress, values: job.values, inputs: { from: job.from, to: job.to } });
|
|
1296
|
+
return out;
|
|
1297
|
+
}
|
|
1298
|
+
let cur;
|
|
1299
|
+
if (job.kind === "generator") cur = await this.render(provider, job.effect, job.width, job.height, { time: job.time, values: job.values });
|
|
1300
|
+
else cur = job.input;
|
|
1301
|
+
return this.stack(provider, cur, job.effects);
|
|
1302
|
+
}
|
|
1303
|
+
async stack(provider, input, effects) {
|
|
1304
|
+
let cur = input;
|
|
1305
|
+
for (const e of effects) {
|
|
1306
|
+
const d = await this.describe(provider, e.effect);
|
|
1307
|
+
if (d.inputs.some((i) => i.name === "source")) {
|
|
1308
|
+
const out = await this.render(provider, e.effect, cur.width, cur.height, { time: e.time, values: e.values, inputs: { source: cur } });
|
|
1309
|
+
cur = mixEffectPixels(cur, out, e.opacity);
|
|
1310
|
+
} else {
|
|
1311
|
+
const out = await this.render(provider, e.effect, cur.width, cur.height, { time: e.time, values: e.values });
|
|
1312
|
+
cur = overEffectPixels(cur, out, e.opacity);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return cur;
|
|
1316
|
+
}
|
|
1317
|
+
/** The provider's description of an effect, fetched once. */
|
|
1318
|
+
describe(provider, id) {
|
|
1319
|
+
let d = this.descriptions.get(id);
|
|
1320
|
+
if (!d) {
|
|
1321
|
+
d = Promise.resolve().then(() => provider.describe(id));
|
|
1322
|
+
d.catch(() => this.descriptions.delete(id));
|
|
1323
|
+
this.descriptions.set(id, d);
|
|
1324
|
+
}
|
|
1325
|
+
return d;
|
|
1326
|
+
}
|
|
1327
|
+
/** Renders on a renderer kept per effect and size, one render at a time per renderer. */
|
|
1328
|
+
render(provider, id, width, height, request) {
|
|
1329
|
+
const k = `${id}@${width}x${height}`;
|
|
1330
|
+
let r = this.renderers.get(k);
|
|
1331
|
+
if (!r) {
|
|
1332
|
+
const renderer = Promise.resolve().then(() => provider.createRenderer(id, { width, height }));
|
|
1333
|
+
renderer.catch(() => this.renderers.delete(k));
|
|
1334
|
+
r = { renderer, queue: Promise.resolve() };
|
|
1335
|
+
this.renderers.set(k, r);
|
|
1336
|
+
}
|
|
1337
|
+
const entry = r;
|
|
1338
|
+
const next = entry.queue.then(() => entry.renderer).then((renderer) => renderer.render(request));
|
|
1339
|
+
entry.queue = next.catch(() => void 0);
|
|
1340
|
+
return next;
|
|
1341
|
+
}
|
|
1342
|
+
/** Forgets every picture and failure and disposes the renderers (e.g. after the provider changes). */
|
|
1343
|
+
reset() {
|
|
1344
|
+
this.generation++;
|
|
1345
|
+
this.cache.clear();
|
|
1346
|
+
this.pending.clear();
|
|
1347
|
+
this.failures.clear();
|
|
1348
|
+
this.descriptions.clear();
|
|
1349
|
+
for (const r of this.renderers.values()) r.renderer.then((x) => x.dispose(), () => {
|
|
1350
|
+
});
|
|
1351
|
+
this.renderers.clear();
|
|
1352
|
+
}
|
|
1353
|
+
subscribe(listener) {
|
|
1354
|
+
this.listeners.add(listener);
|
|
1355
|
+
return () => this.listeners.delete(listener);
|
|
1356
|
+
}
|
|
1357
|
+
notify() {
|
|
1358
|
+
for (const l of [...this.listeners]) l();
|
|
1359
|
+
}
|
|
1360
|
+
destroy() {
|
|
1361
|
+
this.reset();
|
|
1362
|
+
this.detach();
|
|
1363
|
+
this.listeners.clear();
|
|
1364
|
+
}
|
|
1365
|
+
}, _class2);
|
|
1366
|
+
var VIDEO_EFFECT_PLACES = {
|
|
1367
|
+
/** A clip's effect stack: filters change the picture, overlays draw over it. */
|
|
1368
|
+
clip: ["filter", "overlay"],
|
|
1369
|
+
transition: ["transition"],
|
|
1370
|
+
/** An effect clip's picture. */
|
|
1371
|
+
generator: ["generator", "title", "overlay"]
|
|
1372
|
+
};
|
|
1373
|
+
function checkVideoEffectPlace(d, place) {
|
|
1374
|
+
const allowed = VIDEO_EFFECT_PLACES[place];
|
|
1375
|
+
if (d.kind === "motion" || !d.uses.some((u) => allowed.includes(u))) {
|
|
1376
|
+
const where = place === "clip" ? "on a clip" : place === "transition" ? "as a transition" : "as an effect clip";
|
|
1377
|
+
const instead = d.uses.includes("transition") ? "set_video_effect_transition" : d.uses.some((u) => VIDEO_EFFECT_PLACES.generator.includes(u)) ? "add_video_effect_clip" : d.uses.some((u) => u === "filter") ? "apply_effect" : null;
|
|
1378
|
+
throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(
|
|
1379
|
+
`"${d.id}" (${d.name}) is a ${d.uses.join(" / ")} effect and cannot be used ${where}, which takes ${allowed.join(" or ")} effects${instead ? `; use ${instead} instead` : ""}. search_effects with use "${allowed[0]}" lists ones that fit.`,
|
|
1380
|
+
d.id
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function checkVideoEffectValues(d, values, options = {}) {
|
|
1385
|
+
const plain = {};
|
|
1386
|
+
const errors = [];
|
|
1387
|
+
for (const [k, v] of Object.entries(_nullishCoalesce(values, () => ( {})))) {
|
|
1388
|
+
if (!isVideoKeyframes(v)) {
|
|
1389
|
+
plain[k] = v;
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
const schema = d.controls.properties[k];
|
|
1393
|
+
const type = _optionalChain([schema, 'optionalAccess', _45 => _45.type]);
|
|
1394
|
+
if (!options.keyframes) errors.push({ path: `/${k}`, message: "cannot be keyframed here; give a plain value." });
|
|
1395
|
+
else if (!schema) errors.push({ path: `/${k}`, message: `is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ") || "none"}.` });
|
|
1396
|
+
else if (type !== "number" && type !== "integer") errors.push({ path: `/${k}`, message: "only number controls can be keyframed." });
|
|
1397
|
+
else for (const [i, key] of v.keys.entries()) for (const e of _chunkRIWPYSKUcjs.validateJsonSchema.call(void 0, key.v, schema)) errors.push({ path: `/${k}/keys/${i}/v`, message: e.message });
|
|
1398
|
+
}
|
|
1399
|
+
for (const e of _chunkIOXKXQNWcjs.effectValueErrors.call(void 0, d, plain)) {
|
|
1400
|
+
const name = e.path.split("/")[1];
|
|
1401
|
+
const about = name && _optionalChain([d, 'access', _46 => _46.controls, 'access', _47 => _47.properties, 'access', _48 => _48[name], 'optionalAccess', _49 => _49.description]);
|
|
1402
|
+
errors.push(about ? { path: e.path, message: `${e.message} (${about})` } : e);
|
|
1403
|
+
}
|
|
1404
|
+
if (errors.length) throw new (0, _chunkIOXKXQNWcjs.EffectControlError)(d.id, errors);
|
|
1405
|
+
return { ..._nullishCoalesce(options.base, () => ( d.defaults)), ..._nullishCoalesce(values, () => ( {})) };
|
|
1111
1406
|
}
|
|
1112
1407
|
|
|
1113
1408
|
// src/video-editor/render.ts
|
|
@@ -1127,7 +1422,7 @@ function videoWipeRect(rect, wipe) {
|
|
|
1127
1422
|
function videoFrameToSvg(frame, options = {}, assets = []) {
|
|
1128
1423
|
const defs = [];
|
|
1129
1424
|
const posters = new Map(assets.map((a) => [a.id, a.poster]));
|
|
1130
|
-
const draw = (layers, prefix) => layers.map((layer, index) => {
|
|
1425
|
+
const draw = (layers, prefix) => videoLayersWithoutTransitionEffects(layers).map((layer, index) => {
|
|
1131
1426
|
const i = `${prefix}${index}`;
|
|
1132
1427
|
const { rect } = layer;
|
|
1133
1428
|
const attrs = [];
|
|
@@ -1137,7 +1432,7 @@ function videoFrameToSvg(frame, options = {}, assets = []) {
|
|
|
1137
1432
|
if (layer.layers) style.push("isolation:isolate");
|
|
1138
1433
|
if (style.length) attrs.push(`style="${style.join(";")}"`);
|
|
1139
1434
|
if (layer.grade) {
|
|
1140
|
-
defs.push(videoGradeSvgFilter(`g${i}`, layer.grade, layer.grade.lut ? _optionalChain([options, 'access',
|
|
1435
|
+
defs.push(videoGradeSvgFilter(`g${i}`, layer.grade, layer.grade.lut ? _optionalChain([options, 'access', _50 => _50.lutFor, 'optionalCall', _51 => _51(layer.grade.lut.src)]) : null));
|
|
1141
1436
|
attrs.push(`filter="url(#g${i})"`);
|
|
1142
1437
|
}
|
|
1143
1438
|
if (layer.rotation) attrs.push(`transform="rotate(${n(layer.rotation)} ${n(rect.x + rect.width / 2)} ${n(rect.y + rect.height / 2)})"`);
|
|
@@ -1168,7 +1463,7 @@ function videoFrameToSvg(frame, options = {}, assets = []) {
|
|
|
1168
1463
|
const href = options.imageFor ? options.imageFor(layer, frame) : layer.assetKind === "image" ? _nullishCoalesce(layer.src, () => ( null)) : _nullishCoalesce(posters.get(_nullishCoalesce(layer.asset, () => ( ""))), () => ( null));
|
|
1169
1464
|
if (href) inner = `<image href="${esc(href)}" ${box} preserveAspectRatio="none"/>`;
|
|
1170
1465
|
else {
|
|
1171
|
-
const label = `${_nullishCoalesce(layer.asset, () => ( layer.clip))} \xB7 ${formatVideoTimecode(_nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(options.fps, () => ( 30)))}`;
|
|
1466
|
+
const label = layer.generator ? `Effect: ${_nullishCoalesce(layer.generator.name, () => ( layer.generator.effect))}` : `${_nullishCoalesce(layer.asset, () => ( layer.clip))} \xB7 ${formatVideoTimecode(_nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(options.fps, () => ( 30)))}`;
|
|
1172
1467
|
const size = Math.max(10, Math.min(rect.height / 8, rect.width / Math.max(8, label.length * 0.6)));
|
|
1173
1468
|
inner = `<rect ${box} fill="#334155" stroke="#94a3b8" stroke-width="2"/><text x="${n(rect.x + rect.width / 2)}" y="${n(rect.y + rect.height / 2)}" text-anchor="middle" dominant-baseline="central" font-family="sans-serif" font-size="${n(size)}" fill="#e2e8f0">${esc(label)}</text>`;
|
|
1174
1469
|
}
|
|
@@ -1256,6 +1551,14 @@ function paintVideoLayer(ctx, frame, layer, sourceFor, options) {
|
|
|
1256
1551
|
ctx.strokeStyle = "#94a3b8";
|
|
1257
1552
|
ctx.lineWidth = 2;
|
|
1258
1553
|
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
1554
|
+
if (layer.generator) {
|
|
1555
|
+
const label = `Effect: ${_nullishCoalesce(layer.generator.name, () => ( layer.generator.effect))}`;
|
|
1556
|
+
ctx.font = `600 ${Math.max(12, Math.min(rect.height / 10, rect.width / Math.max(8, label.length * 0.6)))}px sans-serif`;
|
|
1557
|
+
ctx.textAlign = "center";
|
|
1558
|
+
ctx.textBaseline = "middle";
|
|
1559
|
+
ctx.fillStyle = "#e2e8f0";
|
|
1560
|
+
ctx.fillText(label, rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
1561
|
+
}
|
|
1259
1562
|
}
|
|
1260
1563
|
}
|
|
1261
1564
|
}
|
|
@@ -1265,18 +1568,118 @@ function drawVideoFrame(ctx, frame, sourceFor, scale = 1, options = {}) {
|
|
|
1265
1568
|
ctx.globalAlpha = 1;
|
|
1266
1569
|
ctx.fillStyle = frame.background;
|
|
1267
1570
|
ctx.fillRect(0, 0, frame.width, frame.height);
|
|
1268
|
-
|
|
1571
|
+
const state = { missing: 0, waiting: 0 };
|
|
1572
|
+
const counted = (layer) => {
|
|
1573
|
+
const source = sourceFor(layer);
|
|
1574
|
+
if (!source && layer.type === "media") state.missing++;
|
|
1575
|
+
return source;
|
|
1576
|
+
};
|
|
1577
|
+
drawVideoLayers(ctx, frame, frame.layers, counted, scale, options, 0, state);
|
|
1269
1578
|
ctx.restore();
|
|
1270
1579
|
}
|
|
1271
|
-
|
|
1580
|
+
var unplaced = (layer) => {
|
|
1581
|
+
const { rotation: _r, wipe: _w, iris: _i, grade: _g, blend: _b, effects: _e, transitionEffect: _t, ...rest } = layer;
|
|
1582
|
+
return { ...rest, opacity: 1, rotation: 0 };
|
|
1583
|
+
};
|
|
1584
|
+
var readPixels = (s, width, height) => {
|
|
1585
|
+
const img = s.ctx.getImageData(0, 0, width, height);
|
|
1586
|
+
return { width, height, data: img.data };
|
|
1587
|
+
};
|
|
1588
|
+
function begin(s, width, height, scale, dx = 0, dy = 0) {
|
|
1589
|
+
const g = s.ctx;
|
|
1590
|
+
g.save();
|
|
1591
|
+
g.setTransform(1, 0, 0, 1, 0, 0);
|
|
1592
|
+
g.clearRect(0, 0, width, height);
|
|
1593
|
+
g.setTransform(scale, 0, 0, scale, dx, dy);
|
|
1594
|
+
g.globalAlpha = 1;
|
|
1595
|
+
g.globalCompositeOperation = "source-over";
|
|
1596
|
+
return g;
|
|
1597
|
+
}
|
|
1598
|
+
function effectPicture(frame, layer, source, scale, options, depth, state) {
|
|
1599
|
+
const engine = options.effects;
|
|
1600
|
+
if (!_optionalChain([engine, 'optionalAccess', _52 => _52.available])) return null;
|
|
1601
|
+
const { rect } = layer;
|
|
1602
|
+
const w = Math.max(1, Math.round(rect.width * scale));
|
|
1603
|
+
const h = Math.max(1, Math.round(rect.height * scale));
|
|
1604
|
+
const key = videoEffectLayerKey(layer, w, h);
|
|
1605
|
+
const hit = engine.get(key);
|
|
1606
|
+
if (hit !== void 0 || engine.failure(key)) return _nullishCoalesce(hit, () => ( null));
|
|
1607
|
+
let job;
|
|
1608
|
+
if (layer.generator) job = { kind: "generator", width: w, height: h, ...layer.generator, effects: _nullishCoalesce(layer.effects, () => ( [])) };
|
|
1609
|
+
else {
|
|
1610
|
+
const s = _optionalChain([options, 'access', _53 => _53.scratch, 'optionalCall', _54 => _54(w, h, 1e3 + 4 * depth)]);
|
|
1611
|
+
if (!s) return null;
|
|
1612
|
+
const missing = state.missing;
|
|
1613
|
+
const g = begin(s, w, h, scale, -rect.x * scale, -rect.y * scale);
|
|
1614
|
+
const bare = unplaced(layer);
|
|
1615
|
+
paintVideoLayer(g, frame, bare, (l) => source(l === bare ? layer : l), options);
|
|
1616
|
+
g.restore();
|
|
1617
|
+
if (state.missing !== missing) return null;
|
|
1618
|
+
job = { kind: "stack", input: readPixels(s, w, h), effects: _nullishCoalesce(layer.effects, () => ( [])) };
|
|
1619
|
+
}
|
|
1620
|
+
if (engine.request(key, job)) {
|
|
1621
|
+
state.waiting++;
|
|
1622
|
+
_optionalChain([options, 'access', _55 => _55.onEffectWaiting, 'optionalCall', _56 => _56()]);
|
|
1623
|
+
}
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
function drawEffectTransition(ctx, frame, layer, sourceFor, scale, options, depth, state) {
|
|
1627
|
+
const engine = options.effects;
|
|
1628
|
+
const t = layer.transitionEffect;
|
|
1629
|
+
if (!_optionalChain([engine, 'optionalAccess', _57 => _57.available])) return false;
|
|
1630
|
+
const cw = Math.max(1, Math.round(frame.width * scale));
|
|
1631
|
+
const ch = Math.max(1, Math.round(frame.height * scale));
|
|
1632
|
+
const key = videoEffectLayerKey(layer, cw, ch);
|
|
1633
|
+
const hit = engine.get(key);
|
|
1634
|
+
if (hit) {
|
|
1635
|
+
ctx.save();
|
|
1636
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1637
|
+
ctx.globalAlpha = 1;
|
|
1638
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1639
|
+
ctx.drawImage(hit, 0, 0, cw, ch);
|
|
1640
|
+
ctx.restore();
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
if (hit === null || engine.failure(key) || engine.isPending(key)) {
|
|
1644
|
+
if (engine.isPending(key)) {
|
|
1645
|
+
state.waiting++;
|
|
1646
|
+
_optionalChain([options, 'access', _58 => _58.onEffectWaiting, 'optionalCall', _59 => _59()]);
|
|
1647
|
+
}
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
const a = _optionalChain([options, 'access', _60 => _60.scratch, 'optionalCall', _61 => _61(cw, ch, 1e3 + 4 * depth + 1)]);
|
|
1651
|
+
const b = _optionalChain([options, 'access', _62 => _62.scratch, 'optionalCall', _63 => _63(cw, ch, 1e3 + 4 * depth + 2)]);
|
|
1652
|
+
if (!a || !b) return false;
|
|
1653
|
+
const before = { ...state };
|
|
1654
|
+
const ga = begin(a, cw, ch, scale);
|
|
1655
|
+
drawVideoLayers(ga, frame, t.from ? [t.from] : [], sourceFor, scale, options, depth + 1, state);
|
|
1656
|
+
ga.restore();
|
|
1657
|
+
const { transitionEffect: _t, ...incoming } = layer;
|
|
1658
|
+
const gb = begin(b, cw, ch, scale);
|
|
1659
|
+
drawVideoLayers(gb, frame, [incoming], sourceFor, scale, options, depth + 1, state);
|
|
1660
|
+
gb.restore();
|
|
1661
|
+
if (state.missing !== before.missing || state.waiting !== before.waiting) return false;
|
|
1662
|
+
const job = { kind: "transition", effect: t.effect, values: t.values, progress: t.progress, time: t.time, from: readPixels(a, cw, ch), to: readPixels(b, cw, ch) };
|
|
1663
|
+
if (engine.request(key, job)) {
|
|
1664
|
+
state.waiting++;
|
|
1665
|
+
_optionalChain([options, 'access', _64 => _64.onEffectWaiting, 'optionalCall', _65 => _65()]);
|
|
1666
|
+
}
|
|
1667
|
+
return false;
|
|
1668
|
+
}
|
|
1669
|
+
function drawVideoLayers(ctx, frame, layers, sourceFor, scale, options, depth, state) {
|
|
1272
1670
|
const cw = Math.max(1, Math.round(frame.width * scale));
|
|
1273
1671
|
const ch = Math.max(1, Math.round(frame.height * scale));
|
|
1274
|
-
for (const
|
|
1672
|
+
for (const original of layers) {
|
|
1673
|
+
let layer = original;
|
|
1674
|
+
if (layer.transitionEffect) {
|
|
1675
|
+
if (!drawEffectTransition(ctx, frame, layer, sourceFor, scale, options, depth, state)) drawVideoLayers(ctx, frame, videoLayersWithoutTransitionEffects([layer]), sourceFor, scale, options, depth, state);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1275
1678
|
let source = sourceFor;
|
|
1276
1679
|
if (layer.layers) {
|
|
1277
|
-
const group = _optionalChain([options, 'access',
|
|
1680
|
+
const group = _optionalChain([options, 'access', _66 => _66.scratch, 'optionalCall', _67 => _67(cw, ch, 2 * depth)]);
|
|
1278
1681
|
if (!group) {
|
|
1279
|
-
drawVideoLayers(ctx, frame, layer.layers, sourceFor, scale, options, depth + 1);
|
|
1682
|
+
drawVideoLayers(ctx, frame, layer.layers, sourceFor, scale, options, depth + 1, state);
|
|
1280
1683
|
continue;
|
|
1281
1684
|
}
|
|
1282
1685
|
const g = group.ctx;
|
|
@@ -1286,13 +1689,23 @@ function drawVideoLayers(ctx, frame, layers, sourceFor, scale, options, depth) {
|
|
|
1286
1689
|
g.setTransform(scale, 0, 0, scale, 0, 0);
|
|
1287
1690
|
g.globalAlpha = 1;
|
|
1288
1691
|
g.globalCompositeOperation = "source-over";
|
|
1289
|
-
drawVideoLayers(g, frame, layer.layers, sourceFor, scale, options, depth + 1);
|
|
1692
|
+
drawVideoLayers(g, frame, layer.layers, sourceFor, scale, options, depth + 1, state);
|
|
1290
1693
|
g.restore();
|
|
1291
|
-
|
|
1694
|
+
const self = layer;
|
|
1695
|
+
source = (l) => l === self ? group.canvas : sourceFor(l);
|
|
1696
|
+
}
|
|
1697
|
+
if (_optionalChain([layer, 'access', _68 => _68.effects, 'optionalAccess', _69 => _69.length]) || layer.generator) {
|
|
1698
|
+
const picture = effectPicture(frame, layer, source, scale, options, depth, state);
|
|
1699
|
+
if (picture) {
|
|
1700
|
+
const { layers: _l, text: _t, color: _c, generator: _g, effects: _e, ...rest } = layer;
|
|
1701
|
+
const drawn = { ...rest, type: "media" };
|
|
1702
|
+
layer = drawn;
|
|
1703
|
+
source = (l) => l === drawn ? picture : sourceFor(l);
|
|
1704
|
+
}
|
|
1292
1705
|
}
|
|
1293
|
-
const setting = _optionalChain([layer, 'access',
|
|
1294
|
-
const lut = setting ? _optionalChain([options, 'access',
|
|
1295
|
-
const scratch = lut ? _optionalChain([options, 'access',
|
|
1706
|
+
const setting = _optionalChain([layer, 'access', _70 => _70.grade, 'optionalAccess', _71 => _71.lut]);
|
|
1707
|
+
const lut = setting ? _optionalChain([options, 'access', _72 => _72.lutFor, 'optionalCall', _73 => _73(setting.src)]) : null;
|
|
1708
|
+
const scratch = lut ? _optionalChain([options, 'access', _74 => _74.scratch, 'optionalCall', _75 => _75(cw, ch, 2 * depth + 1)]) : null;
|
|
1296
1709
|
ctx.save();
|
|
1297
1710
|
ctx.setTransform(scale, 0, 0, scale, 0, 0);
|
|
1298
1711
|
if (lut && scratch) {
|
|
@@ -1320,9 +1733,44 @@ function drawVideoLayers(ctx, frame, layers, sourceFor, scale, options, depth) {
|
|
|
1320
1733
|
ctx.restore();
|
|
1321
1734
|
}
|
|
1322
1735
|
}
|
|
1736
|
+
async function renderVideoFrameExact(ctx, project, time, sourceFor, options = {}) {
|
|
1737
|
+
const frame = videoFrameAt(project, videoEffectFrameTime(time, project.fps));
|
|
1738
|
+
const engine = options.effects;
|
|
1739
|
+
const before = new Set(_nullishCoalesce(_optionalChain([engine, 'optionalAccess', _76 => _76.failuresList, 'call', _77 => _77(), 'access', _78 => _78.map, 'call', _79 => _79((f) => f.error)]), () => ( [])));
|
|
1740
|
+
let complete = false;
|
|
1741
|
+
for (let pass = 0; pass < (_nullishCoalesce(options.maxPasses, () => ( 8))); pass++) {
|
|
1742
|
+
let waiting = 0;
|
|
1743
|
+
let missing = 0;
|
|
1744
|
+
drawVideoFrame(
|
|
1745
|
+
ctx,
|
|
1746
|
+
frame,
|
|
1747
|
+
(layer) => {
|
|
1748
|
+
if (layer.type === "effect") return null;
|
|
1749
|
+
const s = sourceFor(layer);
|
|
1750
|
+
if (!s) missing++;
|
|
1751
|
+
return s;
|
|
1752
|
+
},
|
|
1753
|
+
_nullishCoalesce(options.scale, () => ( 1)),
|
|
1754
|
+
{
|
|
1755
|
+
...options,
|
|
1756
|
+
onEffectWaiting: () => {
|
|
1757
|
+
waiting++;
|
|
1758
|
+
_optionalChain([options, 'access', _80 => _80.onEffectWaiting, 'optionalCall', _81 => _81()]);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
);
|
|
1762
|
+
if (!waiting) {
|
|
1763
|
+
complete = missing === 0;
|
|
1764
|
+
break;
|
|
1765
|
+
}
|
|
1766
|
+
await engine.idle();
|
|
1767
|
+
}
|
|
1768
|
+
const failures = (_nullishCoalesce(_optionalChain([engine, 'optionalAccess', _82 => _82.failuresList, 'call', _83 => _83()]), () => ( []))).map((f) => f.error).filter((e) => !before.has(e));
|
|
1769
|
+
return { frame, complete: complete && !failures.length, failures };
|
|
1770
|
+
}
|
|
1323
1771
|
|
|
1324
1772
|
// src/video-editor/export.ts
|
|
1325
|
-
var reelName = (asset) => (_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess',
|
|
1773
|
+
var reelName = (asset) => (_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _84 => _84.name]), () => ( _optionalChain([asset, 'optionalAccess', _85 => _85.id]))), () => ( "AX"))).replace(/\.[a-z0-9]+$/i, "").replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 8) || "AX";
|
|
1326
1774
|
function toVideoEdl(project, options = {}) {
|
|
1327
1775
|
const track = options.track !== void 0 ? project.tracks.find((t) => t.id === options.track) : [...project.tracks].reverse().find((t) => t.kind === "video");
|
|
1328
1776
|
if (!track) throw new Error(options.track !== void 0 ? `There is no track "${options.track}".` : "The project has no video track.");
|
|
@@ -1357,14 +1805,20 @@ function toVideoEdl(project, options = {}) {
|
|
|
1357
1805
|
lines.push(row(n2, reel, `${t.type === "crossfade" || t.type === "dip" ? "D" : "W001"} ${frames}`, sourceAt(clip, recIn), sourceAt(clip, recOut), recIn, recOut));
|
|
1358
1806
|
} else lines.push(row(n2, reel, "C", sourceAt(clip, recIn), sourceAt(clip, recOut), recIn, recOut));
|
|
1359
1807
|
if (clip.type === "media" && videoClipSpeed(clip) !== 1) lines.push(`M2 ${reel.padEnd(8)} ${(videoClipSpeed(clip) * fps).toFixed(1).padStart(6)} ${tc(sourceAt(clip, recIn))}`);
|
|
1360
|
-
if (clip.type === "media") lines.push(`* FROM CLIP NAME: ${_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess',
|
|
1808
|
+
if (clip.type === "media") lines.push(`* FROM CLIP NAME: ${_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _86 => _86.name]), () => ( _optionalChain([asset, 'optionalAccess', _87 => _87.src]))), () => ( clip.asset))}`);
|
|
1361
1809
|
if (clip.type === "text") lines.push(`* TITLE: ${clip.text.replace(/\n/g, " ")}`);
|
|
1362
1810
|
if (clip.type === "color") lines.push(`* COLOR: ${clip.color}`);
|
|
1811
|
+
if (clip.type === "effect") lines.push(`* EFFECT CLIP: ${_nullishCoalesce(clip.name, () => ( clip.effect))} (${clip.effect})`);
|
|
1812
|
+
if (_optionalChain([clip, 'access', _88 => _88.transition, 'optionalAccess', _89 => _89.type]) === "effect") lines.push(`* EFFECT TRANSITION: ${_nullishCoalesce(_optionalChain([clip, 'access', _90 => _90.transition, 'access', _91 => _91.effect, 'optionalAccess', _92 => _92.name]), () => ( _optionalChain([clip, 'access', _93 => _93.transition, 'access', _94 => _94.effect, 'optionalAccess', _95 => _95.effect])))}`);
|
|
1813
|
+
for (const e of _nullishCoalesce(clip.effects, () => ( []))) if (e.enabled !== false) lines.push(`* EFFECT: ${_nullishCoalesce(e.name, () => ( e.effect))} (${e.effect})`);
|
|
1363
1814
|
if (clip.type === "sequence") lines.push(`* COMPOUND CLIP: ${_nullishCoalesce(clip.name, () => ( clip.sequence))} (sequence ${clip.sequence} from ${tc(_nullishCoalesce(clip.in, () => ( 0)))}, not flattened)`);
|
|
1364
1815
|
lines.push("");
|
|
1365
1816
|
}
|
|
1366
1817
|
return lines.join("\n");
|
|
1367
1818
|
}
|
|
1819
|
+
function videoFfmpegUnsupportedEffects(project) {
|
|
1820
|
+
return allVideoTracks(project).filter(({ track }) => track.kind === "video" && !track.hidden).flatMap(({ track }) => track.clips.flatMap(videoClipEffectUses)).filter((u) => u.enabled);
|
|
1821
|
+
}
|
|
1368
1822
|
function loudnormFilter(target = {}, measure = false) {
|
|
1369
1823
|
const I = _nullishCoalesce(target.integrated, () => ( -14));
|
|
1370
1824
|
const TP = _nullishCoalesce(target.truePeak, () => ( -1));
|
|
@@ -1447,7 +1901,21 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1447
1901
|
const H = even(project.height);
|
|
1448
1902
|
const fps = project.fps;
|
|
1449
1903
|
const inputs = [];
|
|
1450
|
-
const
|
|
1904
|
+
const frames = measure ? void 0 : options.frames;
|
|
1905
|
+
if (!measure && !frames) {
|
|
1906
|
+
const effects = videoFfmpegUnsupportedEffects(project);
|
|
1907
|
+
if (effects.length) {
|
|
1908
|
+
const list = effects.slice(0, 5).map((u) => `${u.kind === "generator" ? "effect clip" : u.kind === "transition" ? "the transition into" : "an effect on"} "${u.clip}" (${_nullishCoalesce(u.name, () => ( u.effect))})`).join(", ");
|
|
1909
|
+
throw new Error(
|
|
1910
|
+
`ffmpeg cannot draw provider effects, and this project uses ${effects.length}: ${list}${effects.length > 5 ? ", ..." : ""}. Render the picture frame by frame with renderVideoFrameExact (the preview's drawing, with the effects provider) to PNGs or raw RGBA and pass them as frames: { input: "frames/%06d.png" } (or { input: "pipe:0", format: "rawvideo" }); ffmpeg still mixes the sound. Or turn the effects off.`
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
if (frames) {
|
|
1915
|
+
if (frames.format === "rawvideo") inputs.push(["-f", "rawvideo", "-pix_fmt", "rgba", "-s", `${W}x${H}`, "-framerate", String(fps), "-i", frames.input]);
|
|
1916
|
+
else inputs.push(["-framerate", String(fps), "-i", frames.input]);
|
|
1917
|
+
}
|
|
1918
|
+
const graph = measure || frames ? [] : [`color=c=${color(_nullishCoalesce(project.background, () => ( "#000000")))}:s=${W}x${H}:r=${fps}:d=${sec(D)},format=yuva420p[base]`];
|
|
1451
1919
|
let usesDrawtext = false;
|
|
1452
1920
|
const animated = /* @__PURE__ */ new Set();
|
|
1453
1921
|
const ramped = /* @__PURE__ */ new Set();
|
|
@@ -1565,13 +2033,13 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1565
2033
|
}
|
|
1566
2034
|
let index;
|
|
1567
2035
|
if (shows && asset && asset.kind !== "audio") index = input(clip, asset, lead, tail);
|
|
1568
|
-
const audible = clip.type === "sequence" ? !!_optionalChain([compound, 'optionalAccess',
|
|
2036
|
+
const audible = clip.type === "sequence" ? !!_optionalChain([compound, 'optionalAccess', _96 => _96.sound]) : videoAssetHasSound(asset);
|
|
1569
2037
|
if (scope.sound && audible && (clip.type === "media" || clip.type === "sequence") && !clip.muted && !track.muted) {
|
|
1570
2038
|
const trackGain = _nullishCoalesce(track.volume, () => ( 1));
|
|
1571
2039
|
const volume = isVideoKeyframes(clip.volume) ? `volume='${trackGain}*(${expr(clip.volume, `(t*1000${lead ? `-${Math.round(lead)}` : ""})`, 1, "volume")})':eval=frame` : `volume=${(_nullishCoalesce(clip.volume, () => ( 1))) * trackGain}`;
|
|
1572
2040
|
const ramp = clip.type === "media" ? clip.timeRemap : void 0;
|
|
1573
2041
|
const ai = compound ? -1 : _nullishCoalesce(index, () => ( input(clip, asset, lead, tail)));
|
|
1574
|
-
let source2 = _nullishCoalesce(_optionalChain([compound, 'optionalAccess',
|
|
2042
|
+
let source2 = _nullishCoalesce(_optionalChain([compound, 'optionalAccess', _97 => _97.sound]), () => ( `[${ai}:a]`));
|
|
1575
2043
|
if (ramp) {
|
|
1576
2044
|
source2 = `[r${ak}]`;
|
|
1577
2045
|
graph.push(...rampedSound(ramp, `${ai}:a`, source2, lead, clip.duration + tail));
|
|
@@ -1585,7 +2053,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1585
2053
|
if (lead) chain2.push(`afade=t=in:st=0:d=${sec(2 * lead)}${inCurve}`);
|
|
1586
2054
|
if (tail) chain2.push(`afade=t=out:st=${sec(span - 2 * tail)}:d=${sec(2 * tail)}${outCurve}`);
|
|
1587
2055
|
if (before2 && clip.transition.type === "dip") chain2.push(`afade=t=in:st=0:d=${sec(clip.transition.duration / 2)}${inCurve}`);
|
|
1588
|
-
if (_optionalChain([next2, 'optionalAccess',
|
|
2056
|
+
if (_optionalChain([next2, 'optionalAccess', _98 => _98.transition, 'access', _99 => _99.type]) === "dip") chain2.push(`afade=t=out:st=${sec(span - next2.transition.duration / 2)}:d=${sec(next2.transition.duration / 2)}${outCurve}`);
|
|
1589
2057
|
if (clip.fadeIn) chain2.push(`afade=t=in:st=${sec(lead)}:d=${sec(clip.fadeIn)}${afadeCurve(clip.fadeInCurve)}`);
|
|
1590
2058
|
if (clip.fadeOut) chain2.push(`afade=t=out:st=${sec(lead + clip.duration - clip.fadeOut)}:d=${sec(clip.fadeOut)}${afadeCurve(clip.fadeOutCurve)}`);
|
|
1591
2059
|
chain2.push(`adelay=${Math.round(t0)}:all=1`);
|
|
@@ -1598,7 +2066,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1598
2066
|
const onTimeline = `(t*1000-${Math.round(clip.start)})`;
|
|
1599
2067
|
const onChain = (v) => `(${v}*1000${lead ? `-${Math.round(lead)}` : ""})`;
|
|
1600
2068
|
const tf = clip.transform;
|
|
1601
|
-
const moving = VIDEO_TRANSFORM_PROPERTIES.some((p) => isVideoKeyframes(_optionalChain([tf, 'optionalAccess',
|
|
2069
|
+
const moving = VIDEO_TRANSFORM_PROPERTIES.some((p) => isVideoKeyframes(_optionalChain([tf, 'optionalAccess', _100 => _100[p]])));
|
|
1602
2070
|
if (clip.type === "text" && options.titleImage) {
|
|
1603
2071
|
inputs.push(["-loop", "1", "-framerate", String(fps), "-t", sec(span), "-i", options.titleImage(moving ? { ...clip, transform: void 0 } : clip, { width: project.width, height: project.height })]);
|
|
1604
2072
|
index = inputs.length - 1;
|
|
@@ -1608,7 +2076,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1608
2076
|
const op = isVideoKeyframes(clip.opacity) ? `clip(${expr(clip.opacity, onTimeline, 1, "opacity")},0,1)` : String(_nullishCoalesce(clip.opacity, () => ( 1)));
|
|
1609
2077
|
const alpha = [op, clip.fadeIn ? `min(1,(t-${sec(clip.start)})/${sec(clip.fadeIn)})` : "", clip.fadeOut ? `min(1,(${sec(end)}-t)/${sec(clip.fadeOut)})` : ""].filter(Boolean).join("*");
|
|
1610
2078
|
const coord = (axis) => {
|
|
1611
|
-
const v = _optionalChain([tf, 'optionalAccess',
|
|
2079
|
+
const v = _optionalChain([tf, 'optionalAccess', _101 => _101[axis]]);
|
|
1612
2080
|
const base2 = `(${axis === "x" ? "w-text_w" : "h-text_h"})/2`;
|
|
1613
2081
|
return isVideoKeyframes(v) ? `${axis}='${base2}+(${expr(v, onTimeline, 0, "position")})'` : `${axis}=${base2}+${_nullishCoalesce(v, () => ( 0))}`;
|
|
1614
2082
|
};
|
|
@@ -1616,7 +2084,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1616
2084
|
const opts = [
|
|
1617
2085
|
`text=${ffmpegFilterValue(clip.text)}`,
|
|
1618
2086
|
options.fontFile ? `fontfile=${ffmpegFilterValue(options.fontFile)}` : `font=${ffmpegFilterValue(_nullishCoalesce(clip.fontFamily, () => ( "sans-serif")))}`,
|
|
1619
|
-
isVideoKeyframes(_optionalChain([tf, 'optionalAccess',
|
|
2087
|
+
isVideoKeyframes(_optionalChain([tf, 'optionalAccess', _102 => _102.scale])) ? `fontsize='${fontSize}*(${expr(tf.scale, onTimeline, 1, "scale")})'` : `fontsize=${Math.round(fontSize * (_nullishCoalesce(_optionalChain([tf, 'optionalAccess', _103 => _103.scale]), () => ( 1))))}`,
|
|
1620
2088
|
`fontcolor=${color(_nullishCoalesce(clip.color, () => ( "#ffffff")))}`,
|
|
1621
2089
|
coord("x"),
|
|
1622
2090
|
coord("y"),
|
|
@@ -1624,8 +2092,8 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1624
2092
|
...clip.background ? ["box=1", `boxcolor=${color(clip.background)}`, `boxborderw=${Math.round((_nullishCoalesce(clip.fontSize, () => ( 64))) / 2)}`] : [],
|
|
1625
2093
|
`enable='between(t,${sec(clip.start)},${sec(end)})'`
|
|
1626
2094
|
];
|
|
1627
|
-
if (_optionalChain([tf, 'optionalAccess',
|
|
1628
|
-
if (videoGradeFfmpegFilters(clip.grade).length || normalizeVideoGradeLut(_optionalChain([clip, 'access',
|
|
2095
|
+
if (_optionalChain([tf, 'optionalAccess', _104 => _104.rotation])) notes.push(`Title "${clip.id}" is drawn without its rotation (drawtext cannot rotate).`);
|
|
2096
|
+
if (videoGradeFfmpegFilters(clip.grade).length || normalizeVideoGradeLut(_optionalChain([clip, 'access', _105 => _105.grade, 'optionalAccess', _106 => _106.lut]))) notes.push(`Title "${clip.id}" is drawn without its grade (drawtext draws straight onto the picture); pass titleImage to grade titles.`);
|
|
1629
2097
|
if (clip.blend && clip.blend !== "normal") notes.push(`Title "${clip.id}" is drawn without its blend mode (drawtext draws straight onto the picture); pass titleImage to blend titles.`);
|
|
1630
2098
|
graph.push(`[${cur2}]drawtext=${opts.join(":")}[${next}]`);
|
|
1631
2099
|
cur2 = next;
|
|
@@ -1638,7 +2106,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1638
2106
|
const chain = [];
|
|
1639
2107
|
let source;
|
|
1640
2108
|
if (clip.type === "color") source = `color=c=${color(clip.color)}:s=${even(rect.width)}x${even(rect.height)}:r=${fps}:d=${sec(span)}`;
|
|
1641
|
-
else if (_optionalChain([compound, 'optionalAccess',
|
|
2109
|
+
else if (_optionalChain([compound, 'optionalAccess', _107 => _107.picture])) {
|
|
1642
2110
|
source = compound.picture;
|
|
1643
2111
|
chain.push(`scale=${even(rect.width)}:${even(rect.height)}`);
|
|
1644
2112
|
} else {
|
|
@@ -1648,7 +2116,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1648
2116
|
}
|
|
1649
2117
|
const grade = videoGradeFfmpegFilters(clip.grade);
|
|
1650
2118
|
if (grade.length) chain.push("format=rgba", ...grade);
|
|
1651
|
-
const lut = normalizeVideoGradeLut(_optionalChain([clip, 'access',
|
|
2119
|
+
const lut = normalizeVideoGradeLut(_optionalChain([clip, 'access', _108 => _108.grade, 'optionalAccess', _109 => _109.lut]));
|
|
1652
2120
|
if (lut) {
|
|
1653
2121
|
chain.push("format=gbrap");
|
|
1654
2122
|
const path = options.resolveLut ? options.resolveLut(lut.src) : lut.src;
|
|
@@ -1679,10 +2147,10 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1679
2147
|
const r = videoIrisRadius({ width: W, height: H }, 1);
|
|
1680
2148
|
chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='if(lte(hypot(X+${Math.round(rect.x)}-${W / 2},Y+${Math.round(rect.y)}-${H / 2}),${Math.round(r)}*min(1,T/${sec(2 * lead)})),alpha(X,Y),0)'`);
|
|
1681
2149
|
}
|
|
1682
|
-
const rotation = _optionalChain([placed, 'optionalAccess',
|
|
2150
|
+
const rotation = _optionalChain([placed, 'optionalAccess', _110 => _110.rotation]);
|
|
1683
2151
|
if (isVideoKeyframes(rotation)) chain.push(`rotate=a='(${expr(rotation, onChain("t"), 0, "rotation")})*PI/180':c=black@0:ow='hypot(iw,ih)':oh=ow`);
|
|
1684
2152
|
else if (rotation) chain.push(`rotate=${rotation}*PI/180:c=black@0:ow=rotw(${rotation}*PI/180):oh=roth(${rotation}*PI/180)`);
|
|
1685
|
-
if (isVideoKeyframes(_optionalChain([placed, 'optionalAccess',
|
|
2153
|
+
if (isVideoKeyframes(_optionalChain([placed, 'optionalAccess', _111 => _111.scale]))) {
|
|
1686
2154
|
const s = expr(placed.scale, onChain("t"), 1, "scale");
|
|
1687
2155
|
chain.push(`scale=w='max(2,trunc(iw*(${s})/2)*2)':h='max(2,trunc(ih*(${s})/2)*2)':eval=frame`);
|
|
1688
2156
|
}
|
|
@@ -1701,9 +2169,9 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1701
2169
|
const into = lead ? clip.transition.type : void 0;
|
|
1702
2170
|
if (into === "push" || into === "slide") shiftBy(clip.transition.direction, clip.start - lead, 2 * lead, true);
|
|
1703
2171
|
const after = tail ? sortedVideoClips(track).find((c) => c !== clip && Math.abs(c.start - end) < 0.5 && c.transition) : void 0;
|
|
1704
|
-
if (_optionalChain([after, 'optionalAccess',
|
|
2172
|
+
if (_optionalChain([after, 'optionalAccess', _112 => _112.transition, 'access', _113 => _113.type]) === "push") shiftBy(after.transition.direction, end - tail, 2 * tail, false);
|
|
1705
2173
|
const at = (axis, c) => {
|
|
1706
|
-
const v = _optionalChain([placed, 'optionalAccess',
|
|
2174
|
+
const v = _optionalChain([placed, 'optionalAccess', _114 => _114[axis]]);
|
|
1707
2175
|
const extra = shifts[axis].map((s) => `+${s}`).join("");
|
|
1708
2176
|
return isVideoKeyframes(v) ? `${axis}='${Math.round(axis === "x" ? project.width / 2 : project.height / 2)}+(${expr(v, onTimeline, 0, "position")})-${axis === "x" ? "w" : "h"}/2${extra}'` : `${axis}='${Math.round(c)}-${axis === "x" ? "w" : "h"}/2${extra}'`;
|
|
1709
2177
|
};
|
|
@@ -1739,17 +2207,18 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1739
2207
|
}
|
|
1740
2208
|
return { video: cur2, audio: sounds };
|
|
1741
2209
|
};
|
|
1742
|
-
const top = compose(project.tracks, measure ? null : "base", D, { within: [], picture: !measure, sound: true });
|
|
2210
|
+
const top = compose(project.tracks, measure || frames ? null : "base", D, { within: [], picture: !measure && !frames, sound: true });
|
|
1743
2211
|
const audio = top.audio;
|
|
1744
2212
|
const cur = top.video;
|
|
1745
|
-
if (
|
|
2213
|
+
if (frames) graph.push(`[0:v]scale=${W}:${H},format=yuv420p,trim=duration=${sec(D)}[vout]`);
|
|
2214
|
+
else if (!measure) graph.push(`[${cur}]format=yuv420p[vout]`);
|
|
1746
2215
|
if (measure && !audio.length) throw new Error("Nothing on the timeline makes a sound, so there is no loudness to measure.");
|
|
1747
2216
|
const loudness = options.loudness || measure ? `,${loudnormFilter(options.loudness, measure)}` : "";
|
|
1748
2217
|
if (audio.length) graph.push(`${audio.join("")}amix=inputs=${audio.length}:normalize=0:duration=longest,apad,atrim=0:${sec(D)}${loudness}[aout]`);
|
|
1749
2218
|
if (options.loudness && !audio.length) notes.push("Nothing makes a sound, so there was no loudness to normalise.");
|
|
1750
2219
|
else if (options.loudness && !options.loudness.measured)
|
|
1751
2220
|
notes.push("Loudness is normalised in one pass, which adjusts the level as it goes; for a fixed gain that keeps the dynamics, run toVideoLoudnessMeasure first and pass its result as loudness.measured.");
|
|
1752
|
-
if (allVideoTracks(project).some(({ track: t }) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access',
|
|
2221
|
+
if (allVideoTracks(project).some(({ track: t }) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access', _115 => _115.get, 'call', _116 => _116(c.asset), 'optionalAccess', _117 => _117.kind]) === "video" && _optionalChain([assets, 'access', _118 => _118.get, 'call', _119 => _119(c.asset), 'optionalAccess', _120 => _120.hasAudio]) === void 0)))
|
|
1753
2222
|
notes.push('Video assets are assumed to have sound; set "hasAudio": false on any that do not, or ffmpeg will stop at a missing audio stream.');
|
|
1754
2223
|
if (animated.size)
|
|
1755
2224
|
notes.push(`Keyframed ${[...animated].sort().join(", ")} ${animated.size > 1 ? "are" : "is"} rendered as straight lines between keys, with eased segments sampled every two frames${animated.has("scale") ? "; animated scale needs ffmpeg 5 or later (scale with eval=frame)" : ""}.`);
|
|
@@ -1904,5 +2373,20 @@ function buildFfmpeg(project, options, measure) {
|
|
|
1904
2373
|
|
|
1905
2374
|
|
|
1906
2375
|
|
|
1907
|
-
|
|
1908
|
-
|
|
2376
|
+
|
|
2377
|
+
|
|
2378
|
+
|
|
2379
|
+
|
|
2380
|
+
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
|
|
2385
|
+
|
|
2386
|
+
|
|
2387
|
+
|
|
2388
|
+
|
|
2389
|
+
|
|
2390
|
+
|
|
2391
|
+
exports.VIDEO_FITS = VIDEO_FITS; exports.VIDEO_TRANSITION_TYPES = VIDEO_TRANSITION_TYPES; exports.VIDEO_WIPE_DIRECTIONS = VIDEO_WIPE_DIRECTIONS; exports.videoTransitionAudioCurve = videoTransitionAudioCurve; exports.VIDEO_BLEND_MODES = VIDEO_BLEND_MODES; exports.VIDEO_CLIP_ANIMATION_EFFECTS = VIDEO_CLIP_ANIMATION_EFFECTS; exports.VIDEO_FADE_CURVES = VIDEO_FADE_CURVES; exports.VIDEO_PLACEMENT_MODES = VIDEO_PLACEMENT_MODES; exports.VIDEO_CLIP_TYPES = VIDEO_CLIP_TYPES; exports.VIDEO_EASE_NAMES = VIDEO_EASE_NAMES; exports.VIDEO_KEYFRAME_PROPERTIES = VIDEO_KEYFRAME_PROPERTIES; exports.VIDEO_TRANSFORM_PROPERTIES = VIDEO_TRANSFORM_PROPERTIES; exports.VIDEO_KEYFRAME_LIMITS = VIDEO_KEYFRAME_LIMITS; exports.isVideoKeyframes = isVideoKeyframes; exports.videoEaseBezier = videoEaseBezier; exports.videoEaseProgress = videoEaseProgress; exports.videoValueAt = videoValueAt; exports.videoKeysOf = videoKeysOf; exports.retimeVideoAnimatable = retimeVideoAnimatable; exports.splitVideoAnimatable = splitVideoAnimatable; exports.sortVideoKeys = sortVideoKeys; exports.setVideoKey = setVideoKey; exports.removeVideoKey = removeVideoKey; exports.videoKeyframeBreakpoints = videoKeyframeBreakpoints; exports.videoKeyframeFfmpegExpr = videoKeyframeFfmpegExpr; exports.VIDEO_LUT_MAX_SIZE = VIDEO_LUT_MAX_SIZE; exports.parseVideoCubeLut = parseVideoCubeLut; exports.videoLutFromFunction = videoLutFromFunction; exports.videoLutToCube = videoLutToCube; exports.applyVideoLut = applyVideoLut; exports.applyVideoLutToPixels = applyVideoLutToPixels; exports.videoLutSvgApproximation = videoLutSvgApproximation; exports.VideoLutCache = VideoLutCache; exports.VIDEO_GRADE_CONTROLS = VIDEO_GRADE_CONTROLS; exports.VIDEO_GRADE_PRESETS = VIDEO_GRADE_PRESETS; exports.VIDEO_GRADE_PRESET_NAMES = VIDEO_GRADE_PRESET_NAMES; exports.normalizeVideoGradeLut = normalizeVideoGradeLut; exports.normalizeVideoGrade = normalizeVideoGrade; exports.videoGradePreset = videoGradePreset; exports.videoGradeLook = videoGradeLook; exports.videoGradeSteps = videoGradeSteps; exports.videoGradeStepMatrix = videoGradeStepMatrix; exports.applyVideoGrade = applyVideoGrade; exports.videoGradeStepSvgValues = videoGradeStepSvgValues; exports.videoLutSvgPrimitives = videoLutSvgPrimitives; exports.videoGradeSvgFilter = videoGradeSvgFilter; exports.videoGradeGainsFilterId = videoGradeGainsFilterId; exports.videoGradeCssFilter = videoGradeCssFilter; exports.videoGradeSvgDefs = videoGradeSvgDefs; exports.videoGradeFfmpegFilters = videoGradeFfmpegFilters; exports.videoClipSourceTime = videoClipSourceTime; exports.videoClipSpeedAt = videoClipSpeedAt; exports.videoSpeedRampKeys = videoSpeedRampKeys; exports.checkVideoTimeRemap = checkVideoTimeRemap; exports.videoTimeRemapSpan = videoTimeRemapSpan; exports.videoTimeRemapBreakpoints = videoTimeRemapBreakpoints; exports.videoSpeedRampEdit = videoSpeedRampEdit; exports.videoFreezeFrameEdit = videoFreezeFrameEdit; exports.videoSlowMotionRamp = videoSlowMotionRamp; exports.VIDEO_TIME_EPSILON = VIDEO_TIME_EPSILON; exports.videoClipEnd = videoClipEnd; exports.videoTrackSyncLocked = videoTrackSyncLocked; exports.videoClipSpeed = videoClipSpeed; exports.videoClipSourceRange = videoClipSourceRange; exports.videoFrameDuration = videoFrameDuration; exports.snapToVideoFrame = snapToVideoFrame; exports.videoFrames = videoFrames; exports.isVideoDropFrameRate = isVideoDropFrameRate; exports.videoTimecodeFrames = videoTimecodeFrames; exports.formatVideoTimecode = formatVideoTimecode; exports.formatVideoTimecodeFrames = formatVideoTimecodeFrames; exports.videoHourFrames = videoHourFrames; exports.formatVideoSeconds = formatVideoSeconds; exports.videoAssetHasSound = videoAssetHasSound; exports.videoAssetMap = videoAssetMap; exports.videoSequenceMap = videoSequenceMap; exports.videoSequenceDuration = videoSequenceDuration; exports.videoSequenceChildren = videoSequenceChildren; exports.findVideoSequenceCycle = findVideoSequenceCycle; exports.allVideoTracks = allVideoTracks; exports.sortedVideoClips = sortedVideoClips; exports.findVideoClip = findVideoClip; exports.allVideoClipIds = allVideoClipIds; exports.findVideoSequenceClip = findVideoSequenceClip; exports.videoProjectDuration = videoProjectDuration; exports.videoClipBefore = videoClipBefore; exports.videoClipAfter = videoClipAfter; exports.videoClipHandles = videoClipHandles; exports.videoClipOverhang = videoClipOverhang; exports.videoSnapPoints = videoSnapPoints; exports.snapVideoTime = snapVideoTime; exports.findVideoGaps = findVideoGaps; exports.videoOverlap = videoOverlap; exports.nextFreeVideoSlot = nextFreeVideoSlot; exports.videoClipsAt = videoClipsAt; exports.VIDEO_CLIP_ANIMATION_DURATION = VIDEO_CLIP_ANIMATION_DURATION; exports.videoClipAnimationProperties = videoClipAnimationProperties; exports.videoClipAnimationSpans = videoClipAnimationSpans; exports.videoClipAnimated = videoClipAnimated; exports.videoEffectValuesAt = videoEffectValuesAt; exports.videoTransformAt = videoTransformAt; exports.placeVideoRect = placeVideoRect; exports.videoTextStyle = videoTextStyle; exports.videoTitleFrame = videoTitleFrame; exports.videoTransitionEase = videoTransitionEase; exports.videoFadeCurveGain = videoFadeCurveGain; exports.videoClipSoundGain = videoClipSoundGain; exports.videoFrameAt = videoFrameAt; exports.videoLayersWithoutTransitionEffects = videoLayersWithoutTransitionEffects; exports.videoFrameLeafLayers = videoFrameLeafLayers; exports.videoProjectEffects = videoProjectEffects; exports.videoClipEffectUses = videoClipEffectUses; exports.videoLayersUseEffects = videoLayersUseEffects; exports.videoEffectFrameTime = videoEffectFrameTime; exports.videoEffectLayerKey = videoEffectLayerKey; exports.mixEffectPixels = mixEffectPixels; exports.overEffectPixels = overEffectPixels; exports.VideoEffectsEngine = VideoEffectsEngine; exports.VIDEO_EFFECT_PLACES = VIDEO_EFFECT_PLACES; exports.checkVideoEffectPlace = checkVideoEffectPlace; exports.checkVideoEffectValues = checkVideoEffectValues; exports.videoIrisRadius = videoIrisRadius; exports.videoWipeRect = videoWipeRect; exports.videoFrameToSvg = videoFrameToSvg; exports.videoProjectSvg = videoProjectSvg; exports.videoContactSheetSvg = videoContactSheetSvg; exports.drawVideoFrame = drawVideoFrame; exports.renderVideoFrameExact = renderVideoFrameExact; exports.toVideoEdl = toVideoEdl; exports.videoFfmpegUnsupportedEffects = videoFfmpegUnsupportedEffects; exports.parseVideoLoudnessMeasurement = parseVideoLoudnessMeasurement; exports.ffmpegFilterValue = ffmpegFilterValue; exports.videoShellQuote = videoShellQuote; exports.toVideoFfmpeg = toVideoFfmpeg; exports.toVideoLoudnessMeasure = toVideoLoudnessMeasure;
|
|
2392
|
+
//# sourceMappingURL=chunk-IWSMDQE5.cjs.map
|