@pixerate/editor 0.231.0 → 0.232.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 +25 -25
- package/dist/animation-editor/index.js +1 -1
- package/dist/canvas/index.cjs +7 -7
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-OPSK22DL.cjs → chunk-2K5TELHV.cjs} +19 -2
- package/dist/{chunk-CSM5T6SE.js → chunk-6QLTUGMO.js} +1 -1
- package/dist/{chunk-5T3H7CGG.cjs → chunk-A653N65Q.cjs} +7 -7
- package/dist/{chunk-SBPGIZQN.js → chunk-MUSZYMBX.js} +1 -1
- package/dist/{chunk-TCZ2J7PR.js → chunk-PQYDJ2AV.js} +18 -1
- package/dist/{chunk-C73U5UI3.cjs → chunk-RG4O6K2A.cjs} +14 -14
- package/dist/effects/conformance.d.cts +4 -1
- package/dist/effects/conformance.d.ts +4 -1
- package/dist/effects/errors.d.cts +6 -1
- package/dist/effects/errors.d.ts +6 -1
- package/dist/effects/fake-templates.d.cts +17 -0
- package/dist/effects/fake-templates.d.ts +17 -0
- package/dist/effects/fake.d.cts +6 -1
- package/dist/effects/fake.d.ts +6 -1
- package/dist/effects/index.cjs +537 -74
- package/dist/effects/index.d.cts +1 -0
- package/dist/effects/index.d.ts +1 -0
- package/dist/effects/index.js +466 -3
- package/dist/effects/templates.d.cts +56 -0
- package/dist/effects/templates.d.ts +56 -0
- package/dist/effects/types.d.cts +212 -0
- package/dist/effects/types.d.ts +212 -0
- package/dist/image-editor/controller.d.cts +12 -0
- package/dist/image-editor/controller.d.ts +12 -0
- package/dist/image-editor/index.cjs +189 -144
- package/dist/image-editor/index.js +48 -3
- package/dist/paint-editor/index.cjs +7 -7
- package/dist/paint-editor/index.js +2 -2
- package/dist/vector/index.cjs +6 -6
- package/dist/vector/index.js +1 -1
- package/dist/video-editor/index.cjs +329 -329
- package/dist/video-editor/index.js +3 -3
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +37 -0
package/dist/effects/index.d.cts
CHANGED
package/dist/effects/index.d.ts
CHANGED
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-MUSZYMBX.js";
|
|
7
7
|
import {
|
|
8
8
|
EffectRendererPool
|
|
9
9
|
} from "../chunk-EHLOCVWB.js";
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
CAMERA_MOVE_ROLES,
|
|
13
13
|
EFFECT_ERROR_CODES,
|
|
14
14
|
EFFECT_KINDS,
|
|
15
|
+
EFFECT_LAYER_ROLES,
|
|
16
|
+
EFFECT_SUBJECT_KINDS,
|
|
17
|
+
EFFECT_TEMPLATE_SLOT_TYPES,
|
|
18
|
+
EFFECT_TRANSITION_MS,
|
|
15
19
|
EFFECT_USES,
|
|
16
20
|
EffectControlError,
|
|
17
21
|
EffectDisposedError,
|
|
@@ -20,6 +24,7 @@ import {
|
|
|
20
24
|
EffectsSlot,
|
|
21
25
|
NoEffectsProviderError,
|
|
22
26
|
UnknownEffectError,
|
|
27
|
+
UnknownTemplateError,
|
|
23
28
|
applyEffectMask,
|
|
24
29
|
cameraBoxOfView,
|
|
25
30
|
cameraMoveField,
|
|
@@ -45,13 +50,242 @@ import {
|
|
|
45
50
|
sampleEffectCameraMove,
|
|
46
51
|
searchEffectSummaries,
|
|
47
52
|
srgbToLinear
|
|
48
|
-
} from "../chunk-
|
|
53
|
+
} from "../chunk-PQYDJ2AV.js";
|
|
49
54
|
import "../chunk-V2ZCJRDM.js";
|
|
50
55
|
import "../chunk-RNSANXPT.js";
|
|
51
56
|
import {
|
|
52
57
|
ANIMATION_EASING_PRESETS
|
|
53
58
|
} from "../chunk-5UJHB3BW.js";
|
|
54
59
|
|
|
60
|
+
// src/effects/templates.ts
|
|
61
|
+
var SLOT_NAME = /^[A-Za-z][\w-]*$/;
|
|
62
|
+
var COLOR = /^#[0-9a-fA-F]{6}$/;
|
|
63
|
+
var EFFECT_SEQUENCE_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
64
|
+
var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
65
|
+
var isSubjectTemplateSlot = (slot) => slot.type === "image" || slot.type === "video" || slot.type === "svg";
|
|
66
|
+
function effectSubjectKind(subject) {
|
|
67
|
+
if (!isObject(subject)) return void 0;
|
|
68
|
+
for (const k of ["slot", "image", "video", "svg", "text"]) if (typeof subject[k] === "string") return k;
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
var typeName = (value) => value === null ? "null" : Array.isArray(value) ? "a list" : typeof value === "object" ? "an object" : typeof value === "string" ? `the words ${JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}\u2026` : value)}` : `${typeof value === "number" ? "the number" : "the value"} ${String(value)}`;
|
|
72
|
+
function range(slot) {
|
|
73
|
+
const whole = slot.integer ? "whole number" : "number";
|
|
74
|
+
if (slot.min !== void 0 && slot.max !== void 0) return `a ${whole} from ${slot.min} to ${slot.max}`;
|
|
75
|
+
if (slot.min !== void 0) return `a ${whole} of at least ${slot.min}`;
|
|
76
|
+
if (slot.max !== void 0) return `a ${whole} of at most ${slot.max}`;
|
|
77
|
+
return `a ${whole}`;
|
|
78
|
+
}
|
|
79
|
+
function templateSlotTakes(slot) {
|
|
80
|
+
switch (slot.type) {
|
|
81
|
+
case "text":
|
|
82
|
+
return `words${slot.maxLength !== void 0 ? ` (up to ${slot.maxLength} characters)` : ""}`;
|
|
83
|
+
case "color":
|
|
84
|
+
return `a colour written #RRGGBB, like "${slot.default}"`;
|
|
85
|
+
case "number":
|
|
86
|
+
return range(slot);
|
|
87
|
+
case "video":
|
|
88
|
+
return "a video URL";
|
|
89
|
+
case "svg":
|
|
90
|
+
return "SVG markup or an SVG URL";
|
|
91
|
+
default:
|
|
92
|
+
return "a picture: a URL, a data: URL or <svg> markup";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function templateValueProblem(slot, value) {
|
|
96
|
+
const no = (extra = "") => `Slot "${slot.name}" takes ${templateSlotTakes(slot)}${extra}, not ${typeName(value)}.`;
|
|
97
|
+
switch (slot.type) {
|
|
98
|
+
case "text":
|
|
99
|
+
if (typeof value !== "string") return no();
|
|
100
|
+
if (slot.maxLength !== void 0 && value.length > slot.maxLength) return `Slot "${slot.name}" takes up to ${slot.maxLength} characters; the value has ${value.length}. Shorten it.`;
|
|
101
|
+
return void 0;
|
|
102
|
+
case "color":
|
|
103
|
+
return typeof value === "string" && COLOR.test(value) ? void 0 : no();
|
|
104
|
+
case "number":
|
|
105
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return no();
|
|
106
|
+
if (slot.integer && !Number.isInteger(value)) return `Slot "${slot.name}" takes ${range(slot)}; ${value} is not whole.`;
|
|
107
|
+
if (slot.min !== void 0 && value < slot.min || slot.max !== void 0 && value > slot.max) return `Slot "${slot.name}" takes ${range(slot)}; ${value} is out of range.`;
|
|
108
|
+
return void 0;
|
|
109
|
+
default: {
|
|
110
|
+
const host = ` (or { "slot": "${slot.name}" } to leave it for the host)`;
|
|
111
|
+
if (typeof value === "string") return value.trim() ? void 0 : no(host);
|
|
112
|
+
const kind = effectSubjectKind(value);
|
|
113
|
+
if (!kind) return no(host);
|
|
114
|
+
if (kind === "slot") return SLOT_NAME.test(value.slot) ? void 0 : `Slot "${slot.name}" left for the host is { "slot": name }, the name letters, digits, "-" and "_", starting with a letter.`;
|
|
115
|
+
const fits = kind === slot.type || slot.type === "image" && kind === "svg";
|
|
116
|
+
if (!fits) return `Slot "${slot.name}" takes { "${slot.type}": \u2026 }${slot.type === "image" ? ' or { "svg": \u2026 }' : ""}, not { "${kind}": \u2026 }.`;
|
|
117
|
+
return value[kind].trim() ? void 0 : `Slot "${slot.name}"'s "${kind}" is a non-empty string.`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
var slotList = (slots) => slots.length ? `the slots are ${slots.map((s) => `${s.name} (${s.type})`).join(", ")}` : "the template has no slots";
|
|
122
|
+
function checkTemplateValues(slots, values) {
|
|
123
|
+
if (values === void 0) return [];
|
|
124
|
+
if (!isObject(values)) return [`Values are an object of slot names to values; ${slotList(slots)}.`];
|
|
125
|
+
const byName = new Map(slots.map((s) => [s.name, s]));
|
|
126
|
+
const errors = [];
|
|
127
|
+
for (const [name, value] of Object.entries(values)) {
|
|
128
|
+
const slot = byName.get(name);
|
|
129
|
+
if (!slot) errors.push(`There is no slot "${name}"; ${slotList(slots)}.`);
|
|
130
|
+
else if (value !== void 0) {
|
|
131
|
+
const problem = templateValueProblem(slot, value);
|
|
132
|
+
if (problem) errors.push(problem);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return errors;
|
|
136
|
+
}
|
|
137
|
+
function templateSlotSchema(slot) {
|
|
138
|
+
const about = `${slot.label}${slot.description ? `: ${slot.description}` : ""}.`;
|
|
139
|
+
switch (slot.type) {
|
|
140
|
+
case "text":
|
|
141
|
+
return { type: "string", ...slot.maxLength !== void 0 ? { maxLength: slot.maxLength } : {}, description: `${about} Words${slot.maxLength !== void 0 ? `, up to ${slot.maxLength} characters` : ""}. Default ${JSON.stringify(slot.default)}.` };
|
|
142
|
+
case "color":
|
|
143
|
+
return { type: "string", pattern: COLOR.source, description: `${about} A colour as #RRGGBB. Default ${slot.default}.` };
|
|
144
|
+
case "number":
|
|
145
|
+
return {
|
|
146
|
+
type: slot.integer ? "integer" : "number",
|
|
147
|
+
...slot.min !== void 0 ? { minimum: slot.min } : {},
|
|
148
|
+
...slot.max !== void 0 ? { maximum: slot.max } : {},
|
|
149
|
+
description: `${about} ${range(slot).replace(/^a /, "A ")}. Default ${slot.default}.`
|
|
150
|
+
};
|
|
151
|
+
default: {
|
|
152
|
+
const key = slot.type;
|
|
153
|
+
const object2 = (k, what) => ({ type: "object", properties: { [k]: { type: "string", description: what } }, required: [k], additionalProperties: false, description: `{ "${k}": \u2026 }` });
|
|
154
|
+
return {
|
|
155
|
+
anyOf: [
|
|
156
|
+
{ type: "string", minLength: 1, description: key === "video" ? "A video URL." : key === "svg" ? "SVG markup or an SVG URL." : "A picture URL, a data: URL or <svg> markup." },
|
|
157
|
+
object2(key, key === "video" ? "A video URL." : key === "svg" ? "SVG markup or an SVG URL." : "A picture URL or a data: URL."),
|
|
158
|
+
...key === "image" ? [object2("svg", "SVG markup.")] : [],
|
|
159
|
+
object2("slot", "A slot name, to leave the subject for the host to fill.")
|
|
160
|
+
],
|
|
161
|
+
description: `${about} ${templateSlotTakes(slot).replace(/^a /, "A ")}. ${slot.hasDefault ? "Default: a placeholder picture." : "No default: left out, the host fills it."}`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function templateValuesSchema(slots) {
|
|
167
|
+
return {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: Object.fromEntries(slots.map((s) => [s.name, templateSlotSchema(s)])),
|
|
170
|
+
additionalProperties: false,
|
|
171
|
+
description: `Slot values by name; slots left out take their defaults. ${slots.length ? `Slots: ${slots.map((s) => s.name).join(", ")}.` : "This template has no slots."}`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function templateDefaultValues(slots) {
|
|
175
|
+
const out = {};
|
|
176
|
+
for (const s of slots) if (!isSubjectTemplateSlot(s)) out[s.name] = s.default;
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
function templateSlotSummary(slot) {
|
|
180
|
+
const fallback = isSubjectTemplateSlot(slot) ? slot.hasDefault ? "a placeholder by default" : "the host fills it when none is given" : `default ${JSON.stringify(slot.default)}`;
|
|
181
|
+
return `${slot.name} (${slot.type}): ${slot.label}; takes ${templateSlotTakes(slot)}; ${fallback}.`;
|
|
182
|
+
}
|
|
183
|
+
function searchTemplateSummaries(catalog, options = {}) {
|
|
184
|
+
const words = (options.query ?? "").toLowerCase().split(/\s+/).filter(Boolean);
|
|
185
|
+
const scored = [];
|
|
186
|
+
for (const t of catalog) {
|
|
187
|
+
if (!hasEffectTags(t, options.tags)) continue;
|
|
188
|
+
let score = 0;
|
|
189
|
+
const name = t.name.toLowerCase();
|
|
190
|
+
const rest = [t.id, ...t.tags ?? [], t.description].join(" ").toLowerCase();
|
|
191
|
+
if (!words.every((w) => name.includes(w) || rest.includes(w))) continue;
|
|
192
|
+
for (const w of words) score += name.includes(w) ? 2 : 1;
|
|
193
|
+
scored.push({ t, score });
|
|
194
|
+
}
|
|
195
|
+
scored.sort((a, b) => b.score - a.score);
|
|
196
|
+
const out = scored.map((s) => s.t);
|
|
197
|
+
return options.limit !== void 0 ? out.slice(0, Math.max(0, options.limit)) : out;
|
|
198
|
+
}
|
|
199
|
+
var effectLayerRole = (layer) => layer.role ?? (layer.rect ? "subject" : "background");
|
|
200
|
+
function effectLayerInput(description) {
|
|
201
|
+
const images = description.inputs.filter((i) => i.kind === "image");
|
|
202
|
+
return (images.find((i) => i.name === "source") ?? images.find((i) => i.name !== "from" && i.name !== "to"))?.name;
|
|
203
|
+
}
|
|
204
|
+
var EFFECT_LAYER_USES = ["generator", "filter", "overlay", "title"];
|
|
205
|
+
var effectLayerOffered = (e) => (e.kind === "shader" || e.kind === "vector") && e.uses.some((u) => EFFECT_LAYER_USES.includes(u));
|
|
206
|
+
var isRect = (r) => isObject(r) && ["x", "y", "width", "height"].every((k) => Number.isFinite(r[k])) && r.width > 0 && r.height > 0;
|
|
207
|
+
var isLayerTime = (t) => typeof t === "number" && Number.isFinite(t) && t >= 0 || isObject(t) && Number.isInteger(t.shots) && t.shots >= 1;
|
|
208
|
+
function effectSubjectProblem(subject) {
|
|
209
|
+
const kind = effectSubjectKind(subject);
|
|
210
|
+
if (!kind) return `a subject is one of { image }, { video }, { svg }, { text } or { slot }, not ${typeName(subject)}`;
|
|
211
|
+
if (!subject[kind].trim()) return `a subject's "${kind}" is a non-empty string`;
|
|
212
|
+
return void 0;
|
|
213
|
+
}
|
|
214
|
+
function checkEffectSequence(sequence, catalog) {
|
|
215
|
+
const problems = [];
|
|
216
|
+
if (!isObject(sequence)) return ["A sequence is an object with name, stage and shots."];
|
|
217
|
+
if (typeof sequence.name !== "string" || !sequence.name) problems.push("The sequence needs a name.");
|
|
218
|
+
const stage = sequence.stage;
|
|
219
|
+
if (!isObject(stage) || !(Number(stage.width) > 0) || !(Number(stage.height) > 0)) problems.push("The sequence needs a stage { width, height } in pixels, both > 0.");
|
|
220
|
+
if (!Array.isArray(sequence.shots) || !sequence.shots.length) return [...problems, "The sequence needs at least one shot."];
|
|
221
|
+
const byId = catalog ? new Map(catalog.map((e) => [e.id, e])) : void 0;
|
|
222
|
+
const ids = /* @__PURE__ */ new Set();
|
|
223
|
+
sequence.shots.forEach((shot, i) => {
|
|
224
|
+
const at = `shot ${i + 1}`;
|
|
225
|
+
if (!isObject(shot)) return void problems.push(`${at} is not an object.`);
|
|
226
|
+
if (!(Number.isFinite(shot.hold) && shot.hold >= 0)) problems.push(`${at}: hold must be ms \u2265 0.`);
|
|
227
|
+
for (const k of ["title", "caption"]) if (shot[k] !== void 0 && typeof shot[k] !== "string") problems.push(`${at}: ${k} must be words.`);
|
|
228
|
+
if (shot.camera !== void 0) {
|
|
229
|
+
const camera = shot.camera;
|
|
230
|
+
if (!isObject(camera)) problems.push(`${at}: camera must be an object.`);
|
|
231
|
+
else if (camera.framing !== void 0 && !isRect(camera.framing)) problems.push(`${at}: camera.framing must be { x, y, width, height } with width and height > 0.`);
|
|
232
|
+
}
|
|
233
|
+
if (!Array.isArray(shot.layers)) problems.push(`${at}: layers must be a list (it may be empty).`);
|
|
234
|
+
else
|
|
235
|
+
shot.layers.forEach((layer, j) => {
|
|
236
|
+
const where = `${at}, layer ${j + 1}`;
|
|
237
|
+
if (!isObject(layer)) return void problems.push(`${where} is not an object.`);
|
|
238
|
+
if (layer.id !== void 0) {
|
|
239
|
+
if (typeof layer.id !== "string" || !layer.id) problems.push(`${where}: id must be a non-empty string.`);
|
|
240
|
+
else if (ids.has(layer.id)) problems.push(`${where}: id "${layer.id}" is used twice.`);
|
|
241
|
+
else ids.add(layer.id);
|
|
242
|
+
}
|
|
243
|
+
if (layer.effect === void 0 && layer.subject === void 0) problems.push(`${where} has neither an effect nor a subject.`);
|
|
244
|
+
if (layer.effect !== void 0 && (typeof layer.effect !== "string" || !layer.effect)) problems.push(`${where}: effect must be an effect id.`);
|
|
245
|
+
else if (typeof layer.effect === "string" && byId) {
|
|
246
|
+
const e = byId.get(layer.effect);
|
|
247
|
+
if (!e) problems.push(`${where}: the provider has no effect "${layer.effect}".`);
|
|
248
|
+
else if (!effectLayerOffered(e)) problems.push(`${where}: "${layer.effect}" (${e.kind}, ${e.uses.join("/")}) cannot be a layer; a layer's effect is a generator, filter, overlay or title.`);
|
|
249
|
+
}
|
|
250
|
+
if (layer.subject !== void 0) {
|
|
251
|
+
const p = effectSubjectProblem(layer.subject);
|
|
252
|
+
if (p) problems.push(`${where}: ${p}.`);
|
|
253
|
+
}
|
|
254
|
+
if (layer.role !== void 0 && !EFFECT_LAYER_ROLES.includes(layer.role)) problems.push(`${where}: role is one of ${EFFECT_LAYER_ROLES.join(", ")}.`);
|
|
255
|
+
if (layer.rect !== void 0 && !isRect(layer.rect)) problems.push(`${where}: rect must be { x, y, width, height } with width and height > 0.`);
|
|
256
|
+
for (const k of ["from", "until"]) if (layer[k] !== void 0 && !isLayerTime(layer[k])) problems.push(`${where}: ${k} is ms \u2265 0 or { shots: n } with n \u2265 1.`);
|
|
257
|
+
if (layer.opacity !== void 0 && !(typeof layer.opacity === "number" && layer.opacity >= 0 && layer.opacity <= 1)) problems.push(`${where}: opacity is 0\u20131.`);
|
|
258
|
+
if (layer.fade !== void 0 && !(typeof layer.fade === "number" && layer.fade >= 0)) problems.push(`${where}: fade is ms \u2265 0.`);
|
|
259
|
+
if (layer.values !== void 0 && !isObject(layer.values)) problems.push(`${where}: values must be an object of control values.`);
|
|
260
|
+
});
|
|
261
|
+
if (shot.transition !== void 0) {
|
|
262
|
+
const t = shot.transition;
|
|
263
|
+
if (i === 0) problems.push(`${at} has a transition, but there is no shot before it.`);
|
|
264
|
+
if (!isObject(t) || typeof t.effect !== "string" || !t.effect) problems.push(`${at}: transition needs an effect id.`);
|
|
265
|
+
else {
|
|
266
|
+
if (t.ms !== void 0 && !(typeof t.ms === "number" && t.ms > 0)) problems.push(`${at}: transition ms must be > 0.`);
|
|
267
|
+
const e = byId?.get(t.effect);
|
|
268
|
+
if (byId && !e) problems.push(`${at}: the provider has no transition "${t.effect}".`);
|
|
269
|
+
else if (e && !e.uses.includes("transition")) problems.push(`${at}: "${t.effect}" is not a transition (its uses are ${e.uses.join(", ")}).`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return problems;
|
|
274
|
+
}
|
|
275
|
+
function effectSequenceEffects(sequence) {
|
|
276
|
+
const out = /* @__PURE__ */ new Set();
|
|
277
|
+
for (const shot of sequence.shots) {
|
|
278
|
+
for (const l of shot.layers) if (l.effect) out.add(l.effect);
|
|
279
|
+
if (shot.transition) out.add(shot.transition.effect);
|
|
280
|
+
}
|
|
281
|
+
return [...out];
|
|
282
|
+
}
|
|
283
|
+
function effectSequenceSlots(sequence) {
|
|
284
|
+
const out = /* @__PURE__ */ new Set();
|
|
285
|
+
for (const shot of sequence.shots) for (const l of shot.layers) if (l.subject && "slot" in l.subject) out.add(l.subject.slot);
|
|
286
|
+
return [...out];
|
|
287
|
+
}
|
|
288
|
+
|
|
55
289
|
// src/effects/conformance.ts
|
|
56
290
|
var codeOf = (e) => isEffectError(e) ? e.code : e instanceof Error ? `untyped ${e.name}: ${e.message}` : String(e);
|
|
57
291
|
async function rejectsWith(code, run) {
|
|
@@ -191,6 +425,87 @@ async function checkCamera(provider, d, issues) {
|
|
|
191
425
|
}
|
|
192
426
|
}
|
|
193
427
|
}
|
|
428
|
+
var wrongValue = (slot) => slot.type === "number" ? "not a number" : slot.type === "text" ? 42 : slot.type === "color" ? "blue" : 42;
|
|
429
|
+
function checkSlot(templateId, slot, issues) {
|
|
430
|
+
const at = (message) => issues.push({ templateId, message: `slot ${JSON.stringify(slot?.name)}: ${message}` });
|
|
431
|
+
if (!slot || typeof slot.name !== "string" || !/^[A-Za-z][\w-]*$/.test(slot.name)) return at('needs a name of letters, digits, "-" and "_", starting with a letter');
|
|
432
|
+
if (!EFFECT_TEMPLATE_SLOT_TYPES.includes(slot.type)) return at(`type "${slot.type}" is not one of ${EFFECT_TEMPLATE_SLOT_TYPES.join(", ")}`);
|
|
433
|
+
if (typeof slot.label !== "string" || !slot.label) at("needs a label");
|
|
434
|
+
if (isSubjectTemplateSlot(slot)) {
|
|
435
|
+
if (typeof slot.hasDefault !== "boolean") at("a subject slot needs hasDefault (a boolean)");
|
|
436
|
+
if ("default" in slot) at("a subject slot describes hasDefault, not its default");
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const fallback = slot.default;
|
|
440
|
+
if (fallback === void 0) return at("needs a default");
|
|
441
|
+
const problem = templateValueProblem(slot, fallback);
|
|
442
|
+
if (problem) at(`its default does not fit: ${problem}`);
|
|
443
|
+
if (slot.type === "number" && slot.min !== void 0 && slot.max !== void 0 && slot.min > slot.max) at("min is above max");
|
|
444
|
+
}
|
|
445
|
+
async function checkTemplates(provider, list, issues) {
|
|
446
|
+
const top = (message) => issues.push({ message });
|
|
447
|
+
const templates = await provider.listTemplates();
|
|
448
|
+
if (!Array.isArray(templates)) return top("listTemplates did not return a list");
|
|
449
|
+
const seen = /* @__PURE__ */ new Set();
|
|
450
|
+
for (const t of templates) if (seen.has(t.id)) top(`duplicate template id "${t.id}"`);
|
|
451
|
+
else seen.add(t.id);
|
|
452
|
+
const unknownDescribe = await rejectsWith("unknown_template", () => provider.describeTemplate("__no_such_template__"));
|
|
453
|
+
if (unknownDescribe) top(`describeTemplate of an unknown id ${unknownDescribe}`);
|
|
454
|
+
const unknownFill = await rejectsWith("unknown_template", () => provider.instantiateTemplate("__no_such_template__"));
|
|
455
|
+
if (unknownFill) top(`instantiateTemplate of an unknown id ${unknownFill}`);
|
|
456
|
+
for (const summary of templates) {
|
|
457
|
+
const id = summary.id;
|
|
458
|
+
const at = (message) => issues.push({ templateId: id, message });
|
|
459
|
+
if (!id || typeof id !== "string") top("a template has no id");
|
|
460
|
+
if (!summary.name) at("template has no name");
|
|
461
|
+
if (!summary.description) at("template has no description");
|
|
462
|
+
if (!(Number.isInteger(summary.shots) && summary.shots >= 1)) at("shots must be a whole number \u2265 1");
|
|
463
|
+
if (summary.durationMs !== void 0 && !(summary.durationMs > 0)) at("durationMs must be > 0 when present");
|
|
464
|
+
let slots;
|
|
465
|
+
try {
|
|
466
|
+
const d = await provider.describeTemplate(id);
|
|
467
|
+
if (d.id !== id) at(`describeTemplate returned id "${d.id}"`);
|
|
468
|
+
if (!Array.isArray(d.slots)) {
|
|
469
|
+
at("describeTemplate gave no slots list");
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
slots = d.slots;
|
|
473
|
+
const names = /* @__PURE__ */ new Set();
|
|
474
|
+
for (const slot of slots) {
|
|
475
|
+
if (names.has(slot?.name)) at(`slot "${slot.name}" is declared twice`);
|
|
476
|
+
names.add(slot?.name);
|
|
477
|
+
checkSlot(id, slot, issues);
|
|
478
|
+
}
|
|
479
|
+
} catch (e) {
|
|
480
|
+
at(`describeTemplate failed: ${codeOf(e)}`);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
const result = await provider.instantiateTemplate(id);
|
|
485
|
+
if (!result.ok) at(`instantiateTemplate with the defaults failed: ${result.errors.join(" ")}`);
|
|
486
|
+
else {
|
|
487
|
+
for (const p of checkEffectSequence(result.sequence, list)) at(`with the defaults, ${p}`);
|
|
488
|
+
if (result.sequence?.shots?.length !== summary.shots) at(`listTemplates says ${summary.shots} shots; the sequence has ${result.sequence?.shots?.length}`);
|
|
489
|
+
if (!Array.isArray(result.openSlots) || !Array.isArray(result.notes)) at("instantiateTemplate needs openSlots and notes lists");
|
|
490
|
+
else {
|
|
491
|
+
const open = Array.isArray(result.sequence?.shots) ? effectSequenceSlots(result.sequence) : [];
|
|
492
|
+
if (open.some((s) => !result.openSlots.includes(s))) at(`openSlots ${JSON.stringify(result.openSlots)} misses slots the sequence leaves open (${open.join(", ")})`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch (e) {
|
|
496
|
+
at(`instantiateTemplate with the defaults failed: ${codeOf(e)}`);
|
|
497
|
+
}
|
|
498
|
+
const bad = { __no_such_slot__: "x", ...slots[0] ? { [slots[0].name]: wrongValue(slots[0]) } : {} };
|
|
499
|
+
try {
|
|
500
|
+
const result = await provider.instantiateTemplate(id, bad);
|
|
501
|
+
if (result.ok) at("instantiateTemplate accepted an unknown slot and a value of the wrong type");
|
|
502
|
+
else if (!result.errors.length || result.errors.some((e) => typeof e !== "string" || !e)) at("instantiateTemplate refused bad values without a sentence for each");
|
|
503
|
+
else if (slots[0] && result.errors.length < 2) at("instantiateTemplate should refuse every bad value at once (an unknown slot and a wrong type gave one error)");
|
|
504
|
+
} catch (e) {
|
|
505
|
+
at(`instantiateTemplate with bad values threw (${codeOf(e)}) instead of returning { ok: false, errors }`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
194
509
|
async function checkEffectProvider(provider, options = {}) {
|
|
195
510
|
const issues = [];
|
|
196
511
|
const top = (message) => issues.push({ message });
|
|
@@ -205,6 +520,12 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
205
520
|
if (caps?.supportsCamera !== void 0 && typeof caps.supportsCamera !== "boolean") top("capabilities.supportsCamera is not a boolean");
|
|
206
521
|
if (caps?.supportsCamera && typeof provider.cameraView !== "function") top("supportsCamera is set but cameraView is missing");
|
|
207
522
|
if (provider.sampleCameraMove && typeof provider.cameraView !== "function") top("sampleCameraMove is present but cameraView is missing");
|
|
523
|
+
if (caps?.supportsTemplates !== void 0 && typeof caps.supportsTemplates !== "boolean") top("capabilities.supportsTemplates is not a boolean");
|
|
524
|
+
const templateMethods = ["listTemplates", "describeTemplate", "instantiateTemplate"];
|
|
525
|
+
if (caps?.supportsTemplates) {
|
|
526
|
+
for (const m of templateMethods) if (typeof provider[m] !== "function") top(`supportsTemplates is set but ${m} is missing`);
|
|
527
|
+
}
|
|
528
|
+
if (!caps?.supportsTemplates && templateMethods.some((m) => typeof provider[m] === "function")) top("template methods are present but capabilities.supportsTemplates is not set");
|
|
208
529
|
const list = await provider.list();
|
|
209
530
|
const ids = /* @__PURE__ */ new Set();
|
|
210
531
|
for (const e of list) {
|
|
@@ -307,9 +628,125 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
307
628
|
}
|
|
308
629
|
}
|
|
309
630
|
}
|
|
631
|
+
if (caps?.supportsTemplates && options.templates !== false && templateMethods.every((m) => typeof provider[m] === "function")) await checkTemplates(provider, list, issues);
|
|
310
632
|
return issues;
|
|
311
633
|
}
|
|
312
634
|
|
|
635
|
+
// src/effects/fake-templates.ts
|
|
636
|
+
var PLACEHOLDER = /\{\{([A-Za-z][\w-]*)\}\}/g;
|
|
637
|
+
var WHOLE = /^\{\{([A-Za-z][\w-]*)\}\}$/;
|
|
638
|
+
var picture = (label, tint) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 90" width="160" height="90"><rect width="160" height="90" fill="${tint}"/><text x="80" y="50" text-anchor="middle" font-family="sans-serif" font-size="14" fill="#fff">${label}</text></svg>`;
|
|
639
|
+
var TEMPLATES = [
|
|
640
|
+
{
|
|
641
|
+
summary: { id: "fake.title-sequence", name: "Title sequence", description: "A gradient behind a title with a lower-third bar, then a crossfade to a closing shot with a caption.", tags: ["title", "intro"], shots: 2, durationMs: 4600 },
|
|
642
|
+
slots: [
|
|
643
|
+
{ name: "title", type: "text", label: "Title", default: "Hello", maxLength: 40 },
|
|
644
|
+
{ name: "subtitle", type: "text", label: "Closing caption", default: "See you soon", maxLength: 60 },
|
|
645
|
+
{ name: "accent", type: "color", label: "Accent colour", default: "#0ea5e9" },
|
|
646
|
+
{ name: "holdMs", type: "number", label: "Time on each shot (ms)", default: 2e3, min: 500, max: 1e4, integer: true }
|
|
647
|
+
],
|
|
648
|
+
sequence: {
|
|
649
|
+
name: "title-sequence",
|
|
650
|
+
title: "{{title}}",
|
|
651
|
+
stage: { width: 1280, height: 720 },
|
|
652
|
+
shots: [
|
|
653
|
+
{
|
|
654
|
+
hold: "{{holdMs}}",
|
|
655
|
+
title: "{{title}}",
|
|
656
|
+
layers: [
|
|
657
|
+
{ id: "backdrop", effect: "fake.gradient", role: "background", values: { from: "{{accent}}" }, until: { shots: 1 } },
|
|
658
|
+
{ id: "bar", effect: "fake.bar", role: "overlay", values: { color: "{{accent}}" }, from: 200 }
|
|
659
|
+
]
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
hold: "{{holdMs}}",
|
|
663
|
+
caption: "{{subtitle}}",
|
|
664
|
+
transition: { effect: "fake.crossfade", ms: 600 },
|
|
665
|
+
layers: [{ id: "closing", effect: "fake.gradient", role: "background", values: { from: "#111827", to: "{{accent}}" } }]
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
summary: { id: "fake.picture-reveal", name: "Picture reveal", description: "A tinted picture, then a crossfade to the picture in full colour with a logo over it and a caption.", tags: ["product", "reveal"], shots: 2, durationMs: 4800 },
|
|
672
|
+
slots: [
|
|
673
|
+
{ name: "picture", type: "image", label: "Picture", description: "The picture to reveal.", hasDefault: true, subjectDefault: picture("Picture", "#64748b") },
|
|
674
|
+
{ name: "logo", type: "image", label: "Logo", description: "A logo shown over the second shot; the host fills it when none is given.", hasDefault: false },
|
|
675
|
+
{ name: "caption", type: "text", label: "Caption", default: "Now in colour", maxLength: 50 },
|
|
676
|
+
{ name: "holdMs", type: "number", label: "Time on each shot (ms)", default: 2e3, min: 500, max: 1e4, integer: true }
|
|
677
|
+
],
|
|
678
|
+
sequence: {
|
|
679
|
+
name: "picture-reveal",
|
|
680
|
+
stage: { width: 1280, height: 720 },
|
|
681
|
+
shots: [
|
|
682
|
+
{ hold: "{{holdMs}}", layers: [{ id: "tinted", effect: "fake.tint", subject: { slot: "picture" }, values: { color: "#704214", amount: 0.6 } }] },
|
|
683
|
+
{
|
|
684
|
+
hold: "{{holdMs}}",
|
|
685
|
+
caption: "{{caption}}",
|
|
686
|
+
transition: { effect: "fake.crossfade", ms: 800 },
|
|
687
|
+
layers: [
|
|
688
|
+
{ id: "picture", subject: { slot: "picture" }, role: "background" },
|
|
689
|
+
{ id: "logo", subject: { slot: "logo" }, role: "overlay", rect: { x: 1040, y: 40, width: 200, height: 112 }, fade: 300 }
|
|
690
|
+
]
|
|
691
|
+
}
|
|
692
|
+
]
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
];
|
|
696
|
+
var describeOf = (t) => ({
|
|
697
|
+
...t.summary,
|
|
698
|
+
slots: t.slots.map(({ subjectDefault: _d, ...slot }) => slot)
|
|
699
|
+
});
|
|
700
|
+
function subjectOf(type, value) {
|
|
701
|
+
if (typeof value !== "string") return value;
|
|
702
|
+
if (value.trimStart().startsWith("<svg") || type === "svg") return { svg: value };
|
|
703
|
+
return type === "video" ? { video: value } : { image: value };
|
|
704
|
+
}
|
|
705
|
+
function fill(t, values) {
|
|
706
|
+
const slots = new Map(t.slots.map((s) => [s.name, s]));
|
|
707
|
+
const valueOf = (name) => {
|
|
708
|
+
const s = slots.get(name);
|
|
709
|
+
return values[name] ?? (isSubjectTemplateSlot(s) ? s.subjectDefault : s.default);
|
|
710
|
+
};
|
|
711
|
+
const walk = (v, key) => {
|
|
712
|
+
if (typeof v === "string") {
|
|
713
|
+
const whole = WHOLE.exec(v);
|
|
714
|
+
if (whole && slots.has(whole[1])) return valueOf(whole[1]);
|
|
715
|
+
return v.replace(PLACEHOLDER, (m, name) => slots.has(name) ? String(valueOf(name)) : m);
|
|
716
|
+
}
|
|
717
|
+
if (Array.isArray(v)) return v.map((x) => walk(x));
|
|
718
|
+
if (!v || typeof v !== "object") return v;
|
|
719
|
+
if (key === "subject" && typeof v.slot === "string") {
|
|
720
|
+
const s = slots.get(v.slot);
|
|
721
|
+
const value = s ? valueOf(s.name) : void 0;
|
|
722
|
+
return s && value !== void 0 ? subjectOf(s.type, value) : v;
|
|
723
|
+
}
|
|
724
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x, k)]));
|
|
725
|
+
};
|
|
726
|
+
return walk(t.sequence);
|
|
727
|
+
}
|
|
728
|
+
function fakeTemplateMethods() {
|
|
729
|
+
const byId = new Map(TEMPLATES.map((t) => [t.summary.id, t]));
|
|
730
|
+
const get = (id) => {
|
|
731
|
+
const t = byId.get(id);
|
|
732
|
+
if (!t) throw new UnknownTemplateError(id, "the fake provider");
|
|
733
|
+
return t;
|
|
734
|
+
};
|
|
735
|
+
return {
|
|
736
|
+
listTemplates: (options) => searchTemplateSummaries(TEMPLATES.map((t) => t.summary), options).map((s) => structuredClone(s)),
|
|
737
|
+
describeTemplate: (id) => structuredClone(describeOf(get(id))),
|
|
738
|
+
instantiateTemplate(id, values = {}, options = {}) {
|
|
739
|
+
const t = get(id);
|
|
740
|
+
const errors = checkTemplateValues(describeOf(t).slots, values);
|
|
741
|
+
if (options.name !== void 0 && (typeof options.name !== "string" || !EFFECT_SEQUENCE_NAME.test(options.name))) errors.push(`The sequence name is lowercase words and dashes (like "${t.summary.id.replace(/^fake\./, "")}").`);
|
|
742
|
+
if (errors.length) return { ok: false, errors };
|
|
743
|
+
const sequence = fill(t, values);
|
|
744
|
+
if (options.name) sequence.name = options.name;
|
|
745
|
+
return { ok: true, sequence, openSlots: effectSequenceSlots(sequence), notes: [] };
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
313
750
|
// src/effects/fake.ts
|
|
314
751
|
var HEX = "^#[0-9a-fA-F]{6}$";
|
|
315
752
|
var colour = (description) => ({ type: "string", pattern: HEX, description });
|
|
@@ -689,6 +1126,7 @@ function createFakeEffectProvider(options = {}) {
|
|
|
689
1126
|
supportsVector: true,
|
|
690
1127
|
supportsAuthoring,
|
|
691
1128
|
...options.camera ? { supportsCamera: true } : {},
|
|
1129
|
+
...options.templates ? { supportsTemplates: true } : {},
|
|
692
1130
|
maxSize: 8192,
|
|
693
1131
|
...options.capabilities
|
|
694
1132
|
};
|
|
@@ -775,6 +1213,7 @@ function createFakeEffectProvider(options = {}) {
|
|
|
775
1213
|
return sampleCameraViews((u) => camera(id, { from, to, u, ms, stage }, values), { ms, fps, cut: d.camera?.cut });
|
|
776
1214
|
}
|
|
777
1215
|
} : {},
|
|
1216
|
+
...options.templates ? fakeTemplateMethods() : {},
|
|
778
1217
|
...supportsAuthoring ? {
|
|
779
1218
|
openAuthoring: (from) => createEffectAuthoringSession(from, { provider })
|
|
780
1219
|
} : {}
|
|
@@ -1304,6 +1743,12 @@ export {
|
|
|
1304
1743
|
CAMERA_MOVE_ROLES,
|
|
1305
1744
|
EFFECT_ERROR_CODES,
|
|
1306
1745
|
EFFECT_KINDS,
|
|
1746
|
+
EFFECT_LAYER_ROLES,
|
|
1747
|
+
EFFECT_LAYER_USES,
|
|
1748
|
+
EFFECT_SEQUENCE_NAME,
|
|
1749
|
+
EFFECT_SUBJECT_KINDS,
|
|
1750
|
+
EFFECT_TEMPLATE_SLOT_TYPES,
|
|
1751
|
+
EFFECT_TRANSITION_MS,
|
|
1307
1752
|
EFFECT_USES,
|
|
1308
1753
|
EFFECT_USE_LABELS,
|
|
1309
1754
|
EffectControlError,
|
|
@@ -1314,6 +1759,7 @@ export {
|
|
|
1314
1759
|
EffectsSlot,
|
|
1315
1760
|
NoEffectsProviderError,
|
|
1316
1761
|
UnknownEffectError,
|
|
1762
|
+
UnknownTemplateError,
|
|
1317
1763
|
applyEffectMask,
|
|
1318
1764
|
cameraBoxOfView,
|
|
1319
1765
|
cameraMoveField,
|
|
@@ -1322,6 +1768,8 @@ export {
|
|
|
1322
1768
|
cameraMovesFor,
|
|
1323
1769
|
cameraViewOfBox,
|
|
1324
1770
|
checkEffectProvider,
|
|
1771
|
+
checkEffectSequence,
|
|
1772
|
+
checkTemplateValues,
|
|
1325
1773
|
countEffectTags,
|
|
1326
1774
|
createEffectAuthoringSession,
|
|
1327
1775
|
createEffectAuthoringToolProvider,
|
|
@@ -1331,10 +1779,17 @@ export {
|
|
|
1331
1779
|
effectControlField,
|
|
1332
1780
|
effectFieldValue,
|
|
1333
1781
|
effectFontFamilies,
|
|
1782
|
+
effectLayerInput,
|
|
1783
|
+
effectLayerOffered,
|
|
1784
|
+
effectLayerRole,
|
|
1334
1785
|
effectOffered,
|
|
1335
1786
|
effectParameterFields,
|
|
1336
1787
|
effectSampleInputs,
|
|
1337
1788
|
effectSamplePicture,
|
|
1789
|
+
effectSequenceEffects,
|
|
1790
|
+
effectSequenceSlots,
|
|
1791
|
+
effectSubjectKind,
|
|
1792
|
+
effectSubjectProblem,
|
|
1338
1793
|
effectValueErrors,
|
|
1339
1794
|
encodeEffectFloat,
|
|
1340
1795
|
groupEffectsByCategory,
|
|
@@ -1343,6 +1798,7 @@ export {
|
|
|
1343
1798
|
isEffectError,
|
|
1344
1799
|
isEffectFontControl,
|
|
1345
1800
|
isEffectPixels,
|
|
1801
|
+
isSubjectTemplateSlot,
|
|
1346
1802
|
linearToSrgb,
|
|
1347
1803
|
normaliseEffectTag,
|
|
1348
1804
|
renderEffectThumbnail,
|
|
@@ -1351,5 +1807,12 @@ export {
|
|
|
1351
1807
|
sampleCameraViews,
|
|
1352
1808
|
sampleEffectCameraMove,
|
|
1353
1809
|
searchEffectSummaries,
|
|
1354
|
-
|
|
1810
|
+
searchTemplateSummaries,
|
|
1811
|
+
srgbToLinear,
|
|
1812
|
+
templateDefaultValues,
|
|
1813
|
+
templateSlotSchema,
|
|
1814
|
+
templateSlotSummary,
|
|
1815
|
+
templateSlotTakes,
|
|
1816
|
+
templateValueProblem,
|
|
1817
|
+
templateValuesSchema
|
|
1355
1818
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for sequence templates and the sequences they fill to (see `EffectSequence`): the
|
|
3
|
+
* values a template takes as a JSON Schema, checking values before asking the provider, checking
|
|
4
|
+
* a sequence before loading it, and which input a layer's subject feeds. Dependency-free.
|
|
5
|
+
*/
|
|
6
|
+
import type { JsonSchema } from '../tools/schema.cjs';
|
|
7
|
+
import { type EffectDescription, type EffectLayer, type EffectLayerRole, type EffectSequence, type EffectSubjectKind, type EffectSummary, type EffectTemplateSearchOptions, type EffectTemplateSlot, type EffectTemplateSummary } from './types.cjs';
|
|
8
|
+
export declare const EFFECT_SEQUENCE_NAME: RegExp;
|
|
9
|
+
/** True for a subject slot (`image`, `video`, `svg`). */
|
|
10
|
+
export declare const isSubjectTemplateSlot: (slot: Pick<EffectTemplateSlot, "type">) => boolean;
|
|
11
|
+
/** The kind of a subject (`image`, `video`, `svg`, `text`, `slot`), or undefined when it is none of them. */
|
|
12
|
+
export declare function effectSubjectKind(subject: unknown): EffectSubjectKind | undefined;
|
|
13
|
+
/** What a slot takes, in a few words ("words (up to 40 characters)", "a colour like #FF5A36"). */
|
|
14
|
+
export declare function templateSlotTakes(slot: EffectTemplateSlot): string;
|
|
15
|
+
/** The problem with one value for a slot, as a sentence, or undefined when it fits. */
|
|
16
|
+
export declare function templateValueProblem(slot: EffectTemplateSlot, value: unknown): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Every problem with `values` for a template's slots, as sentences: unknown slots, wrong types,
|
|
19
|
+
* out-of-range numbers, text too long. Empty when they fit. Slots left out take their defaults.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkTemplateValues(slots: readonly EffectTemplateSlot[], values: unknown): string[];
|
|
22
|
+
/** One slot's value as a JSON Schema. */
|
|
23
|
+
export declare function templateSlotSchema(slot: EffectTemplateSlot): JsonSchema;
|
|
24
|
+
/** The values a template takes as a strict JSON Schema (every slot optional; `additionalProperties: false`). */
|
|
25
|
+
export declare function templateValuesSchema(slots: readonly EffectTemplateSlot[]): JsonSchema & {
|
|
26
|
+
type: 'object';
|
|
27
|
+
};
|
|
28
|
+
/** The defaults of a template's text, colour and number slots: an example `values` object to change. */
|
|
29
|
+
export declare function templateDefaultValues(slots: readonly EffectTemplateSlot[]): Record<string, string | number>;
|
|
30
|
+
/** One line on a slot: its name, type, what it takes and its default. */
|
|
31
|
+
export declare function templateSlotSummary(slot: EffectTemplateSlot): string;
|
|
32
|
+
/** Templates matching every word of `query` (in name, id, tags or description) and every tag, best first. */
|
|
33
|
+
export declare function searchTemplateSummaries<T extends EffectTemplateSummary>(catalog: readonly T[], options?: EffectTemplateSearchOptions): T[];
|
|
34
|
+
/** Where a layer sits: its `role`, else `subject` when it has a `rect`, else `background`. */
|
|
35
|
+
export declare const effectLayerRole: (layer: Pick<EffectLayer, "role" | "rect">) => EffectLayerRole;
|
|
36
|
+
/**
|
|
37
|
+
* The image input a layer's subject feeds: `source` when the effect has it, else its first image
|
|
38
|
+
* input; undefined when it takes no picture (a generator: then the layer cannot have a subject).
|
|
39
|
+
*/
|
|
40
|
+
export declare function effectLayerInput(description: Pick<EffectDescription, 'inputs'>): string | undefined;
|
|
41
|
+
/** The uses a layer's effect may have (a transition belongs on `EffectSequenceShot.transition`, a camera move on `camera`). */
|
|
42
|
+
export declare const EFFECT_LAYER_USES: readonly ["generator", "filter", "overlay", "title"];
|
|
43
|
+
/** True for an effect a layer can show. */
|
|
44
|
+
export declare const effectLayerOffered: (e: Pick<EffectSummary, "kind" | "uses">) => boolean;
|
|
45
|
+
/** Problems with one subject, or undefined when it is sound. */
|
|
46
|
+
export declare function effectSubjectProblem(subject: unknown): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Every problem with a sequence's shape, as sentences ("shot 2, layer 1: …"). With `catalog`, also
|
|
49
|
+
* effect ids it does not list, layer effects that cannot be layers and transitions without the
|
|
50
|
+
* use `transition`. Empty when the sequence is sound.
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkEffectSequence(sequence: unknown, catalog?: readonly Pick<EffectSummary, 'id' | 'kind' | 'uses'>[]): string[];
|
|
53
|
+
/** Every effect id a sequence names (layers and transitions), each once. */
|
|
54
|
+
export declare function effectSequenceEffects(sequence: EffectSequence): string[];
|
|
55
|
+
/** Subject slots a sequence leaves for the host (`{ slot }` subjects), each once. */
|
|
56
|
+
export declare function effectSequenceSlots(sequence: EffectSequence): string[];
|