@pixerate/editor 0.241.0 → 0.242.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 (41) hide show
  1. package/dist/animation-editor/controller.d.cts +10 -0
  2. package/dist/animation-editor/controller.d.ts +10 -0
  3. package/dist/animation-editor/effects.d.cts +1 -1
  4. package/dist/animation-editor/effects.d.ts +1 -1
  5. package/dist/animation-editor/index.cjs +137 -88
  6. package/dist/animation-editor/index.js +60 -11
  7. package/dist/canvas/index.cjs +7 -7
  8. package/dist/canvas/index.js +1 -1
  9. package/dist/{chunk-A7PQNIBX.cjs → chunk-7V7QMFTI.cjs} +14 -14
  10. package/dist/{chunk-JAOSL334.cjs → chunk-GHCSC2WP.cjs} +38 -16
  11. package/dist/{chunk-JWCKJNDR.cjs → chunk-K5DS2JTA.cjs} +6 -6
  12. package/dist/{chunk-WDDUPWVG.js → chunk-L3JFOTBQ.js} +1 -1
  13. package/dist/{chunk-SIK3LT6M.js → chunk-OFH6Q67U.js} +28 -6
  14. package/dist/{chunk-G3RUKJ5C.js → chunk-RXZL4NG5.js} +1 -1
  15. package/dist/{chunk-TEFQISBY.js → chunk-X3ZINLIP.js} +1 -1
  16. package/dist/{chunk-ZQIWBJGH.cjs → chunk-XNZFVYC2.cjs} +7 -7
  17. package/dist/effects/fake.d.cts +4 -0
  18. package/dist/effects/fake.d.ts +4 -0
  19. package/dist/effects/helpers.d.cts +6 -0
  20. package/dist/effects/helpers.d.ts +6 -0
  21. package/dist/effects/index.cjs +222 -94
  22. package/dist/effects/index.js +131 -3
  23. package/dist/effects/types.d.cts +31 -0
  24. package/dist/effects/types.d.ts +31 -0
  25. package/dist/image-editor/index.cjs +13 -13
  26. package/dist/image-editor/index.js +1 -1
  27. package/dist/paint-editor/index.cjs +7 -7
  28. package/dist/paint-editor/index.js +2 -2
  29. package/dist/vector/index.cjs +6 -6
  30. package/dist/vector/index.js +1 -1
  31. package/dist/video-editor/controller.d.cts +49 -1
  32. package/dist/video-editor/controller.d.ts +49 -1
  33. package/dist/video-editor/effect-path.d.cts +66 -0
  34. package/dist/video-editor/effect-path.d.ts +66 -0
  35. package/dist/video-editor/index.cjs +616 -427
  36. package/dist/video-editor/index.d.cts +1 -0
  37. package/dist/video-editor/index.d.ts +1 -0
  38. package/dist/video-editor/index.js +193 -4
  39. package/package.json +1 -1
  40. package/skills/pixerate-animation-editor/SKILL.md +1 -1
  41. package/skills/pixerate-video-editor/SKILL.md +9 -2
@@ -16,7 +16,7 @@ import {
16
16
  effectSubjectKind,
17
17
  effectTemplateTools,
18
18
  fillEffectTemplate
19
- } from "../chunk-G3RUKJ5C.js";
19
+ } from "../chunk-RXZL4NG5.js";
20
20
  import {
21
21
  IDENTITY_MATRIX,
22
22
  applyMatrix,
@@ -66,9 +66,10 @@ import {
66
66
  cameraViewOfBox,
67
67
  createEffectToolProvider,
68
68
  isEffectError,
69
+ pathControlsOf,
69
70
  resolveCameraMoveSpec,
70
71
  resolveEffectValues
71
- } from "../chunk-SIK3LT6M.js";
72
+ } from "../chunk-OFH6Q67U.js";
72
73
  import "../chunk-V2ZCJRDM.js";
73
74
  import "../chunk-2PBZBRP5.js";
74
75
  import "../chunk-ODBT55Q7.js";
@@ -4606,9 +4607,19 @@ function motionPartsFromVectorEffect(artwork, motion, source, box2 = { x: 0, y:
4606
4607
  const art = imported.scene.artwork;
4607
4608
  const prefix = `${source.instance}-`;
4608
4609
  const { nodes, shots } = prefixed(art.nodes, imported.scene.shots, prefix);
4609
- const k = Math.min(box2.width / art.width, box2.height / art.height);
4610
- const dx = box2.x + (box2.width - art.width * k) / 2;
4611
- const dy = box2.y + (box2.height - art.height * k) / 2;
4610
+ let k;
4611
+ let dx;
4612
+ let dy;
4613
+ if (box2 === "viewBox") {
4614
+ const vb = /viewBox\s*=\s*["']([^"']+)["']/.exec(motion.svg)?.[1].trim().split(/[\s,]+/).map(Number) ?? [];
4615
+ k = 1;
4616
+ dx = vb.length === 4 && Number.isFinite(vb[0]) ? vb[0] : 0;
4617
+ dy = vb.length === 4 && Number.isFinite(vb[1]) ? vb[1] : 0;
4618
+ } else {
4619
+ k = Math.min(box2.width / art.width, box2.height / art.height);
4620
+ dx = box2.x + (box2.width - art.width * k) / 2;
4621
+ dy = box2.y + (box2.height - art.height * k) / 2;
4622
+ }
4612
4623
  const group = { type: "group", id: source.instance, name: source.name ?? source.effect, children: nodes, transform: [k, 0, 0, k, dx, dy] };
4613
4624
  const tagged = shots.map((s, i) => ({ ...s, id: i === 0 ? source.instance + "-shot" : `${source.instance}-shot-${i + 1}`, run: tag(s.run, source) }));
4614
4625
  return { group, shots: tagged.map((s) => s.hold === 0 ? (({ hold: _h, ...rest }) => rest)(s) : s), notes: imported.notes };
@@ -4891,11 +4902,34 @@ var AnimationEditorController = class {
4891
4902
  addVectorEffect(effect, options = {}) {
4892
4903
  return this.effectEdit(async () => {
4893
4904
  const d = await this.describeFor(effect, ["vector"]);
4894
- const values = resolveEffectValues(d, options.values);
4905
+ let given = options.values;
4906
+ let place = options.box;
4907
+ if (options.path) {
4908
+ const controls = pathControlsOf(d);
4909
+ if (!controls.length) throw new Error(`"${d.id}" does not draw along a path. Find one that does with search_effects accepts ["path"] (kind vector).`);
4910
+ const control = options.control ?? (controls.length === 1 ? controls[0] : void 0);
4911
+ if (!control) throw new Error(`"${d.id}" has several path controls (${controls.join(", ")}); pass control.`);
4912
+ if (!controls.includes(control)) throw new Error(`"${control}" is not a path control of "${d.id}"; its path controls are ${controls.join(", ")}.`);
4913
+ let value;
4914
+ if ("node" in options.path) {
4915
+ const entry = indexMotionNodes(this.scene.artwork.nodes).get(options.path.node);
4916
+ if (!entry) throw new Error(`There is no node "${options.path.node}"; find_animation_elements lists the artwork's nodes.`);
4917
+ if (entry.node.type !== "path") throw new Error(`Node "${options.path.node}" is a ${entry.node.type}, not a path; pass a path node (find_animation_elements shows each node's type).`);
4918
+ value = bezierPathToSvg(vectorPathWorldBezier(entry.node, entry.parentWorld, { effects: false }));
4919
+ place ??= "viewBox";
4920
+ } else {
4921
+ const provider = this.effects.require();
4922
+ if (!provider.pathFromText) throw new Error(`The effects provider "${provider.id}" cannot write words as a path; pass a path node instead.`);
4923
+ if (typeof options.path.text !== "string" || !options.path.text.trim()) throw new Error("text is empty; pass the words to write.");
4924
+ value = await provider.pathFromText(options.path.text, { ...options.path.font ? { font: options.path.font } : {}, ...options.path.align ? { align: options.path.align } : {} });
4925
+ }
4926
+ given = { ...given ?? {}, [control]: value };
4927
+ }
4928
+ const values = resolveEffectValues(d, given);
4895
4929
  const instance = options.instance ?? nextMotionEffectInstance(this.scene);
4896
4930
  const motion = await this.effects.require().applyVectorEffect(d.id, motionArtworkSvg(this.scene.artwork), values);
4897
4931
  const source = { instance, effect: d.id, provider: this.effects.require().id, name: d.name, values };
4898
- const { group, shots, notes } = motionPartsFromVectorEffect(this.scene.artwork, motion, source, options.box);
4932
+ const { group, shots, notes } = motionPartsFromVectorEffect(this.scene.artwork, motion, source, place);
4899
4933
  return { scene: { ...this.scene, artwork: { ...this.scene.artwork, nodes: [...this.scene.artwork.nodes, group] }, shots: [...this.scene.shots, ...shots] }, label: `Add ${d.name}`, instance, notes };
4900
4934
  });
4901
4935
  }
@@ -7198,7 +7232,7 @@ function createAnimationEffectsToolProvider(controller, options = {}) {
7198
7232
  {
7199
7233
  name: "add_vector_effect",
7200
7234
  title: "Add a vector effect",
7201
- description: "Adds a provider vector effect that brings its own picture (search_effects with kind vector: lower thirds, loaders, stickers): its SVG becomes an artwork group, fitted into box (default the whole artwork), and its motion becomes shots after the last one. Change it with update_effect (target and effect: the returned instance) or remove it with remove_effect. One undo step. Returns the instance, the group id and any notes on what could not be carried over.",
7235
+ description: 'Adds a provider vector effect that brings its own picture (search_effects with kind vector: lower thirds, loaders, stickers): its SVG becomes an artwork group, fitted into box (default the whole artwork), and its motion becomes shots after the last one. For an effect that draws along a path (search_effects accepts ["path"]: a signature, a route), pass path: { node } to run it along a path node (its outline with every transform applied; the picture then lands on the node at scale 1 unless box is given) or { text } for words the provider writes. Use it instead of follow or draw-on when the effect itself should trace the path. The path is copied when added; add it again after reshaping the node. Change it with update_effect (target and effect: the returned instance) or remove it with remove_effect. One undo step. Returns the instance, the group id and any notes on what could not be carried over.',
7202
7236
  inputSchema: {
7203
7237
  type: "object",
7204
7238
  properties: {
@@ -7214,8 +7248,21 @@ function createAnimationEffectsToolProvider(controller, options = {}) {
7214
7248
  },
7215
7249
  required: ["x", "y", "width", "height"],
7216
7250
  additionalProperties: false,
7217
- description: "Where the picture goes in the artwork, fitted keeping its proportions (default the whole artwork)."
7218
- }
7251
+ description: "Where the picture goes in the artwork, fitted keeping its proportions (default the whole artwork; with path { node }, on the node at scale 1)."
7252
+ },
7253
+ path: {
7254
+ type: "object",
7255
+ description: "The path the effect draws along (only for effects with a path control): { node } or { text }.",
7256
+ properties: {
7257
+ node: { type: "string", minLength: 1, description: "A path node id (find_animation_elements)." },
7258
+ text: { type: "string", minLength: 1, description: "Words the provider writes as a single-stroke path." },
7259
+ font: { type: "string", minLength: 1, description: "With text: a single-stroke font the provider offers (default its first)." },
7260
+ align: { type: "string", enum: ["start", "middle", "end"], description: "With text: where the line sits (default start)." }
7261
+ },
7262
+ oneOf: [{ required: ["node"] }, { required: ["text"] }],
7263
+ additionalProperties: false
7264
+ },
7265
+ control: { type: "string", minLength: 1, description: "Which path control takes the path (default the effect's only one; describe_effect pathControls lists them)." }
7219
7266
  },
7220
7267
  required: ["effect"],
7221
7268
  additionalProperties: false
@@ -7223,7 +7270,9 @@ function createAnimationEffectsToolProvider(controller, options = {}) {
7223
7270
  annotations: { title: "Add a vector effect", ...ctx.annotations.write },
7224
7271
  handler: async (args) => {
7225
7272
  ctx.requireProvider();
7226
- const r = await controller.addVectorEffect(args.effect, { values: args.values, box: args.box });
7273
+ const path = args.path ? args.path.node !== void 0 ? { node: args.path.node } : { text: args.path.text, font: args.path.font, align: args.path.align } : void 0;
7274
+ if (args.path && args.path.node !== void 0 && args.path.text !== void 0) throw new Error("Pass path as { node } or { text }, not both.");
7275
+ const r = await controller.addVectorEffect(args.effect, { values: args.values, box: args.box, path, control: args.control });
7227
7276
  if (!r.ok) throw new Error(r.error);
7228
7277
  return { instance: r.effect.instance, group: r.effect.group, effect: info(r.effect), ...r.notes.length ? { notes: r.notes } : {} };
7229
7278
  }
@@ -220,7 +220,7 @@ var _chunkPY4GWSE2cjs = require('../chunk-PY4GWSE2.cjs');
220
220
 
221
221
 
222
222
 
223
- var _chunkJAOSL334cjs = require('../chunk-JAOSL334.cjs');
223
+ var _chunkGHCSC2WPcjs = require('../chunk-GHCSC2WP.cjs');
224
224
  require('../chunk-WOFXZ3XU.cjs');
225
225
 
226
226
 
@@ -11426,14 +11426,14 @@ function canvasEffectErrorReason(effect, error) {
11426
11426
  }
11427
11427
  var CANVAS_EFFECT_MISSING_PATTERN = "repeating-linear-gradient(45deg, rgba(128,128,128,0.28) 0 2px, transparent 2px 8px)";
11428
11428
  function createCanvasEffectsController(options) {
11429
- const slot = new (0, _chunkJAOSL334cjs.EffectsSlot)();
11429
+ const slot = new (0, _chunkGHCSC2WPcjs.EffectsSlot)();
11430
11430
  if (options.effects) slot.set(options.effects);
11431
11431
  const describe = async (effect, place2) => {
11432
11432
  const d = await slot.require().describe(effect);
11433
11433
  const allowed = CANVAS_EFFECT_PLACES[place2];
11434
11434
  if (d.kind === "motion" || !d.uses.some((u) => allowed.includes(u))) {
11435
11435
  const where = place2 === "transition" ? "a scene transition" : place2 === "fill" ? "a fill" : "an overlay";
11436
- throw new (0, _chunkJAOSL334cjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ")} effect and cannot be ${where}, which takes ${allowed.join(" or ")} effects; search_effects with use "${allowed[0]}" lists ones that fit.`, d.id);
11436
+ throw new (0, _chunkGHCSC2WPcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ")} effect and cannot be ${where}, which takes ${allowed.join(" or ")} effects; search_effects with use "${allowed[0]}" lists ones that fit.`, d.id);
11437
11437
  }
11438
11438
  return d;
11439
11439
  };
@@ -11464,7 +11464,7 @@ function createCanvasEffectsController(options) {
11464
11464
  else if (k in d.controls.properties) base[k] = d.defaults[k];
11465
11465
  else throw new Error(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
11466
11466
  }
11467
- return { ...base, ..._chunkJAOSL334cjs.resolveEffectValues.call(void 0, { ...d, defaults: base }, given) };
11467
+ return { ...base, ..._chunkGHCSC2WPcjs.resolveEffectValues.call(void 0, { ...d, defaults: base }, given) };
11468
11468
  };
11469
11469
  return {
11470
11470
  /** The provider slot. */
@@ -11498,7 +11498,7 @@ function createCanvasEffectsController(options) {
11498
11498
  const mode = _nullishCoalesce(opts.mode, () => ( "fill"));
11499
11499
  const d = await describe(effect, mode);
11500
11500
  if (opts.opacity !== void 0 && !(opts.opacity >= 0 && opts.opacity <= 1)) throw new Error("opacity must be from 0 to 1.");
11501
- const next = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkJAOSL334cjs.resolveEffectValues.call(void 0, d, opts.values), mode, ...opts.opacity !== void 0 && opts.opacity !== 1 ? { opacity: opts.opacity } : {} };
11501
+ const next = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkGHCSC2WPcjs.resolveEffectValues.call(void 0, d, opts.values), mode, ...opts.opacity !== void 0 && opts.opacity !== 1 ? { opacity: opts.opacity } : {} };
11502
11502
  commit(`Add effect ${d.name}`, withEffect(nodeId, next));
11503
11503
  return next;
11504
11504
  },
@@ -11555,7 +11555,7 @@ function createCanvasEffectsController(options) {
11555
11555
  const d = await describe(effect, "transition");
11556
11556
  const duration = _nullishCoalesce(_nullishCoalesce(opts.duration, () => ( d.duration)), () => ( 600));
11557
11557
  if (!(duration > 0)) throw new Error("duration must be a number of ms above 0.");
11558
- const transition = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkJAOSL334cjs.resolveEffectValues.call(void 0, d, opts.values), duration };
11558
+ const transition = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkGHCSC2WPcjs.resolveEffectValues.call(void 0, d, opts.values), duration };
11559
11559
  scenes2.update(sceneId, { transition });
11560
11560
  return transition;
11561
11561
  }
@@ -11699,7 +11699,7 @@ function createCanvasEffectsToolProvider(controller, options = {}) {
11699
11699
  const once = (instance) => {
11700
11700
  if (instance !== "effect") throw new Error('A shape node has one effect, "effect".');
11701
11701
  };
11702
- return _chunkJAOSL334cjs.createEffectToolProvider.call(void 0, {
11702
+ return _chunkGHCSC2WPcjs.createEffectToolProvider.call(void 0, {
11703
11703
  id: _nullishCoalesce(options.id, () => ( "canvas-effects")),
11704
11704
  namespace: options.namespace,
11705
11705
  title: "Canvas effects",
@@ -220,7 +220,7 @@ import {
220
220
  EffectsSlot,
221
221
  createEffectToolProvider,
222
222
  resolveEffectValues
223
- } from "../chunk-SIK3LT6M.js";
223
+ } from "../chunk-OFH6Q67U.js";
224
224
  import "../chunk-V2ZCJRDM.js";
225
225
  import {
226
226
  brushstrokeScaleBucket
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkJAOSL334cjs = require('./chunk-JAOSL334.cjs');
6
+ var _chunkGHCSC2WPcjs = require('./chunk-GHCSC2WP.cjs');
7
7
 
8
8
 
9
9
  var _chunkFJ2MSI43cjs = require('./chunk-FJ2MSI43.cjs');
@@ -26,7 +26,7 @@ function documentErrors(doc, description) {
26
26
  const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
27
27
  if (doc.values !== void 0) {
28
28
  if (!isRecord(doc.values)) errors.push({ path: "/values", message: "must be an object of control values" });
29
- else errors.push(..._chunkJAOSL334cjs.effectValueErrors.call(void 0, description, doc.values).map((e) => ({ ...e, path: `/values${e.path}` })));
29
+ else errors.push(..._chunkGHCSC2WPcjs.effectValueErrors.call(void 0, description, doc.values).map((e) => ({ ...e, path: `/values${e.path}` })));
30
30
  }
31
31
  if (doc.tracks !== void 0) {
32
32
  if (!isRecord(doc.tracks)) errors.push({ path: "/tracks", message: "must be an object of keyframe lists" });
@@ -39,7 +39,7 @@ function documentErrors(doc, description) {
39
39
  kfs.forEach((kf, i) => {
40
40
  if (!isRecord(kf)) return errors.push({ path: `${at}/${i}`, message: "must be a keyframe object" });
41
41
  if (typeof kf.time !== "number" || !Number.isFinite(kf.time) || kf.time < 0) errors.push({ path: `${at}/${i}/time`, message: "must be a finite number of ms >= 0" });
42
- errors.push(..._chunkJAOSL334cjs.effectValueErrors.call(void 0, description, { [control]: kf.value }).map((e) => ({ ...e, path: `${at}/${i}/value` })));
42
+ errors.push(..._chunkGHCSC2WPcjs.effectValueErrors.call(void 0, description, { [control]: kf.value }).map((e) => ({ ...e, path: `${at}/${i}/value` })));
43
43
  if (kf.easing !== void 0 && !_chunkDWJJI6RTcjs.isAnimationEasing.call(void 0, kf.easing)) errors.push({ path: `${at}/${i}/easing`, message: "is not a valid easing" });
44
44
  });
45
45
  }
@@ -49,7 +49,7 @@ function documentErrors(doc, description) {
49
49
  else
50
50
  doc.presets.forEach((p, i) => {
51
51
  if (!isRecord(p) || typeof p.name !== "string" || !p.name) return errors.push({ path: `/presets/${i}`, message: "must have a name" });
52
- errors.push(..._chunkJAOSL334cjs.effectValueErrors.call(void 0, description, p.values).map((e) => ({ ...e, path: `/presets/${i}/values${e.path}` })));
52
+ errors.push(..._chunkGHCSC2WPcjs.effectValueErrors.call(void 0, description, p.values).map((e) => ({ ...e, path: `/presets/${i}/values${e.path}` })));
53
53
  });
54
54
  }
55
55
  if (doc.duration !== void 0 && (typeof doc.duration !== "number" || !Number.isFinite(doc.duration) || !(doc.duration > 0))) {
@@ -170,7 +170,7 @@ async function createEffectAuthoringSession(source, options) {
170
170
  if (isDocumentSource(source)) {
171
171
  const doc = source.document;
172
172
  if (doc.version !== 1 || !doc.effectId) {
173
- throw new (0, _chunkJAOSL334cjs.EffectUnsupportedError)("Invalid effect authoring document: version 1 required");
173
+ throw new (0, _chunkGHCSC2WPcjs.EffectUnsupportedError)("Invalid effect authoring document: version 1 required");
174
174
  }
175
175
  effectId = doc.effectId;
176
176
  initialDoc = doc;
@@ -178,15 +178,15 @@ async function createEffectAuthoringSession(source, options) {
178
178
  effectId = source.effectId;
179
179
  requestedPreset = source.preset;
180
180
  } else {
181
- throw new (0, _chunkJAOSL334cjs.EffectUnsupportedError)("Invalid effect authoring source");
181
+ throw new (0, _chunkGHCSC2WPcjs.EffectUnsupportedError)("Invalid effect authoring source");
182
182
  }
183
183
  const description = await provider.describe(effectId);
184
184
  if (initialDoc) {
185
185
  const problems = documentErrors(initialDoc, description);
186
- if (problems.length) throw new (0, _chunkJAOSL334cjs.EffectControlError)(effectId, problems);
186
+ if (problems.length) throw new (0, _chunkGHCSC2WPcjs.EffectControlError)(effectId, problems);
187
187
  }
188
188
  if (description.kind === "motion") {
189
- throw new (0, _chunkJAOSL334cjs.EffectUnsupportedError)("Motion effects cannot be authored as pixel effects", effectId);
189
+ throw new (0, _chunkGHCSC2WPcjs.EffectUnsupportedError)("Motion effects cannot be authored as pixel effects", effectId);
190
190
  }
191
191
  let disposed = false;
192
192
  let playing = false;
@@ -207,7 +207,7 @@ async function createEffectAuthoringSession(source, options) {
207
207
  const found = description.presets.find((p) => p.name === requestedPreset);
208
208
  if (!found) {
209
209
  const validNames = description.presets.map((p) => `"${p.name}"`).join(", ");
210
- throw new (0, _chunkJAOSL334cjs.EffectUnsupportedError)(
210
+ throw new (0, _chunkGHCSC2WPcjs.EffectUnsupportedError)(
211
211
  `Unknown preset "${requestedPreset}" for effect "${effectId}". Valid presets are: ${validNames || "none"}`,
212
212
  effectId
213
213
  );
@@ -321,7 +321,7 @@ async function createEffectAuthoringSession(source, options) {
321
321
  if (!validNames.includes(name)) {
322
322
  return { ok: false, message: `there is no control "${name}"; the controls are ${validNames.join(", ")}` };
323
323
  }
324
- const errors = _chunkJAOSL334cjs.effectValueErrors.call(void 0, description, { [name]: value });
324
+ const errors = _chunkGHCSC2WPcjs.effectValueErrors.call(void 0, description, { [name]: value });
325
325
  if (errors.length > 0) {
326
326
  return { ok: false, message: valueErrorMessage(errors) };
327
327
  }
@@ -391,7 +391,7 @@ async function createEffectAuthoringSession(source, options) {
391
391
  if (!validNames.includes(k)) {
392
392
  return { ok: false, message: `there is no control "${k}"; the controls are ${validNames.join(", ")}` };
393
393
  }
394
- const errors = _chunkJAOSL334cjs.effectValueErrors.call(void 0, description, { [k]: v });
394
+ const errors = _chunkGHCSC2WPcjs.effectValueErrors.call(void 0, description, { [k]: v });
395
395
  if (errors.length > 0) {
396
396
  return { ok: false, message: valueErrorMessage(errors) };
397
397
  }
@@ -528,7 +528,7 @@ async function createEffectAuthoringSession(source, options) {
528
528
  return { ok: false, message: `invalid easing: ${String(easing)}` };
529
529
  }
530
530
  const targetValue = value !== void 0 ? value : valuesAtTime(time, baseValues, tracks)[name];
531
- const errors = _chunkJAOSL334cjs.effectValueErrors.call(void 0, description, { [name]: targetValue });
531
+ const errors = _chunkGHCSC2WPcjs.effectValueErrors.call(void 0, description, { [name]: targetValue });
532
532
  if (errors.length > 0) {
533
533
  return { ok: false, message: valueErrorMessage(errors) };
534
534
  }
@@ -705,7 +705,7 @@ async function createEffectAuthoringSession(source, options) {
705
705
  },
706
706
  async createPreview(opts) {
707
707
  if (disposed) {
708
- throw new (0, _chunkJAOSL334cjs.EffectDisposedError)(effectId);
708
+ throw new (0, _chunkGHCSC2WPcjs.EffectDisposedError)(effectId);
709
709
  }
710
710
  let renderer;
711
711
  try {
@@ -730,7 +730,7 @@ async function createEffectAuthoringSession(source, options) {
730
730
  height: renderer.height,
731
731
  depth: renderer.depth,
732
732
  render: async (req) => {
733
- if (disposed) throw new (0, _chunkJAOSL334cjs.EffectDisposedError)(effectId);
733
+ if (disposed) throw new (0, _chunkGHCSC2WPcjs.EffectDisposedError)(effectId);
734
734
  const effectiveValues = _nullishCoalesce(req.values, () => ( valuesAtTime(req.time, baseValues, tracks)));
735
735
  const pixels = await renderer.render({
736
736
  ...req,
@@ -6,6 +6,8 @@ var _chunkWOFXZ3XUcjs = require('./chunk-WOFXZ3XU.cjs');
6
6
  var EFFECT_KINDS = ["shader", "vector", "motion", "camera"];
7
7
  var EFFECT_USES = ["filter", "transition", "generator", "overlay", "title", "motion", "interactive", "camera"];
8
8
  var CAMERA_MOVE_ROLES = ["transition", "hold"];
9
+ var EFFECT_ACCEPTS = ["path"];
10
+ var EFFECT_PATH_FORMAT = "svg-path";
9
11
  var EFFECT_SUBJECT_KINDS = ["image", "video", "svg", "text", "slot"];
10
12
  var EFFECT_LAYER_ROLES = ["background", "subject", "overlay"];
11
13
  var EFFECT_TRANSITION_MS = 800;
@@ -104,7 +106,11 @@ function countEffectTags(effects) {
104
106
  for (const e of effects) for (const tag of new Set((_nullishCoalesce(e.tags, () => ( []))).map(normaliseEffectTag))) if (tag) counts.set(tag, (_nullishCoalesce(counts.get(tag), () => ( 0))) + 1);
105
107
  return [...counts].map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
106
108
  }
107
- var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use)) && hasEffectTags(e, o.tags);
109
+ var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use)) && hasEffectTags(e, o.tags) && (!_optionalChain([o, 'access', _2 => _2.accepts, 'optionalAccess', _3 => _3.length]) || o.accepts.every((a) => _optionalChain([e, 'access', _4 => _4.accepts, 'optionalAccess', _5 => _5.includes, 'call', _6 => _6(a)])));
110
+ function pathControlsOf(d) {
111
+ const isPath = (s) => !!s && (s.format === EFFECT_PATH_FORMAT || [..._nullishCoalesce(s.anyOf, () => ( [])), ..._nullishCoalesce(s.oneOf, () => ( []))].some((x) => isPath(x)));
112
+ return Object.entries(_nullishCoalesce(_optionalChain([d, 'access', _7 => _7.controls, 'optionalAccess', _8 => _8.properties]), () => ( {}))).filter(([, s]) => isPath(s)).map(([name]) => name);
113
+ }
108
114
  function searchEffectSummaries(catalog, query = "", options = {}) {
109
115
  const words = query.toLowerCase().split(/\s+/).filter(Boolean);
110
116
  const scored = [];
@@ -228,7 +234,7 @@ async function sampleEffectCameraMove(provider, id, request) {
228
234
  }
229
235
  var cameraMoveField = (role) => role === "transition" ? "in" : "during";
230
236
  function cameraMovesFor(catalog, role) {
231
- return catalog.filter((e) => e.kind === "camera" && _optionalChain([e, 'access', _2 => _2.camera, 'optionalAccess', _3 => _3.role]) === role);
237
+ return catalog.filter((e) => e.kind === "camera" && _optionalChain([e, 'access', _9 => _9.camera, 'optionalAccess', _10 => _10.role]) === role);
232
238
  }
233
239
  function cameraMoveIdList(catalog, role) {
234
240
  const ids = cameraMovesFor(catalog, role).map((e) => JSON.stringify(e.id));
@@ -318,11 +324,12 @@ var summaryOut = (e) => ({
318
324
  category: e.category,
319
325
  uses: e.uses,
320
326
  ...e.description ? { description: e.description } : {},
321
- ..._optionalChain([e, 'access', _4 => _4.tags, 'optionalAccess', _5 => _5.length]) ? { tags: e.tags } : {},
327
+ ..._optionalChain([e, 'access', _11 => _11.tags, 'optionalAccess', _12 => _12.length]) ? { tags: e.tags } : {},
322
328
  transparent: e.transparent,
323
329
  loop: e.loop,
324
330
  ...e.duration !== void 0 ? { duration: e.duration } : {},
325
- ...e.camera ? { camera: e.camera } : {}
331
+ ...e.camera ? { camera: e.camera } : {},
332
+ ..._optionalChain([e, 'access', _13 => _13.accepts, 'optionalAccess', _14 => _14.length]) ? { accepts: e.accepts } : {}
326
333
  });
327
334
  function createEffectToolProvider(host) {
328
335
  let catalog = null;
@@ -330,7 +337,7 @@ function createEffectToolProvider(host) {
330
337
  const notify = () => listeners.forEach((l) => l());
331
338
  const catalogFor = (provider) => {
332
339
  if (!provider) return null;
333
- if (_optionalChain([catalog, 'optionalAccess', _6 => _6.provider]) !== provider) {
340
+ if (_optionalChain([catalog, 'optionalAccess', _15 => _15.provider]) !== provider) {
334
341
  const entry = { provider, effects: null };
335
342
  catalog = entry;
336
343
  Promise.resolve().then(() => provider.list()).then((effects) => {
@@ -366,7 +373,7 @@ function createEffectToolProvider(host) {
366
373
  description: `${what}: control name \u2192 value, as listed by describe_effect (its "controls" schema: types, ranges, units and defaults). Only the given controls change; colours are "#rrggbb".${keyframes ? " A number control may instead be keyframes { keys: [{ t: ms from the start, v, ease }] }." : ""} Unknown controls and out-of-range values are refused with the valid ones.`
367
374
  });
368
375
  const effectIdSchema = (uses) => {
369
- const effects = _optionalChain([catalogFor, 'call', _7 => _7(host.provider()), 'optionalAccess', _8 => _8.filter, 'call', _9 => _9((e) => effectOffered(e, uses))]);
376
+ const effects = _optionalChain([catalogFor, 'call', _16 => _16(host.provider()), 'optionalAccess', _17 => _17.filter, 'call', _18 => _18((e) => effectOffered(e, uses))]);
370
377
  const base = `An effect id from search_effects (use ${uses.map((u) => `"${u}"`).join(" or ")}).`;
371
378
  return effects && effects.length && effects.length <= ENUM_LIMIT ? { type: "string", enum: effects.map((e) => e.id), description: `${base} One of: ${effects.map((e) => `${e.id} (${e.name})`).join(", ")}.` } : { type: "string", minLength: 1, description: base };
372
379
  };
@@ -380,24 +387,35 @@ function createEffectToolProvider(host) {
380
387
  {
381
388
  name: "search_effects",
382
389
  title: "Search effects",
383
- description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion, camera), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one). For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms), camera: { role, durationMs, cut } for camera moves }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
390
+ description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion, camera), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one), and accepts ["path"] for effects that draw along a path you give. For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms), camera: { role, durationMs, cut } for camera moves, accepts }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
384
391
  inputSchema: object({
385
392
  query: { type: "string", description: "Words to match (default: every effect)." },
386
393
  kind: { type: "string", enum: [...EFFECT_KINDS], description: "shader (pixels), vector (animated SVG), motion (animates vector art) or camera (moves a scene's camera; no pixels)." },
387
394
  category: { type: "string", minLength: 1, description: `The provider's category, e.g. "gradient", "light", "transition".` },
388
395
  use: { type: "string", enum: [...EFFECT_USES], description: "What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input, camera moves a scene's camera." },
389
396
  tags: { type: "array", items: { type: "string", minLength: 1 }, description: "Only effects carrying every one of these tags, as search_effects lists them (any case; spaces count as hyphens)." },
390
- limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." }
397
+ limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." },
398
+ accepts: { type: "array", items: { type: "string", enum: [...EFFECT_ACCEPTS], description: "path: the effect has a control that draws along a path given at run time." }, description: "Only effects that take every one of these (path: effects that draw along a path you give; describe_effect names the controls in pathControls)." }
391
399
  }),
392
400
  annotations: { title: "Search effects", ...ANNOTATIONS.read },
393
401
  handler: async (args) => {
394
402
  const p = host.provider();
395
403
  if (!p) return { available: false, reason: NO_PROVIDER, effects: [], total: 0 };
396
404
  const tags = args.tags;
397
- const found = (await explain(async () => p.search(_nullishCoalesce(args.query, () => ( "")), { kind: args.kind, category: args.category, use: args.use, tags }))).filter((e) => hasEffectTags(e, tags));
405
+ const accepts = args.accepts;
406
+ let found = (await explain(async () => p.search(_nullishCoalesce(args.query, () => ( "")), { kind: args.kind, category: args.category, use: args.use, tags, ..._optionalChain([accepts, 'optionalAccess', _19 => _19.length]) ? { accepts } : {} }))).filter((e) => hasEffectTags(e, tags));
407
+ if (_optionalChain([accepts, 'optionalAccess', _20 => _20.includes, 'call', _21 => _21("path")])) {
408
+ const kept = [];
409
+ for (const e of found) {
410
+ if (e.accepts) {
411
+ if (accepts.every((a) => e.accepts.includes(a))) kept.push(e);
412
+ } else if (kept.length < 200 && pathControlsOf(await explain(async () => p.describe(e.id))).length) kept.push(e);
413
+ }
414
+ found = kept;
415
+ }
398
416
  const limit = _nullishCoalesce(args.limit, () => ( 20));
399
417
  const out = { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length, tags: countEffectTags(found).slice(0, TAG_LIMIT) };
400
- if (_optionalChain([tags, 'optionalAccess', _10 => _10.length]) && !found.length) {
418
+ if (_optionalChain([tags, 'optionalAccess', _22 => _22.length]) && !found.length) {
401
419
  const known = countEffectTags(await explain(async () => p.list()));
402
420
  const have = new Set(known.map((k) => k.tag));
403
421
  const unknown = tags.filter((t) => !have.has(normaliseEffectTag(t)));
@@ -416,7 +434,8 @@ function createEffectToolProvider(host) {
416
434
  const p = host.provider();
417
435
  if (!p) return { available: false, reason: NO_PROVIDER };
418
436
  const d = await explain(async () => p.describe(args.effect));
419
- return { available: true, ...summaryOut(d), controls: d.controls, defaults: d.defaults, presets: d.presets, inputs: d.inputs, appliesHere: effectOffered(d, host.applyUses) };
437
+ const pathControls = pathControlsOf(d);
438
+ return { available: true, ...summaryOut(d), controls: d.controls, defaults: d.defaults, presets: d.presets, inputs: d.inputs, appliesHere: effectOffered(d, host.applyUses), ...pathControls.length ? { pathControls } : {} };
420
439
  }
421
440
  },
422
441
  {
@@ -512,16 +531,16 @@ function createEffectToolProvider(host) {
512
531
  ...host.description ? { description: host.description } : {},
513
532
  ...host.skill ? { skill: host.skill } : {},
514
533
  tools: () => {
515
- const extra = _nullishCoalesce(_optionalChain([host, 'access', _11 => _11.extraTools, 'optionalCall', _12 => _12(context)]), () => ( []));
516
- if (host.readOnlyReason()) return [...readTools(), ...extra.filter((t) => _optionalChain([t, 'access', _13 => _13.annotations, 'optionalAccess', _14 => _14.readOnlyHint]))];
534
+ const extra = _nullishCoalesce(_optionalChain([host, 'access', _23 => _23.extraTools, 'optionalCall', _24 => _24(context)]), () => ( []));
535
+ if (host.readOnlyReason()) return [...readTools(), ...extra.filter((t) => _optionalChain([t, 'access', _25 => _25.annotations, 'optionalAccess', _26 => _26.readOnlyHint]))];
517
536
  return [...readTools(), ...writeTools(), ...extra];
518
537
  },
519
538
  subscribe: (l) => {
520
539
  listeners.add(l);
521
- const off = _optionalChain([host, 'access', _15 => _15.subscribe, 'optionalCall', _16 => _16(l)]);
540
+ const off = _optionalChain([host, 'access', _27 => _27.subscribe, 'optionalCall', _28 => _28(l)]);
522
541
  return () => {
523
542
  listeners.delete(l);
524
- _optionalChain([off, 'optionalCall', _17 => _17()]);
543
+ _optionalChain([off, 'optionalCall', _29 => _29()]);
525
544
  };
526
545
  }
527
546
  };
@@ -570,4 +589,7 @@ function createEffectToolProvider(host) {
570
589
 
571
590
 
572
591
 
573
- exports.EFFECT_KINDS = EFFECT_KINDS; exports.EFFECT_USES = EFFECT_USES; exports.CAMERA_MOVE_ROLES = CAMERA_MOVE_ROLES; exports.EFFECT_SUBJECT_KINDS = EFFECT_SUBJECT_KINDS; exports.EFFECT_LAYER_ROLES = EFFECT_LAYER_ROLES; exports.EFFECT_TRANSITION_MS = EFFECT_TRANSITION_MS; exports.EFFECT_TEMPLATE_SLOT_TYPES = EFFECT_TEMPLATE_SLOT_TYPES; exports.EFFECT_ERROR_CODES = EFFECT_ERROR_CODES; exports.EffectError = EffectError; exports.UnknownEffectError = UnknownEffectError; exports.UnknownTemplateError = UnknownTemplateError; exports.EffectControlError = EffectControlError; exports.EffectUnsupportedError = EffectUnsupportedError; exports.EffectDisposedError = EffectDisposedError; exports.NoEffectsProviderError = NoEffectsProviderError; exports.isEffectError = isEffectError; exports.effectValueErrors = effectValueErrors; exports.resolveEffectValues = resolveEffectValues; exports.effectOffered = effectOffered; exports.normaliseEffectTag = normaliseEffectTag; exports.hasEffectTags = hasEffectTags; exports.countEffectTags = countEffectTags; exports.searchEffectSummaries = searchEffectSummaries; exports.createEffectPixels = createEffectPixels; exports.isEffectPixels = isEffectPixels; exports.srgbToLinear = srgbToLinear; exports.linearToSrgb = linearToSrgb; exports.encodeEffectFloat = encodeEffectFloat; exports.applyEffectMask = applyEffectMask; exports.EffectsSlot = EffectsSlot; exports.isCameraView = isCameraView; exports.sampleCameraViews = sampleCameraViews; exports.sampleEffectCameraMove = sampleEffectCameraMove; exports.cameraMoveField = cameraMoveField; exports.cameraMovesFor = cameraMovesFor; exports.cameraMoveIdList = cameraMoveIdList; exports.cameraMoveParts = cameraMoveParts; exports.CAMERA_MOVE_MAX_MS = CAMERA_MOVE_MAX_MS; exports.resolveCameraMoveSpec = resolveCameraMoveSpec; exports.cameraViewOfBox = cameraViewOfBox; exports.cameraBoxOfView = cameraBoxOfView; exports.createEffectToolProvider = createEffectToolProvider;
592
+
593
+
594
+
595
+ exports.EFFECT_KINDS = EFFECT_KINDS; exports.EFFECT_USES = EFFECT_USES; exports.CAMERA_MOVE_ROLES = CAMERA_MOVE_ROLES; exports.EFFECT_ACCEPTS = EFFECT_ACCEPTS; exports.EFFECT_PATH_FORMAT = EFFECT_PATH_FORMAT; exports.EFFECT_SUBJECT_KINDS = EFFECT_SUBJECT_KINDS; exports.EFFECT_LAYER_ROLES = EFFECT_LAYER_ROLES; exports.EFFECT_TRANSITION_MS = EFFECT_TRANSITION_MS; exports.EFFECT_TEMPLATE_SLOT_TYPES = EFFECT_TEMPLATE_SLOT_TYPES; exports.EFFECT_ERROR_CODES = EFFECT_ERROR_CODES; exports.EffectError = EffectError; exports.UnknownEffectError = UnknownEffectError; exports.UnknownTemplateError = UnknownTemplateError; exports.EffectControlError = EffectControlError; exports.EffectUnsupportedError = EffectUnsupportedError; exports.EffectDisposedError = EffectDisposedError; exports.NoEffectsProviderError = NoEffectsProviderError; exports.isEffectError = isEffectError; exports.effectValueErrors = effectValueErrors; exports.resolveEffectValues = resolveEffectValues; exports.effectOffered = effectOffered; exports.normaliseEffectTag = normaliseEffectTag; exports.hasEffectTags = hasEffectTags; exports.countEffectTags = countEffectTags; exports.pathControlsOf = pathControlsOf; exports.searchEffectSummaries = searchEffectSummaries; exports.createEffectPixels = createEffectPixels; exports.isEffectPixels = isEffectPixels; exports.srgbToLinear = srgbToLinear; exports.linearToSrgb = linearToSrgb; exports.encodeEffectFloat = encodeEffectFloat; exports.applyEffectMask = applyEffectMask; exports.EffectsSlot = EffectsSlot; exports.isCameraView = isCameraView; exports.sampleCameraViews = sampleCameraViews; exports.sampleEffectCameraMove = sampleEffectCameraMove; exports.cameraMoveField = cameraMoveField; exports.cameraMovesFor = cameraMovesFor; exports.cameraMoveIdList = cameraMoveIdList; exports.cameraMoveParts = cameraMoveParts; exports.CAMERA_MOVE_MAX_MS = CAMERA_MOVE_MAX_MS; exports.resolveCameraMoveSpec = resolveCameraMoveSpec; exports.cameraViewOfBox = cameraViewOfBox; exports.cameraBoxOfView = cameraBoxOfView; exports.createEffectToolProvider = createEffectToolProvider;
@@ -4,7 +4,7 @@
4
4
 
5
5
 
6
6
 
7
- var _chunkJAOSL334cjs = require('./chunk-JAOSL334.cjs');
7
+ var _chunkGHCSC2WPcjs = require('./chunk-GHCSC2WP.cjs');
8
8
 
9
9
  // src/effects/templates.ts
10
10
  var SLOT_NAME = /^[A-Za-z][\w-]*$/;
@@ -133,7 +133,7 @@ function searchTemplateSummaries(catalog, options = {}) {
133
133
  const words = (_nullishCoalesce(options.query, () => ( ""))).toLowerCase().split(/\s+/).filter(Boolean);
134
134
  const scored = [];
135
135
  for (const t of catalog) {
136
- if (!_chunkJAOSL334cjs.hasEffectTags.call(void 0, t, options.tags)) continue;
136
+ if (!_chunkGHCSC2WPcjs.hasEffectTags.call(void 0, t, options.tags)) continue;
137
137
  let score = 0;
138
138
  const name = t.name.toLowerCase();
139
139
  const rest = [t.id, ..._nullishCoalesce(t.tags, () => ( [])), t.description].join(" ").toLowerCase();
@@ -200,7 +200,7 @@ function checkEffectSequence(sequence, catalog) {
200
200
  const p = effectSubjectProblem(layer.subject);
201
201
  if (p) problems.push(`${where}: ${p}.`);
202
202
  }
203
- if (layer.role !== void 0 && !_chunkJAOSL334cjs.EFFECT_LAYER_ROLES.includes(layer.role)) problems.push(`${where}: role is one of ${_chunkJAOSL334cjs.EFFECT_LAYER_ROLES.join(", ")}.`);
203
+ if (layer.role !== void 0 && !_chunkGHCSC2WPcjs.EFFECT_LAYER_ROLES.includes(layer.role)) problems.push(`${where}: role is one of ${_chunkGHCSC2WPcjs.EFFECT_LAYER_ROLES.join(", ")}.`);
204
204
  if (layer.rect !== void 0 && !isRect(layer.rect)) problems.push(`${where}: rect must be { x, y, width, height } with width and height > 0.`);
205
205
  for (const k of ["from", "until"]) if (layer[k] !== void 0 && !isLayerTime(layer[k])) problems.push(`${where}: ${k} is ms \u2265 0 or { shots: n } with n \u2265 1.`);
206
206
  if (layer.opacity !== void 0 && !(typeof layer.opacity === "number" && layer.opacity >= 0 && layer.opacity <= 1)) problems.push(`${where}: opacity is 0\u20131.`);
@@ -235,8 +235,8 @@ function effectSequenceSlots(sequence) {
235
235
  return [...out];
236
236
  }
237
237
  async function fillEffectTemplate(provider, id, values = {}, options = {}) {
238
- if (!provider) throw new (0, _chunkJAOSL334cjs.NoEffectsProviderError)();
239
- if (!provider.capabilities.supportsTemplates || !provider.describeTemplate || !provider.instantiateTemplate) throw new (0, _chunkJAOSL334cjs.EffectUnsupportedError)(`${provider.name} has no sequence templates (capabilities.supportsTemplates is not set).`);
238
+ if (!provider) throw new (0, _chunkGHCSC2WPcjs.NoEffectsProviderError)();
239
+ if (!provider.capabilities.supportsTemplates || !provider.describeTemplate || !provider.instantiateTemplate) throw new (0, _chunkGHCSC2WPcjs.EffectUnsupportedError)(`${provider.name} has no sequence templates (capabilities.supportsTemplates is not set).`);
240
240
  const template = await provider.describeTemplate(id);
241
241
  const errors = checkTemplateValues(template.slots, values);
242
242
  if (errors.length) throw new Error(`${errors.join(" ")} describe_template lists what each slot takes.`);
@@ -256,7 +256,7 @@ function effectSequenceTiming(sequence) {
256
256
  const leadIns = [];
257
257
  let t = 0;
258
258
  sequence.shots.forEach((shot, i) => {
259
- const transition = i > 0 && shot.transition ? _nullishCoalesce(shot.transition.ms, () => ( _chunkJAOSL334cjs.EFFECT_TRANSITION_MS)) : 0;
259
+ const transition = i > 0 && shot.transition ? _nullishCoalesce(shot.transition.ms, () => ( _chunkGHCSC2WPcjs.EFFECT_TRANSITION_MS)) : 0;
260
260
  const move = typeof _optionalChain([shot, 'access', _4 => _4.camera, 'optionalAccess', _5 => _5.in]) === "object" && i > 0 ? _nullishCoalesce(shot.camera.in.ms, () => ( 0)) : 0;
261
261
  const lead = Math.max(transition, move);
262
262
  starts.push(t);
@@ -7,7 +7,7 @@ import {
7
7
  EffectsSlot,
8
8
  createEffectPixels,
9
9
  effectValueErrors
10
- } from "./chunk-SIK3LT6M.js";
10
+ } from "./chunk-OFH6Q67U.js";
11
11
  import {
12
12
  validateJsonSchema
13
13
  } from "./chunk-V2ZCJRDM.js";
@@ -6,6 +6,8 @@ import {
6
6
  var EFFECT_KINDS = ["shader", "vector", "motion", "camera"];
7
7
  var EFFECT_USES = ["filter", "transition", "generator", "overlay", "title", "motion", "interactive", "camera"];
8
8
  var CAMERA_MOVE_ROLES = ["transition", "hold"];
9
+ var EFFECT_ACCEPTS = ["path"];
10
+ var EFFECT_PATH_FORMAT = "svg-path";
9
11
  var EFFECT_SUBJECT_KINDS = ["image", "video", "svg", "text", "slot"];
10
12
  var EFFECT_LAYER_ROLES = ["background", "subject", "overlay"];
11
13
  var EFFECT_TRANSITION_MS = 800;
@@ -104,7 +106,11 @@ function countEffectTags(effects) {
104
106
  for (const e of effects) for (const tag of new Set((e.tags ?? []).map(normaliseEffectTag))) if (tag) counts.set(tag, (counts.get(tag) ?? 0) + 1);
105
107
  return [...counts].map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
106
108
  }
107
- var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use)) && hasEffectTags(e, o.tags);
109
+ var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use)) && hasEffectTags(e, o.tags) && (!o.accepts?.length || o.accepts.every((a) => e.accepts?.includes(a)));
110
+ function pathControlsOf(d) {
111
+ const isPath = (s) => !!s && (s.format === EFFECT_PATH_FORMAT || [...s.anyOf ?? [], ...s.oneOf ?? []].some((x) => isPath(x)));
112
+ return Object.entries(d.controls?.properties ?? {}).filter(([, s]) => isPath(s)).map(([name]) => name);
113
+ }
108
114
  function searchEffectSummaries(catalog, query = "", options = {}) {
109
115
  const words = query.toLowerCase().split(/\s+/).filter(Boolean);
110
116
  const scored = [];
@@ -322,7 +328,8 @@ var summaryOut = (e) => ({
322
328
  transparent: e.transparent,
323
329
  loop: e.loop,
324
330
  ...e.duration !== void 0 ? { duration: e.duration } : {},
325
- ...e.camera ? { camera: e.camera } : {}
331
+ ...e.camera ? { camera: e.camera } : {},
332
+ ...e.accepts?.length ? { accepts: e.accepts } : {}
326
333
  });
327
334
  function createEffectToolProvider(host) {
328
335
  let catalog = null;
@@ -380,21 +387,32 @@ function createEffectToolProvider(host) {
380
387
  {
381
388
  name: "search_effects",
382
389
  title: "Search effects",
383
- description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion, camera), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one). For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms), camera: { role, durationMs, cut } for camera moves }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
390
+ description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion, camera), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one), and accepts ["path"] for effects that draw along a path you give. For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms), camera: { role, durationMs, cut } for camera moves, accepts }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
384
391
  inputSchema: object({
385
392
  query: { type: "string", description: "Words to match (default: every effect)." },
386
393
  kind: { type: "string", enum: [...EFFECT_KINDS], description: "shader (pixels), vector (animated SVG), motion (animates vector art) or camera (moves a scene's camera; no pixels)." },
387
394
  category: { type: "string", minLength: 1, description: `The provider's category, e.g. "gradient", "light", "transition".` },
388
395
  use: { type: "string", enum: [...EFFECT_USES], description: "What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input, camera moves a scene's camera." },
389
396
  tags: { type: "array", items: { type: "string", minLength: 1 }, description: "Only effects carrying every one of these tags, as search_effects lists them (any case; spaces count as hyphens)." },
390
- limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." }
397
+ limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." },
398
+ accepts: { type: "array", items: { type: "string", enum: [...EFFECT_ACCEPTS], description: "path: the effect has a control that draws along a path given at run time." }, description: "Only effects that take every one of these (path: effects that draw along a path you give; describe_effect names the controls in pathControls)." }
391
399
  }),
392
400
  annotations: { title: "Search effects", ...ANNOTATIONS.read },
393
401
  handler: async (args) => {
394
402
  const p = host.provider();
395
403
  if (!p) return { available: false, reason: NO_PROVIDER, effects: [], total: 0 };
396
404
  const tags = args.tags;
397
- const found = (await explain(async () => p.search(args.query ?? "", { kind: args.kind, category: args.category, use: args.use, tags }))).filter((e) => hasEffectTags(e, tags));
405
+ const accepts = args.accepts;
406
+ let found = (await explain(async () => p.search(args.query ?? "", { kind: args.kind, category: args.category, use: args.use, tags, ...accepts?.length ? { accepts } : {} }))).filter((e) => hasEffectTags(e, tags));
407
+ if (accepts?.includes("path")) {
408
+ const kept = [];
409
+ for (const e of found) {
410
+ if (e.accepts) {
411
+ if (accepts.every((a) => e.accepts.includes(a))) kept.push(e);
412
+ } else if (kept.length < 200 && pathControlsOf(await explain(async () => p.describe(e.id))).length) kept.push(e);
413
+ }
414
+ found = kept;
415
+ }
398
416
  const limit = args.limit ?? 20;
399
417
  const out = { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length, tags: countEffectTags(found).slice(0, TAG_LIMIT) };
400
418
  if (tags?.length && !found.length) {
@@ -416,7 +434,8 @@ function createEffectToolProvider(host) {
416
434
  const p = host.provider();
417
435
  if (!p) return { available: false, reason: NO_PROVIDER };
418
436
  const d = await explain(async () => p.describe(args.effect));
419
- return { available: true, ...summaryOut(d), controls: d.controls, defaults: d.defaults, presets: d.presets, inputs: d.inputs, appliesHere: effectOffered(d, host.applyUses) };
437
+ const pathControls = pathControlsOf(d);
438
+ return { available: true, ...summaryOut(d), controls: d.controls, defaults: d.defaults, presets: d.presets, inputs: d.inputs, appliesHere: effectOffered(d, host.applyUses), ...pathControls.length ? { pathControls } : {} };
420
439
  }
421
440
  },
422
441
  {
@@ -531,6 +550,8 @@ export {
531
550
  EFFECT_KINDS,
532
551
  EFFECT_USES,
533
552
  CAMERA_MOVE_ROLES,
553
+ EFFECT_ACCEPTS,
554
+ EFFECT_PATH_FORMAT,
534
555
  EFFECT_SUBJECT_KINDS,
535
556
  EFFECT_LAYER_ROLES,
536
557
  EFFECT_TRANSITION_MS,
@@ -550,6 +571,7 @@ export {
550
571
  normaliseEffectTag,
551
572
  hasEffectTags,
552
573
  countEffectTags,
574
+ pathControlsOf,
553
575
  searchEffectSummaries,
554
576
  createEffectPixels,
555
577
  isEffectPixels,