@pixerate/editor 0.147.0 → 0.148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/effects/authoring-session.d.cts +25 -0
- package/dist/effects/authoring-session.d.ts +25 -0
- package/dist/effects/authoring-tools.d.cts +31 -0
- package/dist/effects/authoring-tools.d.ts +31 -0
- package/dist/effects/authoring.d.cts +152 -0
- package/dist/effects/authoring.d.ts +152 -0
- package/dist/effects/conformance.d.cts +2 -0
- package/dist/effects/conformance.d.ts +2 -0
- package/dist/effects/fake.d.cts +2 -0
- package/dist/effects/fake.d.ts +2 -0
- package/dist/effects/index.cjs +1216 -9
- package/dist/effects/index.d.cts +3 -0
- package/dist/effects/index.d.ts +3 -0
- package/dist/effects/index.js +1210 -3
- package/dist/effects/types.d.cts +9 -2
- package/dist/effects/types.d.ts +9 -2
- package/dist/effects/ui.d.cts +8 -0
- package/dist/effects/ui.d.ts +8 -0
- package/package.json +1 -1
- package/skills/README.md +1 -0
- package/skills/pixerate-effect-authoring/SKILL.md +80 -0
package/dist/effects/index.cjs
CHANGED
|
@@ -26,6 +26,14 @@ var _chunkPY4GWSE2cjs = require('../chunk-PY4GWSE2.cjs');
|
|
|
26
26
|
var _chunkRUZQMYXHcjs = require('../chunk-RUZQMYXH.cjs');
|
|
27
27
|
require('../chunk-WOFXZ3XU.cjs');
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
var _chunkFJ2MSI43cjs = require('../chunk-FJ2MSI43.cjs');
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
var _chunkDWJJI6RTcjs = require('../chunk-DWJJI6RT.cjs');
|
|
36
|
+
|
|
29
37
|
// src/effects/conformance.ts
|
|
30
38
|
var codeOf = (e) => _chunkRUZQMYXHcjs.isEffectError.call(void 0, e) ? e.code : e instanceof Error ? `untyped ${e.name}: ${e.message}` : String(e);
|
|
31
39
|
async function rejectsWith(code, run) {
|
|
@@ -107,7 +115,9 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
107
115
|
for (const k of ["supportsWorker", "supportsFloat", "supportsMask", "supportsHostImages", "supportsVector"]) {
|
|
108
116
|
if (typeof _optionalChain([caps, 'optionalAccess', _4 => _4[k]]) !== "boolean") top(`capabilities.${k} is not a boolean`);
|
|
109
117
|
}
|
|
110
|
-
if (_optionalChain([caps, 'optionalAccess', _5 => _5.
|
|
118
|
+
if (_optionalChain([caps, 'optionalAccess', _5 => _5.supportsAuthoring]) !== void 0 && typeof caps.supportsAuthoring !== "boolean") top("capabilities.supportsAuthoring is not a boolean");
|
|
119
|
+
if (_optionalChain([caps, 'optionalAccess', _6 => _6.supportsVector]) && typeof provider.applyVectorEffect !== "function") top("supportsVector is set but applyVectorEffect is missing");
|
|
120
|
+
if (_optionalChain([caps, 'optionalAccess', _7 => _7.supportsAuthoring]) && typeof provider.openAuthoring !== "function") top("supportsAuthoring is set but openAuthoring is missing");
|
|
111
121
|
const list = await provider.list();
|
|
112
122
|
const ids = /* @__PURE__ */ new Set();
|
|
113
123
|
for (const e of list) {
|
|
@@ -117,7 +127,7 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
117
127
|
}
|
|
118
128
|
const all = await provider.search("");
|
|
119
129
|
if (all.length !== list.length) top(`search("") returned ${all.length} effects, list() ${list.length}`);
|
|
120
|
-
const firstUse = _optionalChain([list, 'access',
|
|
130
|
+
const firstUse = _optionalChain([list, 'access', _8 => _8[0], 'optionalAccess', _9 => _9.uses, 'access', _10 => _10[0]]);
|
|
121
131
|
if (firstUse) {
|
|
122
132
|
const filtered = await provider.search("", { use: firstUse });
|
|
123
133
|
if (filtered.some((e) => !e.uses.includes(firstUse))) top(`search("", { use: "${firstUse}" }) returned effects without that use`);
|
|
@@ -149,9 +159,810 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
149
159
|
if (bad) issues.push({ effectId: id, message: `applyVectorEffect with an unknown control ${bad}` });
|
|
150
160
|
}
|
|
151
161
|
}
|
|
162
|
+
if (_optionalChain([caps, 'optionalAccess', _11 => _11.supportsAuthoring]) && options.authoring !== false && typeof provider.openAuthoring === "function") {
|
|
163
|
+
const targetEffect = (await Promise.all((_nullishCoalesce(options.ids, () => ( [...ids]))).map((id) => provider.describe(id)))).find(
|
|
164
|
+
(d) => d.kind !== "motion" && d.kind !== "vector"
|
|
165
|
+
);
|
|
166
|
+
if (targetEffect) {
|
|
167
|
+
try {
|
|
168
|
+
const session = await provider.openAuthoring({ effectId: targetEffect.id });
|
|
169
|
+
const s1 = session.getState();
|
|
170
|
+
const s2 = session.getState();
|
|
171
|
+
if (s1 !== s2) {
|
|
172
|
+
issues.push({ effectId: targetEffect.id, message: "session.getState() returned different objects without changes" });
|
|
173
|
+
}
|
|
174
|
+
checkDescription(targetEffect.id, s1.description, issues);
|
|
175
|
+
const badRes = session.setValue("__no_such_control__", 123);
|
|
176
|
+
if (badRes.ok) {
|
|
177
|
+
issues.push({ effectId: targetEffect.id, message: "setValue with unknown control did not fail" });
|
|
178
|
+
} else if (session.getState() !== s1) {
|
|
179
|
+
issues.push({ effectId: targetEffect.id, message: "setValue with unknown control changed state" });
|
|
180
|
+
}
|
|
181
|
+
const firstKey = Object.keys(targetEffect.defaults)[0];
|
|
182
|
+
if (firstKey) {
|
|
183
|
+
const val = targetEffect.defaults[firstKey];
|
|
184
|
+
session.setValue(firstKey, val);
|
|
185
|
+
if (!session.getState().canUndo) {
|
|
186
|
+
issues.push({ effectId: targetEffect.id, message: "setValue did not enable canUndo" });
|
|
187
|
+
}
|
|
188
|
+
const undone = session.undo();
|
|
189
|
+
if (!undone) {
|
|
190
|
+
issues.push({ effectId: targetEffect.id, message: "undo() returned false" });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const doc = session.exportDocument();
|
|
194
|
+
const cloned = JSON.parse(JSON.stringify(doc));
|
|
195
|
+
const restored = await provider.openAuthoring({ document: cloned });
|
|
196
|
+
if (JSON.stringify(restored.getState().values) !== JSON.stringify(session.getState().values)) {
|
|
197
|
+
issues.push({ effectId: targetEffect.id, message: "reopening exported document gave different values" });
|
|
198
|
+
}
|
|
199
|
+
restored.dispose();
|
|
200
|
+
if (s1.sourceLanguage !== null && typeof session.setSource !== "function") {
|
|
201
|
+
issues.push({ effectId: targetEffect.id, message: "sourceLanguage is not null but setSource is missing" });
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
session.dispose();
|
|
205
|
+
} catch (e) {
|
|
206
|
+
issues.push({ effectId: targetEffect.id, message: `session.dispose() threw: ${codeOf(e)}` });
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
issues.push({ effectId: targetEffect.id, message: `authoring check failed: ${codeOf(e)}` });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
152
213
|
return issues;
|
|
153
214
|
}
|
|
154
215
|
|
|
216
|
+
// src/effects/authoring-session.ts
|
|
217
|
+
var HEX_RE = /^#([0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
218
|
+
function valueErrorMessage(errors) {
|
|
219
|
+
return errors.map((e) => {
|
|
220
|
+
const [control, ...rest] = e.path.replace(/^\//, "").split("/");
|
|
221
|
+
return `control "${control}"${rest.length ? ` (item ${rest.join("/")})` : ""}: ${e.message}`;
|
|
222
|
+
}).join("; ");
|
|
223
|
+
}
|
|
224
|
+
function documentErrors(doc, description) {
|
|
225
|
+
const errors = [];
|
|
226
|
+
const controls = Object.keys(description.controls.properties);
|
|
227
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
228
|
+
if (doc.values !== void 0) {
|
|
229
|
+
if (!isRecord(doc.values)) errors.push({ path: "/values", message: "must be an object of control values" });
|
|
230
|
+
else errors.push(..._chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, doc.values).map((e) => ({ ...e, path: `/values${e.path}` })));
|
|
231
|
+
}
|
|
232
|
+
if (doc.tracks !== void 0) {
|
|
233
|
+
if (!isRecord(doc.tracks)) errors.push({ path: "/tracks", message: "must be an object of keyframe lists" });
|
|
234
|
+
else
|
|
235
|
+
for (const [control, kfs] of Object.entries(doc.tracks)) {
|
|
236
|
+
const at = `/tracks/${control}`;
|
|
237
|
+
if (!controls.includes(control)) errors.push({ path: at, message: `there is no control "${control}"; the controls are ${controls.join(", ")}` });
|
|
238
|
+
else if (!Array.isArray(kfs)) errors.push({ path: at, message: "must be a list of keyframes" });
|
|
239
|
+
else
|
|
240
|
+
kfs.forEach((kf, i) => {
|
|
241
|
+
if (!isRecord(kf)) return errors.push({ path: `${at}/${i}`, message: "must be a keyframe object" });
|
|
242
|
+
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" });
|
|
243
|
+
errors.push(..._chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [control]: kf.value }).map((e) => ({ ...e, path: `${at}/${i}/value` })));
|
|
244
|
+
if (kf.easing !== void 0 && !_chunkDWJJI6RTcjs.isAnimationEasing.call(void 0, kf.easing)) errors.push({ path: `${at}/${i}/easing`, message: "is not a valid easing" });
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (doc.presets !== void 0) {
|
|
249
|
+
if (!Array.isArray(doc.presets)) errors.push({ path: "/presets", message: "must be a list of presets" });
|
|
250
|
+
else
|
|
251
|
+
doc.presets.forEach((p, i) => {
|
|
252
|
+
if (!isRecord(p) || typeof p.name !== "string" || !p.name) return errors.push({ path: `/presets/${i}`, message: "must have a name" });
|
|
253
|
+
errors.push(..._chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, p.values).map((e) => ({ ...e, path: `/presets/${i}/values${e.path}` })));
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (doc.duration !== void 0 && (typeof doc.duration !== "number" || !Number.isFinite(doc.duration) || !(doc.duration > 0))) {
|
|
257
|
+
errors.push({ path: "/duration", message: "must be a finite number of ms > 0" });
|
|
258
|
+
}
|
|
259
|
+
if (doc.loop !== void 0 && typeof doc.loop !== "boolean") errors.push({ path: "/loop", message: "must be a boolean" });
|
|
260
|
+
return errors;
|
|
261
|
+
}
|
|
262
|
+
function parseHex(hex) {
|
|
263
|
+
if (!HEX_RE.test(hex)) return null;
|
|
264
|
+
const body = hex.slice(1);
|
|
265
|
+
const r = parseInt(body.slice(0, 2), 16);
|
|
266
|
+
const g = parseInt(body.slice(2, 4), 16);
|
|
267
|
+
const b = parseInt(body.slice(4, 6), 16);
|
|
268
|
+
const hasAlpha = body.length === 8;
|
|
269
|
+
const a = hasAlpha ? parseInt(body.slice(6, 8), 16) : 255;
|
|
270
|
+
return { r, g, b, a, hasAlpha };
|
|
271
|
+
}
|
|
272
|
+
function toHex2(n) {
|
|
273
|
+
const clamped = Math.round(Math.max(0, Math.min(255, n)));
|
|
274
|
+
return clamped.toString(16).padStart(2, "0");
|
|
275
|
+
}
|
|
276
|
+
function lerpHex(c1, c2, t) {
|
|
277
|
+
const p1 = parseHex(c1);
|
|
278
|
+
const p2 = parseHex(c2);
|
|
279
|
+
if (!p1 || !p2) return t >= 1 ? c2 : c1;
|
|
280
|
+
const r = p1.r + (p2.r - p1.r) * t;
|
|
281
|
+
const g = p1.g + (p2.g - p1.g) * t;
|
|
282
|
+
const b = p1.b + (p2.b - p1.b) * t;
|
|
283
|
+
const a = p1.a + (p2.a - p1.a) * t;
|
|
284
|
+
const withAlpha = p1.hasAlpha || p2.hasAlpha;
|
|
285
|
+
return `#${toHex2(r)}${toHex2(g)}${toHex2(b)}${withAlpha ? toHex2(a) : ""}`;
|
|
286
|
+
}
|
|
287
|
+
function interpolateValue(v1, v2, progress) {
|
|
288
|
+
if (typeof v1 === "number" && typeof v2 === "number") {
|
|
289
|
+
return v1 + (v2 - v1) * progress;
|
|
290
|
+
}
|
|
291
|
+
if (Array.isArray(v1) && Array.isArray(v2) && v1.length === v2.length) {
|
|
292
|
+
return v1.map((num, i) => {
|
|
293
|
+
const other = v2[i];
|
|
294
|
+
return typeof num === "number" && typeof other === "number" ? num + (other - num) * progress : num;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (typeof v1 === "string" && typeof v2 === "string" && v1.startsWith("#") && v2.startsWith("#")) {
|
|
298
|
+
return lerpHex(v1, v2, progress);
|
|
299
|
+
}
|
|
300
|
+
return progress >= 1 ? v2 : v1;
|
|
301
|
+
}
|
|
302
|
+
function valuesAtTime(time, baseValues, tracks) {
|
|
303
|
+
const result = { ...baseValues };
|
|
304
|
+
for (const [control, kfs] of Object.entries(tracks)) {
|
|
305
|
+
if (!kfs || kfs.length === 0) continue;
|
|
306
|
+
if (kfs.length === 1) {
|
|
307
|
+
result[control] = kfs[0].value;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (time <= kfs[0].time) {
|
|
311
|
+
result[control] = kfs[0].value;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const last = kfs[kfs.length - 1];
|
|
315
|
+
if (time >= last.time) {
|
|
316
|
+
result[control] = last.value;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
let idx = 0;
|
|
320
|
+
while (idx < kfs.length - 1 && kfs[idx + 1].time <= time) {
|
|
321
|
+
idx++;
|
|
322
|
+
}
|
|
323
|
+
const kf1 = kfs[idx];
|
|
324
|
+
const kf2 = kfs[idx + 1];
|
|
325
|
+
const span = kf2.time - kf1.time;
|
|
326
|
+
const rawProgress = span > 0 ? (time - kf1.time) / span : 0;
|
|
327
|
+
const easing = _nullishCoalesce(kf1.easing, () => ( "linear"));
|
|
328
|
+
const eased = _chunkDWJJI6RTcjs.evaluateEasing.call(void 0, easing, rawProgress);
|
|
329
|
+
result[control] = interpolateValue(kf1.value, kf2.value, eased);
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
var defaultSchedule = (frame) => {
|
|
334
|
+
if (typeof requestAnimationFrame === "function") {
|
|
335
|
+
let handle2;
|
|
336
|
+
let running2 = true;
|
|
337
|
+
const tick2 = (now) => {
|
|
338
|
+
if (!running2) return;
|
|
339
|
+
frame(now);
|
|
340
|
+
if (running2) handle2 = requestAnimationFrame(tick2);
|
|
341
|
+
};
|
|
342
|
+
handle2 = requestAnimationFrame(tick2);
|
|
343
|
+
return () => {
|
|
344
|
+
running2 = false;
|
|
345
|
+
cancelAnimationFrame(handle2);
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
let handle;
|
|
349
|
+
let running = true;
|
|
350
|
+
const interval = 16;
|
|
351
|
+
const tick = () => {
|
|
352
|
+
if (!running) return;
|
|
353
|
+
frame(Date.now());
|
|
354
|
+
if (running) handle = setTimeout(tick, interval);
|
|
355
|
+
};
|
|
356
|
+
handle = setTimeout(tick, interval);
|
|
357
|
+
return () => {
|
|
358
|
+
running = false;
|
|
359
|
+
clearTimeout(handle);
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
function isDocumentSource(source) {
|
|
363
|
+
return "document" in source && typeof source.document === "object" && source.document !== null;
|
|
364
|
+
}
|
|
365
|
+
async function createEffectAuthoringSession(source, options) {
|
|
366
|
+
const { provider, history = new (0, _chunkFJ2MSI43cjs.HistoryManager)(), mergeWindowMs = 400 } = options;
|
|
367
|
+
const scheduleFn = _nullishCoalesce(options.schedule, () => ( defaultSchedule));
|
|
368
|
+
let effectId;
|
|
369
|
+
let requestedPreset;
|
|
370
|
+
let initialDoc = null;
|
|
371
|
+
if (isDocumentSource(source)) {
|
|
372
|
+
const doc = source.document;
|
|
373
|
+
if (doc.version !== 1 || !doc.effectId) {
|
|
374
|
+
throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("Invalid effect authoring document: version 1 required");
|
|
375
|
+
}
|
|
376
|
+
effectId = doc.effectId;
|
|
377
|
+
initialDoc = doc;
|
|
378
|
+
} else if ("effectId" in source && typeof source.effectId === "string") {
|
|
379
|
+
effectId = source.effectId;
|
|
380
|
+
requestedPreset = source.preset;
|
|
381
|
+
} else {
|
|
382
|
+
throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("Invalid effect authoring source");
|
|
383
|
+
}
|
|
384
|
+
const description = await provider.describe(effectId);
|
|
385
|
+
if (initialDoc) {
|
|
386
|
+
const problems = documentErrors(initialDoc, description);
|
|
387
|
+
if (problems.length) throw new (0, _chunkRUZQMYXHcjs.EffectControlError)(effectId, problems);
|
|
388
|
+
}
|
|
389
|
+
if (description.kind === "motion") {
|
|
390
|
+
throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("Motion effects cannot be authored as pixel effects", effectId);
|
|
391
|
+
}
|
|
392
|
+
let disposed = false;
|
|
393
|
+
let playing = false;
|
|
394
|
+
let cancelSchedule = null;
|
|
395
|
+
let lastScheduleTime = null;
|
|
396
|
+
let playheadTime = 0;
|
|
397
|
+
let duration = _nullishCoalesce(_nullishCoalesce(_optionalChain([initialDoc, 'optionalAccess', _12 => _12.duration]), () => ( description.duration)), () => ( 1e3));
|
|
398
|
+
if (!(duration > 0)) duration = 1e3;
|
|
399
|
+
let loop = _nullishCoalesce(_nullishCoalesce(_optionalChain([initialDoc, 'optionalAccess', _13 => _13.loop]), () => ( description.loop)), () => ( false));
|
|
400
|
+
let compiled = null;
|
|
401
|
+
let savedPresets = initialDoc ? [..._nullishCoalesce(initialDoc.presets, () => ( []))] : [];
|
|
402
|
+
let baseValues = { ...description.defaults };
|
|
403
|
+
if (_optionalChain([initialDoc, 'optionalAccess', _14 => _14.values])) {
|
|
404
|
+
baseValues = { ...baseValues, ...initialDoc.values };
|
|
405
|
+
} else if (requestedPreset) {
|
|
406
|
+
const found = description.presets.find((p) => p.name === requestedPreset);
|
|
407
|
+
if (!found) {
|
|
408
|
+
const validNames = description.presets.map((p) => `"${p.name}"`).join(", ");
|
|
409
|
+
throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(
|
|
410
|
+
`Unknown preset "${requestedPreset}" for effect "${effectId}". Valid presets are: ${validNames || "none"}`,
|
|
411
|
+
effectId
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
baseValues = { ...baseValues, ...found.values };
|
|
415
|
+
}
|
|
416
|
+
let tracks = {};
|
|
417
|
+
if (_optionalChain([initialDoc, 'optionalAccess', _15 => _15.tracks])) {
|
|
418
|
+
for (const [k, kfs] of Object.entries(initialDoc.tracks)) {
|
|
419
|
+
tracks[k] = [...kfs].sort((a, b) => a.time - b.time);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const livePreviews = /* @__PURE__ */ new Set();
|
|
423
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
424
|
+
let cachedState = null;
|
|
425
|
+
function mergePresets() {
|
|
426
|
+
const result = [];
|
|
427
|
+
const seen = /* @__PURE__ */ new Set();
|
|
428
|
+
for (const p of savedPresets) {
|
|
429
|
+
result.push(p);
|
|
430
|
+
seen.add(p.name);
|
|
431
|
+
}
|
|
432
|
+
for (const p of description.presets) {
|
|
433
|
+
if (!seen.has(p.name)) {
|
|
434
|
+
result.push(p);
|
|
435
|
+
seen.add(p.name);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
function currentMergedDescription() {
|
|
441
|
+
return {
|
|
442
|
+
...description,
|
|
443
|
+
presets: mergePresets()
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function buildState() {
|
|
447
|
+
const desc = currentMergedDescription();
|
|
448
|
+
const currentValues = valuesAtTime(playheadTime, baseValues, tracks);
|
|
449
|
+
const historyState = history.getState();
|
|
450
|
+
return {
|
|
451
|
+
providerId: provider.id,
|
|
452
|
+
effectId,
|
|
453
|
+
name: _nullishCoalesce(_optionalChain([initialDoc, 'optionalAccess', _16 => _16.name]), () => ( description.name)),
|
|
454
|
+
kind: description.kind,
|
|
455
|
+
description: desc,
|
|
456
|
+
source: null,
|
|
457
|
+
sourceLanguage: null,
|
|
458
|
+
values: currentValues,
|
|
459
|
+
tracks: { ...tracks },
|
|
460
|
+
issues: [],
|
|
461
|
+
compiled,
|
|
462
|
+
time: playheadTime,
|
|
463
|
+
duration,
|
|
464
|
+
loop,
|
|
465
|
+
playing,
|
|
466
|
+
canUndo: historyState.canUndo,
|
|
467
|
+
canRedo: historyState.canRedo
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function getState() {
|
|
471
|
+
if (!cachedState) {
|
|
472
|
+
cachedState = buildState();
|
|
473
|
+
}
|
|
474
|
+
return cachedState;
|
|
475
|
+
}
|
|
476
|
+
function invalidateState() {
|
|
477
|
+
cachedState = null;
|
|
478
|
+
const next = getState();
|
|
479
|
+
listeners.forEach((l) => l(next));
|
|
480
|
+
}
|
|
481
|
+
function validControlNames() {
|
|
482
|
+
return Object.keys(description.controls.properties);
|
|
483
|
+
}
|
|
484
|
+
function checkDisposed() {
|
|
485
|
+
if (disposed) {
|
|
486
|
+
return { ok: false, message: "the session is closed" };
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
function pauseInternal() {
|
|
491
|
+
if (!playing) return;
|
|
492
|
+
playing = false;
|
|
493
|
+
if (cancelSchedule) {
|
|
494
|
+
cancelSchedule();
|
|
495
|
+
cancelSchedule = null;
|
|
496
|
+
}
|
|
497
|
+
lastScheduleTime = null;
|
|
498
|
+
invalidateState();
|
|
499
|
+
}
|
|
500
|
+
function seekInternal(t) {
|
|
501
|
+
const clamped = Math.max(0, Math.min(duration, t));
|
|
502
|
+
if (clamped !== playheadTime) {
|
|
503
|
+
playheadTime = clamped;
|
|
504
|
+
invalidateState();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const session = {
|
|
508
|
+
getState,
|
|
509
|
+
subscribe(listener) {
|
|
510
|
+
listeners.add(listener);
|
|
511
|
+
return () => {
|
|
512
|
+
listeners.delete(listener);
|
|
513
|
+
};
|
|
514
|
+
},
|
|
515
|
+
setValue(name, value, opts) {
|
|
516
|
+
const disp = checkDisposed();
|
|
517
|
+
if (disp) return disp;
|
|
518
|
+
const validNames = validControlNames();
|
|
519
|
+
if (!validNames.includes(name)) {
|
|
520
|
+
return { ok: false, message: `there is no control "${name}"; the controls are ${validNames.join(", ")}` };
|
|
521
|
+
}
|
|
522
|
+
const errors = _chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [name]: value });
|
|
523
|
+
if (errors.length > 0) {
|
|
524
|
+
return { ok: false, message: valueErrorMessage(errors) };
|
|
525
|
+
}
|
|
526
|
+
const isAnimated = (_nullishCoalesce(_optionalChain([tracks, 'access', _17 => _17[name], 'optionalAccess', _18 => _18.length]), () => ( 0))) > 0;
|
|
527
|
+
if (isAnimated) {
|
|
528
|
+
const oldTracks = { ...tracks };
|
|
529
|
+
const t = playheadTime;
|
|
530
|
+
const currentTrack = [..._nullishCoalesce(tracks[name], () => ( []))];
|
|
531
|
+
const existingIdx = currentTrack.findIndex((kf) => kf.time === t);
|
|
532
|
+
const prevKf = existingIdx >= 0 ? currentTrack[existingIdx] : void 0;
|
|
533
|
+
const execute2 = () => {
|
|
534
|
+
const track = [..._nullishCoalesce(tracks[name], () => ( []))];
|
|
535
|
+
const idx = track.findIndex((kf) => kf.time === t);
|
|
536
|
+
if (idx >= 0) {
|
|
537
|
+
track[idx] = { ...track[idx], value };
|
|
538
|
+
} else {
|
|
539
|
+
track.push({ time: t, value });
|
|
540
|
+
track.sort((a, b) => a.time - b.time);
|
|
541
|
+
}
|
|
542
|
+
tracks[name] = track;
|
|
543
|
+
invalidateState();
|
|
544
|
+
};
|
|
545
|
+
const undo2 = () => {
|
|
546
|
+
tracks = { ...oldTracks };
|
|
547
|
+
invalidateState();
|
|
548
|
+
};
|
|
549
|
+
const cmd2 = {
|
|
550
|
+
name: `Set ${name}`,
|
|
551
|
+
tag: name,
|
|
552
|
+
execute: execute2,
|
|
553
|
+
undo: undo2
|
|
554
|
+
};
|
|
555
|
+
if (_optionalChain([opts, 'optionalAccess', _19 => _19.gesture])) {
|
|
556
|
+
history.executeMerged(cmd2, { key: opts.gesture });
|
|
557
|
+
} else {
|
|
558
|
+
history.executeMerged(cmd2, { windowMs: mergeWindowMs });
|
|
559
|
+
}
|
|
560
|
+
invalidateState();
|
|
561
|
+
return { ok: true };
|
|
562
|
+
}
|
|
563
|
+
const prevVal = baseValues[name];
|
|
564
|
+
const execute = () => {
|
|
565
|
+
baseValues[name] = value;
|
|
566
|
+
};
|
|
567
|
+
const undo = () => {
|
|
568
|
+
baseValues[name] = prevVal;
|
|
569
|
+
};
|
|
570
|
+
const cmd = {
|
|
571
|
+
name: `Set ${name}`,
|
|
572
|
+
tag: name,
|
|
573
|
+
execute,
|
|
574
|
+
undo
|
|
575
|
+
};
|
|
576
|
+
if (_optionalChain([opts, 'optionalAccess', _20 => _20.gesture])) {
|
|
577
|
+
history.executeMerged(cmd, { key: opts.gesture });
|
|
578
|
+
} else {
|
|
579
|
+
history.executeMerged(cmd, { windowMs: mergeWindowMs });
|
|
580
|
+
}
|
|
581
|
+
invalidateState();
|
|
582
|
+
return { ok: true };
|
|
583
|
+
},
|
|
584
|
+
setValues(values, opts) {
|
|
585
|
+
const disp = checkDisposed();
|
|
586
|
+
if (disp) return disp;
|
|
587
|
+
const validNames = validControlNames();
|
|
588
|
+
for (const [k, v] of Object.entries(values)) {
|
|
589
|
+
if (!validNames.includes(k)) {
|
|
590
|
+
return { ok: false, message: `there is no control "${k}"; the controls are ${validNames.join(", ")}` };
|
|
591
|
+
}
|
|
592
|
+
const errors = _chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [k]: v });
|
|
593
|
+
if (errors.length > 0) {
|
|
594
|
+
return { ok: false, message: valueErrorMessage(errors) };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const prevBase = { ...baseValues };
|
|
598
|
+
const prevTracks = { ...tracks };
|
|
599
|
+
const t = playheadTime;
|
|
600
|
+
const execute = () => {
|
|
601
|
+
for (const [k, v] of Object.entries(values)) {
|
|
602
|
+
const isAnimated = (_nullishCoalesce(_optionalChain([tracks, 'access', _21 => _21[k], 'optionalAccess', _22 => _22.length]), () => ( 0))) > 0;
|
|
603
|
+
if (isAnimated) {
|
|
604
|
+
const track = [..._nullishCoalesce(tracks[k], () => ( []))];
|
|
605
|
+
const idx = track.findIndex((kf) => kf.time === t);
|
|
606
|
+
if (idx >= 0) {
|
|
607
|
+
track[idx] = { ...track[idx], value: v };
|
|
608
|
+
} else {
|
|
609
|
+
track.push({ time: t, value: v });
|
|
610
|
+
track.sort((a, b) => a.time - b.time);
|
|
611
|
+
}
|
|
612
|
+
tracks[k] = track;
|
|
613
|
+
} else {
|
|
614
|
+
baseValues[k] = v;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
const undo = () => {
|
|
619
|
+
baseValues = { ...prevBase };
|
|
620
|
+
tracks = { ...prevTracks };
|
|
621
|
+
};
|
|
622
|
+
const cmd = {
|
|
623
|
+
name: "Set values",
|
|
624
|
+
execute,
|
|
625
|
+
undo
|
|
626
|
+
};
|
|
627
|
+
if (_optionalChain([opts, 'optionalAccess', _23 => _23.gesture])) {
|
|
628
|
+
history.executeMerged(cmd, { key: opts.gesture });
|
|
629
|
+
} else {
|
|
630
|
+
history.execute(cmd);
|
|
631
|
+
}
|
|
632
|
+
invalidateState();
|
|
633
|
+
return { ok: true };
|
|
634
|
+
},
|
|
635
|
+
resetValues(names) {
|
|
636
|
+
const disp = checkDisposed();
|
|
637
|
+
if (disp) return disp;
|
|
638
|
+
const targets = names ? [...names] : validControlNames();
|
|
639
|
+
const prevBase = { ...baseValues };
|
|
640
|
+
const prevTracks = { ...tracks };
|
|
641
|
+
const execute = () => {
|
|
642
|
+
for (const name of targets) {
|
|
643
|
+
if (name in description.defaults) {
|
|
644
|
+
baseValues[name] = description.defaults[name];
|
|
645
|
+
}
|
|
646
|
+
delete tracks[name];
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
const undo = () => {
|
|
650
|
+
baseValues = { ...prevBase };
|
|
651
|
+
tracks = { ...prevTracks };
|
|
652
|
+
};
|
|
653
|
+
history.execute({
|
|
654
|
+
name: "Reset values",
|
|
655
|
+
execute,
|
|
656
|
+
undo
|
|
657
|
+
});
|
|
658
|
+
invalidateState();
|
|
659
|
+
return { ok: true };
|
|
660
|
+
},
|
|
661
|
+
applyPreset(name) {
|
|
662
|
+
const disp = checkDisposed();
|
|
663
|
+
if (disp) return disp;
|
|
664
|
+
const allPresets = mergePresets();
|
|
665
|
+
const preset = allPresets.find((p) => p.name === name);
|
|
666
|
+
if (!preset) {
|
|
667
|
+
const available = allPresets.map((p) => `"${p.name}"`).join(", ");
|
|
668
|
+
return { ok: false, message: `unknown preset "${name}"; available presets are: ${available || "none"}` };
|
|
669
|
+
}
|
|
670
|
+
const prevBase = { ...baseValues };
|
|
671
|
+
const prevTracks = { ...tracks };
|
|
672
|
+
const execute = () => {
|
|
673
|
+
for (const [k, v] of Object.entries(preset.values)) {
|
|
674
|
+
baseValues[k] = v;
|
|
675
|
+
delete tracks[k];
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
const undo = () => {
|
|
679
|
+
baseValues = { ...prevBase };
|
|
680
|
+
tracks = { ...prevTracks };
|
|
681
|
+
};
|
|
682
|
+
history.execute({
|
|
683
|
+
name: `Apply preset "${name}"`,
|
|
684
|
+
execute,
|
|
685
|
+
undo
|
|
686
|
+
});
|
|
687
|
+
invalidateState();
|
|
688
|
+
return { ok: true };
|
|
689
|
+
},
|
|
690
|
+
savePreset(name) {
|
|
691
|
+
const disp = checkDisposed();
|
|
692
|
+
if (disp) return disp;
|
|
693
|
+
if (!name || !name.trim()) {
|
|
694
|
+
return { ok: false, message: "preset name cannot be empty" };
|
|
695
|
+
}
|
|
696
|
+
const trimmed = name.trim();
|
|
697
|
+
const currentValues = valuesAtTime(playheadTime, baseValues, tracks);
|
|
698
|
+
const prevSaved = [...savedPresets];
|
|
699
|
+
const execute = () => {
|
|
700
|
+
const next = savedPresets.filter((p) => p.name !== trimmed);
|
|
701
|
+
next.push({ name: trimmed, values: { ...currentValues } });
|
|
702
|
+
savedPresets = next;
|
|
703
|
+
};
|
|
704
|
+
const undo = () => {
|
|
705
|
+
savedPresets = [...prevSaved];
|
|
706
|
+
};
|
|
707
|
+
history.execute({
|
|
708
|
+
name: `Save preset "${trimmed}"`,
|
|
709
|
+
execute,
|
|
710
|
+
undo
|
|
711
|
+
});
|
|
712
|
+
invalidateState();
|
|
713
|
+
return { ok: true };
|
|
714
|
+
},
|
|
715
|
+
setKeyframe(name, time, value, easing) {
|
|
716
|
+
const disp = checkDisposed();
|
|
717
|
+
if (disp) return disp;
|
|
718
|
+
const validNames = validControlNames();
|
|
719
|
+
if (!validNames.includes(name)) {
|
|
720
|
+
return { ok: false, message: `there is no control "${name}"; the controls are ${validNames.join(", ")}` };
|
|
721
|
+
}
|
|
722
|
+
if (typeof time !== "number" || !Number.isFinite(time) || time < 0) {
|
|
723
|
+
return { ok: false, message: "keyframe time must be a finite number >= 0" };
|
|
724
|
+
}
|
|
725
|
+
if (easing !== void 0 && !_chunkDWJJI6RTcjs.isAnimationEasing.call(void 0, easing)) {
|
|
726
|
+
return { ok: false, message: `invalid easing: ${String(easing)}` };
|
|
727
|
+
}
|
|
728
|
+
const targetValue = value !== void 0 ? value : valuesAtTime(time, baseValues, tracks)[name];
|
|
729
|
+
const errors = _chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [name]: targetValue });
|
|
730
|
+
if (errors.length > 0) {
|
|
731
|
+
return { ok: false, message: valueErrorMessage(errors) };
|
|
732
|
+
}
|
|
733
|
+
const prevTrack = tracks[name] ? [...tracks[name]] : void 0;
|
|
734
|
+
const execute = () => {
|
|
735
|
+
const track = prevTrack ? [...prevTrack] : [];
|
|
736
|
+
const idx = track.findIndex((kf2) => kf2.time === time);
|
|
737
|
+
const kf = {
|
|
738
|
+
time,
|
|
739
|
+
value: targetValue,
|
|
740
|
+
...easing !== void 0 ? { easing } : {}
|
|
741
|
+
};
|
|
742
|
+
if (idx >= 0) {
|
|
743
|
+
track[idx] = kf;
|
|
744
|
+
} else {
|
|
745
|
+
track.push(kf);
|
|
746
|
+
track.sort((a, b) => a.time - b.time);
|
|
747
|
+
}
|
|
748
|
+
tracks[name] = track;
|
|
749
|
+
};
|
|
750
|
+
const undo = () => {
|
|
751
|
+
if (prevTrack === void 0) {
|
|
752
|
+
delete tracks[name];
|
|
753
|
+
} else {
|
|
754
|
+
tracks[name] = [...prevTrack];
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
history.execute({
|
|
758
|
+
name: `Set keyframe on ${name}`,
|
|
759
|
+
execute,
|
|
760
|
+
undo
|
|
761
|
+
});
|
|
762
|
+
invalidateState();
|
|
763
|
+
return { ok: true };
|
|
764
|
+
},
|
|
765
|
+
removeKeyframe(name, time) {
|
|
766
|
+
const disp = checkDisposed();
|
|
767
|
+
if (disp) return disp;
|
|
768
|
+
const validNames = validControlNames();
|
|
769
|
+
if (!validNames.includes(name)) {
|
|
770
|
+
return { ok: false, message: `there is no control "${name}"; the controls are ${validNames.join(", ")}` };
|
|
771
|
+
}
|
|
772
|
+
const track = tracks[name];
|
|
773
|
+
if (!track || !track.some((kf) => kf.time === time)) {
|
|
774
|
+
const times = _optionalChain([track, 'optionalAccess', _24 => _24.map, 'call', _25 => _25((kf) => `${kf.time}`), 'access', _26 => _26.join, 'call', _27 => _27(", ")]);
|
|
775
|
+
return {
|
|
776
|
+
ok: false,
|
|
777
|
+
message: times ? `no keyframe at ${time} ms on control "${name}"; its keyframes are at ${times} ms` : `control "${name}" has no keyframes`
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
const prevTrack = [...track];
|
|
781
|
+
const execute = () => {
|
|
782
|
+
const next = tracks[name].filter((kf) => kf.time !== time);
|
|
783
|
+
if (next.length === 0) {
|
|
784
|
+
delete tracks[name];
|
|
785
|
+
} else {
|
|
786
|
+
tracks[name] = next;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
const undo = () => {
|
|
790
|
+
tracks[name] = [...prevTrack];
|
|
791
|
+
};
|
|
792
|
+
history.execute({
|
|
793
|
+
name: `Remove keyframe on ${name}`,
|
|
794
|
+
execute,
|
|
795
|
+
undo
|
|
796
|
+
});
|
|
797
|
+
invalidateState();
|
|
798
|
+
return { ok: true };
|
|
799
|
+
},
|
|
800
|
+
clearTrack(name) {
|
|
801
|
+
const disp = checkDisposed();
|
|
802
|
+
if (disp) return disp;
|
|
803
|
+
const validNames = validControlNames();
|
|
804
|
+
if (!validNames.includes(name)) {
|
|
805
|
+
return { ok: false, message: `there is no control "${name}"; the controls are ${validNames.join(", ")}` };
|
|
806
|
+
}
|
|
807
|
+
const prevTrack = tracks[name] ? [...tracks[name]] : void 0;
|
|
808
|
+
if (!prevTrack || prevTrack.length === 0) {
|
|
809
|
+
const animated = Object.keys(tracks).filter((k) => _optionalChain([tracks, 'access', _28 => _28[k], 'optionalAccess', _29 => _29.length]));
|
|
810
|
+
return {
|
|
811
|
+
ok: false,
|
|
812
|
+
message: `control "${name}" is not animated, so there is nothing to clear; ${animated.length ? `the animated controls are ${animated.join(", ")}` : "no control is animated"}`
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
const currentValue = valuesAtTime(playheadTime, baseValues, tracks)[name];
|
|
816
|
+
const prevBase = baseValues[name];
|
|
817
|
+
const execute = () => {
|
|
818
|
+
delete tracks[name];
|
|
819
|
+
baseValues[name] = currentValue;
|
|
820
|
+
};
|
|
821
|
+
const undo = () => {
|
|
822
|
+
tracks[name] = [...prevTrack];
|
|
823
|
+
baseValues[name] = prevBase;
|
|
824
|
+
};
|
|
825
|
+
history.execute({
|
|
826
|
+
name: `Clear animation on ${name}`,
|
|
827
|
+
execute,
|
|
828
|
+
undo
|
|
829
|
+
});
|
|
830
|
+
invalidateState();
|
|
831
|
+
return { ok: true };
|
|
832
|
+
},
|
|
833
|
+
setTiming(timing) {
|
|
834
|
+
const disp = checkDisposed();
|
|
835
|
+
if (disp) return disp;
|
|
836
|
+
if (timing.duration !== void 0 && (!(timing.duration > 0) || !Number.isFinite(timing.duration))) {
|
|
837
|
+
return { ok: false, message: "duration must be a finite number > 0" };
|
|
838
|
+
}
|
|
839
|
+
const prevDuration = duration;
|
|
840
|
+
const prevLoop = loop;
|
|
841
|
+
const prevTime = playheadTime;
|
|
842
|
+
const execute = () => {
|
|
843
|
+
if (timing.duration !== void 0) {
|
|
844
|
+
duration = timing.duration;
|
|
845
|
+
if (playheadTime > duration) playheadTime = duration;
|
|
846
|
+
}
|
|
847
|
+
if (timing.loop !== void 0) {
|
|
848
|
+
loop = timing.loop;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
const undo = () => {
|
|
852
|
+
duration = prevDuration;
|
|
853
|
+
loop = prevLoop;
|
|
854
|
+
playheadTime = prevTime;
|
|
855
|
+
};
|
|
856
|
+
history.execute({
|
|
857
|
+
name: "Set timing",
|
|
858
|
+
execute,
|
|
859
|
+
undo
|
|
860
|
+
});
|
|
861
|
+
invalidateState();
|
|
862
|
+
return { ok: true };
|
|
863
|
+
},
|
|
864
|
+
play() {
|
|
865
|
+
if (disposed || playing) return;
|
|
866
|
+
playing = true;
|
|
867
|
+
lastScheduleTime = null;
|
|
868
|
+
cancelSchedule = scheduleFn((now) => {
|
|
869
|
+
if (!playing) return;
|
|
870
|
+
const delta = lastScheduleTime === null ? 0 : Math.max(0, now - lastScheduleTime);
|
|
871
|
+
lastScheduleTime = now;
|
|
872
|
+
let nextTime = playheadTime + delta;
|
|
873
|
+
if (nextTime >= duration) {
|
|
874
|
+
if (loop) {
|
|
875
|
+
nextTime = duration > 0 ? nextTime % duration : 0;
|
|
876
|
+
} else {
|
|
877
|
+
nextTime = duration;
|
|
878
|
+
pauseInternal();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
seekInternal(nextTime);
|
|
882
|
+
});
|
|
883
|
+
invalidateState();
|
|
884
|
+
},
|
|
885
|
+
pause() {
|
|
886
|
+
pauseInternal();
|
|
887
|
+
},
|
|
888
|
+
seek(time) {
|
|
889
|
+
if (disposed) return;
|
|
890
|
+
seekInternal(time);
|
|
891
|
+
},
|
|
892
|
+
undo() {
|
|
893
|
+
if (disposed) return false;
|
|
894
|
+
const changed = history.undo();
|
|
895
|
+
if (changed) invalidateState();
|
|
896
|
+
return changed;
|
|
897
|
+
},
|
|
898
|
+
redo() {
|
|
899
|
+
if (disposed) return false;
|
|
900
|
+
const changed = history.redo();
|
|
901
|
+
if (changed) invalidateState();
|
|
902
|
+
return changed;
|
|
903
|
+
},
|
|
904
|
+
async createPreview(opts) {
|
|
905
|
+
if (disposed) {
|
|
906
|
+
throw new (0, _chunkRUZQMYXHcjs.EffectDisposedError)(effectId);
|
|
907
|
+
}
|
|
908
|
+
const renderer = await provider.createRenderer(effectId, opts);
|
|
909
|
+
livePreviews.add(renderer);
|
|
910
|
+
const preview = {
|
|
911
|
+
effectId: renderer.effectId,
|
|
912
|
+
width: renderer.width,
|
|
913
|
+
height: renderer.height,
|
|
914
|
+
depth: renderer.depth,
|
|
915
|
+
render: async (req) => {
|
|
916
|
+
if (disposed) throw new (0, _chunkRUZQMYXHcjs.EffectDisposedError)(effectId);
|
|
917
|
+
const effectiveValues = _nullishCoalesce(req.values, () => ( valuesAtTime(req.time, baseValues, tracks)));
|
|
918
|
+
const pixels = await renderer.render({
|
|
919
|
+
...req,
|
|
920
|
+
values: effectiveValues
|
|
921
|
+
});
|
|
922
|
+
if (compiled !== true) {
|
|
923
|
+
compiled = true;
|
|
924
|
+
invalidateState();
|
|
925
|
+
}
|
|
926
|
+
return pixels;
|
|
927
|
+
},
|
|
928
|
+
dispose: () => {
|
|
929
|
+
livePreviews.delete(renderer);
|
|
930
|
+
renderer.dispose();
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
return preview;
|
|
934
|
+
},
|
|
935
|
+
exportDocument() {
|
|
936
|
+
const currentValues = valuesAtTime(playheadTime, baseValues, tracks);
|
|
937
|
+
return {
|
|
938
|
+
version: 1,
|
|
939
|
+
providerId: provider.id,
|
|
940
|
+
effectId,
|
|
941
|
+
name: _nullishCoalesce(_optionalChain([initialDoc, 'optionalAccess', _30 => _30.name]), () => ( description.name)),
|
|
942
|
+
values: { ...currentValues },
|
|
943
|
+
tracks: JSON.parse(JSON.stringify(tracks)),
|
|
944
|
+
presets: JSON.parse(JSON.stringify(savedPresets)),
|
|
945
|
+
duration,
|
|
946
|
+
loop
|
|
947
|
+
};
|
|
948
|
+
},
|
|
949
|
+
dispose() {
|
|
950
|
+
if (disposed) return;
|
|
951
|
+
disposed = true;
|
|
952
|
+
pauseInternal();
|
|
953
|
+
listeners.clear();
|
|
954
|
+
for (const p of livePreviews) {
|
|
955
|
+
try {
|
|
956
|
+
p.dispose();
|
|
957
|
+
} catch (e2) {
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
livePreviews.clear();
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
return session;
|
|
964
|
+
}
|
|
965
|
+
|
|
155
966
|
// src/effects/fake.ts
|
|
156
967
|
var HEX = "^#[0-9a-fA-F]{6}$";
|
|
157
968
|
var colour = (description) => ({ type: "string", pattern: HEX, description });
|
|
@@ -325,7 +1136,7 @@ var hexToRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
|
325
1136
|
var smooth = (t) => t * t * (3 - 2 * t);
|
|
326
1137
|
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
327
1138
|
function input(request, name, id, width, height) {
|
|
328
|
-
const src = _optionalChain([request, 'access',
|
|
1139
|
+
const src = _optionalChain([request, 'access', _31 => _31.inputs, 'optionalAccess', _32 => _32[name]]);
|
|
329
1140
|
if (!src) throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(`Effect "${id}" needs an input named "${name}".`, id);
|
|
330
1141
|
if (!_chunkRUZQMYXHcjs.isEffectPixels.call(void 0, src)) throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(`The fake provider only takes EffectPixels inputs ("${name}").`, id);
|
|
331
1142
|
if (src.width !== width || src.height !== height) {
|
|
@@ -426,12 +1237,14 @@ function maskResult(out, mask, source, id) {
|
|
|
426
1237
|
}
|
|
427
1238
|
}
|
|
428
1239
|
function createFakeEffectProvider(options = {}) {
|
|
1240
|
+
const supportsAuthoring = _nullishCoalesce(options.authoring, () => ( true));
|
|
429
1241
|
const capabilities = {
|
|
430
1242
|
supportsWorker: false,
|
|
431
1243
|
supportsFloat: true,
|
|
432
1244
|
supportsMask: true,
|
|
433
1245
|
supportsHostImages: false,
|
|
434
1246
|
supportsVector: true,
|
|
1247
|
+
supportsAuthoring,
|
|
435
1248
|
maxSize: 8192,
|
|
436
1249
|
...options.capabilities
|
|
437
1250
|
};
|
|
@@ -470,11 +1283,11 @@ function createFakeEffectProvider(options = {}) {
|
|
|
470
1283
|
throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(`time must be a finite number of ms \u2265 0, got ${request.time}.`, id);
|
|
471
1284
|
}
|
|
472
1285
|
const values = _chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, d, request.values);
|
|
473
|
-
_optionalChain([options, 'access',
|
|
1286
|
+
_optionalChain([options, 'access', _33 => _33.onRender, 'optionalCall', _34 => _34(id, request)]);
|
|
474
1287
|
const out = renderPixels(d, values, request, width, height);
|
|
475
1288
|
if (request.mask) {
|
|
476
1289
|
if (!capabilities.supportsMask) throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("This provider does not take masks.", id);
|
|
477
|
-
const source = d.uses.includes("filter") ? _optionalChain([request, 'access',
|
|
1290
|
+
const source = d.uses.includes("filter") ? _optionalChain([request, 'access', _35 => _35.inputs, 'optionalAccess', _36 => _36.source]) : void 0;
|
|
478
1291
|
maskResult(out, request.mask, source, id);
|
|
479
1292
|
}
|
|
480
1293
|
return depth > 8 && capabilities.supportsFloat ? toFloat(out) : out;
|
|
@@ -501,7 +1314,10 @@ function createFakeEffectProvider(options = {}) {
|
|
|
501
1314
|
duration: ms + Math.max(0, ids.length - 1) * stagger,
|
|
502
1315
|
loop: false
|
|
503
1316
|
};
|
|
504
|
-
}
|
|
1317
|
+
},
|
|
1318
|
+
...supportsAuthoring ? {
|
|
1319
|
+
openAuthoring: (from) => createEffectAuthoringSession(from, { provider })
|
|
1320
|
+
} : {}
|
|
505
1321
|
};
|
|
506
1322
|
return provider;
|
|
507
1323
|
}
|
|
@@ -559,6 +1375,11 @@ function effectSamplePicture(width, height, variant = 0) {
|
|
|
559
1375
|
}
|
|
560
1376
|
return p;
|
|
561
1377
|
}
|
|
1378
|
+
function effectSampleInputs(description, size) {
|
|
1379
|
+
const inputs = {};
|
|
1380
|
+
for (const input2 of description.inputs) if (input2.kind === "image") inputs[input2.name] = effectSamplePicture(size.width, size.height, input2.name === "to" ? 1 : 0);
|
|
1381
|
+
return inputs;
|
|
1382
|
+
}
|
|
562
1383
|
function renderEffectThumbnail(provider, effect, size = { width: 96, height: 54 }) {
|
|
563
1384
|
let cache = thumbnails.get(provider);
|
|
564
1385
|
if (!cache) thumbnails.set(provider, cache = /* @__PURE__ */ new Map());
|
|
@@ -579,8 +1400,7 @@ async function renderThumbnail(provider, effect, size) {
|
|
|
579
1400
|
const renderer = await provider.createRenderer(d.id, size);
|
|
580
1401
|
try {
|
|
581
1402
|
const time = d.duration ? d.duration / 2 : 1e3;
|
|
582
|
-
const inputs =
|
|
583
|
-
for (const input2 of d.inputs) if (input2.kind === "image") inputs[input2.name] = effectSamplePicture(size.width, size.height, input2.name === "to" ? 1 : 0);
|
|
1403
|
+
const inputs = effectSampleInputs(d, size);
|
|
584
1404
|
return await renderer.render({ time, inputs, ...d.uses.includes("transition") ? { progress: 0.5 } : {} });
|
|
585
1405
|
} finally {
|
|
586
1406
|
renderer.dispose();
|
|
@@ -596,6 +1416,393 @@ function groupEffectsByCategory(effects) {
|
|
|
596
1416
|
return [...groups].map(([category, list]) => ({ category, effects: list }));
|
|
597
1417
|
}
|
|
598
1418
|
|
|
1419
|
+
// src/effects/authoring-tools.ts
|
|
1420
|
+
var ANNOTATIONS = {
|
|
1421
|
+
read: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1422
|
+
write: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1423
|
+
writeIdempotent: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1424
|
+
destructive: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }
|
|
1425
|
+
};
|
|
1426
|
+
var object = (properties, required = []) => ({
|
|
1427
|
+
type: "object",
|
|
1428
|
+
properties,
|
|
1429
|
+
...required.length ? { required } : {},
|
|
1430
|
+
additionalProperties: false
|
|
1431
|
+
});
|
|
1432
|
+
var EASING_SCHEMA = {
|
|
1433
|
+
description: 'Easing into the next keyframe: a preset name, or a curve { "type": "cubic-bezier", x1, y1, x2, y2 }, { "type": "spring", stiffness, damping, mass, velocity } or { "type": "elastic", amplitude, period, mode }. Default: linear.',
|
|
1434
|
+
anyOf: [
|
|
1435
|
+
{ type: "string", enum: [..._chunkDWJJI6RTcjs.ANIMATION_EASING_PRESETS] },
|
|
1436
|
+
object(
|
|
1437
|
+
{
|
|
1438
|
+
type: { type: "string", enum: ["cubic-bezier", "spring", "elastic"], description: "Curve kind." },
|
|
1439
|
+
x1: { type: "number", description: "cubic-bezier: out handle x (0\u20131)." },
|
|
1440
|
+
y1: { type: "number", description: "cubic-bezier: out handle y." },
|
|
1441
|
+
x2: { type: "number", description: "cubic-bezier: in handle x (0\u20131)." },
|
|
1442
|
+
y2: { type: "number", description: "cubic-bezier: in handle y." },
|
|
1443
|
+
stiffness: { type: "number", exclusiveMinimum: 0, description: "spring: default 170." },
|
|
1444
|
+
damping: { type: "number", minimum: 0, description: "spring: default 26, lower bounces more." },
|
|
1445
|
+
mass: { type: "number", exclusiveMinimum: 0, description: "spring: default 1." },
|
|
1446
|
+
velocity: { type: "number", description: "spring: starting speed (default 0)." },
|
|
1447
|
+
amplitude: { type: "number", minimum: 0, description: "elastic: overshoot (default 1)." },
|
|
1448
|
+
period: { type: "number", exclusiveMinimum: 0, description: "elastic: fraction of the length (default 0.3)." },
|
|
1449
|
+
mode: { type: "string", enum: ["out", "in", "in-out"], description: "elastic: default out." }
|
|
1450
|
+
},
|
|
1451
|
+
["type"]
|
|
1452
|
+
)
|
|
1453
|
+
]
|
|
1454
|
+
};
|
|
1455
|
+
var NO_SESSION = "No effect authoring session is currently open. Open an effect using EffectProvider.openAuthoring() or in an authoring editor before calling effect authoring tools.";
|
|
1456
|
+
function createEffectAuthoringToolProvider(host, options) {
|
|
1457
|
+
const id = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _37 => _37.id]), () => ( "effect-authoring"));
|
|
1458
|
+
const namespace = _optionalChain([options, 'optionalAccess', _38 => _38.namespace]);
|
|
1459
|
+
const title = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _39 => _39.title]), () => ( "Effect authoring"));
|
|
1460
|
+
const description = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _40 => _40.description]), () => ( "Author shaders and procedural effects, inspect parameters, adjust values, and manage keyframe animations."));
|
|
1461
|
+
const skill = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _41 => _41.skill]), () => ( "pixerate-effect-authoring"));
|
|
1462
|
+
const requireSession = () => {
|
|
1463
|
+
const s = host.session();
|
|
1464
|
+
if (!s) throw new Error(NO_SESSION);
|
|
1465
|
+
return s;
|
|
1466
|
+
};
|
|
1467
|
+
const describeTool = {
|
|
1468
|
+
name: "effect_authoring_describe",
|
|
1469
|
+
title: "Describe open effect",
|
|
1470
|
+
description: "Describes the effect currently open for authoring: name, kind, source language, controls schema and current values, presets, animated tracks with keyframe counts, duration, loop, and compile issues.",
|
|
1471
|
+
inputSchema: object({}),
|
|
1472
|
+
annotations: { title: "Describe open effect", ...ANNOTATIONS.read },
|
|
1473
|
+
handler: () => {
|
|
1474
|
+
const session = requireSession();
|
|
1475
|
+
const state = session.getState();
|
|
1476
|
+
return {
|
|
1477
|
+
name: state.description.name,
|
|
1478
|
+
kind: state.description.kind,
|
|
1479
|
+
sourceLanguage: state.sourceLanguage,
|
|
1480
|
+
controls: effectParameterFields(state.description),
|
|
1481
|
+
values: state.values,
|
|
1482
|
+
presets: _nullishCoalesce(_optionalChain([state, 'access', _42 => _42.description, 'access', _43 => _43.presets, 'optionalAccess', _44 => _44.map, 'call', _45 => _45((p) => p.name)]), () => ( [])),
|
|
1483
|
+
tracks: Object.entries(state.tracks).map(([control, track]) => ({
|
|
1484
|
+
control,
|
|
1485
|
+
keyframes: track.length
|
|
1486
|
+
})),
|
|
1487
|
+
duration: state.duration,
|
|
1488
|
+
loop: state.loop,
|
|
1489
|
+
issues: state.issues
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
const getSourceTool = {
|
|
1494
|
+
name: "effect_authoring_get_source",
|
|
1495
|
+
title: "Get effect source code",
|
|
1496
|
+
description: "Gets the shader or script source code and language of the currently open effect. Refused if the effect does not use source code.",
|
|
1497
|
+
inputSchema: object({}),
|
|
1498
|
+
annotations: { title: "Get effect source code", ...ANNOTATIONS.read },
|
|
1499
|
+
handler: () => {
|
|
1500
|
+
const session = requireSession();
|
|
1501
|
+
const state = session.getState();
|
|
1502
|
+
if (state.sourceLanguage === null || state.source === null) {
|
|
1503
|
+
throw new Error(
|
|
1504
|
+
`The currently open effect (${state.description.name}) does not use shader or script source code.`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
return { language: state.sourceLanguage, source: state.source };
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
const setSourceTool = {
|
|
1511
|
+
name: "effect_authoring_set_source",
|
|
1512
|
+
title: "Set effect source code",
|
|
1513
|
+
description: "Replaces the shader or script source code of the currently open effect and recompiles it. Returns any compile issues.",
|
|
1514
|
+
inputSchema: object(
|
|
1515
|
+
{
|
|
1516
|
+
source: { type: "string", description: "The new shader or script source code to compile and apply." }
|
|
1517
|
+
},
|
|
1518
|
+
["source"]
|
|
1519
|
+
),
|
|
1520
|
+
annotations: { title: "Set effect source code", ...ANNOTATIONS.write },
|
|
1521
|
+
handler: async (args) => {
|
|
1522
|
+
const session = requireSession();
|
|
1523
|
+
if (!session.setSource) {
|
|
1524
|
+
throw new Error("The currently open session does not support setting source code.");
|
|
1525
|
+
}
|
|
1526
|
+
const res = await session.setSource(args.source);
|
|
1527
|
+
if (!res.ok) throw new Error(res.message);
|
|
1528
|
+
return { issues: res.issues };
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
const setValuesTool = {
|
|
1532
|
+
name: "effect_authoring_set_values",
|
|
1533
|
+
title: "Set effect control values",
|
|
1534
|
+
description: "Sets one or more control values on the currently open effect at the current playhead time. All-or-nothing: if any control name or value is invalid, no values are changed. One undo step.",
|
|
1535
|
+
inputSchema: object(
|
|
1536
|
+
{
|
|
1537
|
+
values: {
|
|
1538
|
+
type: "object",
|
|
1539
|
+
description: 'Map of control names to their new values (numbers, booleans, "#rrggbb" colors, vectors).'
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
["values"]
|
|
1543
|
+
),
|
|
1544
|
+
annotations: { title: "Set effect control values", ...ANNOTATIONS.writeIdempotent },
|
|
1545
|
+
handler: (args) => {
|
|
1546
|
+
const session = requireSession();
|
|
1547
|
+
const res = session.setValues(args.values);
|
|
1548
|
+
if (!res.ok) throw new Error(res.message);
|
|
1549
|
+
return { values: session.getState().values };
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
const resetValuesTool = {
|
|
1553
|
+
name: "effect_authoring_reset_values",
|
|
1554
|
+
title: "Reset effect control values",
|
|
1555
|
+
description: "Resets specified controls (or all controls if names is omitted) to their default values. One undo step.",
|
|
1556
|
+
inputSchema: object({
|
|
1557
|
+
names: {
|
|
1558
|
+
type: "array",
|
|
1559
|
+
items: { type: "string" },
|
|
1560
|
+
description: "Names of the controls to reset. If omitted, resets all controls to their defaults."
|
|
1561
|
+
}
|
|
1562
|
+
}),
|
|
1563
|
+
annotations: { title: "Reset effect control values", ...ANNOTATIONS.writeIdempotent },
|
|
1564
|
+
handler: (args) => {
|
|
1565
|
+
const session = requireSession();
|
|
1566
|
+
const res = session.resetValues(args.names);
|
|
1567
|
+
if (!res.ok) throw new Error(res.message);
|
|
1568
|
+
return { values: session.getState().values };
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
const applyPresetTool = {
|
|
1572
|
+
name: "effect_authoring_apply_preset",
|
|
1573
|
+
title: "Apply effect preset",
|
|
1574
|
+
description: "Applies a saved or built-in preset by name, setting its values as one undo step.",
|
|
1575
|
+
inputSchema: object(
|
|
1576
|
+
{
|
|
1577
|
+
name: { type: "string", description: "Name of the preset to apply." }
|
|
1578
|
+
},
|
|
1579
|
+
["name"]
|
|
1580
|
+
),
|
|
1581
|
+
annotations: { title: "Apply effect preset", ...ANNOTATIONS.write },
|
|
1582
|
+
handler: (args) => {
|
|
1583
|
+
const session = requireSession();
|
|
1584
|
+
const res = session.applyPreset(args.name);
|
|
1585
|
+
if (!res.ok) throw new Error(res.message);
|
|
1586
|
+
return { values: session.getState().values };
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
const savePresetTool = {
|
|
1590
|
+
name: "effect_authoring_save_preset",
|
|
1591
|
+
title: "Save effect preset",
|
|
1592
|
+
description: "Saves the current values as a preset under the given name (or updates an existing preset with that name). One undo step.",
|
|
1593
|
+
inputSchema: object(
|
|
1594
|
+
{
|
|
1595
|
+
name: { type: "string", description: "Name for the preset." }
|
|
1596
|
+
},
|
|
1597
|
+
["name"]
|
|
1598
|
+
),
|
|
1599
|
+
annotations: { title: "Save effect preset", ...ANNOTATIONS.write },
|
|
1600
|
+
handler: (args) => {
|
|
1601
|
+
const session = requireSession();
|
|
1602
|
+
const res = session.savePreset(args.name);
|
|
1603
|
+
if (!res.ok) throw new Error(res.message);
|
|
1604
|
+
return { name: args.name, values: session.getState().values };
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
const setKeyframeTool = {
|
|
1608
|
+
name: "effect_authoring_set_keyframe",
|
|
1609
|
+
title: "Set effect keyframe",
|
|
1610
|
+
description: "Adds or updates a keyframe on a control track at a specific time in milliseconds. If value is omitted, uses the current resolved value of the control at that time. One undo step.",
|
|
1611
|
+
inputSchema: object(
|
|
1612
|
+
{
|
|
1613
|
+
control: { type: "string", description: "Name of the control to animate." },
|
|
1614
|
+
time: { type: "number", minimum: 0, description: "Time in milliseconds from the start of the effect." },
|
|
1615
|
+
value: { description: "Value at this keyframe. If omitted, captures the control value at that time." },
|
|
1616
|
+
easing: EASING_SCHEMA
|
|
1617
|
+
},
|
|
1618
|
+
["control", "time"]
|
|
1619
|
+
),
|
|
1620
|
+
annotations: { title: "Set effect keyframe", ...ANNOTATIONS.writeIdempotent },
|
|
1621
|
+
handler: (args) => {
|
|
1622
|
+
const session = requireSession();
|
|
1623
|
+
const res = session.setKeyframe(
|
|
1624
|
+
args.control,
|
|
1625
|
+
args.time,
|
|
1626
|
+
args.value,
|
|
1627
|
+
args.easing
|
|
1628
|
+
);
|
|
1629
|
+
if (!res.ok) throw new Error(res.message);
|
|
1630
|
+
const track = session.getState().tracks[args.control];
|
|
1631
|
+
return { control: args.control, keyframes: _nullishCoalesce(track, () => ( [])) };
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
const removeKeyframeTool = {
|
|
1635
|
+
name: "effect_authoring_remove_keyframe",
|
|
1636
|
+
title: "Remove effect keyframe",
|
|
1637
|
+
description: "Removes a keyframe at the exact time in milliseconds for the specified control. One undo step.",
|
|
1638
|
+
inputSchema: object(
|
|
1639
|
+
{
|
|
1640
|
+
control: { type: "string", description: "Name of the control." },
|
|
1641
|
+
time: { type: "number", minimum: 0, description: "Time in milliseconds of the keyframe to remove." }
|
|
1642
|
+
},
|
|
1643
|
+
["control", "time"]
|
|
1644
|
+
),
|
|
1645
|
+
annotations: { title: "Remove effect keyframe", ...ANNOTATIONS.write },
|
|
1646
|
+
handler: (args) => {
|
|
1647
|
+
const session = requireSession();
|
|
1648
|
+
const res = session.removeKeyframe(args.control, args.time);
|
|
1649
|
+
if (!res.ok) throw new Error(res.message);
|
|
1650
|
+
const track = session.getState().tracks[args.control];
|
|
1651
|
+
return { control: args.control, keyframes: _nullishCoalesce(track, () => ( [])) };
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
const clearTrackTool = {
|
|
1655
|
+
name: "effect_authoring_clear_track",
|
|
1656
|
+
title: "Clear effect animation track",
|
|
1657
|
+
description: "Removes all keyframes from an animated control. The value it has at the playhead becomes its static value. One undo step.",
|
|
1658
|
+
inputSchema: object(
|
|
1659
|
+
{
|
|
1660
|
+
control: { type: "string", description: "Name of the control whose animation track to remove." }
|
|
1661
|
+
},
|
|
1662
|
+
["control"]
|
|
1663
|
+
),
|
|
1664
|
+
annotations: { title: "Clear effect animation track", ...ANNOTATIONS.destructive },
|
|
1665
|
+
handler: (args) => {
|
|
1666
|
+
const session = requireSession();
|
|
1667
|
+
const res = session.clearTrack(args.control);
|
|
1668
|
+
if (!res.ok) throw new Error(res.message);
|
|
1669
|
+
return { control: args.control, value: session.getState().values[args.control] };
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
const setTimingTool = {
|
|
1673
|
+
name: "effect_authoring_set_timing",
|
|
1674
|
+
title: "Set effect timing",
|
|
1675
|
+
description: "Sets the total duration in milliseconds and/or whether the effect loops. One undo step.",
|
|
1676
|
+
inputSchema: object({
|
|
1677
|
+
duration: { type: "number", minimum: 1, description: "Total duration of the effect in milliseconds (must be positive)." },
|
|
1678
|
+
loop: { type: "boolean", description: "Whether playback loops back to the start when it reaches duration." }
|
|
1679
|
+
}),
|
|
1680
|
+
annotations: { title: "Set effect timing", ...ANNOTATIONS.writeIdempotent },
|
|
1681
|
+
handler: (args) => {
|
|
1682
|
+
const session = requireSession();
|
|
1683
|
+
if (args.duration === void 0 && args.loop === void 0) {
|
|
1684
|
+
throw new Error("Provide at least one of duration or loop to change.");
|
|
1685
|
+
}
|
|
1686
|
+
const res = session.setTiming({
|
|
1687
|
+
duration: args.duration,
|
|
1688
|
+
loop: args.loop
|
|
1689
|
+
});
|
|
1690
|
+
if (!res.ok) throw new Error(res.message);
|
|
1691
|
+
const state = session.getState();
|
|
1692
|
+
return { duration: state.duration, loop: state.loop };
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
const seekTool = {
|
|
1696
|
+
name: "effect_authoring_seek",
|
|
1697
|
+
title: "Seek effect playhead",
|
|
1698
|
+
description: "Moves the preview playhead to a specific time in milliseconds (clamped between 0 and duration). Returns the resolved control values at that time.",
|
|
1699
|
+
inputSchema: object(
|
|
1700
|
+
{
|
|
1701
|
+
time: { type: "number", minimum: 0, description: "Time in milliseconds to move the playhead to." }
|
|
1702
|
+
},
|
|
1703
|
+
["time"]
|
|
1704
|
+
),
|
|
1705
|
+
annotations: { title: "Seek effect playhead", ...ANNOTATIONS.writeIdempotent },
|
|
1706
|
+
handler: (args) => {
|
|
1707
|
+
const session = requireSession();
|
|
1708
|
+
if (typeof args.time !== "number" || !Number.isFinite(args.time) || args.time < 0) {
|
|
1709
|
+
throw new Error("time must be a finite non-negative number.");
|
|
1710
|
+
}
|
|
1711
|
+
session.seek(args.time);
|
|
1712
|
+
const state = session.getState();
|
|
1713
|
+
return { time: state.time, values: state.values };
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
const undoTool = {
|
|
1717
|
+
name: "effect_authoring_undo",
|
|
1718
|
+
title: "Undo effect authoring edit",
|
|
1719
|
+
description: "Undoes the most recent edit in the authoring session.",
|
|
1720
|
+
inputSchema: object({}),
|
|
1721
|
+
annotations: { title: "Undo effect authoring edit", ...ANNOTATIONS.write },
|
|
1722
|
+
handler: () => {
|
|
1723
|
+
const session = requireSession();
|
|
1724
|
+
const changed = session.undo();
|
|
1725
|
+
const state = session.getState();
|
|
1726
|
+
return { changed, canUndo: state.canUndo, canRedo: state.canRedo };
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
const redoTool = {
|
|
1730
|
+
name: "effect_authoring_redo",
|
|
1731
|
+
title: "Redo effect authoring edit",
|
|
1732
|
+
description: "Redoes the most recently undone edit in the authoring session.",
|
|
1733
|
+
inputSchema: object({}),
|
|
1734
|
+
annotations: { title: "Redo effect authoring edit", ...ANNOTATIONS.write },
|
|
1735
|
+
handler: () => {
|
|
1736
|
+
const session = requireSession();
|
|
1737
|
+
const changed = session.redo();
|
|
1738
|
+
const state = session.getState();
|
|
1739
|
+
return { changed, canUndo: state.canUndo, canRedo: state.canRedo };
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
const viewTool = {
|
|
1743
|
+
name: "effect_authoring_view",
|
|
1744
|
+
title: "View effect preview image",
|
|
1745
|
+
description: "Captures an image of the effect preview at the current playhead time or at a specified time in milliseconds.",
|
|
1746
|
+
inputSchema: object({
|
|
1747
|
+
time: { type: "number", minimum: 0, description: "Optional time in milliseconds to view the preview at. Moves playhead to this time before capturing." }
|
|
1748
|
+
}),
|
|
1749
|
+
annotations: { title: "View effect preview image", ...ANNOTATIONS.read },
|
|
1750
|
+
handler: async (args) => {
|
|
1751
|
+
const session = requireSession();
|
|
1752
|
+
if (!host.capture) throw new Error("Capturing a preview image is not supported by this host.");
|
|
1753
|
+
if (args.time !== void 0) {
|
|
1754
|
+
if (typeof args.time !== "number" || !Number.isFinite(args.time) || args.time < 0) {
|
|
1755
|
+
throw new Error("time must be a finite non-negative number.");
|
|
1756
|
+
}
|
|
1757
|
+
session.seek(args.time);
|
|
1758
|
+
}
|
|
1759
|
+
const dataUrl = await host.capture();
|
|
1760
|
+
const comma = dataUrl.indexOf(",");
|
|
1761
|
+
const mimeMatch = /^data:(image\/[a-z0-9.+-]+);base64,/i.exec(dataUrl);
|
|
1762
|
+
const mimeType = mimeMatch ? mimeMatch[1] : "image/png";
|
|
1763
|
+
const data = comma !== -1 ? dataUrl.slice(comma + 1) : dataUrl;
|
|
1764
|
+
return {
|
|
1765
|
+
content: [
|
|
1766
|
+
{ type: "image", data, mimeType },
|
|
1767
|
+
{ type: "text", text: `Captured effect preview at ${session.getState().time}ms.` }
|
|
1768
|
+
]
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
return {
|
|
1773
|
+
id,
|
|
1774
|
+
...namespace !== void 0 ? { namespace } : {},
|
|
1775
|
+
title,
|
|
1776
|
+
description,
|
|
1777
|
+
skill,
|
|
1778
|
+
tools: () => {
|
|
1779
|
+
const session = host.session();
|
|
1780
|
+
const tools = [
|
|
1781
|
+
describeTool,
|
|
1782
|
+
getSourceTool,
|
|
1783
|
+
..._optionalChain([session, 'optionalAccess', _46 => _46.setSource]) ? [setSourceTool] : [],
|
|
1784
|
+
setValuesTool,
|
|
1785
|
+
resetValuesTool,
|
|
1786
|
+
applyPresetTool,
|
|
1787
|
+
savePresetTool,
|
|
1788
|
+
setKeyframeTool,
|
|
1789
|
+
removeKeyframeTool,
|
|
1790
|
+
clearTrackTool,
|
|
1791
|
+
setTimingTool,
|
|
1792
|
+
seekTool,
|
|
1793
|
+
undoTool,
|
|
1794
|
+
redoTool,
|
|
1795
|
+
...host.capture ? [viewTool] : []
|
|
1796
|
+
];
|
|
1797
|
+
return tools;
|
|
1798
|
+
},
|
|
1799
|
+
...host.subscribe ? { subscribe: host.subscribe } : {}
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
|
|
599
1806
|
|
|
600
1807
|
|
|
601
1808
|
|
|
@@ -627,4 +1834,4 @@ function groupEffectsByCategory(effects) {
|
|
|
627
1834
|
|
|
628
1835
|
|
|
629
1836
|
|
|
630
|
-
exports.EFFECT_ERROR_CODES = _chunkRUZQMYXHcjs.EFFECT_ERROR_CODES; exports.EFFECT_KINDS = _chunkRUZQMYXHcjs.EFFECT_KINDS; exports.EFFECT_USES = _chunkRUZQMYXHcjs.EFFECT_USES; exports.EFFECT_USE_LABELS = EFFECT_USE_LABELS; exports.EffectControlError = _chunkRUZQMYXHcjs.EffectControlError; exports.EffectDisposedError = _chunkRUZQMYXHcjs.EffectDisposedError; exports.EffectError = _chunkRUZQMYXHcjs.EffectError; exports.EffectRendererPool = _chunkPY4GWSE2cjs.EffectRendererPool; exports.EffectUnsupportedError = _chunkRUZQMYXHcjs.EffectUnsupportedError; exports.EffectsSlot = _chunkRUZQMYXHcjs.EffectsSlot; exports.NoEffectsProviderError = _chunkRUZQMYXHcjs.NoEffectsProviderError; exports.UnknownEffectError = _chunkRUZQMYXHcjs.UnknownEffectError; exports.applyEffectMask = _chunkRUZQMYXHcjs.applyEffectMask; exports.checkEffectProvider = checkEffectProvider; exports.createEffectPixels = _chunkRUZQMYXHcjs.createEffectPixels; exports.createEffectToolProvider = _chunkRUZQMYXHcjs.createEffectToolProvider; exports.createFakeEffectProvider = createFakeEffectProvider; exports.effectControlField = effectControlField; exports.effectFieldValue = effectFieldValue; exports.effectParameterFields = effectParameterFields; exports.effectSamplePicture = effectSamplePicture; exports.effectValueErrors = _chunkRUZQMYXHcjs.effectValueErrors; exports.encodeEffectFloat = _chunkRUZQMYXHcjs.encodeEffectFloat; exports.groupEffectsByCategory = groupEffectsByCategory; exports.isEffectError = _chunkRUZQMYXHcjs.isEffectError; exports.isEffectPixels = _chunkRUZQMYXHcjs.isEffectPixels; exports.linearToSrgb = _chunkRUZQMYXHcjs.linearToSrgb; exports.renderEffectThumbnail = renderEffectThumbnail; exports.resolveEffectValues = _chunkRUZQMYXHcjs.resolveEffectValues; exports.searchEffectSummaries = _chunkRUZQMYXHcjs.searchEffectSummaries; exports.srgbToLinear = _chunkRUZQMYXHcjs.srgbToLinear;
|
|
1837
|
+
exports.EFFECT_ERROR_CODES = _chunkRUZQMYXHcjs.EFFECT_ERROR_CODES; exports.EFFECT_KINDS = _chunkRUZQMYXHcjs.EFFECT_KINDS; exports.EFFECT_USES = _chunkRUZQMYXHcjs.EFFECT_USES; exports.EFFECT_USE_LABELS = EFFECT_USE_LABELS; exports.EffectControlError = _chunkRUZQMYXHcjs.EffectControlError; exports.EffectDisposedError = _chunkRUZQMYXHcjs.EffectDisposedError; exports.EffectError = _chunkRUZQMYXHcjs.EffectError; exports.EffectRendererPool = _chunkPY4GWSE2cjs.EffectRendererPool; exports.EffectUnsupportedError = _chunkRUZQMYXHcjs.EffectUnsupportedError; exports.EffectsSlot = _chunkRUZQMYXHcjs.EffectsSlot; exports.NoEffectsProviderError = _chunkRUZQMYXHcjs.NoEffectsProviderError; exports.UnknownEffectError = _chunkRUZQMYXHcjs.UnknownEffectError; exports.applyEffectMask = _chunkRUZQMYXHcjs.applyEffectMask; exports.checkEffectProvider = checkEffectProvider; exports.createEffectAuthoringSession = createEffectAuthoringSession; exports.createEffectAuthoringToolProvider = createEffectAuthoringToolProvider; exports.createEffectPixels = _chunkRUZQMYXHcjs.createEffectPixels; exports.createEffectToolProvider = _chunkRUZQMYXHcjs.createEffectToolProvider; exports.createFakeEffectProvider = createFakeEffectProvider; exports.effectControlField = effectControlField; exports.effectFieldValue = effectFieldValue; exports.effectParameterFields = effectParameterFields; exports.effectSampleInputs = effectSampleInputs; exports.effectSamplePicture = effectSamplePicture; exports.effectValueErrors = _chunkRUZQMYXHcjs.effectValueErrors; exports.encodeEffectFloat = _chunkRUZQMYXHcjs.encodeEffectFloat; exports.groupEffectsByCategory = groupEffectsByCategory; exports.isEffectError = _chunkRUZQMYXHcjs.isEffectError; exports.isEffectPixels = _chunkRUZQMYXHcjs.isEffectPixels; exports.linearToSrgb = _chunkRUZQMYXHcjs.linearToSrgb; exports.renderEffectThumbnail = renderEffectThumbnail; exports.resolveEffectValues = _chunkRUZQMYXHcjs.resolveEffectValues; exports.searchEffectSummaries = _chunkRUZQMYXHcjs.searchEffectSummaries; exports.srgbToLinear = _chunkRUZQMYXHcjs.srgbToLinear;
|