@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.
Files changed (116) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.js +3 -3
  4. package/dist/animation/index.d.cts +2 -1
  5. package/dist/animation/index.d.ts +2 -1
  6. package/dist/animation-editor/index.cjs.map +1 -1
  7. package/dist/animation-editor/index.d.cts +11 -733
  8. package/dist/animation-editor/index.d.ts +11 -733
  9. package/dist/animation-editor/index.js.map +1 -1
  10. package/dist/{bezier-C-W9LFyD.d.cts → bezier-CBzKKDjl.d.cts} +2 -66
  11. package/dist/{bezier-DQZE5dxA.d.ts → bezier-CWv6LQal.d.ts} +2 -66
  12. package/dist/{brushstrokeRender-M4S65DHT.cjs → brushstrokeRender-EELTV7U4.cjs} +8 -8
  13. package/dist/{brushstrokeRender-M4S65DHT.cjs.map → brushstrokeRender-EELTV7U4.cjs.map} +1 -1
  14. package/dist/{brushstrokeRender-CDDRSV2W.js → brushstrokeRender-LRV7GXGD.js} +3 -3
  15. package/dist/canvas/index.cjs +6 -6
  16. package/dist/canvas/index.d.cts +10 -6
  17. package/dist/canvas/index.d.ts +10 -6
  18. package/dist/canvas/index.js +1 -1
  19. package/dist/{chunk-TVXFHE4T.js → chunk-64PYNEHJ.js} +45 -12
  20. package/dist/chunk-64PYNEHJ.js.map +1 -0
  21. package/dist/{chunk-2IW3BWRT.cjs → chunk-7LQO6OHH.cjs} +2 -2
  22. package/dist/{chunk-2IW3BWRT.cjs.map → chunk-7LQO6OHH.cjs.map} +1 -1
  23. package/dist/chunk-IOXKXQNW.cjs +178 -0
  24. package/dist/chunk-IOXKXQNW.cjs.map +1 -0
  25. package/dist/{chunk-SMJGE7HM.cjs → chunk-IWSMDQE5.cjs} +523 -39
  26. package/dist/chunk-IWSMDQE5.cjs.map +1 -0
  27. package/dist/{chunk-KMBC4HNV.js → chunk-J67OLQUQ.js} +2 -2
  28. package/dist/{chunk-6CJRDJCU.cjs → chunk-PEHYWUVV.cjs} +176 -111
  29. package/dist/chunk-PEHYWUVV.cjs.map +1 -0
  30. package/dist/chunk-TGKT5QAB.js +178 -0
  31. package/dist/chunk-TGKT5QAB.js.map +1 -0
  32. package/dist/chunk-TK53AB4Q.cjs +215 -0
  33. package/dist/chunk-TK53AB4Q.cjs.map +1 -0
  34. package/dist/chunk-VFAKN46Z.js +215 -0
  35. package/dist/chunk-VFAKN46Z.js.map +1 -0
  36. package/dist/{chunk-6ZBR4J62.js → chunk-WATAQ2Z2.js} +79 -14
  37. package/dist/chunk-WATAQ2Z2.js.map +1 -0
  38. package/dist/{chunk-IPFRWILU.js → chunk-YVVSTD3B.js} +499 -15
  39. package/dist/chunk-YVVSTD3B.js.map +1 -0
  40. package/dist/{chunk-RLRUAPBJ.cjs → chunk-Z6OUCS2K.cjs} +47 -14
  41. package/dist/chunk-Z6OUCS2K.cjs.map +1 -0
  42. package/dist/effects/index.cjs +588 -0
  43. package/dist/effects/index.cjs.map +1 -0
  44. package/dist/effects/index.d.cts +131 -0
  45. package/dist/effects/index.d.ts +131 -0
  46. package/dist/effects/index.js +588 -0
  47. package/dist/effects/index.js.map +1 -0
  48. package/dist/evaluate-2S3vSPLy.d.cts +55 -0
  49. package/dist/evaluate-CfmT6xA3.d.ts +55 -0
  50. package/dist/{export-DfjzHAJI.d.ts → export-8RnlngAv.d.ts} +534 -128
  51. package/dist/{export-Dv0-ZVrV.d.ts → export-BzSRXyH6.d.cts} +390 -5
  52. package/dist/{export-Dv0-ZVrV.d.cts → export-DM3h7QEI.d.ts} +390 -5
  53. package/dist/{export-lU5qSpHE.d.cts → export-TqXMOlDt.d.cts} +534 -128
  54. package/dist/flair-CxB3fOcU.d.ts +739 -0
  55. package/dist/flair-D7BxyBwL.d.cts +739 -0
  56. package/dist/gradient-KDfCvhuj.d.cts +66 -0
  57. package/dist/gradient-KDfCvhuj.d.ts +66 -0
  58. package/dist/helpers-777mC6dc.d.cts +253 -0
  59. package/dist/helpers-DX2_7L5t.d.ts +253 -0
  60. package/dist/image-editor/index.cjs +1307 -805
  61. package/dist/image-editor/index.cjs.map +1 -1
  62. package/dist/image-editor/index.d.cts +39 -182
  63. package/dist/image-editor/index.d.ts +39 -182
  64. package/dist/image-editor/index.js +508 -6
  65. package/dist/image-editor/index.js.map +1 -1
  66. package/dist/{opentype-p8Xxf-Wh.d.ts → opentype-14PDI5D2.d.ts} +1 -1
  67. package/dist/{opentype-BV7wWatY.d.cts → opentype-D80AVmMu.d.cts} +1 -1
  68. package/dist/paint/index.cjs +2 -2
  69. package/dist/paint/index.d.cts +32 -10
  70. package/dist/paint/index.d.ts +32 -10
  71. package/dist/paint/index.js +1 -1
  72. package/dist/paint-editor/index.cjs +10 -8
  73. package/dist/paint-editor/index.cjs.map +1 -1
  74. package/dist/paint-editor/index.d.cts +11 -5
  75. package/dist/paint-editor/index.d.ts +11 -5
  76. package/dist/paint-editor/index.js +4 -2
  77. package/dist/paint-editor/index.js.map +1 -1
  78. package/dist/parameters/index.d.cts +4 -184
  79. package/dist/parameters/index.d.ts +4 -184
  80. package/dist/{pathfinder-IxcyhO00.d.cts → pathfinder-C0UoFypx.d.cts} +1 -1
  81. package/dist/{pathfinder-StVChJOT.d.ts → pathfinder-DjISxkUv.d.ts} +1 -1
  82. package/dist/{player-DrcP2xqj.d.cts → player-CBtEoU2x.d.cts} +2 -54
  83. package/dist/{player-DV4Jujdq.d.ts → player-cGLrngL_.d.ts} +2 -54
  84. package/dist/{svg-BtND4TJu.d.cts → svg-BDrjRnL1.d.cts} +3 -2
  85. package/dist/{svg-BfVXG7hL.d.ts → svg-C-vZ3q50.d.ts} +3 -2
  86. package/dist/tools/index.cjs +2 -2
  87. package/dist/tools/index.cjs.map +1 -1
  88. package/dist/tools/index.js +3 -3
  89. package/dist/tools-C7JtLYzo.d.ts +126 -0
  90. package/dist/tools-DHKGHiSK.d.cts +126 -0
  91. package/dist/{types-fuXEme9j.d.ts → types-BcX7sCsn.d.ts} +2 -2
  92. package/dist/{types-DJF3Ghn5.d.cts → types-CznabG7m.d.cts} +2 -2
  93. package/dist/types-D3-J3TFf.d.ts +185 -0
  94. package/dist/types-DMcf_z4J.d.cts +185 -0
  95. package/dist/vector/index.d.cts +10 -10
  96. package/dist/vector/index.d.ts +10 -10
  97. package/dist/video-editor/index.cjs +749 -277
  98. package/dist/video-editor/index.cjs.map +1 -1
  99. package/dist/video-editor/index.d.cts +265 -138
  100. package/dist/video-editor/index.d.ts +265 -138
  101. package/dist/video-editor/index.js +490 -18
  102. package/dist/video-editor/index.js.map +1 -1
  103. package/dist/{warp-BjmAgQwq.d.cts → warp-CH9MM435.d.cts} +1 -1
  104. package/dist/{warp-Ca5fedzS.d.ts → warp-k_PdtXOG.d.ts} +1 -1
  105. package/package.json +6 -1
  106. package/skills/pixerate-image-editor/SKILL.md +7 -0
  107. package/skills/pixerate-image-editor/references/provider-effects.md +35 -0
  108. package/skills/pixerate-video-editor/SKILL.md +39 -1
  109. package/dist/chunk-6CJRDJCU.cjs.map +0 -1
  110. package/dist/chunk-6ZBR4J62.js.map +0 -1
  111. package/dist/chunk-IPFRWILU.js.map +0 -1
  112. package/dist/chunk-RLRUAPBJ.cjs.map +0 -1
  113. package/dist/chunk-SMJGE7HM.cjs.map +0 -1
  114. package/dist/chunk-TVXFHE4T.js.map +0 -1
  115. /package/dist/{brushstrokeRender-CDDRSV2W.js.map → brushstrokeRender-LRV7GXGD.js.map} +0 -0
  116. /package/dist/{chunk-KMBC4HNV.js.map → chunk-J67OLQUQ.js.map} +0 -0
@@ -1,13 +1,24 @@
1
+ import {
2
+ EffectControlError,
3
+ EffectUnsupportedError,
4
+ EffectsSlot,
5
+ createEffectPixels,
6
+ effectValueErrors
7
+ } from "./chunk-TGKT5QAB.js";
8
+ import {
9
+ validateJsonSchema
10
+ } from "./chunk-4FX5ACVG.js";
11
+
1
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) => 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(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(transform?.x, clipTime, 0), y: videoValueAt(transform?.y, clipTime, 0), scale: videoValueAt(transform?.scale, clipTime, 1), rotation: videoValueAt(transform?.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: 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 = 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 = (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) => [...l.transitionEffect?.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 (clip.transition?.type === "effect" && clip.transition.effect) out.push({ clip: clip.id, kind: "transition", ...refFields(clip.transition.effect), enabled: true });
1161
+ for (const e of 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) => !!(l.effects?.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 = createEffectPixels(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 = createEffectPixels(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
+ c.getContext("2d")?.putImageData(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
+ c.getContext("2d")?.putImageData(image, 0, 0);
1207
+ return c;
1208
+ }
1209
+ return null;
1210
+ }
1211
+ var VideoEffectsEngine = class {
1212
+ slot;
1213
+ cache = /* @__PURE__ */ new Map();
1214
+ pending = /* @__PURE__ */ new Map();
1215
+ failures = /* @__PURE__ */ new Map();
1216
+ renderers = /* @__PURE__ */ new Map();
1217
+ descriptions = /* @__PURE__ */ new Map();
1218
+ listeners = /* @__PURE__ */ new Set();
1219
+ maxEntries;
1220
+ toImage;
1221
+ generation = 0;
1222
+ detach;
1223
+ constructor(options = {}) {
1224
+ this.slot = options.slot ?? new EffectsSlot();
1225
+ this.maxEntries = options.maxEntries ?? 120;
1226
+ this.toImage = 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 this.cache.get(key)?.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
+ };
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 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(values ?? {})) {
1388
+ if (!isVideoKeyframes(v)) {
1389
+ plain[k] = v;
1390
+ continue;
1391
+ }
1392
+ const schema = d.controls.properties[k];
1393
+ const type = schema?.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 validateJsonSchema(key.v, schema)) errors.push({ path: `/${k}/keys/${i}/v`, message: e.message });
1398
+ }
1399
+ for (const e of effectValueErrors(d, plain)) {
1400
+ const name = e.path.split("/")[1];
1401
+ const about = name && d.controls.properties[name]?.description;
1402
+ errors.push(about ? { path: e.path, message: `${e.message} (${about})` } : e);
1403
+ }
1404
+ if (errors.length) throw new EffectControlError(d.id, errors);
1405
+ return { ...options.base ?? d.defaults, ...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 = [];
@@ -1168,7 +1463,7 @@ function videoFrameToSvg(frame, options = {}, assets = []) {
1168
1463
  const href = options.imageFor ? options.imageFor(layer, frame) : layer.assetKind === "image" ? layer.src ?? null : posters.get(layer.asset ?? "") ?? null;
1169
1464
  if (href) inner = `<image href="${esc(href)}" ${box} preserveAspectRatio="none"/>`;
1170
1465
  else {
1171
- const label = `${layer.asset ?? layer.clip} \xB7 ${formatVideoTimecode(layer.sourceTime ?? 0, options.fps ?? 30)}`;
1466
+ const label = layer.generator ? `Effect: ${layer.generator.name ?? layer.generator.effect}` : `${layer.asset ?? layer.clip} \xB7 ${formatVideoTimecode(layer.sourceTime ?? 0, 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: ${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
- drawVideoLayers(ctx, frame, frame.layers, sourceFor, scale, options, 0);
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
- function drawVideoLayers(ctx, frame, layers, sourceFor, scale, options, depth) {
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 (!engine?.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 hit ?? null;
1607
+ let job;
1608
+ if (layer.generator) job = { kind: "generator", width: w, height: h, ...layer.generator, effects: layer.effects ?? [] };
1609
+ else {
1610
+ const s = options.scratch?.(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: layer.effects ?? [] };
1619
+ }
1620
+ if (engine.request(key, job)) {
1621
+ state.waiting++;
1622
+ options.onEffectWaiting?.();
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 (!engine?.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
+ options.onEffectWaiting?.();
1647
+ }
1648
+ return false;
1649
+ }
1650
+ const a = options.scratch?.(cw, ch, 1e3 + 4 * depth + 1);
1651
+ const b = options.scratch?.(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
+ options.onEffectWaiting?.();
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 layer of layers) {
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
1680
  const group = options.scratch?.(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,9 +1689,19 @@ 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
- source = (l) => l === layer ? group.canvas : sourceFor(l);
1694
+ const self = layer;
1695
+ source = (l) => l === self ? group.canvas : sourceFor(l);
1696
+ }
1697
+ if (layer.effects?.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
1706
  const setting = layer.grade?.lut;
1294
1707
  const lut = setting ? options.lutFor?.(setting.src) : null;
@@ -1320,6 +1733,41 @@ 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(engine?.failuresList().map((f) => f.error) ?? []);
1740
+ let complete = false;
1741
+ for (let pass = 0; pass < (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
+ options.scale ?? 1,
1754
+ {
1755
+ ...options,
1756
+ onEffectWaiting: () => {
1757
+ waiting++;
1758
+ options.onEffectWaiting?.();
1759
+ }
1760
+ }
1761
+ );
1762
+ if (!waiting) {
1763
+ complete = missing === 0;
1764
+ break;
1765
+ }
1766
+ await engine.idle();
1767
+ }
1768
+ const failures = (engine?.failuresList() ?? []).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
1773
  var reelName = (asset) => (asset?.name ?? asset?.id ?? "AX").replace(/\.[a-z0-9]+$/i, "").replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 8) || "AX";
@@ -1360,11 +1808,17 @@ function toVideoEdl(project, options = {}) {
1360
1808
  if (clip.type === "media") lines.push(`* FROM CLIP NAME: ${asset?.name ?? asset?.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: ${clip.name ?? clip.effect} (${clip.effect})`);
1812
+ if (clip.transition?.type === "effect") lines.push(`* EFFECT TRANSITION: ${clip.transition.effect?.name ?? clip.transition.effect?.effect}`);
1813
+ for (const e of clip.effects ?? []) if (e.enabled !== false) lines.push(`* EFFECT: ${e.name ?? e.effect} (${e.effect})`);
1363
1814
  if (clip.type === "sequence") lines.push(`* COMPOUND CLIP: ${clip.name ?? clip.sequence} (sequence ${clip.sequence} from ${tc(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 = target.integrated ?? -14;
1370
1824
  const TP = 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 graph = measure ? [] : [`color=c=${color(project.background ?? "#000000")}:s=${W}x${H}:r=${fps}:d=${sec(D)},format=yuva420p[base]`];
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}" (${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(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();
@@ -1739,10 +2207,11 @@ 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 (!measure) graph.push(`[${cur}]format=yuv420p[vout]`);
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]`);
@@ -1883,6 +2352,7 @@ export {
1883
2352
  videoClipAnimationProperties,
1884
2353
  videoClipAnimationSpans,
1885
2354
  videoClipAnimated,
2355
+ videoEffectValuesAt,
1886
2356
  videoTransformAt,
1887
2357
  placeVideoRect,
1888
2358
  videoTextStyle,
@@ -1891,18 +2361,32 @@ export {
1891
2361
  videoFadeCurveGain,
1892
2362
  videoClipSoundGain,
1893
2363
  videoFrameAt,
2364
+ videoLayersWithoutTransitionEffects,
1894
2365
  videoFrameLeafLayers,
2366
+ videoProjectEffects,
2367
+ videoClipEffectUses,
2368
+ videoLayersUseEffects,
2369
+ videoEffectFrameTime,
2370
+ videoEffectLayerKey,
2371
+ mixEffectPixels,
2372
+ overEffectPixels,
2373
+ VideoEffectsEngine,
2374
+ VIDEO_EFFECT_PLACES,
2375
+ checkVideoEffectPlace,
2376
+ checkVideoEffectValues,
1895
2377
  videoIrisRadius,
1896
2378
  videoWipeRect,
1897
2379
  videoFrameToSvg,
1898
2380
  videoProjectSvg,
1899
2381
  videoContactSheetSvg,
1900
2382
  drawVideoFrame,
2383
+ renderVideoFrameExact,
1901
2384
  toVideoEdl,
2385
+ videoFfmpegUnsupportedEffects,
1902
2386
  parseVideoLoudnessMeasurement,
1903
2387
  ffmpegFilterValue,
1904
2388
  videoShellQuote,
1905
2389
  toVideoFfmpeg,
1906
2390
  toVideoLoudnessMeasure
1907
2391
  };
1908
- //# sourceMappingURL=chunk-IPFRWILU.js.map
2392
+ //# sourceMappingURL=chunk-YVVSTD3B.js.map