@pixerate/editor 0.252.0 → 0.254.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/animation-editor/index.cjs +53 -53
- package/dist/animation-editor/index.js +2 -2
- package/dist/brushes/index.cjs +229 -2014
- package/dist/brushes/index.d.cts +5 -1
- package/dist/brushes/index.d.ts +5 -1
- package/dist/brushes/index.js +236 -2021
- package/dist/brushes/patch.d.cts +23 -0
- package/dist/brushes/patch.d.ts +23 -0
- package/dist/brushes/ui.d.cts +105 -0
- package/dist/brushes/ui.d.ts +105 -0
- package/dist/canvas/index.cjs +7 -7
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-QKXO6JPP.cjs → chunk-2OQGPEHH.cjs} +14 -14
- package/dist/chunk-34KLWUH4.cjs +2284 -0
- package/dist/{chunk-Z3BRH4JK.cjs → chunk-557BDWKO.cjs} +6 -6
- package/dist/{chunk-ZBCT5MEA.cjs → chunk-CBVNCWJM.cjs} +7 -7
- package/dist/{chunk-226QDRO3.js → chunk-DHQPRKXF.js} +1 -1
- package/dist/{chunk-ABL7IWFZ.js → chunk-GBEF2D4P.js} +1 -1
- package/dist/{chunk-ISIO63Q5.cjs → chunk-L2JTLW6B.cjs} +150 -25
- package/dist/{chunk-JA57JIT5.js → chunk-MV7CYFMJ.js} +133 -8
- package/dist/{chunk-5E2Q7ZHI.js → chunk-MWUZCEFT.js} +1 -1
- package/dist/chunk-QPHNVZHO.js +2284 -0
- package/dist/effects/conformance.d.cts +1 -0
- package/dist/effects/conformance.d.ts +1 -0
- package/dist/effects/fake.d.cts +7 -1
- package/dist/effects/fake.d.ts +7 -1
- package/dist/effects/helpers.d.cts +36 -4
- package/dist/effects/helpers.d.ts +36 -4
- package/dist/effects/index.cjs +331 -102
- package/dist/effects/index.js +238 -9
- package/dist/effects/types.d.cts +84 -5
- package/dist/effects/types.d.ts +84 -5
- package/dist/image-editor/index.cjs +15 -15
- package/dist/image-editor/index.js +1 -1
- package/dist/paint-editor/brush-controls.d.cts +57 -0
- package/dist/paint-editor/brush-controls.d.ts +57 -0
- package/dist/paint-editor/brush-library-tools.d.cts +15 -0
- package/dist/paint-editor/brush-library-tools.d.ts +15 -0
- package/dist/paint-editor/brush-library.d.cts +29 -0
- package/dist/paint-editor/brush-library.d.ts +29 -0
- package/dist/paint-editor/controller.d.cts +76 -17
- package/dist/paint-editor/controller.d.ts +76 -17
- package/dist/paint-editor/index.cjs +543 -858
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +329 -644
- package/dist/paint-editor/serialize.d.cts +7 -4
- package/dist/paint-editor/serialize.d.ts +7 -4
- package/dist/paint-editor/sticker-tools.d.cts +1 -1
- package/dist/paint-editor/sticker-tools.d.ts +1 -1
- package/dist/paint-editor/strokes.d.cts +10 -1
- package/dist/paint-editor/strokes.d.ts +10 -1
- package/dist/paint-editor/tools.d.cts +1 -1
- package/dist/paint-editor/tools.d.ts +1 -1
- package/dist/paint-editor/ui.d.cts +3 -51
- package/dist/paint-editor/ui.d.ts +3 -51
- package/dist/paint-editor/wet-tools.d.cts +1 -1
- package/dist/paint-editor/wet-tools.d.ts +1 -1
- package/dist/vector/index.cjs +6 -6
- package/dist/vector/index.js +1 -1
- package/dist/video-editor/index.cjs +344 -344
- package/dist/video-editor/index.js +4 -4
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +5 -3
- package/skills/pixerate-paint-editor/SKILL.md +26 -24
- package/skills/pixerate-paint-editor/references/brush-facets.md +30 -0
- package/skills/pixerate-paint-editor/references/gradient-paint.md +1 -1
- package/skills/pixerate-paint-editor/references/light-paint.md +2 -2
- package/skills/pixerate-paint-editor/references/oils.md +1 -1
- package/skills/pixerate-paint-editor/references/particle-brushes.md +8 -5
- package/skills/pixerate-paint-oil-impasto/SKILL.md +1 -1
- package/skills/pixerate-stickers/SKILL.md +1 -1
- package/skills/pixerate-video-editor/SKILL.md +1 -1
package/dist/effects/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "../chunk-N4ZXW3WB.js";
|
|
4
4
|
import {
|
|
5
5
|
createEffectAuthoringSession
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-GBEF2D4P.js";
|
|
7
7
|
import {
|
|
8
8
|
EFFECT_LAYER_USES,
|
|
9
9
|
EFFECT_SEQUENCE_NAME,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
templateSlotTakes,
|
|
29
29
|
templateValueProblem,
|
|
30
30
|
templateValuesSchema
|
|
31
|
-
} from "../chunk-
|
|
31
|
+
} from "../chunk-DHQPRKXF.js";
|
|
32
32
|
import {
|
|
33
33
|
EffectRendererPool
|
|
34
34
|
} from "../chunk-EHLOCVWB.js";
|
|
@@ -50,15 +50,22 @@ import {
|
|
|
50
50
|
EffectUnsupportedError,
|
|
51
51
|
EffectsSlot,
|
|
52
52
|
NoEffectsProviderError,
|
|
53
|
+
TIMING_BLENDS,
|
|
54
|
+
TIMING_DEFAULT_FPS,
|
|
55
|
+
TIMING_MAX_FPS,
|
|
56
|
+
TIMING_MAX_FRAMES,
|
|
53
57
|
UnknownEffectError,
|
|
54
58
|
UnknownTemplateError,
|
|
55
59
|
applyEffectMask,
|
|
60
|
+
blendTimingFrames,
|
|
56
61
|
cameraBoxOfView,
|
|
57
62
|
cameraMoveField,
|
|
58
63
|
cameraMoveIdList,
|
|
59
64
|
cameraMoveParts,
|
|
60
65
|
cameraMovesFor,
|
|
61
66
|
cameraViewOfBox,
|
|
67
|
+
checkTimingFootage,
|
|
68
|
+
checkTimingRequest,
|
|
62
69
|
countEffectTags,
|
|
63
70
|
createEffectPixels,
|
|
64
71
|
createEffectToolProvider,
|
|
@@ -72,14 +79,16 @@ import {
|
|
|
72
79
|
layerImageInput,
|
|
73
80
|
linearToSrgb,
|
|
74
81
|
normaliseEffectTag,
|
|
82
|
+
normaliseTimingSample,
|
|
75
83
|
pathControlsOf,
|
|
76
84
|
resolveCameraMoveSpec,
|
|
77
85
|
resolveEffectValues,
|
|
78
86
|
sampleCameraViews,
|
|
79
87
|
sampleEffectCameraMove,
|
|
80
88
|
searchEffectSummaries,
|
|
81
|
-
srgbToLinear
|
|
82
|
-
|
|
89
|
+
srgbToLinear,
|
|
90
|
+
timingSampleProblems
|
|
91
|
+
} from "../chunk-MV7CYFMJ.js";
|
|
83
92
|
import "../chunk-V2ZCJRDM.js";
|
|
84
93
|
import "../chunk-NS62CQ2B.js";
|
|
85
94
|
import {
|
|
@@ -124,6 +133,10 @@ function checkSummary(e, issues) {
|
|
|
124
133
|
if (e.uses?.includes("camera")) at('only camera effects may have the use "camera"');
|
|
125
134
|
if (e.camera !== void 0) at("only camera effects may have camera");
|
|
126
135
|
}
|
|
136
|
+
if (e.kind === "timing") {
|
|
137
|
+
if (e.uses?.length !== 1 || e.uses[0] !== "timing") at('a timing effect must have exactly the use "timing"');
|
|
138
|
+
if (e.duration !== void 0) at("a timing effect has no duration: its length depends on its footage (timingLength)");
|
|
139
|
+
} else if (e.uses?.includes("timing")) at('only timing effects may have the use "timing"');
|
|
127
140
|
}
|
|
128
141
|
function checkDescription(id, d, issues) {
|
|
129
142
|
const at = (message) => issues.push({ effectId: id, message });
|
|
@@ -167,7 +180,7 @@ async function checkRender(provider, d, size, issues) {
|
|
|
167
180
|
try {
|
|
168
181
|
renderer = await provider.createRenderer(d.id, { width: size, height: size });
|
|
169
182
|
} catch (e) {
|
|
170
|
-
if (!((d.kind === "vector" || d.kind === "camera") && codeOf(e) === "unsupported")) at(`createRenderer failed: ${codeOf(e)}`);
|
|
183
|
+
if (!((d.kind === "vector" || d.kind === "camera" || d.kind === "timing") && codeOf(e) === "unsupported")) at(`createRenderer failed: ${codeOf(e)}`);
|
|
171
184
|
else if (!(e instanceof Error && e.message.trim())) at("createRenderer refused without saying why");
|
|
172
185
|
return;
|
|
173
186
|
}
|
|
@@ -229,6 +242,51 @@ async function checkCamera(provider, d, issues) {
|
|
|
229
242
|
}
|
|
230
243
|
}
|
|
231
244
|
}
|
|
245
|
+
var FOOTAGE = [{ footage: 3e3 }, { footage: 1e3, fps: 24 }];
|
|
246
|
+
var isPromise = (v) => !!v && typeof v.then === "function";
|
|
247
|
+
async function checkTiming(provider, d, issues) {
|
|
248
|
+
const at = (message) => issues.push({ effectId: d.id, message });
|
|
249
|
+
if (d.inputs.length) at("a timing effect takes no inputs: the host decodes its footage");
|
|
250
|
+
for (const probe of FOOTAGE) {
|
|
251
|
+
const fpsText = probe.fps ? ` at ${probe.fps} fps` : "";
|
|
252
|
+
let length;
|
|
253
|
+
try {
|
|
254
|
+
length = provider.timingLength(d.id, probe.footage);
|
|
255
|
+
if (isPromise(length)) return at("timingLength must be synchronous; it returned a Promise");
|
|
256
|
+
if (!(typeof length === "number" && Number.isFinite(length) && length > 0)) {
|
|
257
|
+
at(`timingLength for ${probe.footage} ms of footage is ${String(length)}; it must be a number of ms > 0`);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (provider.timingLength(d.id, probe.footage) !== length) at(`timingLength is not pure: two calls for ${probe.footage} ms gave different lengths`);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
at(`timingLength for ${probe.footage} ms of footage failed: ${codeOf(e)}`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
for (const share of [0, 0.25, 0.5, 0.75, 1]) {
|
|
266
|
+
const request2 = { time: share * length, ...probe };
|
|
267
|
+
try {
|
|
268
|
+
const sample = provider.timingSample(d.id, request2);
|
|
269
|
+
if (isPromise(sample)) return at("timingSample must be synchronous; it returned a Promise");
|
|
270
|
+
const problems = timingSampleProblems(sample, request2);
|
|
271
|
+
for (const p of problems) at(`timingSample at ${+request2.time.toFixed(3)} ms of ${probe.footage} ms footage${fpsText}: ${p}`);
|
|
272
|
+
if (!problems.length && JSON.stringify(provider.timingSample(d.id, request2)) !== JSON.stringify(sample)) at(`timingSample is not pure: two calls at ${+request2.time.toFixed(3)} ms gave different samples`);
|
|
273
|
+
} catch (e) {
|
|
274
|
+
at(`timingSample at ${+request2.time.toFixed(3)} ms of ${probe.footage} ms footage${fpsText} failed: ${codeOf(e)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const request = { time: 0, footage: 1e3 };
|
|
279
|
+
const badLength = await rejectsWith("invalid_values", () => provider.timingLength(d.id, 1e3, { __not_a_control__: 1 }));
|
|
280
|
+
if (badLength) at(`timingLength with an unknown control ${badLength}`);
|
|
281
|
+
const badSample = await rejectsWith("invalid_values", () => provider.timingSample(d.id, request, { __not_a_control__: 1 }));
|
|
282
|
+
if (badSample) at(`timingSample with an unknown control ${badSample}`);
|
|
283
|
+
const noFootage = await rejectsWith("unsupported", () => provider.timingLength(d.id, 0));
|
|
284
|
+
if (noFootage) at(`timingLength of 0 ms of footage ${noFootage}`);
|
|
285
|
+
const badFps = await rejectsWith("unsupported", () => provider.timingSample(d.id, { ...request, fps: 0 }));
|
|
286
|
+
if (badFps) at(`timingSample at 0 fps ${badFps}`);
|
|
287
|
+
const badTime = await rejectsWith("unsupported", () => provider.timingSample(d.id, { ...request, time: Number.NaN }));
|
|
288
|
+
if (badTime) at(`timingSample at time NaN ${badTime}`);
|
|
289
|
+
}
|
|
232
290
|
var wrongValue = (slot) => slot.type === "number" ? "not a number" : slot.type === "text" ? 42 : slot.type === "color" ? "blue" : 42;
|
|
233
291
|
function checkSlot(templateId, slot, issues) {
|
|
234
292
|
const at = (message) => issues.push({ templateId, message: `slot ${JSON.stringify(slot?.name)}: ${message}` });
|
|
@@ -324,6 +382,8 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
324
382
|
if (caps?.supportsCamera !== void 0 && typeof caps.supportsCamera !== "boolean") top("capabilities.supportsCamera is not a boolean");
|
|
325
383
|
if (caps?.supportsCamera && typeof provider.cameraView !== "function") top("supportsCamera is set but cameraView is missing");
|
|
326
384
|
if (provider.sampleCameraMove && typeof provider.cameraView !== "function") top("sampleCameraMove is present but cameraView is missing");
|
|
385
|
+
if (caps?.supportsTiming !== void 0 && typeof caps.supportsTiming !== "boolean") top("capabilities.supportsTiming is not a boolean");
|
|
386
|
+
for (const m of ["timingLength", "timingSample"]) if (caps?.supportsTiming && typeof provider[m] !== "function") top(`supportsTiming is set but ${m} is missing`);
|
|
327
387
|
if (caps?.supportsTemplates !== void 0 && typeof caps.supportsTemplates !== "boolean") top("capabilities.supportsTemplates is not a boolean");
|
|
328
388
|
const templateMethods = ["listTemplates", "describeTemplate", "instantiateTemplate"];
|
|
329
389
|
if (caps?.supportsTemplates) {
|
|
@@ -338,6 +398,7 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
338
398
|
checkSummary(e, issues);
|
|
339
399
|
}
|
|
340
400
|
if (!caps?.supportsCamera && list.some((e) => e.kind === "camera")) top("the catalog lists camera effects but capabilities.supportsCamera is not set");
|
|
401
|
+
if (!caps?.supportsTiming && list.some((e) => e.kind === "timing")) top("the catalog lists timing effects but capabilities.supportsTiming is not set");
|
|
341
402
|
const all = await provider.search("");
|
|
342
403
|
if (all.length !== list.length) top(`search("") returned ${all.length} effects, list() ${list.length}`);
|
|
343
404
|
const firstUse = list[0]?.uses[0];
|
|
@@ -357,6 +418,21 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
357
418
|
const wrongKind = notCamera && await rejectsWith("unsupported", () => provider.cameraView(notCamera.id, probe));
|
|
358
419
|
if (wrongKind) issues.push({ effectId: notCamera.id, message: `cameraView of a ${notCamera.kind} effect ${wrongKind}` });
|
|
359
420
|
}
|
|
421
|
+
const timing = caps?.supportsTiming && provider.timingLength && provider.timingSample;
|
|
422
|
+
if (timing) {
|
|
423
|
+
const request = { time: 0, footage: 1e3 };
|
|
424
|
+
const unknownLength = await rejectsWith("unknown_effect", () => provider.timingLength("__no_such_effect__", 1e3));
|
|
425
|
+
if (unknownLength) top(`timingLength of an unknown id ${unknownLength}`);
|
|
426
|
+
const unknownSample = await rejectsWith("unknown_effect", () => provider.timingSample("__no_such_effect__", request));
|
|
427
|
+
if (unknownSample) top(`timingSample of an unknown id ${unknownSample}`);
|
|
428
|
+
const notTiming = list.find((e) => e.kind !== "timing");
|
|
429
|
+
if (notTiming) {
|
|
430
|
+
const wrongLength = await rejectsWith("unsupported", () => provider.timingLength(notTiming.id, 1e3));
|
|
431
|
+
if (wrongLength) issues.push({ effectId: notTiming.id, message: `timingLength of a ${notTiming.kind} effect ${wrongLength}` });
|
|
432
|
+
const wrongSample = await rejectsWith("unsupported", () => provider.timingSample(notTiming.id, request));
|
|
433
|
+
if (wrongSample) issues.push({ effectId: notTiming.id, message: `timingSample of a ${notTiming.kind} effect ${wrongSample}` });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
360
436
|
const size = options.size ?? 8;
|
|
361
437
|
for (const id of options.ids ?? [...ids]) {
|
|
362
438
|
let d;
|
|
@@ -369,6 +445,7 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
369
445
|
checkDescription(id, d, issues);
|
|
370
446
|
if (options.render !== false && d.kind !== "motion") await checkRender(provider, d, size, issues);
|
|
371
447
|
if (d.kind === "camera" && d.camera && caps.supportsCamera && provider.cameraView) await checkCamera(provider, d, issues);
|
|
448
|
+
if (d.kind === "timing" && timing) await checkTiming(provider, d, issues);
|
|
372
449
|
if ((d.kind === "vector" || d.kind === "motion") && caps.supportsVector && provider.applyVectorEffect) {
|
|
373
450
|
try {
|
|
374
451
|
const m = await provider.applyVectorEffect(id, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><rect id="r" width="10" height="10"/></svg>');
|
|
@@ -383,7 +460,7 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
383
460
|
}
|
|
384
461
|
if (caps?.supportsAuthoring && options.authoring !== false && typeof provider.openAuthoring === "function") {
|
|
385
462
|
const targetEffect = (await Promise.all((options.ids ?? [...ids]).map((id) => provider.describe(id)))).find(
|
|
386
|
-
(d) => d.kind !== "motion" && d.kind !== "vector" && d.kind !== "camera"
|
|
463
|
+
(d) => d.kind !== "motion" && d.kind !== "vector" && d.kind !== "camera" && d.kind !== "timing"
|
|
387
464
|
);
|
|
388
465
|
if (targetEffect) {
|
|
389
466
|
try {
|
|
@@ -862,6 +939,79 @@ var CAMERA_DESCRIPTIONS = [
|
|
|
862
939
|
inputs: []
|
|
863
940
|
}
|
|
864
941
|
];
|
|
942
|
+
var TIMING_DESCRIPTIONS = [
|
|
943
|
+
{
|
|
944
|
+
id: "fake.speed",
|
|
945
|
+
name: "Speed change",
|
|
946
|
+
kind: "timing",
|
|
947
|
+
category: "timing",
|
|
948
|
+
uses: ["timing"],
|
|
949
|
+
description: "Footage played slower or faster throughout; slowed frames blend the two either side, sped-up ones average the frames they pass over.",
|
|
950
|
+
tags: ["retime", "slow motion", "time-lapse"],
|
|
951
|
+
transparent: false,
|
|
952
|
+
loop: false,
|
|
953
|
+
controls: {
|
|
954
|
+
type: "object",
|
|
955
|
+
additionalProperties: false,
|
|
956
|
+
properties: {
|
|
957
|
+
speed: { type: "number", minimum: 0.1, maximum: 8, description: "Playback speed as a multiple of real time, 0.1\u20138 (0.5 is half speed). Default 0.5." },
|
|
958
|
+
smooth: { type: "boolean", description: "Blend frames: in-betweens when slowed, motion blur when sped up. Default true." }
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
defaults: { speed: 0.5, smooth: true },
|
|
962
|
+
presets: [
|
|
963
|
+
{ name: "Slow motion", values: { speed: 0.25 } },
|
|
964
|
+
{ name: "Double speed", values: { speed: 2 } }
|
|
965
|
+
],
|
|
966
|
+
inputs: []
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
id: "fake.freeze",
|
|
970
|
+
name: "Freeze frame",
|
|
971
|
+
kind: "timing",
|
|
972
|
+
category: "timing",
|
|
973
|
+
uses: ["timing"],
|
|
974
|
+
description: "Footage that stops on one frame, holds it, then plays on from where it stopped.",
|
|
975
|
+
tags: ["retime", "freeze", "hold"],
|
|
976
|
+
transparent: false,
|
|
977
|
+
loop: false,
|
|
978
|
+
controls: {
|
|
979
|
+
type: "object",
|
|
980
|
+
additionalProperties: false,
|
|
981
|
+
properties: {
|
|
982
|
+
at: { type: "number", minimum: 0, maximum: 1, description: "Where it freezes, as a share of the footage, 0\u20131. Default 0.5." },
|
|
983
|
+
hold: { type: "number", minimum: 0, maximum: 1e4, description: "How long it holds (ms). Default 1000." }
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
defaults: { at: 0.5, hold: 1e3 },
|
|
987
|
+
presets: [{ name: "Opening still", values: { at: 0, hold: 2e3 } }],
|
|
988
|
+
inputs: []
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
id: "fake.echo",
|
|
992
|
+
name: "Frame echo",
|
|
993
|
+
kind: "timing",
|
|
994
|
+
category: "timing",
|
|
995
|
+
uses: ["timing"],
|
|
996
|
+
description: "Each frame with fading copies of the frames just before it, so moving things leave a trail.",
|
|
997
|
+
tags: ["retime", "echo", "trail"],
|
|
998
|
+
transparent: false,
|
|
999
|
+
loop: false,
|
|
1000
|
+
controls: {
|
|
1001
|
+
type: "object",
|
|
1002
|
+
additionalProperties: false,
|
|
1003
|
+
properties: {
|
|
1004
|
+
echoes: { type: "integer", minimum: 1, maximum: 8, description: "Copies of earlier frames, 1\u20138. Default 3." },
|
|
1005
|
+
spacing: { type: "number", minimum: 10, maximum: 500, description: "Footage time between copies (ms). Default 100." },
|
|
1006
|
+
decay: { type: "number", minimum: 0.1, maximum: 0.95, description: "Each copy's weight as a share of the one after it, 0.1\u20130.95. Default 0.5." },
|
|
1007
|
+
lighten: { type: "boolean", description: "Keep the brightest of the copies (light trails) instead of averaging them. Default false." }
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
defaults: { echoes: 3, spacing: 100, decay: 0.5, lighten: false },
|
|
1011
|
+
presets: [{ name: "Light trails", values: { echoes: 6, spacing: 50, decay: 0.8, lighten: true } }],
|
|
1012
|
+
inputs: []
|
|
1013
|
+
}
|
|
1014
|
+
];
|
|
865
1015
|
var summaryOf = ({ controls: _c, defaults: _d, presets: _p, inputs: _i, ...summary }) => summary;
|
|
866
1016
|
var hexToRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
867
1017
|
var smooth = (t) => t * t * (3 - 2 * t);
|
|
@@ -998,6 +1148,53 @@ function cameraViewOf(d, ctx, v) {
|
|
|
998
1148
|
throw new EffectUnsupportedError(`"${d.id}" is a ${d.kind} effect, not a camera move.`, d.id);
|
|
999
1149
|
}
|
|
1000
1150
|
}
|
|
1151
|
+
function timingLengthOf(d, footage, v) {
|
|
1152
|
+
switch (d.id) {
|
|
1153
|
+
case "fake.speed":
|
|
1154
|
+
return footage / v.speed;
|
|
1155
|
+
case "fake.freeze":
|
|
1156
|
+
return footage + v.hold;
|
|
1157
|
+
case "fake.echo":
|
|
1158
|
+
return footage;
|
|
1159
|
+
default:
|
|
1160
|
+
throw new EffectUnsupportedError(`"${d.id}" is a ${d.kind} effect, not a timing effect.`, d.id);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function timingFramesOf(d, request, v) {
|
|
1164
|
+
const { time, fps } = request;
|
|
1165
|
+
const frameMs = 1e3 / fps;
|
|
1166
|
+
switch (d.id) {
|
|
1167
|
+
case "fake.speed": {
|
|
1168
|
+
const speed = v.speed;
|
|
1169
|
+
const s = time * speed;
|
|
1170
|
+
if (!v.smooth) return { frames: [{ time: s, weight: 1 }] };
|
|
1171
|
+
if (speed <= 1) {
|
|
1172
|
+
const i = s / frameMs;
|
|
1173
|
+
const f = Math.floor(i + 1e-6);
|
|
1174
|
+
const frac = Math.max(0, i - f);
|
|
1175
|
+
return { frames: [{ time: f * frameMs, weight: 1 - frac }, { time: (f + 1) * frameMs, weight: frac }] };
|
|
1176
|
+
}
|
|
1177
|
+
const n = Math.ceil(speed);
|
|
1178
|
+
return { frames: Array.from({ length: n }, (_, k) => ({ time: s + k * speed * frameMs / n, weight: 1 })) };
|
|
1179
|
+
}
|
|
1180
|
+
case "fake.freeze": {
|
|
1181
|
+
const at = Math.floor(v.at * request.footage / frameMs + 1e-6) * frameMs;
|
|
1182
|
+
const hold = v.hold;
|
|
1183
|
+
return { frames: [{ time: time < at ? time : time < at + hold ? at : time - hold, weight: 1 }] };
|
|
1184
|
+
}
|
|
1185
|
+
case "fake.echo": {
|
|
1186
|
+
const frames = [];
|
|
1187
|
+
for (let k = 0; k <= v.echoes; k++) {
|
|
1188
|
+
const t = time - k * v.spacing;
|
|
1189
|
+
if (t < 0) break;
|
|
1190
|
+
frames.push({ time: t, weight: Math.pow(v.decay, k) });
|
|
1191
|
+
}
|
|
1192
|
+
return { frames, lighten: v.lighten };
|
|
1193
|
+
}
|
|
1194
|
+
default:
|
|
1195
|
+
throw new EffectUnsupportedError(`"${d.id}" is a ${d.kind} effect, not a timing effect.`, d.id);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1001
1198
|
function toFloat(p) {
|
|
1002
1199
|
const f = new Float32Array(p.data.length);
|
|
1003
1200
|
for (let i = 0; i < f.length; i += 4) {
|
|
@@ -1029,11 +1226,12 @@ function createFakeEffectProvider(options = {}) {
|
|
|
1029
1226
|
supportsVector: true,
|
|
1030
1227
|
supportsAuthoring,
|
|
1031
1228
|
...options.camera ? { supportsCamera: true } : {},
|
|
1229
|
+
...options.timing ? { supportsTiming: true } : {},
|
|
1032
1230
|
...options.templates ? { supportsTemplates: true } : {},
|
|
1033
1231
|
maxSize: 8192,
|
|
1034
1232
|
...options.capabilities
|
|
1035
1233
|
};
|
|
1036
|
-
const all = options.camera ? [
|
|
1234
|
+
const all = [...DESCRIPTIONS, ...options.camera ? CAMERA_DESCRIPTIONS : [], ...options.timing ? TIMING_DESCRIPTIONS : []];
|
|
1037
1235
|
const byId = new Map(all.map((d) => [d.id, d]));
|
|
1038
1236
|
const get = (id) => {
|
|
1039
1237
|
const d = byId.get(id);
|
|
@@ -1047,6 +1245,25 @@ function createFakeEffectProvider(options = {}) {
|
|
|
1047
1245
|
if (!(ctx.u >= 0 && ctx.u <= 1)) throw new EffectUnsupportedError(`u must be 0\u20131 through the move, got ${ctx.u}.`, id);
|
|
1048
1246
|
return cameraViewOf(d, ctx, resolveEffectValues(d, values));
|
|
1049
1247
|
};
|
|
1248
|
+
const timingEffect = (id) => {
|
|
1249
|
+
const d = get(id);
|
|
1250
|
+
if (d.kind !== "timing") {
|
|
1251
|
+
const ids = TIMING_DESCRIPTIONS.map((t) => `"${t.id}"`).join(", ");
|
|
1252
|
+
throw new EffectUnsupportedError(`"${id}" is a ${d.kind} effect, not a timing effect; the timing effects are ${ids}.`, id);
|
|
1253
|
+
}
|
|
1254
|
+
return d;
|
|
1255
|
+
};
|
|
1256
|
+
const timingLength = (id, footageMs, values) => {
|
|
1257
|
+
const d = timingEffect(id);
|
|
1258
|
+
checkTimingFootage(footageMs, id);
|
|
1259
|
+
return timingLengthOf(d, footageMs, resolveEffectValues(d, values));
|
|
1260
|
+
};
|
|
1261
|
+
const timingSample = (id, request, values) => {
|
|
1262
|
+
const d = timingEffect(id);
|
|
1263
|
+
const fps = checkTimingRequest(request, id);
|
|
1264
|
+
const raw = timingFramesOf(d, { time: request.time, footage: request.footage, fps }, resolveEffectValues(d, values));
|
|
1265
|
+
return normaliseTimingSample(raw.frames, { ...request, fps }, raw.lighten ? "lighten" : "average");
|
|
1266
|
+
};
|
|
1050
1267
|
const provider = {
|
|
1051
1268
|
id: "fake",
|
|
1052
1269
|
name: "Fake effects",
|
|
@@ -1058,6 +1275,7 @@ function createFakeEffectProvider(options = {}) {
|
|
|
1058
1275
|
createRenderer(id, ro) {
|
|
1059
1276
|
const d = get(id);
|
|
1060
1277
|
if (d.kind === "camera") throw new EffectUnsupportedError(`"${id}" is a camera move: it moves a scene's camera and draws no pixels. Use cameraView.`, id);
|
|
1278
|
+
if (d.kind === "timing") throw new EffectUnsupportedError(`"${id}" is a timing effect: it retimes video footage and draws no pixels of its own. Use timingSample and blend the footage frames it names.`, id);
|
|
1061
1279
|
if (d.kind !== "shader") throw new EffectUnsupportedError(`"${id}" is a ${d.kind} effect; use applyVectorEffect.`, id);
|
|
1062
1280
|
const { width, height } = ro;
|
|
1063
1281
|
const depth = ro.depth ?? 8;
|
|
@@ -1135,6 +1353,7 @@ function createFakeEffectProvider(options = {}) {
|
|
|
1135
1353
|
return sampleCameraViews((u) => camera(id, { from, to, u, ms, stage }, values), { ms, fps, cut: d.camera?.cut });
|
|
1136
1354
|
}
|
|
1137
1355
|
} : {},
|
|
1356
|
+
...options.timing ? { timingLength, timingSample } : {},
|
|
1138
1357
|
...options.templates ? fakeTemplateMethods() : {},
|
|
1139
1358
|
...supportsAuthoring ? {
|
|
1140
1359
|
openAuthoring: (from) => createEffectAuthoringSession(from, { provider })
|
|
@@ -1152,7 +1371,8 @@ var EFFECT_USE_LABELS = {
|
|
|
1152
1371
|
title: "Title",
|
|
1153
1372
|
motion: "Motion",
|
|
1154
1373
|
interactive: "Interactive",
|
|
1155
|
-
camera: "Camera"
|
|
1374
|
+
camera: "Camera",
|
|
1375
|
+
timing: "Timing"
|
|
1156
1376
|
};
|
|
1157
1377
|
var HEX2 = /^#[0-9a-f]{6}([0-9a-f]{2})?$/i;
|
|
1158
1378
|
var typeOf = (s) => Array.isArray(s.type) ? s.type.find((t) => t !== "null") : s.type;
|
|
@@ -1682,9 +1902,14 @@ export {
|
|
|
1682
1902
|
EffectUnsupportedError,
|
|
1683
1903
|
EffectsSlot,
|
|
1684
1904
|
NoEffectsProviderError,
|
|
1905
|
+
TIMING_BLENDS,
|
|
1906
|
+
TIMING_DEFAULT_FPS,
|
|
1907
|
+
TIMING_MAX_FPS,
|
|
1908
|
+
TIMING_MAX_FRAMES,
|
|
1685
1909
|
UnknownEffectError,
|
|
1686
1910
|
UnknownTemplateError,
|
|
1687
1911
|
applyEffectMask,
|
|
1912
|
+
blendTimingFrames,
|
|
1688
1913
|
cameraBoxOfView,
|
|
1689
1914
|
cameraMoveField,
|
|
1690
1915
|
cameraMoveIdList,
|
|
@@ -1694,6 +1919,8 @@ export {
|
|
|
1694
1919
|
checkEffectProvider,
|
|
1695
1920
|
checkEffectSequence,
|
|
1696
1921
|
checkTemplateValues,
|
|
1922
|
+
checkTimingFootage,
|
|
1923
|
+
checkTimingRequest,
|
|
1697
1924
|
countEffectTags,
|
|
1698
1925
|
createEffectAuthoringSession,
|
|
1699
1926
|
createEffectAuthoringToolProvider,
|
|
@@ -1730,6 +1957,7 @@ export {
|
|
|
1730
1957
|
layerImageInput,
|
|
1731
1958
|
linearToSrgb,
|
|
1732
1959
|
normaliseEffectTag,
|
|
1960
|
+
normaliseTimingSample,
|
|
1733
1961
|
pathControlsOf,
|
|
1734
1962
|
renderEffectThumbnail,
|
|
1735
1963
|
resolveCameraMoveSpec,
|
|
@@ -1744,5 +1972,6 @@ export {
|
|
|
1744
1972
|
templateSlotSummary,
|
|
1745
1973
|
templateSlotTakes,
|
|
1746
1974
|
templateValueProblem,
|
|
1747
|
-
templateValuesSchema
|
|
1975
|
+
templateValuesSchema,
|
|
1976
|
+
timingSampleProblems
|
|
1748
1977
|
};
|
package/dist/effects/types.d.cts
CHANGED
|
@@ -11,9 +11,10 @@ import type { FlairSvgAnimation } from '../animation-editor/flair.cjs';
|
|
|
11
11
|
import type { EffectAuthoringSession, EffectAuthoringSource } from './authoring.cjs';
|
|
12
12
|
/**
|
|
13
13
|
* How an effect is drawn: a pixel shader, an animated vector picture, motion applied to someone
|
|
14
|
-
* else's vector art,
|
|
14
|
+
* else's vector art, a camera move, which moves a scene's camera and draws no pixels, or a
|
|
15
|
+
* timing effect, which retimes video footage and draws no pixels of its own.
|
|
15
16
|
*/
|
|
16
|
-
export type EffectKind = 'shader' | 'vector' | 'motion' | 'camera';
|
|
17
|
+
export type EffectKind = 'shader' | 'vector' | 'motion' | 'camera' | 'timing';
|
|
17
18
|
/**
|
|
18
19
|
* What an effect is for. An effect may have several uses.
|
|
19
20
|
*
|
|
@@ -26,8 +27,10 @@ export type EffectKind = 'shader' | 'vector' | 'motion' | 'camera';
|
|
|
26
27
|
* - `interactive`: reacts to pointer, scroll or audio input at run time.
|
|
27
28
|
* - `camera`: moves a scene's camera (`cameraView`). Only `camera` effects have it, and they have
|
|
28
29
|
* no other use, so editors that draw pixels never offer them.
|
|
30
|
+
* - `timing`: retimes video footage (`timingSample`): slow motion, a reverse, a freeze, a long
|
|
31
|
+
* exposure. Only `timing` effects have it, and they have no other use.
|
|
29
32
|
*/
|
|
30
|
-
export type EffectUse = 'filter' | 'transition' | 'generator' | 'overlay' | 'title' | 'motion' | 'interactive' | 'camera';
|
|
33
|
+
export type EffectUse = 'filter' | 'transition' | 'generator' | 'overlay' | 'title' | 'motion' | 'interactive' | 'camera' | 'timing';
|
|
31
34
|
export declare const EFFECT_KINDS: readonly EffectKind[];
|
|
32
35
|
export declare const EFFECT_USES: readonly EffectUse[];
|
|
33
36
|
/**
|
|
@@ -66,7 +69,10 @@ export interface EffectSummary {
|
|
|
66
69
|
transparent: boolean;
|
|
67
70
|
/** True when the effect loops; its `duration` is then one cycle. */
|
|
68
71
|
loop: boolean;
|
|
69
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Length of one play (ms). Absent for effects that do not change over time, and for `timing`
|
|
74
|
+
* effects, whose length depends on their footage (`timingLength`).
|
|
75
|
+
*/
|
|
70
76
|
duration?: number;
|
|
71
77
|
/**
|
|
72
78
|
* The control (a number from 0 to 1) setting how opaque the effect's own background is: at 0
|
|
@@ -300,6 +306,59 @@ export interface CameraMoveSample {
|
|
|
300
306
|
/** Ms of the cut, when the move has one. */
|
|
301
307
|
cut?: number;
|
|
302
308
|
}
|
|
309
|
+
/** The frame rate footage is taken to have when a timing request does not say. */
|
|
310
|
+
export declare const TIMING_DEFAULT_FPS = 30;
|
|
311
|
+
/** Most footage frames one retimed frame blends (`TimingSample.frames`). */
|
|
312
|
+
export declare const TIMING_MAX_FRAMES = 128;
|
|
313
|
+
/** Highest footage frame rate a timing request may give. */
|
|
314
|
+
export declare const TIMING_MAX_FPS = 240;
|
|
315
|
+
/**
|
|
316
|
+
* How a retimed frame's footage frames combine (straight-alpha RGBA in, straight alpha out;
|
|
317
|
+
* `blendTimingFrames` is the reference):
|
|
318
|
+
*
|
|
319
|
+
* - `average`: the weighted mean in premultiplied alpha, so transparent edges do not darken.
|
|
320
|
+
* Colour is Σ(c·a·w) / Σ(a·w) and alpha Σ(a·w) / Σw, with `a` the frame's alpha (0–1).
|
|
321
|
+
* - `lighten`: each channel's brightest. Every frame is first scaled by `weight / maxWeight`
|
|
322
|
+
* (`k`): colour by `a·k`, alpha by `k`. The result keeps the largest scaled colour and the
|
|
323
|
+
* largest scaled alpha, and its colour is not divided back up by its alpha, so a faded trail
|
|
324
|
+
* stays dimmer.
|
|
325
|
+
*
|
|
326
|
+
* A single frame is shown as it is either way.
|
|
327
|
+
*/
|
|
328
|
+
export type TimingBlend = 'average' | 'lighten';
|
|
329
|
+
export declare const TIMING_BLENDS: readonly TimingBlend[];
|
|
330
|
+
/** Where a retimed frame is: what `timingSample` takes. */
|
|
331
|
+
export interface TimingSampleRequest {
|
|
332
|
+
/** Ms into the retimed clip (any finite number; frames are held within the footage). */
|
|
333
|
+
time: number;
|
|
334
|
+
/** Length of the footage (ms); > 0. */
|
|
335
|
+
footage: number;
|
|
336
|
+
/** Footage frames per second, > 0 and at most `TIMING_MAX_FPS`. Default `TIMING_DEFAULT_FPS`. */
|
|
337
|
+
fps?: number;
|
|
338
|
+
}
|
|
339
|
+
/** One footage frame of a retimed frame. */
|
|
340
|
+
export interface TimingFrame {
|
|
341
|
+
/** Footage time (ms): the start of a frame, `index * 1000 / fps`. */
|
|
342
|
+
time: number;
|
|
343
|
+
/** Share of the blend, > 0; a sample's weights add up to 1. */
|
|
344
|
+
weight: number;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The footage frames one retimed frame is made of, and how to blend them. The host decodes the
|
|
348
|
+
* frames from its own footage and blends them (`blendTimingFrames` does it for `EffectPixels`).
|
|
349
|
+
*
|
|
350
|
+
* - Each `time` is a footage frame's start, `index * 1000 / fps` with a whole `index` from 0 to
|
|
351
|
+
* the footage's last frame (`ceil(footage * fps / 1000) - 1`): a time before the footage is held
|
|
352
|
+
* on its first frame and one after it on its last.
|
|
353
|
+
* - Times are distinct (frames that land on the same footage frame are merged, their weights
|
|
354
|
+
* added), sorted earliest first, and at most `TIMING_MAX_FRAMES` (spaced evenly when an effect
|
|
355
|
+
* wants more).
|
|
356
|
+
* - Weights are > 0 and add up to 1.
|
|
357
|
+
*/
|
|
358
|
+
export interface TimingSample {
|
|
359
|
+
frames: readonly TimingFrame[];
|
|
360
|
+
blend: TimingBlend;
|
|
361
|
+
}
|
|
303
362
|
/** A box in a sequence's stage units (pixels of `EffectSequence.stage`). */
|
|
304
363
|
export interface EffectRect {
|
|
305
364
|
x: number;
|
|
@@ -516,6 +575,8 @@ export interface EffectProviderCapabilities {
|
|
|
516
575
|
supportsAuthoring?: boolean;
|
|
517
576
|
/** Lists `camera` effects and implements `cameraView` (and maybe `sampleCameraMove`). */
|
|
518
577
|
supportsCamera?: boolean;
|
|
578
|
+
/** Lists `timing` effects and implements `timingLength` and `timingSample`. */
|
|
579
|
+
supportsTiming?: boolean;
|
|
519
580
|
/** Implements `listTemplates`, `describeTemplate` and `instantiateTemplate`. */
|
|
520
581
|
supportsTemplates?: boolean;
|
|
521
582
|
}
|
|
@@ -539,7 +600,8 @@ export interface EffectProvider {
|
|
|
539
600
|
describe(id: string): MaybePromise<EffectDescription>;
|
|
540
601
|
/**
|
|
541
602
|
* A pixel renderer. A `vector` effect may refuse with `EffectUnsupportedError` where it cannot
|
|
542
|
-
* lay out SVG (a Worker);
|
|
603
|
+
* lay out SVG (a Worker); `camera` and `timing` effects draw no pixels of their own and may
|
|
604
|
+
* refuse with one saying so.
|
|
543
605
|
*/
|
|
544
606
|
createRenderer(id: string, options: EffectRendererOptions): MaybePromise<EffectRenderer>;
|
|
545
607
|
/**
|
|
@@ -573,6 +635,23 @@ export interface EffectProvider {
|
|
|
573
635
|
* `sampleEffectCameraMove` falls back to `cameraView` per frame. Errors as for `cameraView`.
|
|
574
636
|
*/
|
|
575
637
|
sampleCameraMove?(id: string, request: CameraMoveSampleRequest): MaybePromise<CameraMoveSample>;
|
|
638
|
+
/**
|
|
639
|
+
* Ms a timing effect's retimed clip lasts for footage `footageMs` long (a speed change stretches
|
|
640
|
+
* or squeezes it, a freeze adds its hold, a boomerang goes there and back); > 0. Present when
|
|
641
|
+
* `capabilities.supportsTiming`. Synchronous and pure. Unknown ids throw `UnknownEffectError`,
|
|
642
|
+
* invalid values `EffectControlError`, and an effect that is not a timing effect (or a footage
|
|
643
|
+
* length that is not a number of ms > 0) `EffectUnsupportedError`, each with a sentence naming
|
|
644
|
+
* what is valid.
|
|
645
|
+
*/
|
|
646
|
+
timingLength?(id: string, footageMs: number, values?: EffectValues): number;
|
|
647
|
+
/**
|
|
648
|
+
* The footage frames retimed frame `request.time` shows and how to blend them (see
|
|
649
|
+
* `TimingSample` for exactly what it holds). Present when `capabilities.supportsTiming`.
|
|
650
|
+
* Synchronous and pure: a frame can be drawn on its own, in any order, and the same id,
|
|
651
|
+
* request and values always give the same sample. Errors as for `timingLength`; a time that is
|
|
652
|
+
* not finite or an `fps` outside 0–`TIMING_MAX_FPS` also throw `EffectUnsupportedError`.
|
|
653
|
+
*/
|
|
654
|
+
timingSample?(id: string, request: TimingSampleRequest, values?: EffectValues): TimingSample;
|
|
576
655
|
/** Sequence templates, best match first. Present when `capabilities.supportsTemplates`. */
|
|
577
656
|
listTemplates?(options?: EffectTemplateSearchOptions): MaybePromise<readonly EffectTemplateSummary[]>;
|
|
578
657
|
/** A template with its slots. Unknown ids throw `UnknownTemplateError`. */
|