@cutcraft/kit 0.1.0 → 0.2.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/README.md +40 -56
- package/dist/{index-CLhXqOpF.d.mts → index-BYYpzZuS.d.mts} +351 -327
- package/dist/index.d.mts +4260 -13987
- package/dist/index.mjs +1 -1
- package/dist/source/index-public.d.mts +57 -57
- package/dist/source/index-public.mjs +214 -50
- package/dist/{src-BsBU2JLf.mjs → src-CcFjz_DV.mjs} +2241 -2069
- package/package.json +16 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { GRAPHIC_KINDS, GRAPHIC_KIND_NAMES, graphicControls, hash, random } from "@cutcraft/graphics";
|
|
2
|
+
import { z } from "zod";
|
|
3
3
|
import { Children, Fragment, createElement, isValidElement } from "react";
|
|
4
4
|
// \0rolldown/runtime.js
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -12,429 +12,151 @@ var __exportAll = (all, no_symbols) => {
|
|
|
12
12
|
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
13
|
return target;
|
|
14
14
|
};
|
|
15
|
-
// src/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
15
|
+
// src/patches/log.ts
|
|
16
|
+
var Log = class {
|
|
17
|
+
list = [];
|
|
18
|
+
reduced = /* @__PURE__ */ new Map();
|
|
19
|
+
v = 0;
|
|
20
|
+
isSource;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.isSource = options.isSource ?? (() => false);
|
|
23
|
+
}
|
|
24
|
+
get entries() {
|
|
25
|
+
return this.list;
|
|
26
|
+
}
|
|
27
|
+
get net() {
|
|
28
|
+
return this.reduced;
|
|
29
|
+
}
|
|
30
|
+
get version() {
|
|
31
|
+
return this.v;
|
|
32
|
+
}
|
|
33
|
+
append(patch) {
|
|
34
|
+
this.list.push(patch);
|
|
35
|
+
reduceInto(this.reduced, patch, this.isSource);
|
|
36
|
+
this.v += 1;
|
|
37
|
+
}
|
|
38
|
+
mark() {
|
|
39
|
+
return this.list.length;
|
|
40
|
+
}
|
|
41
|
+
truncate(mark) {
|
|
42
|
+
if (mark >= this.list.length) return;
|
|
43
|
+
this.list.length = Math.max(0, mark);
|
|
44
|
+
this.reduced.clear();
|
|
45
|
+
for (const p of this.list) reduceInto(this.reduced, p, this.isSource);
|
|
46
|
+
this.v += 1;
|
|
47
|
+
}
|
|
48
|
+
clear() {
|
|
49
|
+
this.list.length = 0;
|
|
50
|
+
this.reduced.clear();
|
|
51
|
+
this.v += 1;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function createPatchLog(options = {}) {
|
|
55
|
+
return new Log(options);
|
|
56
|
+
}
|
|
57
|
+
function entry(net, id) {
|
|
58
|
+
let e = net.get(id);
|
|
59
|
+
if (!e) {
|
|
60
|
+
e = {
|
|
61
|
+
id,
|
|
62
|
+
presence: "present",
|
|
63
|
+
props: /* @__PURE__ */ new Map(),
|
|
64
|
+
bake: false
|
|
65
|
+
};
|
|
66
|
+
net.set(id, e);
|
|
67
|
+
}
|
|
68
|
+
return e;
|
|
69
|
+
}
|
|
70
|
+
/** Keep `net` reduced: last write wins per prop, structural ops collapse, removal of an insert vanishes. */
|
|
71
|
+
function reduceInto(net, patch, isSource = () => false) {
|
|
72
|
+
switch (patch.op) {
|
|
73
|
+
case "set": {
|
|
74
|
+
const e = entry(net, patch.id);
|
|
75
|
+
if (e.presence === "removed") throw new Error(`patch set on removed element ${patch.id}`);
|
|
76
|
+
e.props.set(patch.prop, patch.value);
|
|
77
|
+
return;
|
|
51
78
|
}
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
case "text": {
|
|
80
|
+
const e = entry(net, patch.id);
|
|
81
|
+
if (e.presence === "removed") throw new Error(`patch text on removed element ${patch.id}`);
|
|
82
|
+
e.text = patch.text;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
case "move": {
|
|
86
|
+
const e = entry(net, patch.id);
|
|
87
|
+
if (e.presence === "removed") throw new Error(`patch move on removed element ${patch.id}`);
|
|
88
|
+
e.place = {
|
|
89
|
+
parent: patch.parent,
|
|
90
|
+
after: patch.after
|
|
91
|
+
};
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
case "remove": {
|
|
95
|
+
if (net.get(patch.id)?.presence === "inserted" && !isSource(patch.id)) {
|
|
96
|
+
net.delete(patch.id);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const r = entry(net, patch.id);
|
|
100
|
+
r.presence = "removed";
|
|
101
|
+
r.props.clear();
|
|
102
|
+
delete r.text;
|
|
103
|
+
delete r.place;
|
|
104
|
+
delete r.node;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
case "insert": {
|
|
108
|
+
const e = entry(net, patch.id);
|
|
109
|
+
e.presence = e.presence === "removed" && e.node !== void 0 && nodesEqual(e.node, patch.node) ? "present" : "inserted";
|
|
110
|
+
e.props.clear();
|
|
111
|
+
e.node = patch.node;
|
|
112
|
+
e.place = {
|
|
113
|
+
parent: patch.parent,
|
|
114
|
+
after: patch.after
|
|
115
|
+
};
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
case "bake":
|
|
119
|
+
entry(net, patch.id).bake = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
54
122
|
}
|
|
55
|
-
function
|
|
56
|
-
return
|
|
123
|
+
function nodesEqual(a, b) {
|
|
124
|
+
return a.id === b.id && a.kind === b.kind && stableJson(a.props) === stableJson(b.props) && a.text === b.text && a.children.length === b.children.length && a.children.every((c, i) => nodesEqual(c, b.children[i]));
|
|
57
125
|
}
|
|
58
|
-
function
|
|
59
|
-
return
|
|
126
|
+
function stableJson(value) {
|
|
127
|
+
return JSON.stringify(value, (_k, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a < b ? -1 : 1)) : v);
|
|
60
128
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
129
|
+
function asJson(value) {
|
|
130
|
+
if (value === void 0) return void 0;
|
|
131
|
+
return JSON.parse(JSON.stringify(value));
|
|
132
|
+
}
|
|
133
|
+
// src/jsx/capabilities.ts
|
|
134
|
+
const none = {
|
|
135
|
+
timed: false,
|
|
136
|
+
visual: false,
|
|
137
|
+
audio: false,
|
|
138
|
+
trimSource: false,
|
|
139
|
+
transformable: false,
|
|
140
|
+
container: false,
|
|
141
|
+
childKinds: [],
|
|
142
|
+
placementScope: "none"
|
|
143
|
+
};
|
|
144
|
+
const with_ = (over) => ({
|
|
145
|
+
...none,
|
|
146
|
+
...over
|
|
147
|
+
});
|
|
148
|
+
const CLIP_CHILDREN = [
|
|
149
|
+
"effect",
|
|
150
|
+
"colorGrade",
|
|
151
|
+
"keyframe",
|
|
152
|
+
"mask",
|
|
153
|
+
"speedKey",
|
|
154
|
+
"freeze"
|
|
76
155
|
];
|
|
77
|
-
const
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"overlay",
|
|
82
|
-
"darken",
|
|
83
|
-
"lighten",
|
|
84
|
-
"color-dodge",
|
|
85
|
-
"color-burn",
|
|
86
|
-
"hard-light",
|
|
87
|
-
"soft-light",
|
|
88
|
-
"difference",
|
|
89
|
-
"exclusion",
|
|
90
|
-
"hue",
|
|
91
|
-
"saturation",
|
|
92
|
-
"color",
|
|
93
|
-
"luminosity",
|
|
94
|
-
"add",
|
|
95
|
-
"linear-dodge"
|
|
96
|
-
];
|
|
97
|
-
const EASINGS = [
|
|
98
|
-
"linear",
|
|
99
|
-
"ease",
|
|
100
|
-
"ease-in",
|
|
101
|
-
"ease-out",
|
|
102
|
-
"ease-in-out",
|
|
103
|
-
"hold",
|
|
104
|
-
"bezier",
|
|
105
|
-
"smoothstep",
|
|
106
|
-
"smootherstep",
|
|
107
|
-
"snappy",
|
|
108
|
-
"smooth",
|
|
109
|
-
"spring",
|
|
110
|
-
"easeInQuad",
|
|
111
|
-
"easeOutQuad",
|
|
112
|
-
"easeInOutQuad",
|
|
113
|
-
"easeInCubic",
|
|
114
|
-
"easeOutCubic",
|
|
115
|
-
"easeInOutCubic",
|
|
116
|
-
"easeInQuart",
|
|
117
|
-
"easeOutQuart",
|
|
118
|
-
"easeInOutQuart",
|
|
119
|
-
"easeInQuint",
|
|
120
|
-
"easeOutQuint",
|
|
121
|
-
"easeInOutQuint",
|
|
122
|
-
"easeInSine",
|
|
123
|
-
"easeOutSine",
|
|
124
|
-
"easeInOutSine",
|
|
125
|
-
"easeInExpo",
|
|
126
|
-
"easeOutExpo",
|
|
127
|
-
"easeInOutExpo",
|
|
128
|
-
"easeInCirc",
|
|
129
|
-
"easeOutCirc",
|
|
130
|
-
"easeInOutCirc",
|
|
131
|
-
"easeInBack",
|
|
132
|
-
"easeOutBack",
|
|
133
|
-
"easeInOutBack",
|
|
134
|
-
"easeInBackStrong",
|
|
135
|
-
"easeOutBackStrong",
|
|
136
|
-
"easeInOutBackStrong",
|
|
137
|
-
"easeInElastic",
|
|
138
|
-
"easeOutElastic",
|
|
139
|
-
"easeInOutElastic",
|
|
140
|
-
"easeInElasticSoft",
|
|
141
|
-
"easeOutElasticSoft",
|
|
142
|
-
"easeInOutElasticSoft",
|
|
143
|
-
"easeInBounce",
|
|
144
|
-
"easeOutBounce",
|
|
145
|
-
"easeInOutBounce",
|
|
146
|
-
"easeInBounceSmall",
|
|
147
|
-
"easeOutBounceSmall",
|
|
148
|
-
"easeInOutBounceSmall"
|
|
149
|
-
];
|
|
150
|
-
const MASK_EASINGS = [
|
|
151
|
-
"linear",
|
|
152
|
-
"ease-in",
|
|
153
|
-
"ease-out",
|
|
154
|
-
"ease-in-out"
|
|
155
|
-
];
|
|
156
|
-
const TRANSITION_TYPES = [
|
|
157
|
-
"crossfade",
|
|
158
|
-
"dipToBlack",
|
|
159
|
-
"dipToWhite",
|
|
160
|
-
"wipe",
|
|
161
|
-
"slide",
|
|
162
|
-
"zoom",
|
|
163
|
-
"push",
|
|
164
|
-
"circleReveal",
|
|
165
|
-
"blur",
|
|
166
|
-
"whipPan",
|
|
167
|
-
"radialWipe",
|
|
168
|
-
"pixelate",
|
|
169
|
-
"glitch",
|
|
170
|
-
"blinds",
|
|
171
|
-
"diamondReveal",
|
|
172
|
-
"spin",
|
|
173
|
-
"flip",
|
|
174
|
-
"splitReveal",
|
|
175
|
-
"flash",
|
|
176
|
-
"filmBurn",
|
|
177
|
-
"mosaic",
|
|
178
|
-
"ripple",
|
|
179
|
-
"pageTurn",
|
|
180
|
-
"colorSplit"
|
|
181
|
-
];
|
|
182
|
-
const VIDEO_EFFECT_TYPES = [
|
|
183
|
-
"brightness",
|
|
184
|
-
"contrast",
|
|
185
|
-
"saturation",
|
|
186
|
-
"hue",
|
|
187
|
-
"temperature",
|
|
188
|
-
"tint",
|
|
189
|
-
"blur",
|
|
190
|
-
"sharpen",
|
|
191
|
-
"vignette",
|
|
192
|
-
"grain",
|
|
193
|
-
"shadow",
|
|
194
|
-
"glow",
|
|
195
|
-
"motion-blur",
|
|
196
|
-
"radial-blur",
|
|
197
|
-
"chromatic-aberration",
|
|
198
|
-
"grayscale",
|
|
199
|
-
"sepia",
|
|
200
|
-
"invert",
|
|
201
|
-
"levels",
|
|
202
|
-
"hue-saturation"
|
|
203
|
-
];
|
|
204
|
-
const AUDIO_EFFECT_TYPES = [
|
|
205
|
-
"gain",
|
|
206
|
-
"pan",
|
|
207
|
-
"eq",
|
|
208
|
-
"compressor",
|
|
209
|
-
"reverb",
|
|
210
|
-
"delay",
|
|
211
|
-
"noiseReduction",
|
|
212
|
-
"fadeIn",
|
|
213
|
-
"fadeOut"
|
|
214
|
-
];
|
|
215
|
-
const TEXT_ANIMATION_PRESETS = [
|
|
216
|
-
"none",
|
|
217
|
-
"typewriter",
|
|
218
|
-
"fade",
|
|
219
|
-
"slide-up",
|
|
220
|
-
"slide-down",
|
|
221
|
-
"slide-left",
|
|
222
|
-
"slide-right",
|
|
223
|
-
"scale",
|
|
224
|
-
"blur",
|
|
225
|
-
"bounce",
|
|
226
|
-
"rotate",
|
|
227
|
-
"pop",
|
|
228
|
-
"flip",
|
|
229
|
-
"split",
|
|
230
|
-
"word-by-word",
|
|
231
|
-
"rise",
|
|
232
|
-
"drop",
|
|
233
|
-
"elastic",
|
|
234
|
-
"swing",
|
|
235
|
-
"zoom-blur",
|
|
236
|
-
"cascade",
|
|
237
|
-
"wave",
|
|
238
|
-
"shake",
|
|
239
|
-
"rainbow",
|
|
240
|
-
"glitch"
|
|
241
|
-
];
|
|
242
|
-
const CAPTION_ANIMATIONS = [
|
|
243
|
-
"none",
|
|
244
|
-
"word-highlight",
|
|
245
|
-
"word-by-word",
|
|
246
|
-
"karaoke",
|
|
247
|
-
"bounce",
|
|
248
|
-
"typewriter"
|
|
249
|
-
];
|
|
250
|
-
const GRAPHIC_ANIMATIONS = [
|
|
251
|
-
"none",
|
|
252
|
-
"fade",
|
|
253
|
-
"slide-left",
|
|
254
|
-
"slide-right",
|
|
255
|
-
"slide-up",
|
|
256
|
-
"slide-down",
|
|
257
|
-
"scale",
|
|
258
|
-
"rotate",
|
|
259
|
-
"bounce",
|
|
260
|
-
"pop",
|
|
261
|
-
"draw",
|
|
262
|
-
"wipe-left",
|
|
263
|
-
"wipe-right",
|
|
264
|
-
"wipe-up",
|
|
265
|
-
"wipe-down",
|
|
266
|
-
"reveal-center",
|
|
267
|
-
"reveal-edges",
|
|
268
|
-
"elastic",
|
|
269
|
-
"flip-horizontal",
|
|
270
|
-
"flip-vertical"
|
|
271
|
-
];
|
|
272
|
-
const EMPHASIS_ANIMATIONS = [
|
|
273
|
-
"none",
|
|
274
|
-
"pulse",
|
|
275
|
-
"shake",
|
|
276
|
-
"bounce",
|
|
277
|
-
"float",
|
|
278
|
-
"spin",
|
|
279
|
-
"flash",
|
|
280
|
-
"heartbeat",
|
|
281
|
-
"swing",
|
|
282
|
-
"wobble",
|
|
283
|
-
"jello",
|
|
284
|
-
"rubber-band",
|
|
285
|
-
"tada",
|
|
286
|
-
"vibrate",
|
|
287
|
-
"flicker",
|
|
288
|
-
"glow",
|
|
289
|
-
"breathe",
|
|
290
|
-
"wave",
|
|
291
|
-
"tilt",
|
|
292
|
-
"zoom-pulse",
|
|
293
|
-
"focus-zoom",
|
|
294
|
-
"pan-left",
|
|
295
|
-
"pan-right",
|
|
296
|
-
"pan-up",
|
|
297
|
-
"pan-down",
|
|
298
|
-
"ken-burns"
|
|
299
|
-
];
|
|
300
|
-
const SHAPE_TYPES = [
|
|
301
|
-
"rectangle",
|
|
302
|
-
"circle",
|
|
303
|
-
"ellipse",
|
|
304
|
-
"triangle",
|
|
305
|
-
"arrow",
|
|
306
|
-
"line",
|
|
307
|
-
"polygon",
|
|
308
|
-
"path",
|
|
309
|
-
"star"
|
|
310
|
-
];
|
|
311
|
-
const KEYFRAME_PROPERTIES = [
|
|
312
|
-
"transform.position.x",
|
|
313
|
-
"transform.position.y",
|
|
314
|
-
"transform.position.z",
|
|
315
|
-
"transform.scale.x",
|
|
316
|
-
"transform.scale.y",
|
|
317
|
-
"transform.rotation",
|
|
318
|
-
"transform.rotation.x",
|
|
319
|
-
"transform.rotation.y",
|
|
320
|
-
"transform.perspective",
|
|
321
|
-
"transform.opacity",
|
|
322
|
-
"transform.anchor.x",
|
|
323
|
-
"transform.anchor.y",
|
|
324
|
-
"transform.borderRadius",
|
|
325
|
-
"transform.crop.x",
|
|
326
|
-
"transform.crop.y",
|
|
327
|
-
"transform.crop.width",
|
|
328
|
-
"transform.crop.height",
|
|
329
|
-
"audio.volume",
|
|
330
|
-
"audio.pan",
|
|
331
|
-
"colorGrade.temperature",
|
|
332
|
-
"colorGrade.tint",
|
|
333
|
-
"path"
|
|
334
|
-
];
|
|
335
|
-
/** `effect.<effectId>.<param>` is also a valid keyframe property; see `isKeyframeProperty`. */
|
|
336
|
-
const KEYFRAME_PROPERTY_PATTERN = /^(effect\.[A-Za-z0-9_-]+\.[A-Za-z0-9_]+)$/;
|
|
337
|
-
function isKeyframeProperty(value) {
|
|
338
|
-
return KEYFRAME_PROPERTIES.includes(value) || KEYFRAME_PROPERTY_PATTERN.test(value);
|
|
339
|
-
}
|
|
340
|
-
const CAPTION_POSITIONS = [
|
|
341
|
-
"top",
|
|
342
|
-
"center",
|
|
343
|
-
"bottom"
|
|
344
|
-
];
|
|
345
|
-
const TEXT_ALIGNS = [
|
|
346
|
-
"left",
|
|
347
|
-
"center",
|
|
348
|
-
"right",
|
|
349
|
-
"justify"
|
|
350
|
-
];
|
|
351
|
-
const TEXT_VALIGNS = [
|
|
352
|
-
"top",
|
|
353
|
-
"middle",
|
|
354
|
-
"bottom"
|
|
355
|
-
];
|
|
356
|
-
const TEXT_DECORATIONS = [
|
|
357
|
-
"none",
|
|
358
|
-
"underline",
|
|
359
|
-
"line-through",
|
|
360
|
-
"overline"
|
|
361
|
-
];
|
|
362
|
-
const ANIMATION_UNITS = [
|
|
363
|
-
"character",
|
|
364
|
-
"word",
|
|
365
|
-
"line"
|
|
366
|
-
];
|
|
367
|
-
const MASK_TYPES = [
|
|
368
|
-
"shape",
|
|
369
|
-
"drawn",
|
|
370
|
-
"matte"
|
|
371
|
-
];
|
|
372
|
-
const MATTE_CHANNELS = [
|
|
373
|
-
"alpha",
|
|
374
|
-
"luminance",
|
|
375
|
-
"bounds"
|
|
376
|
-
];
|
|
377
|
-
const INTERPOLATION_QUALITIES = [
|
|
378
|
-
"low",
|
|
379
|
-
"medium",
|
|
380
|
-
"high"
|
|
381
|
-
];
|
|
382
|
-
const STABILIZE_CROP_MODES = ["auto", "none"];
|
|
383
|
-
const SVG_COLOR_MODES = [
|
|
384
|
-
"none",
|
|
385
|
-
"tint",
|
|
386
|
-
"replace"
|
|
387
|
-
];
|
|
388
|
-
const AUTOMATION_LANES = ["volume", "pan"];
|
|
389
|
-
const TRACK_FLAGS = [
|
|
390
|
-
"locked",
|
|
391
|
-
"hidden",
|
|
392
|
-
"muted",
|
|
393
|
-
"solo"
|
|
394
|
-
];
|
|
395
|
-
const zTrackLayout = z.enum(TRACK_LAYOUTS);
|
|
396
|
-
const zTrackRole = z.enum(TRACK_ROLES);
|
|
397
|
-
const zFitMode = z.enum(FIT_MODES);
|
|
398
|
-
const zBlendMode = z.enum(BLEND_MODES);
|
|
399
|
-
const zEasing = z.enum(EASINGS);
|
|
400
|
-
const zMaskEasing = z.enum(MASK_EASINGS);
|
|
401
|
-
const zTransitionType = z.enum(TRANSITION_TYPES);
|
|
402
|
-
const zVideoEffectType = z.enum(VIDEO_EFFECT_TYPES);
|
|
403
|
-
const zAudioEffectType = z.enum(AUDIO_EFFECT_TYPES);
|
|
404
|
-
const zTextAnimationPreset = z.enum(TEXT_ANIMATION_PRESETS);
|
|
405
|
-
const zCaptionAnimation = z.enum(CAPTION_ANIMATIONS);
|
|
406
|
-
const zGraphicAnimation = z.enum(GRAPHIC_ANIMATIONS);
|
|
407
|
-
const zEmphasisAnimation = z.enum(EMPHASIS_ANIMATIONS);
|
|
408
|
-
const zShapeType = z.enum(SHAPE_TYPES);
|
|
409
|
-
const zKeyframeProperty = z.string().refine(isKeyframeProperty, "unknown keyframe property");
|
|
410
|
-
// src/jsx/capabilities.ts
|
|
411
|
-
const none = {
|
|
412
|
-
timed: false,
|
|
413
|
-
visual: false,
|
|
414
|
-
audio: false,
|
|
415
|
-
trimSource: false,
|
|
416
|
-
transformable: false,
|
|
417
|
-
container: false,
|
|
418
|
-
childKinds: [],
|
|
419
|
-
placementScope: "none"
|
|
420
|
-
};
|
|
421
|
-
const with_ = (over) => ({
|
|
422
|
-
...none,
|
|
423
|
-
...over
|
|
424
|
-
});
|
|
425
|
-
const CLIP_CHILDREN = [
|
|
426
|
-
"effect",
|
|
427
|
-
"audioEffect",
|
|
428
|
-
"colorGrade",
|
|
429
|
-
"keyframe",
|
|
430
|
-
"mask",
|
|
431
|
-
"speedKey",
|
|
432
|
-
"freeze"
|
|
433
|
-
];
|
|
434
|
-
const OVERLAY_CHILDREN = [
|
|
435
|
-
"effect",
|
|
436
|
-
"keyframe",
|
|
437
|
-
"mask"
|
|
156
|
+
const OVERLAY_CHILDREN = [
|
|
157
|
+
"effect",
|
|
158
|
+
"keyframe",
|
|
159
|
+
"mask"
|
|
438
160
|
];
|
|
439
161
|
/**
|
|
440
162
|
* What each element kind can do. Ported from OpenReel's timeline-items table,
|
|
@@ -486,7 +208,7 @@ const KIND_CAPABILITIES = {
|
|
|
486
208
|
audio: true,
|
|
487
209
|
trimSource: true,
|
|
488
210
|
container: true,
|
|
489
|
-
childKinds: ["
|
|
211
|
+
childKinds: ["keyframe"],
|
|
490
212
|
placementScope: "track"
|
|
491
213
|
}),
|
|
492
214
|
gap: with_({
|
|
@@ -585,11 +307,10 @@ const KIND_CAPABILITIES = {
|
|
|
585
307
|
visual: true,
|
|
586
308
|
transformable: true,
|
|
587
309
|
container: true,
|
|
588
|
-
childKinds: ["keyframe"],
|
|
310
|
+
childKinds: ["effect", "keyframe"],
|
|
589
311
|
placementScope: "track"
|
|
590
312
|
}),
|
|
591
313
|
effect: with_({ placementScope: "child" }),
|
|
592
|
-
audioEffect: with_({ placementScope: "child" }),
|
|
593
314
|
colorGrade: with_({ placementScope: "child" }),
|
|
594
315
|
keyframe: with_({ placementScope: "child" }),
|
|
595
316
|
speedKey: with_({ placementScope: "child" }),
|
|
@@ -598,308 +319,299 @@ const KIND_CAPABILITIES = {
|
|
|
598
319
|
function capabilitiesOf(kind) {
|
|
599
320
|
return KIND_CAPABILITIES[kind] ?? none;
|
|
600
321
|
}
|
|
601
|
-
// src/jsx/
|
|
322
|
+
// src/jsx/define.ts
|
|
602
323
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
324
|
+
* A vocabulary element is an ordinary React component that also carries its
|
|
325
|
+
* kind, its Zod prop schema and its editing capabilities, so the resolver and
|
|
326
|
+
* the editor can read the element tree without rendering it.
|
|
606
327
|
*/
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
];
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
prop: "type",
|
|
657
|
-
values: RENDERED_TRANSITIONS,
|
|
658
|
-
reason: "draws as a crossfade"
|
|
659
|
-
},
|
|
660
|
-
{
|
|
661
|
-
kind: "effect",
|
|
662
|
-
prop: "type",
|
|
663
|
-
values: RENDERED_EFFECTS,
|
|
664
|
-
reason: "is ignored: the picture renders without it"
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
kind: "audioEffect",
|
|
668
|
-
prop: "type",
|
|
669
|
-
values: RENDERED_AUDIO_EFFECTS,
|
|
670
|
-
reason: "is ignored: the audio plays without it"
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
kind: "title",
|
|
674
|
-
prop: "animation.preset",
|
|
675
|
-
values: RENDERED_TITLE_ANIMATIONS,
|
|
676
|
-
reason: "shows as a fade"
|
|
677
|
-
},
|
|
678
|
-
{
|
|
679
|
-
kind: "captions",
|
|
680
|
-
prop: "animation",
|
|
681
|
-
values: RENDERED_CAPTION_ANIMATIONS,
|
|
682
|
-
reason: "shows as karaoke"
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
kind: "mask",
|
|
686
|
-
prop: "type",
|
|
687
|
-
values: RENDERED_MASK_TYPES,
|
|
688
|
-
reason: "is ignored: matte masks are not rendered, the element shows unmasked"
|
|
689
|
-
},
|
|
690
|
-
...[
|
|
691
|
-
"title",
|
|
692
|
-
"shape",
|
|
693
|
-
"svg",
|
|
694
|
-
"sticker",
|
|
695
|
-
"graphic",
|
|
696
|
-
"layer"
|
|
697
|
-
].map((kind) => ({
|
|
698
|
-
kind,
|
|
699
|
-
prop: "blend",
|
|
700
|
-
values: ["normal"],
|
|
701
|
-
reason: "blends with the footage in the export only: the preview draws it unblended over the footage"
|
|
702
|
-
}))
|
|
328
|
+
function defineElement(def) {
|
|
329
|
+
const component = ((props) => {
|
|
330
|
+
if (!def.render) return null;
|
|
331
|
+
const { children, ...rest } = props;
|
|
332
|
+
const parsed = def.schema.parse(rest);
|
|
333
|
+
return def.render({
|
|
334
|
+
...parsed,
|
|
335
|
+
children
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
Object.defineProperties(component, {
|
|
339
|
+
kind: {
|
|
340
|
+
value: def.kind,
|
|
341
|
+
enumerable: true
|
|
342
|
+
},
|
|
343
|
+
schema: {
|
|
344
|
+
value: def.schema,
|
|
345
|
+
enumerable: true
|
|
346
|
+
},
|
|
347
|
+
isVocabulary: {
|
|
348
|
+
value: true,
|
|
349
|
+
enumerable: true
|
|
350
|
+
},
|
|
351
|
+
capabilities: {
|
|
352
|
+
value: def.capabilities,
|
|
353
|
+
enumerable: true
|
|
354
|
+
},
|
|
355
|
+
displayName: {
|
|
356
|
+
value: def.kind,
|
|
357
|
+
enumerable: true
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return component;
|
|
361
|
+
}
|
|
362
|
+
function isVocabulary(type) {
|
|
363
|
+
return typeof type === "function" && type.isVocabulary === true;
|
|
364
|
+
}
|
|
365
|
+
function kindOf(type) {
|
|
366
|
+
return isVocabulary(type) ? type.kind : null;
|
|
367
|
+
}
|
|
368
|
+
// src/jsx/enums.ts
|
|
369
|
+
const TRACK_LAYOUTS = ["sequence", "free"];
|
|
370
|
+
const TRACK_ROLES = [
|
|
371
|
+
"general",
|
|
372
|
+
"captions",
|
|
373
|
+
"dialogue",
|
|
374
|
+
"music",
|
|
375
|
+
"effects",
|
|
376
|
+
"ambience"
|
|
703
377
|
];
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
reason: "is ignored: emphasis animations are not rendered"
|
|
710
|
-
},
|
|
711
|
-
{
|
|
712
|
-
kind: "*",
|
|
713
|
-
prop: "transform.rotate3d",
|
|
714
|
-
reason: THREE_D
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
kind: "*",
|
|
718
|
-
prop: "transform.perspective",
|
|
719
|
-
reason: THREE_D
|
|
720
|
-
},
|
|
721
|
-
{
|
|
722
|
-
kind: "*",
|
|
723
|
-
prop: "transform.transformStyle",
|
|
724
|
-
reason: THREE_D
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
kind: "keyframe",
|
|
728
|
-
prop: "property",
|
|
729
|
-
value: "transform.position.z",
|
|
730
|
-
reason: THREE_D
|
|
731
|
-
},
|
|
732
|
-
{
|
|
733
|
-
kind: "keyframe",
|
|
734
|
-
prop: "property",
|
|
735
|
-
value: "transform.rotation.x",
|
|
736
|
-
reason: THREE_D
|
|
737
|
-
},
|
|
738
|
-
{
|
|
739
|
-
kind: "keyframe",
|
|
740
|
-
prop: "property",
|
|
741
|
-
value: "transform.rotation.y",
|
|
742
|
-
reason: THREE_D
|
|
743
|
-
},
|
|
744
|
-
{
|
|
745
|
-
kind: "keyframe",
|
|
746
|
-
prop: "property",
|
|
747
|
-
value: "transform.perspective",
|
|
748
|
-
reason: THREE_D
|
|
749
|
-
},
|
|
750
|
-
{
|
|
751
|
-
kind: "keyframe",
|
|
752
|
-
prop: "property",
|
|
753
|
-
value: "path",
|
|
754
|
-
reason: "is ignored: paths do not animate"
|
|
755
|
-
},
|
|
756
|
-
{
|
|
757
|
-
kind: "keyframe",
|
|
758
|
-
prop: "handles",
|
|
759
|
-
reason: "is ignored: the segment follows its easing"
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
kind: "keyframe",
|
|
763
|
-
prop: "roving",
|
|
764
|
-
reason: "is ignored: the key stays at its time"
|
|
765
|
-
},
|
|
766
|
-
{
|
|
767
|
-
kind: "clip",
|
|
768
|
-
prop: "chromaKey",
|
|
769
|
-
reason: "is ignored: the green screen stays in the picture"
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
kind: "clip",
|
|
773
|
-
prop: "stabilize",
|
|
774
|
-
reason: "is ignored: the footage renders unstabilised"
|
|
775
|
-
},
|
|
776
|
-
{
|
|
777
|
-
kind: "clip",
|
|
778
|
-
prop: "behindSubject",
|
|
779
|
-
reason: "is ignored: the clip draws in its track order"
|
|
780
|
-
},
|
|
781
|
-
{
|
|
782
|
-
kind: "title",
|
|
783
|
-
prop: "behindSubject",
|
|
784
|
-
reason: "is ignored: the title draws over the footage"
|
|
785
|
-
},
|
|
786
|
-
{
|
|
787
|
-
kind: "title",
|
|
788
|
-
prop: "text3d",
|
|
789
|
-
reason: "is ignored: the title renders as flat text"
|
|
790
|
-
},
|
|
791
|
-
{
|
|
792
|
-
kind: "clip",
|
|
793
|
-
prop: "smoothSlowMo",
|
|
794
|
-
reason: "is ignored: slow motion repeats frames"
|
|
795
|
-
},
|
|
796
|
-
{
|
|
797
|
-
kind: "clip",
|
|
798
|
-
prop: "interpolation",
|
|
799
|
-
reason: "is ignored: slow motion repeats frames"
|
|
800
|
-
},
|
|
801
|
-
{
|
|
802
|
-
kind: "clip",
|
|
803
|
-
prop: "pitchCorrection",
|
|
804
|
-
reason: "is ignored: sped-up or slowed audio changes pitch"
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
kind: "audio",
|
|
808
|
-
prop: "pitchCorrection",
|
|
809
|
-
reason: "is ignored: sped-up or slowed audio changes pitch"
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
kind: "clip",
|
|
813
|
-
prop: "audioTrack",
|
|
814
|
-
reason: "is ignored: the asset's first audio stream plays"
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
kind: "audio",
|
|
818
|
-
prop: "audioTrack",
|
|
819
|
-
reason: "is ignored: the asset's first audio stream plays"
|
|
820
|
-
},
|
|
821
|
-
{
|
|
822
|
-
kind: "speedKey",
|
|
823
|
-
parent: "nested",
|
|
824
|
-
reason: "is ignored inside <Nested>: the sequence plays at its constant speed"
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
kind: "freeze",
|
|
828
|
-
parent: "nested",
|
|
829
|
-
reason: "is ignored inside <Nested>: the sequence plays at its constant speed"
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
kind: "effect",
|
|
833
|
-
prop: "params.gamma",
|
|
834
|
-
reason: "is ignored: levels render their black and white points only"
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
kind: "transition",
|
|
838
|
-
prop: "params.direction",
|
|
839
|
-
value: "diagonal",
|
|
840
|
-
reason: "draws as a wipe to the left"
|
|
841
|
-
},
|
|
842
|
-
{
|
|
843
|
-
kind: "transition",
|
|
844
|
-
prop: "params.softness",
|
|
845
|
-
reason: "is ignored: the wipe has a hard edge"
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
kind: "adjustment",
|
|
849
|
-
prop: "affects",
|
|
850
|
-
reason: "is ignored: the adjustment filters every track below it"
|
|
851
|
-
},
|
|
852
|
-
{
|
|
853
|
-
kind: "composition",
|
|
854
|
-
prop: "background.mode",
|
|
855
|
-
value: "blur",
|
|
856
|
-
reason: "draws as the plain colour when an overlay sits beneath the footage"
|
|
857
|
-
},
|
|
858
|
-
{
|
|
859
|
-
kind: "mask",
|
|
860
|
-
prop: "matte",
|
|
861
|
-
reason: "is ignored: matte masks are not rendered, the element shows unmasked"
|
|
862
|
-
},
|
|
863
|
-
{
|
|
864
|
-
kind: "captions",
|
|
865
|
-
prop: "animation",
|
|
866
|
-
value: "bounce",
|
|
867
|
-
reason: "has no motion: it shows as karaoke"
|
|
868
|
-
},
|
|
869
|
-
{
|
|
870
|
-
kind: "captions",
|
|
871
|
-
prop: "animation",
|
|
872
|
-
value: "typewriter",
|
|
873
|
-
reason: "types out only cues without word timings; cues with words show as karaoke"
|
|
874
|
-
},
|
|
875
|
-
...[
|
|
876
|
-
"wheels",
|
|
877
|
-
"curves",
|
|
878
|
-
"lut",
|
|
879
|
-
"hsl"
|
|
880
|
-
].map((prop) => ({
|
|
881
|
-
kind: "colorGrade",
|
|
882
|
-
prop,
|
|
883
|
-
reason: `is ignored: only ${RENDERED_COLOR_GRADE.join(" and ")} render`
|
|
884
|
-
}))
|
|
378
|
+
const FIT_MODES = [
|
|
379
|
+
"contain",
|
|
380
|
+
"cover",
|
|
381
|
+
"stretch",
|
|
382
|
+
"none"
|
|
885
383
|
];
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
384
|
+
const BLEND_MODES = [
|
|
385
|
+
"normal",
|
|
386
|
+
"multiply",
|
|
387
|
+
"screen",
|
|
388
|
+
"overlay",
|
|
389
|
+
"darken",
|
|
390
|
+
"lighten",
|
|
391
|
+
"color-dodge",
|
|
392
|
+
"color-burn",
|
|
393
|
+
"hard-light",
|
|
394
|
+
"soft-light",
|
|
395
|
+
"difference",
|
|
396
|
+
"exclusion",
|
|
397
|
+
"hue",
|
|
398
|
+
"saturation",
|
|
399
|
+
"color",
|
|
400
|
+
"luminosity",
|
|
401
|
+
"add",
|
|
402
|
+
"linear-dodge"
|
|
403
|
+
];
|
|
404
|
+
const EASINGS = [
|
|
405
|
+
"linear",
|
|
406
|
+
"ease",
|
|
407
|
+
"ease-in",
|
|
408
|
+
"ease-out",
|
|
409
|
+
"ease-in-out",
|
|
410
|
+
"hold",
|
|
411
|
+
"bezier",
|
|
412
|
+
"smoothstep",
|
|
413
|
+
"smootherstep",
|
|
414
|
+
"snappy",
|
|
415
|
+
"smooth",
|
|
416
|
+
"spring",
|
|
417
|
+
"easeInQuad",
|
|
418
|
+
"easeOutQuad",
|
|
419
|
+
"easeInOutQuad",
|
|
420
|
+
"easeInCubic",
|
|
421
|
+
"easeOutCubic",
|
|
422
|
+
"easeInOutCubic",
|
|
423
|
+
"easeInQuart",
|
|
424
|
+
"easeOutQuart",
|
|
425
|
+
"easeInOutQuart",
|
|
426
|
+
"easeInQuint",
|
|
427
|
+
"easeOutQuint",
|
|
428
|
+
"easeInOutQuint",
|
|
429
|
+
"easeInSine",
|
|
430
|
+
"easeOutSine",
|
|
431
|
+
"easeInOutSine",
|
|
432
|
+
"easeInExpo",
|
|
433
|
+
"easeOutExpo",
|
|
434
|
+
"easeInOutExpo",
|
|
435
|
+
"easeInCirc",
|
|
436
|
+
"easeOutCirc",
|
|
437
|
+
"easeInOutCirc",
|
|
438
|
+
"easeInBack",
|
|
439
|
+
"easeOutBack",
|
|
440
|
+
"easeInOutBack",
|
|
441
|
+
"easeInBackStrong",
|
|
442
|
+
"easeOutBackStrong",
|
|
443
|
+
"easeInOutBackStrong",
|
|
444
|
+
"easeInElastic",
|
|
445
|
+
"easeOutElastic",
|
|
446
|
+
"easeInOutElastic",
|
|
447
|
+
"easeInElasticSoft",
|
|
448
|
+
"easeOutElasticSoft",
|
|
449
|
+
"easeInOutElasticSoft",
|
|
450
|
+
"easeInBounce",
|
|
451
|
+
"easeOutBounce",
|
|
452
|
+
"easeInOutBounce",
|
|
453
|
+
"easeInBounceSmall",
|
|
454
|
+
"easeOutBounceSmall",
|
|
455
|
+
"easeInOutBounceSmall"
|
|
456
|
+
];
|
|
457
|
+
const MASK_EASINGS = [
|
|
458
|
+
"linear",
|
|
459
|
+
"ease-in",
|
|
460
|
+
"ease-out",
|
|
461
|
+
"ease-in-out"
|
|
462
|
+
];
|
|
463
|
+
const TRANSITION_TYPES = [
|
|
464
|
+
"crossfade",
|
|
465
|
+
"dipToBlack",
|
|
466
|
+
"dipToWhite",
|
|
467
|
+
"wipe",
|
|
468
|
+
"slide",
|
|
469
|
+
"push"
|
|
470
|
+
];
|
|
471
|
+
const VIDEO_EFFECT_TYPES = [
|
|
472
|
+
"brightness",
|
|
473
|
+
"contrast",
|
|
474
|
+
"saturation",
|
|
475
|
+
"hue",
|
|
476
|
+
"temperature",
|
|
477
|
+
"tint",
|
|
478
|
+
"blur",
|
|
479
|
+
"vignette",
|
|
480
|
+
"grain",
|
|
481
|
+
"shadow",
|
|
482
|
+
"grayscale",
|
|
483
|
+
"sepia",
|
|
484
|
+
"invert",
|
|
485
|
+
"levels"
|
|
486
|
+
];
|
|
487
|
+
const TEXT_ANIMATION_PRESETS = [
|
|
488
|
+
"none",
|
|
489
|
+
"typewriter",
|
|
490
|
+
"fade",
|
|
491
|
+
"slide-up",
|
|
492
|
+
"slide-down",
|
|
493
|
+
"slide-left",
|
|
494
|
+
"slide-right",
|
|
495
|
+
"scale",
|
|
496
|
+
"word-by-word"
|
|
497
|
+
];
|
|
498
|
+
const CAPTION_ANIMATIONS = [
|
|
499
|
+
"none",
|
|
500
|
+
"word-highlight",
|
|
501
|
+
"word-by-word",
|
|
502
|
+
"karaoke",
|
|
503
|
+
"typewriter"
|
|
504
|
+
];
|
|
505
|
+
const GRAPHIC_ANIMATIONS = [
|
|
506
|
+
"none",
|
|
507
|
+
"fade",
|
|
508
|
+
"slide-left",
|
|
509
|
+
"slide-right",
|
|
510
|
+
"slide-up",
|
|
511
|
+
"slide-down",
|
|
512
|
+
"scale",
|
|
513
|
+
"rotate",
|
|
514
|
+
"bounce",
|
|
515
|
+
"pop",
|
|
516
|
+
"draw",
|
|
517
|
+
"wipe-left",
|
|
518
|
+
"wipe-right",
|
|
519
|
+
"wipe-up",
|
|
520
|
+
"wipe-down",
|
|
521
|
+
"reveal-center",
|
|
522
|
+
"reveal-edges",
|
|
523
|
+
"elastic",
|
|
524
|
+
"flip-horizontal",
|
|
525
|
+
"flip-vertical"
|
|
526
|
+
];
|
|
527
|
+
const SHAPE_TYPES = [
|
|
528
|
+
"rectangle",
|
|
529
|
+
"circle",
|
|
530
|
+
"ellipse",
|
|
531
|
+
"triangle",
|
|
532
|
+
"arrow",
|
|
533
|
+
"line",
|
|
534
|
+
"polygon",
|
|
535
|
+
"path",
|
|
536
|
+
"star"
|
|
537
|
+
];
|
|
538
|
+
const KEYFRAME_PROPERTIES = [
|
|
539
|
+
"transform.position.x",
|
|
540
|
+
"transform.position.y",
|
|
541
|
+
"transform.scale.x",
|
|
542
|
+
"transform.scale.y",
|
|
543
|
+
"transform.rotation",
|
|
544
|
+
"transform.opacity",
|
|
545
|
+
"transform.anchor.x",
|
|
546
|
+
"transform.anchor.y",
|
|
547
|
+
"transform.borderRadius",
|
|
548
|
+
"transform.crop.x",
|
|
549
|
+
"transform.crop.y",
|
|
550
|
+
"transform.crop.width",
|
|
551
|
+
"transform.crop.height",
|
|
552
|
+
"audio.volume",
|
|
553
|
+
"audio.pan",
|
|
554
|
+
"colorGrade.temperature",
|
|
555
|
+
"colorGrade.tint"
|
|
556
|
+
];
|
|
557
|
+
/** `effect.<effectId>.<param>` is also a valid keyframe property; see `isKeyframeProperty`. */
|
|
558
|
+
const KEYFRAME_PROPERTY_PATTERN = /^(effect\.[A-Za-z0-9_-]+\.[A-Za-z0-9_]+)$/;
|
|
559
|
+
function isKeyframeProperty(value) {
|
|
560
|
+
return KEYFRAME_PROPERTIES.includes(value) || KEYFRAME_PROPERTY_PATTERN.test(value);
|
|
561
|
+
}
|
|
562
|
+
const CAPTION_POSITIONS = [
|
|
563
|
+
"top",
|
|
564
|
+
"center",
|
|
565
|
+
"bottom"
|
|
566
|
+
];
|
|
567
|
+
const TEXT_ALIGNS = [
|
|
568
|
+
"left",
|
|
569
|
+
"center",
|
|
570
|
+
"right",
|
|
571
|
+
"justify"
|
|
572
|
+
];
|
|
573
|
+
const TEXT_VALIGNS = [
|
|
574
|
+
"top",
|
|
575
|
+
"middle",
|
|
576
|
+
"bottom"
|
|
577
|
+
];
|
|
578
|
+
const TEXT_DECORATIONS = [
|
|
579
|
+
"none",
|
|
580
|
+
"underline",
|
|
581
|
+
"line-through",
|
|
582
|
+
"overline"
|
|
583
|
+
];
|
|
584
|
+
const ANIMATION_UNITS = [
|
|
585
|
+
"character",
|
|
586
|
+
"word",
|
|
587
|
+
"line"
|
|
588
|
+
];
|
|
589
|
+
const MASK_TYPES = ["shape", "drawn"];
|
|
590
|
+
const SVG_COLOR_MODES = [
|
|
591
|
+
"none",
|
|
592
|
+
"tint",
|
|
593
|
+
"replace"
|
|
594
|
+
];
|
|
595
|
+
const AUTOMATION_LANES = ["volume", "pan"];
|
|
596
|
+
const TRACK_FLAGS = [
|
|
597
|
+
"locked",
|
|
598
|
+
"hidden",
|
|
599
|
+
"muted",
|
|
600
|
+
"solo"
|
|
601
|
+
];
|
|
602
|
+
const zTrackLayout = z.enum(TRACK_LAYOUTS);
|
|
603
|
+
const zTrackRole = z.enum(TRACK_ROLES);
|
|
604
|
+
const zFitMode = z.enum(FIT_MODES);
|
|
605
|
+
const zBlendMode = z.enum(BLEND_MODES);
|
|
606
|
+
const zEasing = z.enum(EASINGS);
|
|
607
|
+
const zMaskEasing = z.enum(MASK_EASINGS);
|
|
608
|
+
const zTransitionType = z.enum(TRANSITION_TYPES);
|
|
609
|
+
const zVideoEffectType = z.enum(VIDEO_EFFECT_TYPES);
|
|
610
|
+
const zTextAnimationPreset = z.enum(TEXT_ANIMATION_PRESETS);
|
|
611
|
+
const zCaptionAnimation = z.enum(CAPTION_ANIMATIONS);
|
|
612
|
+
const zGraphicAnimation = z.enum(GRAPHIC_ANIMATIONS);
|
|
613
|
+
const zShapeType = z.enum(SHAPE_TYPES);
|
|
614
|
+
const zKeyframeProperty = z.string().refine(isKeyframeProperty, "unknown keyframe property");
|
|
903
615
|
// src/jsx/schemas/common.ts
|
|
904
616
|
const ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
905
617
|
const TIME_REF_PATTERN = /^(word|shot|silence):\d+(\.end)?$|^marker:[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
@@ -936,7 +648,7 @@ const vec2Schema = z.strictObject({
|
|
|
936
648
|
x: z.number(),
|
|
937
649
|
y: z.number()
|
|
938
650
|
});
|
|
939
|
-
|
|
651
|
+
z.strictObject({
|
|
940
652
|
x: z.number(),
|
|
941
653
|
y: z.number(),
|
|
942
654
|
z: z.number()
|
|
@@ -953,27 +665,13 @@ const transformSchema = z.strictObject({
|
|
|
953
665
|
y: z.number().min(0).max(1),
|
|
954
666
|
width: z.number().min(0).max(1),
|
|
955
667
|
height: z.number().min(0).max(1)
|
|
956
|
-
}).optional()
|
|
957
|
-
rotate3d: vec3Schema.optional(),
|
|
958
|
-
perspective: z.number().min(100).max(5e3).optional(),
|
|
959
|
-
transformStyle: z.enum(["flat", "preserve-3d"]).optional()
|
|
668
|
+
}).optional()
|
|
960
669
|
});
|
|
961
670
|
const visualProps = {
|
|
962
671
|
transform: transformSchema.optional(),
|
|
963
672
|
fit: zFitMode.optional(),
|
|
964
673
|
blend: zBlendMode.optional(),
|
|
965
|
-
blendOpacity: z.number().min(0).max(1).optional()
|
|
966
|
-
emphasis: z.union([zEmphasisAnimation, z.strictObject({
|
|
967
|
-
type: zEmphasisAnimation,
|
|
968
|
-
speed: z.number().positive().optional(),
|
|
969
|
-
intensity: z.number().min(0).optional(),
|
|
970
|
-
loop: z.boolean().optional(),
|
|
971
|
-
focusPoint: vec2Schema.optional(),
|
|
972
|
-
zoomScale: z.number().positive().optional(),
|
|
973
|
-
holdDuration: z.number().nonnegative().optional(),
|
|
974
|
-
startTime: z.number().nonnegative().optional(),
|
|
975
|
-
animationDuration: z.number().positive().optional()
|
|
976
|
-
})]).optional()
|
|
674
|
+
blendOpacity: z.number().min(0).max(1).optional()
|
|
977
675
|
};
|
|
978
676
|
const automationPointsSchema = z.array(z.tuple([z.number().nonnegative(), z.number()]));
|
|
979
677
|
const audioProps = {
|
|
@@ -1025,6 +723,15 @@ const entranceProps = {
|
|
|
1025
723
|
enter: graphicAnimationSchema.optional(),
|
|
1026
724
|
exit: graphicAnimationSchema.optional()
|
|
1027
725
|
};
|
|
726
|
+
// src/jsx/schemas/enums-local.ts
|
|
727
|
+
const zCaptionPosition = z.enum(CAPTION_POSITIONS);
|
|
728
|
+
const zTextAlign = z.enum(TEXT_ALIGNS);
|
|
729
|
+
const zTextValign = z.enum(TEXT_VALIGNS);
|
|
730
|
+
const zTextDecoration = z.enum(TEXT_DECORATIONS);
|
|
731
|
+
const zAnimationUnit = z.enum(ANIMATION_UNITS);
|
|
732
|
+
const zMaskType = z.enum(MASK_TYPES);
|
|
733
|
+
const zSvgColorMode = z.enum(SVG_COLOR_MODES);
|
|
734
|
+
z.enum(AUTOMATION_LANES);
|
|
1028
735
|
// src/jsx/schemas/captions.ts
|
|
1029
736
|
const captionStyleSchema = z.strictObject({
|
|
1030
737
|
font: z.string().optional(),
|
|
@@ -1053,7 +760,9 @@ const captionsSchema = z.strictObject({
|
|
|
1053
760
|
/** Left out: as many as fit one line at the caption size on this frame (at most 42). */
|
|
1054
761
|
maxChars: z.number().int().positive().optional(),
|
|
1055
762
|
maxDuration: z.number().positive().default(5),
|
|
1056
|
-
maxGap: z.number().nonnegative().default(.8)
|
|
763
|
+
maxGap: z.number().nonnegative().default(.8),
|
|
764
|
+
/** Words a generated cue always ends after, as `word:N` refs on `src`: `breakAfter={["word:5", "word:12"]}`. */
|
|
765
|
+
breakAfter: z.array(z.string().regex(/^word:\d+$/, "a word ref such as \"word:12\"")).optional()
|
|
1057
766
|
});
|
|
1058
767
|
const captionSchema = z.strictObject({
|
|
1059
768
|
id: idSchema,
|
|
@@ -1077,48 +786,13 @@ const paramValue = z.union([
|
|
|
1077
786
|
const paramsSchema = z.record(z.string(), paramValue);
|
|
1078
787
|
const effectSchema = z.strictObject({
|
|
1079
788
|
id: idSchema,
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
enabled: z.boolean().default(true)
|
|
1083
|
-
});
|
|
1084
|
-
const audioEffectSchema = z.strictObject({
|
|
1085
|
-
id: idSchema,
|
|
1086
|
-
type: zAudioEffectType,
|
|
789
|
+
/** A built-in effect, or one the file declares with `defineEffect()`. */
|
|
790
|
+
type: z.union([zVideoEffectType, z.string().min(1)]),
|
|
1087
791
|
params: paramsSchema.default({}),
|
|
1088
792
|
enabled: z.boolean().default(true)
|
|
1089
793
|
});
|
|
1090
|
-
const rgb = z.strictObject({
|
|
1091
|
-
r: z.number().min(-1).max(1),
|
|
1092
|
-
g: z.number().min(-1).max(1),
|
|
1093
|
-
b: z.number().min(-1).max(1)
|
|
1094
|
-
});
|
|
1095
|
-
const curve = z.array(vec2Schema).min(2);
|
|
1096
|
-
const eightBands = z.array(z.number()).length(8);
|
|
1097
794
|
const colorGradeSchema = z.strictObject({
|
|
1098
795
|
id: idSchema,
|
|
1099
|
-
wheels: z.strictObject({
|
|
1100
|
-
shadows: rgb.optional(),
|
|
1101
|
-
midtones: rgb.optional(),
|
|
1102
|
-
highlights: rgb.optional(),
|
|
1103
|
-
shadowsLift: z.number().min(-1).max(1).optional(),
|
|
1104
|
-
midtonesGamma: z.number().min(.1).max(4).optional(),
|
|
1105
|
-
highlightsGain: z.number().min(0).max(4).optional()
|
|
1106
|
-
}).optional(),
|
|
1107
|
-
curves: z.strictObject({
|
|
1108
|
-
rgb: curve.optional(),
|
|
1109
|
-
red: curve.optional(),
|
|
1110
|
-
green: curve.optional(),
|
|
1111
|
-
blue: curve.optional()
|
|
1112
|
-
}).optional(),
|
|
1113
|
-
lut: z.strictObject({
|
|
1114
|
-
src: z.string(),
|
|
1115
|
-
intensity: z.number().min(0).max(1).default(1)
|
|
1116
|
-
}).optional(),
|
|
1117
|
-
hsl: z.strictObject({
|
|
1118
|
-
hue: eightBands.optional(),
|
|
1119
|
-
saturation: eightBands.optional(),
|
|
1120
|
-
luminance: eightBands.optional()
|
|
1121
|
-
}).optional(),
|
|
1122
796
|
temperature: z.number().min(-100).max(100).optional(),
|
|
1123
797
|
tint: z.number().min(-100).max(100).optional()
|
|
1124
798
|
});
|
|
@@ -1128,12 +802,7 @@ const keyframeSchema = z.strictObject({
|
|
|
1128
802
|
/** Element-relative seconds. */
|
|
1129
803
|
at: z.number().nonnegative(),
|
|
1130
804
|
value: z.unknown(),
|
|
1131
|
-
easing: zEasing.default("linear")
|
|
1132
|
-
handles: z.strictObject({
|
|
1133
|
-
in: vec2Schema,
|
|
1134
|
-
out: vec2Schema
|
|
1135
|
-
}).optional(),
|
|
1136
|
-
roving: z.boolean().optional()
|
|
805
|
+
easing: zEasing.default("linear")
|
|
1137
806
|
});
|
|
1138
807
|
const speedKeySchema = z.strictObject({
|
|
1139
808
|
id: idSchema,
|
|
@@ -1182,11 +851,7 @@ const maskSchema = z.strictObject({
|
|
|
1182
851
|
inverted: z.boolean().default(false),
|
|
1183
852
|
expansion: z.number().min(-100).max(100).default(0),
|
|
1184
853
|
opacity: z.number().min(0).max(1).default(1),
|
|
1185
|
-
easing: zMaskEasing.default("linear")
|
|
1186
|
-
matte: z.strictObject({
|
|
1187
|
-
source: idSchema,
|
|
1188
|
-
channel: zMatteChannel.default("bounds")
|
|
1189
|
-
}).optional()
|
|
854
|
+
easing: zMaskEasing.default("linear")
|
|
1190
855
|
});
|
|
1191
856
|
// src/theme/builtins.ts
|
|
1192
857
|
/**
|
|
@@ -1617,7 +1282,16 @@ const compositionSchema = z.strictObject({
|
|
|
1617
1282
|
gradient: gradientSchema.optional()
|
|
1618
1283
|
}).optional(),
|
|
1619
1284
|
/** The video's look: palette, fonts, radius, motion style and element presets. Elements use it through `$tokens` and `style`. */
|
|
1620
|
-
theme: themeSchema.optional()
|
|
1285
|
+
theme: themeSchema.optional(),
|
|
1286
|
+
/**
|
|
1287
|
+
* A film camera's blur on everything that moves, in the export: each frame averages `samples` moments across the
|
|
1288
|
+
* time its shutter is open, `shutterAngle` degrees of the frame (180 is half the frame, the film default). Drawn by
|
|
1289
|
+
* the browser export, which a file with it uses on its own; the preview stays sharp.
|
|
1290
|
+
*/
|
|
1291
|
+
motionBlur: z.strictObject({
|
|
1292
|
+
shutterAngle: z.number().min(1).max(360).default(180),
|
|
1293
|
+
samples: z.number().int().min(2).max(64).default(8)
|
|
1294
|
+
}).optional()
|
|
1621
1295
|
});
|
|
1622
1296
|
const trackSchema = z.strictObject({
|
|
1623
1297
|
id: idSchema,
|
|
@@ -1641,26 +1315,12 @@ const gapSchema = z.strictObject({
|
|
|
1641
1315
|
duration: z.number().positive()
|
|
1642
1316
|
});
|
|
1643
1317
|
// src/jsx/schemas/media.ts
|
|
1644
|
-
const chromaKeySchema = z.strictObject({
|
|
1645
|
-
enabled: z.boolean().default(true),
|
|
1646
|
-
color: colorSchema.default("#00ff00"),
|
|
1647
|
-
tolerance: z.number().min(0).max(1).default(.3),
|
|
1648
|
-
edgeSoftness: z.number().min(0).max(1).default(.1),
|
|
1649
|
-
spillSuppression: z.number().min(0).max(1).default(.5)
|
|
1650
|
-
});
|
|
1651
|
-
const stabilizeSchema = z.strictObject({
|
|
1652
|
-
enabled: z.boolean().default(true),
|
|
1653
|
-
strength: z.number().min(0).max(1).default(.5),
|
|
1654
|
-
crop: zStabilizeCrop.default("auto")
|
|
1655
|
-
});
|
|
1656
1318
|
const sourceProps = {
|
|
1657
1319
|
src: assetRefSchema,
|
|
1658
1320
|
in: timeRefSchema.optional(),
|
|
1659
1321
|
out: timeRefSchema.optional(),
|
|
1660
1322
|
speed: z.number().min(.1).max(20).default(1),
|
|
1661
|
-
reverse: z.boolean().default(false)
|
|
1662
|
-
pitchCorrection: z.boolean().default(false),
|
|
1663
|
-
audioTrack: z.number().int().nonnegative().default(0)
|
|
1323
|
+
reverse: z.boolean().default(false)
|
|
1664
1324
|
};
|
|
1665
1325
|
const clipSchema = z.strictObject({
|
|
1666
1326
|
...timedProps,
|
|
@@ -1675,12 +1335,7 @@ const clipSchema = z.strictObject({
|
|
|
1675
1335
|
]), z.strictObject({
|
|
1676
1336
|
x: z.number().min(0).max(1),
|
|
1677
1337
|
y: z.number().min(0).max(1).optional()
|
|
1678
|
-
})]).default("center") }).optional()
|
|
1679
|
-
smoothSlowMo: z.boolean().default(false),
|
|
1680
|
-
interpolation: zInterpolation.optional(),
|
|
1681
|
-
stabilize: stabilizeSchema.optional(),
|
|
1682
|
-
chromaKey: chromaKeySchema.optional(),
|
|
1683
|
-
behindSubject: z.boolean().default(false)
|
|
1338
|
+
})]).default("center") }).optional()
|
|
1684
1339
|
});
|
|
1685
1340
|
/** `<Audio>` is `<Clip>` for audio-only assets: the same schema, no visual props accepted. */
|
|
1686
1341
|
const audioSchema = z.strictObject({
|
|
@@ -1726,18 +1381,6 @@ const textAnimationSchema = z.strictObject({
|
|
|
1726
1381
|
})
|
|
1727
1382
|
])).optional()
|
|
1728
1383
|
});
|
|
1729
|
-
const text3dSchema = z.strictObject({
|
|
1730
|
-
enabled: z.boolean().default(true),
|
|
1731
|
-
depth: z.number().nonnegative().default(10),
|
|
1732
|
-
bevelThickness: z.number().nonnegative().optional(),
|
|
1733
|
-
bevelSize: z.number().nonnegative().optional(),
|
|
1734
|
-
bevelSegments: z.number().int().nonnegative().optional(),
|
|
1735
|
-
frontColor: colorSchema.optional(),
|
|
1736
|
-
sideColor: colorSchema.optional(),
|
|
1737
|
-
metalness: z.number().min(0).max(1).optional(),
|
|
1738
|
-
roughness: z.number().min(0).max(1).optional(),
|
|
1739
|
-
material: z.enum(["basic", "physical"]).optional()
|
|
1740
|
-
});
|
|
1741
1384
|
const titleSchema = z.strictObject({
|
|
1742
1385
|
...timedProps,
|
|
1743
1386
|
...visualProps,
|
|
@@ -1763,9 +1406,7 @@ const titleSchema = z.strictObject({
|
|
|
1763
1406
|
lineHeight: z.number().min(.1).max(10).default(1.2),
|
|
1764
1407
|
letterSpacing: z.number().min(-100).max(100).default(0),
|
|
1765
1408
|
decoration: zTextDecoration.optional(),
|
|
1766
|
-
animation: textAnimationSchema.optional()
|
|
1767
|
-
text3d: text3dSchema.optional(),
|
|
1768
|
-
behindSubject: z.boolean().default(false)
|
|
1409
|
+
animation: textAnimationSchema.optional()
|
|
1769
1410
|
});
|
|
1770
1411
|
const fillSchema = z.union([colorSchema, z.strictObject({
|
|
1771
1412
|
type: z.enum([
|
|
@@ -1868,7 +1509,6 @@ const stickerSchema = z.strictObject({
|
|
|
1868
1509
|
});
|
|
1869
1510
|
const adjustmentSchema = z.strictObject({
|
|
1870
1511
|
...timedProps,
|
|
1871
|
-
affects: z.union([z.literal("all"), z.array(idSchema).min(1)]).default("all"),
|
|
1872
1512
|
opacity: z.number().min(0).max(1).default(1),
|
|
1873
1513
|
blend: zBlendMode.default("normal"),
|
|
1874
1514
|
enabled: z.boolean().default(true),
|
|
@@ -1889,6 +1529,41 @@ const layerSchema = z.strictObject({
|
|
|
1889
1529
|
transform: transformSchema.optional(),
|
|
1890
1530
|
blend: zBlendMode.optional()
|
|
1891
1531
|
});
|
|
1532
|
+
const vec3Schema = z.tuple([
|
|
1533
|
+
z.number(),
|
|
1534
|
+
z.number(),
|
|
1535
|
+
z.number()
|
|
1536
|
+
]);
|
|
1537
|
+
/**
|
|
1538
|
+
* `<Three>`: a Layer that holds a three.js scene. `setup` takes `{ scene, camera, renderer, THREE, width, height }`
|
|
1539
|
+
* once and may return `update(frame)`, called on every frame with the layer's time; JSX children are React Three
|
|
1540
|
+
* Fiber. The camera and background are literal props, so the inspector edits them.
|
|
1541
|
+
*/
|
|
1542
|
+
const threeSchema = layerSchema.extend({
|
|
1543
|
+
engine: z.literal("three"),
|
|
1544
|
+
camera: z.strictObject({
|
|
1545
|
+
position: vec3Schema.optional(),
|
|
1546
|
+
lookAt: vec3Schema.optional(),
|
|
1547
|
+
fov: z.number().min(1).max(179).optional(),
|
|
1548
|
+
near: z.number().positive().optional(),
|
|
1549
|
+
far: z.number().positive().optional(),
|
|
1550
|
+
zoom: z.number().positive().optional(),
|
|
1551
|
+
orthographic: z.boolean().optional()
|
|
1552
|
+
}).optional(),
|
|
1553
|
+
background: colorSchema.optional(),
|
|
1554
|
+
setup: z.custom((v) => typeof v === "function", "setup must be a function").optional()
|
|
1555
|
+
});
|
|
1556
|
+
/** One step of a Motion sequence: a selector inside the tag, its keyframes, and options (`duration`, `at`, `ease`…). */
|
|
1557
|
+
const motionStepSchema = z.union([z.tuple([z.string(), z.record(z.string(), z.unknown())]), z.tuple([
|
|
1558
|
+
z.string(),
|
|
1559
|
+
z.record(z.string(), z.unknown()),
|
|
1560
|
+
z.record(z.string(), z.unknown())
|
|
1561
|
+
])]);
|
|
1562
|
+
/** `<Motion>`: a Layer whose children are animated by a Motion sequence on the video's clock. */
|
|
1563
|
+
const motionSchema = layerSchema.extend({
|
|
1564
|
+
engine: z.literal("motion"),
|
|
1565
|
+
sequence: z.array(motionStepSchema).default([])
|
|
1566
|
+
});
|
|
1892
1567
|
// src/jsx/schemas/transition.ts
|
|
1893
1568
|
const dir4 = z.enum([
|
|
1894
1569
|
"left",
|
|
@@ -1896,11 +1571,7 @@ const dir4 = z.enum([
|
|
|
1896
1571
|
"up",
|
|
1897
1572
|
"down"
|
|
1898
1573
|
]);
|
|
1899
|
-
|
|
1900
|
-
x: .5,
|
|
1901
|
-
y: .5
|
|
1902
|
-
});
|
|
1903
|
-
/** Per-type parameter schemas with OpenReel's engine defaults, so defaults validate. */
|
|
1574
|
+
/** Per-type parameter schemas, with defaults that validate. */
|
|
1904
1575
|
const TRANSITION_PARAMS = {
|
|
1905
1576
|
crossfade: z.strictObject({ curve: z.enum([
|
|
1906
1577
|
"linear",
|
|
@@ -1910,80 +1581,25 @@ const TRANSITION_PARAMS = {
|
|
|
1910
1581
|
"ease-in-out"
|
|
1911
1582
|
]).default("ease") }),
|
|
1912
1583
|
dipToBlack: z.strictObject({ holdDuration: z.number().min(0).max(30).default(.1) }),
|
|
1913
|
-
dipToWhite: z.strictObject({ holdDuration: z.number().min(0).max(30).default(.1) }),
|
|
1914
|
-
wipe: z.strictObject({
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
"up",
|
|
1919
|
-
"down",
|
|
1920
|
-
"diagonal"
|
|
1921
|
-
]).default("left"),
|
|
1922
|
-
softness: z.number().min(0).max(1).default(0)
|
|
1923
|
-
}),
|
|
1924
|
-
slide: z.strictObject({
|
|
1925
|
-
direction: dir4.default("left"),
|
|
1926
|
-
pushOut: z.boolean().default(false)
|
|
1927
|
-
}),
|
|
1928
|
-
zoom: z.strictObject({
|
|
1929
|
-
scale: z.number().min(.01).max(100).default(2),
|
|
1930
|
-
center
|
|
1931
|
-
}),
|
|
1932
|
-
push: z.strictObject({ direction: dir4.default("left") }),
|
|
1933
|
-
circleReveal: z.strictObject({ center }),
|
|
1934
|
-
blur: z.strictObject({ intensity: z.number().min(0).max(10).default(1) }),
|
|
1935
|
-
whipPan: z.strictObject({
|
|
1936
|
-
direction: dir4.default("left"),
|
|
1937
|
-
blurIntensity: z.number().min(0).max(100).default(1)
|
|
1938
|
-
}),
|
|
1939
|
-
radialWipe: z.strictObject({
|
|
1940
|
-
startAngle: z.number().min(-360).max(360).default(-90),
|
|
1941
|
-
clockwise: z.boolean().default(true)
|
|
1942
|
-
}),
|
|
1943
|
-
pixelate: z.strictObject({ maxPixelSize: z.number().min(1).max(1e3).default(48) }),
|
|
1944
|
-
glitch: z.strictObject({
|
|
1945
|
-
intensity: z.number().min(0).max(10).default(.08),
|
|
1946
|
-
slices: z.number().int().min(1).max(100).default(12)
|
|
1947
|
-
}),
|
|
1948
|
-
blinds: z.strictObject({
|
|
1949
|
-
count: z.number().int().min(1).max(100).default(8),
|
|
1950
|
-
direction: z.enum(["vertical", "horizontal"]).default("vertical")
|
|
1951
|
-
}),
|
|
1952
|
-
diamondReveal: z.strictObject({ center }),
|
|
1953
|
-
spin: z.strictObject({ rotations: z.number().min(-20).max(20).default(1) }),
|
|
1954
|
-
flip: z.strictObject({ axis: z.enum(["horizontal", "vertical"]).default("horizontal") }),
|
|
1955
|
-
splitReveal: z.strictObject({ orientation: z.enum(["horizontal", "vertical"]).default("horizontal") }),
|
|
1956
|
-
flash: z.strictObject({ intensity: z.number().min(0).max(10).default(1) }),
|
|
1957
|
-
filmBurn: z.strictObject({
|
|
1958
|
-
intensity: z.number().min(0).max(10).default(1),
|
|
1959
|
-
warmth: z.number().min(0).max(1).default(.75)
|
|
1960
|
-
}),
|
|
1961
|
-
mosaic: z.strictObject({
|
|
1962
|
-
tiles: z.number().int().min(1).max(100).default(8),
|
|
1963
|
-
randomness: z.number().min(0).max(1).default(.85)
|
|
1964
|
-
}),
|
|
1965
|
-
ripple: z.strictObject({
|
|
1966
|
-
amplitude: z.number().min(0).max(1).default(.04),
|
|
1967
|
-
waves: z.number().min(0).max(100).default(3)
|
|
1968
|
-
}),
|
|
1969
|
-
pageTurn: z.strictObject({
|
|
1970
|
-
direction: z.enum(["left", "right"]).default("left"),
|
|
1971
|
-
shadow: z.number().min(0).max(1).default(.55)
|
|
1584
|
+
dipToWhite: z.strictObject({ holdDuration: z.number().min(0).max(30).default(.1) }),
|
|
1585
|
+
wipe: z.strictObject({ direction: dir4.default("left") }),
|
|
1586
|
+
slide: z.strictObject({
|
|
1587
|
+
direction: dir4.default("left"),
|
|
1588
|
+
pushOut: z.boolean().default(false)
|
|
1972
1589
|
}),
|
|
1973
|
-
|
|
1974
|
-
maxOffset: z.number().min(0).max(1e3).default(18),
|
|
1975
|
-
angle: z.number().min(-360).max(360).default(0)
|
|
1976
|
-
})
|
|
1590
|
+
push: z.strictObject({ direction: dir4.default("left") })
|
|
1977
1591
|
};
|
|
1978
1592
|
const transitionSchema = z.strictObject({
|
|
1979
1593
|
id: idSchema,
|
|
1980
|
-
|
|
1594
|
+
/** A built-in transition, or one the file declares with `defineTransition()`. */
|
|
1595
|
+
type: z.union([zTransitionType, z.string().min(1)]).default("crossfade"),
|
|
1981
1596
|
duration: z.number().min(.001).max(30).default(.5),
|
|
1982
1597
|
params: z.record(z.string(), z.unknown()).default({}),
|
|
1983
1598
|
/** Edge form: a transition at one edge of a single clip. Omit for a transition between two siblings. */
|
|
1984
1599
|
on: idSchema.optional(),
|
|
1985
1600
|
edge: z.enum(["in", "out"]).optional()
|
|
1986
1601
|
}).refine((v) => v.on === void 0 === (v.edge === void 0), { message: "on and edge go together" }).superRefine((v, ctx) => {
|
|
1602
|
+
if (!isBuiltInTransition(v.type)) return;
|
|
1987
1603
|
const result = TRANSITION_PARAMS[v.type].safeParse(v.params);
|
|
1988
1604
|
if (!result.success) ctx.addIssue({
|
|
1989
1605
|
code: "custom",
|
|
@@ -1991,6 +1607,9 @@ const transitionSchema = z.strictObject({
|
|
|
1991
1607
|
message: `invalid params for ${v.type}: ${result.error.message}`
|
|
1992
1608
|
});
|
|
1993
1609
|
});
|
|
1610
|
+
function isBuiltInTransition(type) {
|
|
1611
|
+
return TRANSITION_TYPES.includes(type);
|
|
1612
|
+
}
|
|
1994
1613
|
function defaultTransitionParams(type) {
|
|
1995
1614
|
return TRANSITION_PARAMS[type].parse({});
|
|
1996
1615
|
}
|
|
@@ -2010,6 +1629,10 @@ const Title = el("title", titleSchema);
|
|
|
2010
1629
|
const Shape = el("shape", shapeSchema);
|
|
2011
1630
|
const Svg = el("svg", svgSchema);
|
|
2012
1631
|
const Sticker = el("sticker", stickerSchema);
|
|
1632
|
+
/**
|
|
1633
|
+
* @deprecated `<Graphic kind>` will be removed. Draw it with `<Motion>`, `<Three>` or a `<Layer>`; the showcase examples
|
|
1634
|
+
* show each (decision 0020).
|
|
1635
|
+
*/
|
|
2013
1636
|
const Graphic = el("graphic", graphicSchema);
|
|
2014
1637
|
const Captions = el("captions", captionsSchema);
|
|
2015
1638
|
const Caption = el("caption", captionSchema);
|
|
@@ -2021,8 +1644,41 @@ const Multicam = el("multicam", multicamSchema);
|
|
|
2021
1644
|
const Angle = el("angle", angleSchema);
|
|
2022
1645
|
const Cut = el("cut", cutSchema);
|
|
2023
1646
|
const Layer = el("layer", layerSchema);
|
|
1647
|
+
/**
|
|
1648
|
+
* A Layer drawn by a library: kind `layer` to the resolver, the timeline and the editor, with props of its own and
|
|
1649
|
+
* an `engine` the Player looks up among the installed layer engines (`installLayerEngine`).
|
|
1650
|
+
*/
|
|
1651
|
+
const engineLayer = (engine, schema) => {
|
|
1652
|
+
const component = defineElement({
|
|
1653
|
+
kind: "layer",
|
|
1654
|
+
schema,
|
|
1655
|
+
capabilities: capabilitiesOf("layer")
|
|
1656
|
+
});
|
|
1657
|
+
Object.defineProperty(component, "engine", {
|
|
1658
|
+
value: engine,
|
|
1659
|
+
enumerable: true
|
|
1660
|
+
});
|
|
1661
|
+
return component;
|
|
1662
|
+
};
|
|
1663
|
+
/** A three.js scene on the timeline: `setup` for plain three.js, or React Three Fiber children. */
|
|
1664
|
+
const Three = engineLayer("three", threeSchema.omit({ engine: true }));
|
|
1665
|
+
/** Children animated by a Motion `sequence` on the video's clock. */
|
|
1666
|
+
const Motion = engineLayer("motion", motionSchema.omit({ engine: true }));
|
|
1667
|
+
/** The engine Layers by tag, and the schema each is checked against once collected (with its `engine`). */
|
|
1668
|
+
const ENGINE_LAYERS = {
|
|
1669
|
+
Three,
|
|
1670
|
+
Motion
|
|
1671
|
+
};
|
|
1672
|
+
const ENGINE_SCHEMAS = {
|
|
1673
|
+
three: threeSchema,
|
|
1674
|
+
motion: motionSchema
|
|
1675
|
+
};
|
|
1676
|
+
/** The engine a vocabulary component draws with, if it is an engine Layer. */
|
|
1677
|
+
function engineOf(type) {
|
|
1678
|
+
const engine = type?.engine;
|
|
1679
|
+
return typeof engine === "string" ? engine : null;
|
|
1680
|
+
}
|
|
2024
1681
|
const Effect = el("effect", effectSchema);
|
|
2025
|
-
const AudioEffect = el("audioEffect", audioEffectSchema);
|
|
2026
1682
|
const ColorGrade = el("colorGrade", colorGradeSchema);
|
|
2027
1683
|
const Keyframe = el("keyframe", keyframeSchema);
|
|
2028
1684
|
const SpeedKey = el("speedKey", speedKeySchema);
|
|
@@ -2050,277 +1706,186 @@ const ELEMENTS = {
|
|
|
2050
1706
|
Cut,
|
|
2051
1707
|
Layer,
|
|
2052
1708
|
Effect,
|
|
2053
|
-
AudioEffect,
|
|
2054
1709
|
ColorGrade,
|
|
2055
1710
|
Keyframe,
|
|
2056
1711
|
SpeedKey,
|
|
2057
1712
|
Freeze
|
|
2058
1713
|
};
|
|
2059
1714
|
const SCHEMAS_BY_KIND = Object.fromEntries(Object.values(ELEMENTS).map((component) => [component.kind, component.schema]));
|
|
2060
|
-
// src/jsx/
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
*/
|
|
2065
|
-
const slot = globalThis[Symbol.for("@cutcraft/kit/layer-clock")] ??= { impl: null };
|
|
2066
|
-
/** The layer's clock: progress, seconds elapsed and remaining, its duration, the composition time. */
|
|
2067
|
-
function useLayer() {
|
|
2068
|
-
const impl = slot.impl;
|
|
2069
|
-
if (!impl) throw new Error("useLayer() needs a Player. The kit does not render; install one with installLayerClock().");
|
|
2070
|
-
return impl();
|
|
2071
|
-
}
|
|
2072
|
-
/** Normalised time (0..1) inside a `<Layer>`; `useLayer().t`. */
|
|
2073
|
-
function useT() {
|
|
2074
|
-
const impl = slot.impl;
|
|
2075
|
-
if (!impl) throw new Error("useT() needs a Player. The kit does not render; install one with installLayerClock().");
|
|
2076
|
-
return impl().t;
|
|
2077
|
-
}
|
|
2078
|
-
function installLayerClock(fn) {
|
|
2079
|
-
slot.impl = fn;
|
|
2080
|
-
}
|
|
2081
|
-
// src/resolve/speed.ts
|
|
2082
|
-
/** CSS cubic-bezier(x1, y1, x2, y2) solved for y at a given x by bisection. */
|
|
2083
|
-
function bezier(x1, y1, x2, y2) {
|
|
2084
|
-
const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
|
|
2085
|
-
const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
|
|
2086
|
-
const sampleX = (u) => ((ax * u + bx) * u + cx) * u;
|
|
2087
|
-
const sampleY = (u) => ((ay * u + by) * u + cy) * u;
|
|
2088
|
-
return (x) => {
|
|
2089
|
-
if (x <= 0) return 0;
|
|
2090
|
-
if (x >= 1) return 1;
|
|
2091
|
-
let lo = 0, hi = 1, u = x;
|
|
2092
|
-
for (let i = 0; i < 24; i++) {
|
|
2093
|
-
const sx = sampleX(u);
|
|
2094
|
-
if (Math.abs(sx - x) < 1e-6) break;
|
|
2095
|
-
if (sx < x) lo = u;
|
|
2096
|
-
else hi = u;
|
|
2097
|
-
u = (lo + hi) / 2;
|
|
2098
|
-
}
|
|
2099
|
-
return sampleY(u);
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
const inOut = (f) => (x) => x < .5 ? f(2 * x) / 2 : 1 - f(2 - 2 * x) / 2;
|
|
2103
|
-
const out = (f) => (x) => 1 - f(1 - x);
|
|
2104
|
-
const pow = (n) => (x) => Math.pow(x, n);
|
|
2105
|
-
const sineIn = (x) => 1 - Math.cos(x * Math.PI / 2);
|
|
2106
|
-
const expoIn = (x) => x === 0 ? 0 : Math.pow(2, 10 * x - 10);
|
|
2107
|
-
const circIn = (x) => 1 - Math.sqrt(1 - x * x);
|
|
2108
|
-
const backIn = (s) => (x) => (s + 1) * x * x * x - s * x * x;
|
|
2109
|
-
const elasticIn = (p) => (x) => x === 0 ? 0 : x === 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * (2 * Math.PI) / p);
|
|
2110
|
-
const bounceFull = (x) => {
|
|
2111
|
-
const n1 = 7.5625, d1 = 2.75;
|
|
2112
|
-
if (x < 1 / d1) return n1 * x * x;
|
|
2113
|
-
if (x < 2 / d1) {
|
|
2114
|
-
x -= 1.5 / d1;
|
|
2115
|
-
return n1 * x * x + .75;
|
|
2116
|
-
}
|
|
2117
|
-
if (x < 2.5 / d1) {
|
|
2118
|
-
x -= 2.25 / d1;
|
|
2119
|
-
return n1 * x * x + .9375;
|
|
2120
|
-
}
|
|
2121
|
-
x -= 2.625 / d1;
|
|
2122
|
-
return n1 * x * x + .984375;
|
|
1715
|
+
// src/jsx/shaders.ts
|
|
1716
|
+
const STORE_KEY$1 = Symbol.for("cutcraft.shaders.store");
|
|
1717
|
+
const store$1 = globalThis[STORE_KEY$1] ??= {
|
|
1718
|
+
scope: null,
|
|
1719
|
+
global: /* @__PURE__ */ new Map()
|
|
2123
1720
|
};
|
|
2124
|
-
/**
|
|
2125
|
-
const
|
|
1721
|
+
/** The shaders a module loaded by `loadModule` declared, stored on its exports under this key. */
|
|
1722
|
+
const MODULE_SHADERS = Symbol.for("cutcraft.shaders");
|
|
1723
|
+
const NAME = /^[a-z][a-zA-Z0-9-]*$/;
|
|
1724
|
+
/** Names a param cannot take: the inputs every shader gets, and GLSL ES 3.00's built-in functions, which it would hide. */
|
|
1725
|
+
const RESERVED = /* @__PURE__ */ new Set([
|
|
1726
|
+
...[
|
|
1727
|
+
"image",
|
|
1728
|
+
"from",
|
|
1729
|
+
"to",
|
|
1730
|
+
"resolution",
|
|
1731
|
+
"time",
|
|
1732
|
+
"progress",
|
|
1733
|
+
"position",
|
|
1734
|
+
"uv",
|
|
1735
|
+
"main"
|
|
1736
|
+
],
|
|
1737
|
+
...[
|
|
1738
|
+
"radians",
|
|
1739
|
+
"degrees",
|
|
1740
|
+
"sin",
|
|
1741
|
+
"cos",
|
|
1742
|
+
"tan",
|
|
1743
|
+
"asin",
|
|
1744
|
+
"acos",
|
|
1745
|
+
"atan",
|
|
1746
|
+
"sinh",
|
|
1747
|
+
"cosh",
|
|
1748
|
+
"tanh",
|
|
1749
|
+
"asinh",
|
|
1750
|
+
"acosh"
|
|
1751
|
+
],
|
|
1752
|
+
...[
|
|
1753
|
+
"atanh",
|
|
1754
|
+
"pow",
|
|
1755
|
+
"exp",
|
|
1756
|
+
"log",
|
|
1757
|
+
"exp2",
|
|
1758
|
+
"log2",
|
|
1759
|
+
"sqrt",
|
|
1760
|
+
"inversesqrt",
|
|
1761
|
+
"abs",
|
|
1762
|
+
"sign",
|
|
1763
|
+
"floor",
|
|
1764
|
+
"trunc",
|
|
1765
|
+
"round"
|
|
1766
|
+
],
|
|
1767
|
+
...[
|
|
1768
|
+
"roundEven",
|
|
1769
|
+
"ceil",
|
|
1770
|
+
"fract",
|
|
1771
|
+
"mod",
|
|
1772
|
+
"modf",
|
|
1773
|
+
"min",
|
|
1774
|
+
"max",
|
|
1775
|
+
"clamp",
|
|
1776
|
+
"mix",
|
|
1777
|
+
"step",
|
|
1778
|
+
"smoothstep",
|
|
1779
|
+
"isnan"
|
|
1780
|
+
],
|
|
1781
|
+
...[
|
|
1782
|
+
"isinf",
|
|
1783
|
+
"length",
|
|
1784
|
+
"distance",
|
|
1785
|
+
"dot",
|
|
1786
|
+
"cross",
|
|
1787
|
+
"normalize",
|
|
1788
|
+
"faceforward",
|
|
1789
|
+
"reflect",
|
|
1790
|
+
"refract"
|
|
1791
|
+
],
|
|
1792
|
+
...[
|
|
1793
|
+
"matrixCompMult",
|
|
1794
|
+
"outerProduct",
|
|
1795
|
+
"transpose",
|
|
1796
|
+
"determinant",
|
|
1797
|
+
"inverse",
|
|
1798
|
+
"lessThan",
|
|
1799
|
+
"lessThanEqual"
|
|
1800
|
+
],
|
|
1801
|
+
...[
|
|
1802
|
+
"greaterThan",
|
|
1803
|
+
"greaterThanEqual",
|
|
1804
|
+
"equal",
|
|
1805
|
+
"notEqual",
|
|
1806
|
+
"any",
|
|
1807
|
+
"all",
|
|
1808
|
+
"not",
|
|
1809
|
+
"texture",
|
|
1810
|
+
"textureSize"
|
|
1811
|
+
],
|
|
1812
|
+
...[
|
|
1813
|
+
"textureLod",
|
|
1814
|
+
"texelFetch",
|
|
1815
|
+
"dFdx",
|
|
1816
|
+
"dFdy",
|
|
1817
|
+
"fwidth"
|
|
1818
|
+
]
|
|
1819
|
+
]);
|
|
1820
|
+
function declare$1(kind, options) {
|
|
1821
|
+
const { name, shader } = options;
|
|
1822
|
+
const builtIn = kind === "effect" ? VIDEO_EFFECT_TYPES : TRANSITION_TYPES;
|
|
1823
|
+
if (!NAME.test(name)) throw new Error(`${kind} "${name}": a name is lowercase letters, digits and dashes`);
|
|
1824
|
+
if (builtIn.includes(name)) throw new Error(`${kind} "${name}" is built in; give yours another name`);
|
|
1825
|
+
const fn = kind === "effect" ? "effect" : "transition";
|
|
1826
|
+
if (!new RegExp(`vec4\\s+${fn}\\s*\\(\\s*vec2\\s+\\w+\\s*\\)`).test(shader)) throw new Error(`${kind} "${name}": the shader defines vec4 ${fn}(vec2 uv)`);
|
|
1827
|
+
for (const param of Object.keys(options.params?.shape ?? {})) if (RESERVED.has(param)) throw new Error(`${kind} "${name}": param "${param}" is a name every shader already uses (a GLSL function or input); call it something else`);
|
|
1828
|
+
(store$1.scope ?? store$1.global).set(`${kind}:${name}`, {
|
|
1829
|
+
kind,
|
|
1830
|
+
name,
|
|
1831
|
+
params: options.params ?? z.object({}),
|
|
1832
|
+
shader
|
|
1833
|
+
});
|
|
1834
|
+
return name;
|
|
1835
|
+
}
|
|
2126
1836
|
/**
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
1837
|
+
* Declares an effect drawn by a GLSL function, and returns its name for `<Effect type>`. Call it at the top of the file.
|
|
1838
|
+
*
|
|
1839
|
+
* const glitch = defineEffect({
|
|
1840
|
+
* name: "glitch",
|
|
1841
|
+
* params: z.object({ amount: z.number().min(0).max(1).default(0.3) }),
|
|
1842
|
+
* shader: `vec4 effect(vec2 uv) {
|
|
1843
|
+
* float shift = amount * 0.02 * sin(time * 40.0 + uv.y * 80.0);
|
|
1844
|
+
* return vec4(texture(image, uv + vec2(shift, 0)).r, texture(image, uv).g, texture(image, uv - vec2(shift, 0)).b,
|
|
1845
|
+
* texture(image, uv).a);
|
|
1846
|
+
* }`,
|
|
1847
|
+
* });
|
|
1848
|
+
* <Clip id="city" src="asset:city"><Effect id="g" type={glitch} params={{ amount: 0.5 }} /></Clip>
|
|
2129
1849
|
*/
|
|
2130
|
-
function
|
|
2131
|
-
|
|
2132
|
-
const w0 = Math.sqrt(stiffness / mass), zeta = damping / (2 * Math.sqrt(stiffness * mass)), x0 = from - to;
|
|
2133
|
-
if (zeta < 1) {
|
|
2134
|
-
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
2135
|
-
return to + Math.exp(-zeta * w0 * time) * (x0 * Math.cos(wd * time) + zeta * w0 * x0 / wd * Math.sin(wd * time));
|
|
2136
|
-
}
|
|
2137
|
-
if (zeta === 1) return to + Math.exp(-w0 * time) * (x0 + w0 * x0 * time);
|
|
2138
|
-
const r1 = -w0 * (zeta - Math.sqrt(zeta * zeta - 1)), r2 = -w0 * (zeta + Math.sqrt(zeta * zeta - 1));
|
|
2139
|
-
return to + -r2 * x0 / (r1 - r2) * Math.exp(r1 * time) + r1 * x0 / (r1 - r2) * Math.exp(r2 * time);
|
|
2140
|
-
}
|
|
2141
|
-
/** The default spring squeezed into 0..1: it settles (to about 0.1%) at x = 1, overshooting about 16% on the way. */
|
|
2142
|
-
const SPRING_SETTLE = Math.log(1e3) / 5;
|
|
2143
|
-
const springEase = (x) => x >= 1 ? 1 : springAt(x * SPRING_SETTLE, 0, 1);
|
|
2144
|
-
const quadIn = pow(2);
|
|
2145
|
-
const cubicIn = pow(3);
|
|
2146
|
-
const quartIn = pow(4);
|
|
2147
|
-
const quintIn = pow(5);
|
|
2148
|
-
const back = backIn(1.70158);
|
|
2149
|
-
const backStrong = backIn(2.6);
|
|
2150
|
-
const elastic = elasticIn(3);
|
|
2151
|
-
const elasticSoft = elasticIn(4.5);
|
|
2152
|
-
const bounce = bounceOut(1);
|
|
2153
|
-
const bounceSmall = bounceOut(.5);
|
|
2154
|
-
const EASING_FN = {
|
|
2155
|
-
linear: (x) => x,
|
|
2156
|
-
ease: bezier(.25, .1, .25, 1),
|
|
2157
|
-
"ease-in": bezier(.42, 0, 1, 1),
|
|
2158
|
-
"ease-out": bezier(0, 0, .58, 1),
|
|
2159
|
-
"ease-in-out": bezier(.42, 0, .58, 1),
|
|
2160
|
-
hold: (x) => x >= 1 ? 1 : 0,
|
|
2161
|
-
bezier: bezier(.25, .1, .25, 1),
|
|
2162
|
-
smoothstep: (x) => x * x * (3 - 2 * x),
|
|
2163
|
-
smootherstep: (x) => x * x * x * (x * (x * 6 - 15) + 10),
|
|
2164
|
-
snappy: bezier(.2, .9, .3, 1),
|
|
2165
|
-
smooth: inOut(sineIn),
|
|
2166
|
-
spring: springEase,
|
|
2167
|
-
easeInQuad: quadIn,
|
|
2168
|
-
easeOutQuad: out(quadIn),
|
|
2169
|
-
easeInOutQuad: inOut(quadIn),
|
|
2170
|
-
easeInCubic: cubicIn,
|
|
2171
|
-
easeOutCubic: out(cubicIn),
|
|
2172
|
-
easeInOutCubic: inOut(cubicIn),
|
|
2173
|
-
easeInQuart: quartIn,
|
|
2174
|
-
easeOutQuart: out(quartIn),
|
|
2175
|
-
easeInOutQuart: inOut(quartIn),
|
|
2176
|
-
easeInQuint: quintIn,
|
|
2177
|
-
easeOutQuint: out(quintIn),
|
|
2178
|
-
easeInOutQuint: inOut(quintIn),
|
|
2179
|
-
easeInSine: sineIn,
|
|
2180
|
-
easeOutSine: out(sineIn),
|
|
2181
|
-
easeInOutSine: inOut(sineIn),
|
|
2182
|
-
easeInExpo: expoIn,
|
|
2183
|
-
easeOutExpo: out(expoIn),
|
|
2184
|
-
easeInOutExpo: inOut(expoIn),
|
|
2185
|
-
easeInCirc: circIn,
|
|
2186
|
-
easeOutCirc: out(circIn),
|
|
2187
|
-
easeInOutCirc: inOut(circIn),
|
|
2188
|
-
easeInBack: back,
|
|
2189
|
-
easeOutBack: out(back),
|
|
2190
|
-
easeInOutBack: inOut(back),
|
|
2191
|
-
easeInBackStrong: backStrong,
|
|
2192
|
-
easeOutBackStrong: out(backStrong),
|
|
2193
|
-
easeInOutBackStrong: inOut(backStrong),
|
|
2194
|
-
easeInElastic: elastic,
|
|
2195
|
-
easeOutElastic: out(elastic),
|
|
2196
|
-
easeInOutElastic: inOut(elastic),
|
|
2197
|
-
easeInElasticSoft: elasticSoft,
|
|
2198
|
-
easeOutElasticSoft: out(elasticSoft),
|
|
2199
|
-
easeInOutElasticSoft: inOut(elasticSoft),
|
|
2200
|
-
easeInBounce: out(bounce),
|
|
2201
|
-
easeOutBounce: bounce,
|
|
2202
|
-
easeInOutBounce: inOut(out(bounce)),
|
|
2203
|
-
easeInBounceSmall: out(bounceSmall),
|
|
2204
|
-
easeOutBounceSmall: bounceSmall,
|
|
2205
|
-
easeInOutBounceSmall: inOut(out(bounceSmall))
|
|
2206
|
-
};
|
|
2207
|
-
const INTEGRATION_STEPS = 64;
|
|
2208
|
-
/** Speed at clip time `u` under keys sorted by `at`: holds outside the keys, eases between them. */
|
|
2209
|
-
function rampSpeed(keys, u) {
|
|
2210
|
-
const first = keys[0];
|
|
2211
|
-
const last = keys[keys.length - 1];
|
|
2212
|
-
if (u <= first.at) return first.speed;
|
|
2213
|
-
if (u >= last.at) return last.speed;
|
|
2214
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
2215
|
-
const a = keys[i], b = keys[i + 1];
|
|
2216
|
-
if (u < a.at || u >= b.at) continue;
|
|
2217
|
-
const span = b.at - a.at;
|
|
2218
|
-
const fn = EASING_FN[a.easing] ?? EASING_FN.linear;
|
|
2219
|
-
const k = span <= 0 ? 1 : fn(Math.min(1, Math.max(0, (u - a.at) / span)));
|
|
2220
|
-
return a.speed + (b.speed - a.speed) * k;
|
|
2221
|
-
}
|
|
2222
|
-
return last.speed;
|
|
2223
|
-
}
|
|
2224
|
-
/** Source seconds played in `rel` clip seconds: ∫₀ʳᵉˡ speed by the trapezium rule, a fixed step count per key segment. */
|
|
2225
|
-
function rampAdvance(keys, rel) {
|
|
2226
|
-
if (rel <= 0) return 0;
|
|
2227
|
-
const bounds = [
|
|
2228
|
-
0,
|
|
2229
|
-
...keys.map((k) => k.at).filter((at) => at > 0 && at < rel),
|
|
2230
|
-
rel
|
|
2231
|
-
];
|
|
2232
|
-
let total = 0;
|
|
2233
|
-
for (let i = 0; i < bounds.length - 1; i++) {
|
|
2234
|
-
const a = bounds[i], b = bounds[i + 1];
|
|
2235
|
-
if (b <= a) continue;
|
|
2236
|
-
const h = (b - a) / INTEGRATION_STEPS;
|
|
2237
|
-
let sum = (rampSpeed(keys, a) + rampSpeed(keys, b)) / 2;
|
|
2238
|
-
for (let s = 1; s < INTEGRATION_STEPS; s++) sum += rampSpeed(keys, a + s * h);
|
|
2239
|
-
total += sum * h;
|
|
2240
|
-
}
|
|
2241
|
-
return total;
|
|
2242
|
-
}
|
|
2243
|
-
/** Clip seconds that play `advance` source seconds: the inverse of `rampAdvance`, by bisection (speed ≥ 0.1, so it is monotonic). */
|
|
2244
|
-
function rampInverse(keys, advance) {
|
|
2245
|
-
if (advance <= 0) return 0;
|
|
2246
|
-
let lo = 0, hi = advance / .1;
|
|
2247
|
-
for (let i = 0; i < 60 && hi - lo > 1e-9; i++) {
|
|
2248
|
-
const mid = (lo + hi) / 2;
|
|
2249
|
-
if (rampAdvance(keys, mid) < advance) lo = mid;
|
|
2250
|
-
else hi = mid;
|
|
2251
|
-
}
|
|
2252
|
-
return (lo + hi) / 2;
|
|
2253
|
-
}
|
|
2254
|
-
/** Element-relative `rel` with the holds before it taken out, or the held source second when `rel` is inside a hold. Holds sorted by `start`. */
|
|
2255
|
-
function freezeShift(holds, rel) {
|
|
2256
|
-
let shift = 0;
|
|
2257
|
-
for (const h of holds) {
|
|
2258
|
-
if (rel < h.start) break;
|
|
2259
|
-
if (rel < h.start + h.duration) return { held: h.at };
|
|
2260
|
-
shift += h.duration;
|
|
2261
|
-
}
|
|
2262
|
-
return { rel: rel - shift };
|
|
2263
|
-
}
|
|
2264
|
-
/** Element-relative time of clip time `u`: the inverse of `freezeShift` outside the holds. A time at a hold's start plays before it. */
|
|
2265
|
-
function freezeUnshift(holds, u) {
|
|
2266
|
-
let rel = u;
|
|
2267
|
-
for (const h of holds) if (h.start < rel - 1e-9) rel += h.duration;
|
|
2268
|
-
return rel;
|
|
2269
|
-
}
|
|
2270
|
-
/** Total seconds the holds add to a clip. */
|
|
2271
|
-
function holdTotal(holds) {
|
|
2272
|
-
return holds.reduce((n, h) => n + h.duration, 0);
|
|
2273
|
-
}
|
|
2274
|
-
/** A clip's `<SpeedKey>` children, sorted by `at`. */
|
|
2275
|
-
function speedKeysOf(children) {
|
|
2276
|
-
return children.filter((c) => c.kind === "speedKey").map((c) => ({
|
|
2277
|
-
at: Number(c.props.at ?? 0),
|
|
2278
|
-
speed: Number(c.props.speed ?? 1),
|
|
2279
|
-
easing: c.props.easing
|
|
2280
|
-
})).sort((a, b) => a.at - b.at);
|
|
2281
|
-
}
|
|
2282
|
-
/** A clip's `<Freeze>` children, sorted by `start`. */
|
|
2283
|
-
function freezeHoldsOf(children) {
|
|
2284
|
-
return children.filter((c) => c.kind === "freeze").map((c) => ({
|
|
2285
|
-
at: Number(c.props.at ?? 0),
|
|
2286
|
-
start: Number(c.props.start ?? 0),
|
|
2287
|
-
duration: Number(c.props.duration ?? 0)
|
|
2288
|
-
})).sort((a, b) => a.start - b.start);
|
|
1850
|
+
function defineEffect(options) {
|
|
1851
|
+
return declare$1("effect", options);
|
|
2289
1852
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
*
|
|
2293
|
-
*
|
|
1853
|
+
/**
|
|
1854
|
+
* Declares a transition drawn by a GLSL function of the two pictures, and returns its name for `<Transition type>`.
|
|
1855
|
+
*
|
|
1856
|
+
* const sweep = defineTransition({
|
|
1857
|
+
* name: "sweep",
|
|
1858
|
+
* shader: `vec4 transition(vec2 uv) { return uv.x < progress ? texture(to, uv) : texture(from, uv); }`,
|
|
1859
|
+
* });
|
|
2294
1860
|
*/
|
|
2295
|
-
function
|
|
2296
|
-
|
|
2297
|
-
for (let i = 1; i < inputRange.length; i++) if (!(inputRange[i] > inputRange[i - 1])) throw new Error(`interpolate() wants an increasing input range, got [${inputRange.join(", ")}]`);
|
|
2298
|
-
let i = 1;
|
|
2299
|
-
while (i < inputRange.length - 1 && input > inputRange[i]) i++;
|
|
2300
|
-
const a = inputRange[i - 1], b = inputRange[i];
|
|
2301
|
-
let x = (input - a) / (b - a);
|
|
2302
|
-
if (x < 0 && options.extrapolateLeft === "clamp") x = 0;
|
|
2303
|
-
if (x > 1 && options.extrapolateRight === "clamp") x = 1;
|
|
2304
|
-
const k = x >= 0 && x <= 1 && options.easing ? EASING_FN[options.easing](x) : x;
|
|
2305
|
-
const lo = outputRange[i - 1];
|
|
2306
|
-
return lo + (outputRange[i] - lo) * k;
|
|
1861
|
+
function defineTransition(options) {
|
|
1862
|
+
return declare$1("transition", options);
|
|
2307
1863
|
}
|
|
2308
|
-
/**
|
|
2309
|
-
function
|
|
2310
|
-
const
|
|
2311
|
-
|
|
1864
|
+
/** Runs `load` and returns the shaders declared while it ran: how `loadModule` keeps each file's apart. */
|
|
1865
|
+
function collectShaders(load) {
|
|
1866
|
+
const outer = store$1.scope;
|
|
1867
|
+
const scope = /* @__PURE__ */ new Map();
|
|
1868
|
+
store$1.scope = scope;
|
|
1869
|
+
try {
|
|
1870
|
+
load();
|
|
1871
|
+
} finally {
|
|
1872
|
+
store$1.scope = outer;
|
|
1873
|
+
}
|
|
1874
|
+
return [...scope.values()];
|
|
2312
1875
|
}
|
|
2313
|
-
/**
|
|
2314
|
-
function
|
|
2315
|
-
return
|
|
1876
|
+
/** The shaders a module declared: those `loadModule` recorded for it, or else every one declared outside a load. */
|
|
1877
|
+
function shadersOf(mod) {
|
|
1878
|
+
return mod[MODULE_SHADERS] ?? [...store$1.global.values()];
|
|
2316
1879
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
return ref.startsWith("asset:") ? ref.slice(6) : ref;
|
|
1880
|
+
/** The shader a file declares under this name for an `<Effect>` or `<Transition>`, if any. */
|
|
1881
|
+
function shaderFor(shaders, kind, type) {
|
|
1882
|
+
return shaders.find((s) => s.kind === kind && s.name === type);
|
|
2321
1883
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
1884
|
+
/** Whether a file draws anything with a shader it declares: what only a renderer with WebGL draws. */
|
|
1885
|
+
function usesShaders(state) {
|
|
1886
|
+
if (state.shaders.length === 0) return false;
|
|
1887
|
+
for (const p of state.items.values()) if ((p.kind === "effect" || p.kind === "transition") && shaderFor(state.shaders, p.kind, p.props.type)) return true;
|
|
1888
|
+
return false;
|
|
2324
1889
|
}
|
|
2325
1890
|
// src/manifest/timeref.ts
|
|
2326
1891
|
const REF = /^(word|shot|silence):(\d+)(\.end)?$/;
|
|
@@ -2385,347 +1950,432 @@ function resolveTimeRef(ref, asset, markers, ctx) {
|
|
|
2385
1950
|
}
|
|
2386
1951
|
}
|
|
2387
1952
|
}
|
|
2388
|
-
// src/
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
visit(node, depth);
|
|
2393
|
-
for (const child of node.children) walk(child, visit, depth + 1);
|
|
1953
|
+
// src/manifest/types.ts
|
|
1954
|
+
const EMPTY_MANIFEST = { assets: {} };
|
|
1955
|
+
function assetIdOf(ref) {
|
|
1956
|
+
return ref.startsWith("asset:") ? ref.slice(6) : ref;
|
|
2394
1957
|
}
|
|
2395
|
-
function
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
1958
|
+
function lookupAsset(manifest, ref) {
|
|
1959
|
+
return manifest.assets[assetIdOf(ref)] ?? null;
|
|
1960
|
+
}
|
|
1961
|
+
// src/theme/theme.ts
|
|
1962
|
+
/** Kinds whose entrance and exit a motion style fills. */
|
|
1963
|
+
const MOTION_KINDS = /* @__PURE__ */ new Set([
|
|
1964
|
+
"title",
|
|
1965
|
+
"shape",
|
|
1966
|
+
"svg",
|
|
1967
|
+
"sticker",
|
|
1968
|
+
"graphic"
|
|
1969
|
+
]);
|
|
1970
|
+
/** Props whose strings are content, never tokens. */
|
|
1971
|
+
const CONTENT_PROPS = /* @__PURE__ */ new Set([
|
|
1972
|
+
"text",
|
|
1973
|
+
"content",
|
|
1974
|
+
"src",
|
|
1975
|
+
"label",
|
|
1976
|
+
"name",
|
|
1977
|
+
"category",
|
|
1978
|
+
"theme"
|
|
1979
|
+
]);
|
|
1980
|
+
const byInput = /* @__PURE__ */ new WeakMap();
|
|
1981
|
+
let undeclared = null;
|
|
1982
|
+
/** The theme a composition's raw `theme` prop describes. Memoised per object; an invalid theme falls back to its base (the schema reports why). */
|
|
1983
|
+
function resolveTheme(raw) {
|
|
1984
|
+
if (raw === void 0 || raw === null || typeof raw !== "object") return undeclared ??= build(void 0, false);
|
|
1985
|
+
const hit = byInput.get(raw);
|
|
1986
|
+
if (hit) return hit;
|
|
1987
|
+
const parsed = themeSchema.safeParse(raw);
|
|
1988
|
+
const theme = build(parsed.success ? parsed.data : baseOnly(raw), true);
|
|
1989
|
+
byInput.set(raw, theme);
|
|
1990
|
+
return theme;
|
|
1991
|
+
}
|
|
1992
|
+
/** The value of `$name`, or undefined. */
|
|
1993
|
+
function themeToken(theme, name) {
|
|
1994
|
+
return Object.hasOwn(theme.tokens, name) ? theme.tokens[name] : void 0;
|
|
2399
1995
|
}
|
|
2400
|
-
// src/resolve/collect.ts
|
|
2401
|
-
const NO_BINDINGS = Object.freeze({});
|
|
2402
1996
|
/**
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
1997
|
+
* An element's raw props with its theme applied, before the schema parses them:
|
|
1998
|
+
* the preset its `style` names (or the kind's `default` preset) fills the props
|
|
1999
|
+
* the element leaves out, the motion style fills `enter`/`exit` (a transition's
|
|
2000
|
+
* type and duration), and whole-string `$token` values become theme values. The
|
|
2001
|
+
* element's own props always win. Returns the input object when nothing was themed.
|
|
2406
2002
|
*/
|
|
2407
|
-
function
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2003
|
+
function applyTheme(kind, raw, theme) {
|
|
2004
|
+
const named = typeof raw.style === "string" ? raw.style : null;
|
|
2005
|
+
if (!theme.declared && named === null && !hasToken(raw)) return {
|
|
2006
|
+
props: raw,
|
|
2007
|
+
from: EMPTY,
|
|
2008
|
+
issues: []
|
|
2413
2009
|
};
|
|
2414
|
-
const
|
|
2415
|
-
const
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2010
|
+
const out = { ...raw };
|
|
2011
|
+
const from = {};
|
|
2012
|
+
const issues = [];
|
|
2013
|
+
const presets = theme.styles[kind];
|
|
2014
|
+
const presetName = named ?? (theme.declared ? "default" : null);
|
|
2015
|
+
if (presetName !== null && kind !== "composition") {
|
|
2016
|
+
const preset = presets?.[presetName];
|
|
2017
|
+
if (preset) {
|
|
2018
|
+
if (kind === "captions") {
|
|
2019
|
+
const own = typeof raw.style === "object" && raw.style !== null ? raw.style : {};
|
|
2020
|
+
out.style = {
|
|
2021
|
+
...preset,
|
|
2022
|
+
...own
|
|
2023
|
+
};
|
|
2024
|
+
from.style = `style:${presetName}`;
|
|
2025
|
+
} else for (const [key, value] of Object.entries(preset)) if (raw[key] === void 0) {
|
|
2026
|
+
out[key] = value;
|
|
2027
|
+
from[key] = `style:${presetName}`;
|
|
2028
|
+
}
|
|
2029
|
+
} else if (named !== null) {
|
|
2030
|
+
const known = Object.keys(presets ?? {});
|
|
2031
|
+
issues.push({
|
|
2032
|
+
code: "UNKNOWN_STYLE",
|
|
2033
|
+
prop: "style",
|
|
2034
|
+
message: `no ${kind} style "${named}" in the theme${known.length ? ` (${known.join(", ")})` : ""}`
|
|
2035
|
+
});
|
|
2036
|
+
if (kind === "captions") delete out.style;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
const motion = theme.declared ? theme.motion : null;
|
|
2040
|
+
if (motion && MOTION_KINDS.has(kind) && !(kind === "title" && out.animation !== void 0)) {
|
|
2041
|
+
if (out.enter === void 0 && motion.enter) {
|
|
2042
|
+
out.enter = motion.enter;
|
|
2043
|
+
from.enter = "motion";
|
|
2044
|
+
}
|
|
2045
|
+
if (out.exit === void 0 && motion.exit) {
|
|
2046
|
+
out.exit = motion.exit;
|
|
2047
|
+
from.exit = "motion";
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
if (motion && kind === "transition" && motion.transition) {
|
|
2051
|
+
if (out.type === void 0 && motion.transition.type) {
|
|
2052
|
+
out.type = motion.transition.type;
|
|
2053
|
+
from.type = "motion";
|
|
2054
|
+
}
|
|
2055
|
+
if (out.duration === void 0 && motion.transition.duration !== void 0) {
|
|
2056
|
+
out.duration = motion.transition.duration;
|
|
2057
|
+
from.duration = "motion";
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
if (kind === "composition" && theme.declared && out.background === void 0) {
|
|
2061
|
+
out.background = {
|
|
2062
|
+
mode: "color",
|
|
2063
|
+
color: theme.palette.background
|
|
2426
2064
|
};
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2065
|
+
from.background = "theme";
|
|
2066
|
+
}
|
|
2067
|
+
for (const key of Object.keys(out)) {
|
|
2068
|
+
if (CONTENT_PROPS.has(key)) continue;
|
|
2069
|
+
out[key] = substitute(out[key], key, theme, from, issues);
|
|
2070
|
+
}
|
|
2071
|
+
if (Object.keys(from).length === 0 && issues.length === 0) return {
|
|
2072
|
+
props: raw,
|
|
2073
|
+
from: EMPTY,
|
|
2074
|
+
issues
|
|
2075
|
+
};
|
|
2076
|
+
return {
|
|
2077
|
+
props: out,
|
|
2078
|
+
from: Object.keys(from).length ? from : EMPTY,
|
|
2079
|
+
issues
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
const EMPTY = Object.freeze({});
|
|
2083
|
+
function substitute(value, path, theme, from, issues) {
|
|
2084
|
+
if (typeof value === "string") {
|
|
2085
|
+
const m = TOKEN_PATTERN.exec(value);
|
|
2086
|
+
if (!m) return value;
|
|
2087
|
+
const name = m[1];
|
|
2088
|
+
const resolved = themeToken(theme, name);
|
|
2089
|
+
if (resolved === void 0) {
|
|
2090
|
+
issues.push({
|
|
2091
|
+
code: "UNKNOWN_TOKEN",
|
|
2092
|
+
prop: path,
|
|
2093
|
+
message: `unknown theme token ${value}`
|
|
2094
|
+
});
|
|
2095
|
+
return value;
|
|
2096
|
+
}
|
|
2097
|
+
from[path] = value;
|
|
2098
|
+
return resolved;
|
|
2099
|
+
}
|
|
2100
|
+
if (Array.isArray(value)) {
|
|
2101
|
+
let changed = null;
|
|
2102
|
+
value.forEach((v, i) => {
|
|
2103
|
+
const s = substitute(v, `${path}.${i}`, theme, from, issues);
|
|
2104
|
+
if (s !== v) (changed ??= [...value])[i] = s;
|
|
2431
2105
|
});
|
|
2432
|
-
return
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2106
|
+
return changed ?? value;
|
|
2107
|
+
}
|
|
2108
|
+
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
2109
|
+
let changed = null;
|
|
2110
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2111
|
+
const s = substitute(v, `${path}.${k}`, theme, from, issues);
|
|
2112
|
+
if (s !== v) (changed ??= { ...value })[k] = s;
|
|
2113
|
+
}
|
|
2114
|
+
return changed ?? value;
|
|
2115
|
+
}
|
|
2116
|
+
return value;
|
|
2117
|
+
}
|
|
2118
|
+
function hasToken(value) {
|
|
2119
|
+
if (typeof value === "string") return value.startsWith("$");
|
|
2120
|
+
if (Array.isArray(value)) return value.some(hasToken);
|
|
2121
|
+
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
2122
|
+
for (const [k, v] of Object.entries(value)) if (!CONTENT_PROPS.has(k) && hasToken(v)) return true;
|
|
2123
|
+
}
|
|
2124
|
+
return false;
|
|
2125
|
+
}
|
|
2126
|
+
/** For an invalid theme: keep its `extends` when that alone is valid, so the rest of the file still themes sensibly. */
|
|
2127
|
+
function baseOnly(raw) {
|
|
2128
|
+
const ext = raw.extends;
|
|
2129
|
+
const parsed = themeSchema.safeParse(typeof ext === "string" ? { extends: ext } : {});
|
|
2130
|
+
return parsed.success ? parsed.data : void 0;
|
|
2131
|
+
}
|
|
2132
|
+
function build(input, declared) {
|
|
2133
|
+
const base = input?.extends ?? "midnight";
|
|
2134
|
+
const def = THEMES[base];
|
|
2135
|
+
const palette = {
|
|
2136
|
+
...def.palette,
|
|
2137
|
+
...input?.palette
|
|
2138
|
+
};
|
|
2139
|
+
const fonts = {
|
|
2140
|
+
...def.fonts,
|
|
2141
|
+
...input?.fonts
|
|
2142
|
+
};
|
|
2143
|
+
const radius = input?.radius ?? def.radius;
|
|
2144
|
+
const m = input?.motion ?? def.motion;
|
|
2145
|
+
const style = typeof m === "string" ? MOTIONS[m] : m;
|
|
2146
|
+
const motion = style.enter || style.exit || style.transition ? style : null;
|
|
2147
|
+
const styles = {};
|
|
2148
|
+
for (const [kind, presets] of Object.entries(def.styles)) styles[kind] = { ...presets };
|
|
2149
|
+
for (const [kind, presets] of Object.entries(input?.styles ?? {})) {
|
|
2150
|
+
const merged = styles[kind] ??= {};
|
|
2151
|
+
for (const [name, preset] of Object.entries(presets ?? {})) merged[name] = {
|
|
2152
|
+
...merged[name],
|
|
2153
|
+
...preset
|
|
2436
2154
|
};
|
|
2437
2155
|
}
|
|
2438
|
-
const multi = new Set([...walker.helperUses].filter(([, n]) => n > 1).map(([name]) => name));
|
|
2439
|
-
if (multi.size > 0) {
|
|
2440
|
-
for (const node of walker.byId.values()) if (node.origin && multi.has(node.origin.helper) && node.editable === "literal") node.editable = "generated";
|
|
2441
|
-
}
|
|
2442
2156
|
return {
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2157
|
+
declared,
|
|
2158
|
+
base,
|
|
2159
|
+
palette,
|
|
2160
|
+
fonts,
|
|
2161
|
+
radius,
|
|
2162
|
+
motion,
|
|
2163
|
+
styles,
|
|
2164
|
+
tokens: {
|
|
2165
|
+
...palette,
|
|
2166
|
+
...fonts,
|
|
2167
|
+
radius
|
|
2168
|
+
}
|
|
2446
2169
|
};
|
|
2447
2170
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
}
|
|
2466
|
-
if (isVocabulary(type)) {
|
|
2467
|
-
if (type.kind === "layer") {
|
|
2468
|
-
const node = makeNode(element, type.kind, parent, walker, origin, [], true);
|
|
2469
|
-
const interior = interiorOf(element.props.children);
|
|
2470
|
-
if (interior) {
|
|
2471
|
-
const withInterior = {
|
|
2472
|
-
...node,
|
|
2473
|
-
interior
|
|
2474
|
-
};
|
|
2475
|
-
walker.byId.set(node.id, withInterior);
|
|
2476
|
-
out.push(withInterior);
|
|
2477
|
-
} else out.push(node);
|
|
2478
|
-
return;
|
|
2479
|
-
}
|
|
2480
|
-
const placeholder = makeNode(element, type.kind, parent, walker, origin, [], false);
|
|
2481
|
-
const children = collectChildren(element.props.children, placeholder.id, walker, origin);
|
|
2482
|
-
const node = {
|
|
2483
|
-
...placeholder,
|
|
2484
|
-
children
|
|
2485
|
-
};
|
|
2486
|
-
walker.byId.set(node.id, node);
|
|
2487
|
-
out.push(node);
|
|
2488
|
-
return;
|
|
2489
|
-
}
|
|
2490
|
-
if (typeof type === "function") {
|
|
2491
|
-
const helper = type;
|
|
2492
|
-
const name = helper.name || "anonymous";
|
|
2493
|
-
const uses = (walker.helperUses.get(name) ?? 0) + 1;
|
|
2494
|
-
walker.helperUses.set(name, uses);
|
|
2495
|
-
let produced;
|
|
2496
|
-
try {
|
|
2497
|
-
produced = helper(element.props);
|
|
2498
|
-
} catch (error) {
|
|
2499
|
-
walker.errors.push({
|
|
2500
|
-
code: "SCHEMA",
|
|
2501
|
-
id: parent ?? "composition",
|
|
2502
|
-
message: `helper ${name} threw while expanding: ${String(error)}`
|
|
2503
|
-
});
|
|
2504
|
-
return;
|
|
2505
|
-
}
|
|
2506
|
-
const childOrigin = origin ?? {
|
|
2507
|
-
helper: name,
|
|
2508
|
-
callerId: parent,
|
|
2509
|
-
callIndex: uses - 1
|
|
2510
|
-
};
|
|
2511
|
-
out.push(...collectChildren(produced, parent, walker, childOrigin));
|
|
2512
|
-
return;
|
|
2171
|
+
// src/resolve/speed.ts
|
|
2172
|
+
/** CSS cubic-bezier(x1, y1, x2, y2) solved for y at a given x by bisection. */
|
|
2173
|
+
function bezier(x1, y1, x2, y2) {
|
|
2174
|
+
const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
|
|
2175
|
+
const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
|
|
2176
|
+
const sampleX = (u) => ((ax * u + bx) * u + cx) * u;
|
|
2177
|
+
const sampleY = (u) => ((ay * u + by) * u + cy) * u;
|
|
2178
|
+
return (x) => {
|
|
2179
|
+
if (x <= 0) return 0;
|
|
2180
|
+
if (x >= 1) return 1;
|
|
2181
|
+
let lo = 0, hi = 1, u = x;
|
|
2182
|
+
for (let i = 0; i < 24; i++) {
|
|
2183
|
+
const sx = sampleX(u);
|
|
2184
|
+
if (Math.abs(sx - x) < 1e-6) break;
|
|
2185
|
+
if (sx < x) lo = u;
|
|
2186
|
+
else hi = u;
|
|
2187
|
+
u = (lo + hi) / 2;
|
|
2513
2188
|
}
|
|
2514
|
-
|
|
2515
|
-
});
|
|
2516
|
-
return out;
|
|
2517
|
-
}
|
|
2518
|
-
function makeNode(element, kind, parent, walker, origin, children, opaqueInterior) {
|
|
2519
|
-
const { children: rawChildren, ...props } = element.props;
|
|
2520
|
-
const idValue = props.id ?? (kind === "composition" ? "composition" : void 0);
|
|
2521
|
-
const id = typeof idValue === "string" && idValue.length > 0 ? idValue : `${kind}-${walker.byId.size + 1}`;
|
|
2522
|
-
if (typeof idValue !== "string" || idValue.length === 0) walker.errors.push({
|
|
2523
|
-
code: "SCHEMA",
|
|
2524
|
-
id,
|
|
2525
|
-
prop: "id",
|
|
2526
|
-
message: `<${kind}> needs a literal id`
|
|
2527
|
-
});
|
|
2528
|
-
if (walker.byId.has(id)) walker.errors.push({
|
|
2529
|
-
code: "DUPLICATE_ID",
|
|
2530
|
-
id,
|
|
2531
|
-
message: `id "${id}" is used more than once`
|
|
2532
|
-
});
|
|
2533
|
-
const text = textOf(rawChildren);
|
|
2534
|
-
const indexed = walker.index?.elements.get(id);
|
|
2535
|
-
let editable = "literal";
|
|
2536
|
-
if (walker.index && (!indexed || indexed.generated)) editable = "generated";
|
|
2537
|
-
const lockedProps = indexed?.lockedProps ?? [];
|
|
2538
|
-
const boundProps = indexed?.boundProps ?? NO_BINDINGS;
|
|
2539
|
-
const node = {
|
|
2540
|
-
id,
|
|
2541
|
-
kind,
|
|
2542
|
-
props,
|
|
2543
|
-
children,
|
|
2544
|
-
parent,
|
|
2545
|
-
editable,
|
|
2546
|
-
lockedProps,
|
|
2547
|
-
boundProps,
|
|
2548
|
-
...text !== void 0 ? { text } : {},
|
|
2549
|
-
...origin ? { origin } : {}
|
|
2189
|
+
return sampleY(u);
|
|
2550
2190
|
};
|
|
2551
|
-
if (opaqueInterior) {
|
|
2552
|
-
const opaque = {
|
|
2553
|
-
...node,
|
|
2554
|
-
editable: editable === "generated" ? "generated" : "literal"
|
|
2555
|
-
};
|
|
2556
|
-
walker.byId.set(id, opaque);
|
|
2557
|
-
return opaque;
|
|
2558
|
-
}
|
|
2559
|
-
walker.byId.set(id, node);
|
|
2560
|
-
return node;
|
|
2561
2191
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2192
|
+
const inOut = (f) => (x) => x < .5 ? f(2 * x) / 2 : 1 - f(2 - 2 * x) / 2;
|
|
2193
|
+
const out = (f) => (x) => 1 - f(1 - x);
|
|
2194
|
+
const pow = (n) => (x) => x ** n;
|
|
2195
|
+
const sineIn = (x) => 1 - Math.cos(x * Math.PI / 2);
|
|
2196
|
+
const expoIn = (x) => x === 0 ? 0 : 2 ** (10 * x - 10);
|
|
2197
|
+
const circIn = (x) => 1 - Math.sqrt(1 - x * x);
|
|
2198
|
+
const backIn = (s) => (x) => (s + 1) * x * x * x - s * x * x;
|
|
2199
|
+
const elasticIn = (p) => (x) => x === 0 ? 0 : x === 1 ? 1 : -(2 ** (10 * x - 10)) * Math.sin((x * 10 - 10.75) * (2 * Math.PI) / p);
|
|
2200
|
+
const bounceFull = (x) => {
|
|
2201
|
+
const n1 = 7.5625, d1 = 2.75;
|
|
2202
|
+
if (x < 1 / d1) return n1 * x * x;
|
|
2203
|
+
if (x < 2 / d1) {
|
|
2204
|
+
x -= 1.5 / d1;
|
|
2205
|
+
return n1 * x * x + .75;
|
|
2206
|
+
}
|
|
2207
|
+
if (x < 2.5 / d1) {
|
|
2208
|
+
x -= 2.25 / d1;
|
|
2209
|
+
return n1 * x * x + .9375;
|
|
2210
|
+
}
|
|
2211
|
+
x -= 2.625 / d1;
|
|
2212
|
+
return n1 * x * x + .984375;
|
|
2213
|
+
};
|
|
2214
|
+
/** A bounce blended toward linear by `1 - h`: `h = 1` is the full bounce, smaller values damp it. */
|
|
2215
|
+
const bounceOut = (h) => (x) => x + (bounceFull(x) - x) * h;
|
|
2216
|
+
/**
|
|
2217
|
+
* Where a spring released at rest from `from` toward `to` is after `time` seconds: the closed-form
|
|
2218
|
+
* damped oscillator, under-, critically or over-damped. Defaults are mass 1, damping 10, stiffness 100.
|
|
2219
|
+
*/
|
|
2220
|
+
function springAt(time, from, to, { mass = 1, damping = 10, stiffness = 100 } = {}) {
|
|
2221
|
+
if (time <= 0) return from;
|
|
2222
|
+
const w0 = Math.sqrt(stiffness / mass), zeta = damping / (2 * Math.sqrt(stiffness * mass)), x0 = from - to;
|
|
2223
|
+
if (zeta < 1) {
|
|
2224
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
2225
|
+
return to + Math.exp(-zeta * w0 * time) * (x0 * Math.cos(wd * time) + zeta * w0 * x0 / wd * Math.sin(wd * time));
|
|
2226
|
+
}
|
|
2227
|
+
if (zeta === 1) return to + Math.exp(-w0 * time) * (x0 + w0 * x0 * time);
|
|
2228
|
+
const r1 = -w0 * (zeta - Math.sqrt(zeta * zeta - 1)), r2 = -w0 * (zeta + Math.sqrt(zeta * zeta - 1));
|
|
2229
|
+
return to + -r2 * x0 / (r1 - r2) * Math.exp(r1 * time) + r1 * x0 / (r1 - r2) * Math.exp(r2 * time);
|
|
2571
2230
|
}
|
|
2572
|
-
/**
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2231
|
+
/** The default spring squeezed into 0..1: it settles (to about 0.1%) at x = 1, overshooting about 16% on the way. */
|
|
2232
|
+
const SPRING_SETTLE = Math.log(1e3) / 5;
|
|
2233
|
+
const springEase = (x) => x >= 1 ? 1 : springAt(x * SPRING_SETTLE, 0, 1);
|
|
2234
|
+
const quadIn = pow(2);
|
|
2235
|
+
const cubicIn = pow(3);
|
|
2236
|
+
const quartIn = pow(4);
|
|
2237
|
+
const quintIn = pow(5);
|
|
2238
|
+
const back = backIn(1.70158);
|
|
2239
|
+
const backStrong = backIn(2.6);
|
|
2240
|
+
const elastic = elasticIn(3);
|
|
2241
|
+
const elasticSoft = elasticIn(4.5);
|
|
2242
|
+
const bounce = bounceOut(1);
|
|
2243
|
+
const bounceSmall = bounceOut(.5);
|
|
2244
|
+
const EASING_FN = {
|
|
2245
|
+
linear: (x) => x,
|
|
2246
|
+
ease: bezier(.25, .1, .25, 1),
|
|
2247
|
+
"ease-in": bezier(.42, 0, 1, 1),
|
|
2248
|
+
"ease-out": bezier(0, 0, .58, 1),
|
|
2249
|
+
"ease-in-out": bezier(.42, 0, .58, 1),
|
|
2250
|
+
hold: (x) => x >= 1 ? 1 : 0,
|
|
2251
|
+
bezier: bezier(.25, .1, .25, 1),
|
|
2252
|
+
smoothstep: (x) => x * x * (3 - 2 * x),
|
|
2253
|
+
smootherstep: (x) => x * x * x * (x * (x * 6 - 15) + 10),
|
|
2254
|
+
snappy: bezier(.2, .9, .3, 1),
|
|
2255
|
+
smooth: inOut(sineIn),
|
|
2256
|
+
spring: springEase,
|
|
2257
|
+
easeInQuad: quadIn,
|
|
2258
|
+
easeOutQuad: out(quadIn),
|
|
2259
|
+
easeInOutQuad: inOut(quadIn),
|
|
2260
|
+
easeInCubic: cubicIn,
|
|
2261
|
+
easeOutCubic: out(cubicIn),
|
|
2262
|
+
easeInOutCubic: inOut(cubicIn),
|
|
2263
|
+
easeInQuart: quartIn,
|
|
2264
|
+
easeOutQuart: out(quartIn),
|
|
2265
|
+
easeInOutQuart: inOut(quartIn),
|
|
2266
|
+
easeInQuint: quintIn,
|
|
2267
|
+
easeOutQuint: out(quintIn),
|
|
2268
|
+
easeInOutQuint: inOut(quintIn),
|
|
2269
|
+
easeInSine: sineIn,
|
|
2270
|
+
easeOutSine: out(sineIn),
|
|
2271
|
+
easeInOutSine: inOut(sineIn),
|
|
2272
|
+
easeInExpo: expoIn,
|
|
2273
|
+
easeOutExpo: out(expoIn),
|
|
2274
|
+
easeInOutExpo: inOut(expoIn),
|
|
2275
|
+
easeInCirc: circIn,
|
|
2276
|
+
easeOutCirc: out(circIn),
|
|
2277
|
+
easeInOutCirc: inOut(circIn),
|
|
2278
|
+
easeInBack: back,
|
|
2279
|
+
easeOutBack: out(back),
|
|
2280
|
+
easeInOutBack: inOut(back),
|
|
2281
|
+
easeInBackStrong: backStrong,
|
|
2282
|
+
easeOutBackStrong: out(backStrong),
|
|
2283
|
+
easeInOutBackStrong: inOut(backStrong),
|
|
2284
|
+
easeInElastic: elastic,
|
|
2285
|
+
easeOutElastic: out(elastic),
|
|
2286
|
+
easeInOutElastic: inOut(elastic),
|
|
2287
|
+
easeInElasticSoft: elasticSoft,
|
|
2288
|
+
easeOutElasticSoft: out(elasticSoft),
|
|
2289
|
+
easeInOutElasticSoft: inOut(elasticSoft),
|
|
2290
|
+
easeInBounce: out(bounce),
|
|
2291
|
+
easeOutBounce: bounce,
|
|
2292
|
+
easeInOutBounce: inOut(out(bounce)),
|
|
2293
|
+
easeInBounceSmall: out(bounceSmall),
|
|
2294
|
+
easeOutBounceSmall: bounceSmall,
|
|
2295
|
+
easeInOutBounceSmall: inOut(out(bounceSmall))
|
|
2296
|
+
};
|
|
2297
|
+
const INTEGRATION_STEPS = 64;
|
|
2298
|
+
/** Speed at clip time `u` under keys sorted by `at`: holds outside the keys, eases between them. */
|
|
2299
|
+
function rampSpeed(keys, u) {
|
|
2300
|
+
const first = keys[0];
|
|
2301
|
+
const last = keys[keys.length - 1];
|
|
2302
|
+
if (u <= first.at) return first.speed;
|
|
2303
|
+
if (u >= last.at) return last.speed;
|
|
2304
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2305
|
+
const a = keys[i], b = keys[i + 1];
|
|
2306
|
+
if (u < a.at || u >= b.at) continue;
|
|
2307
|
+
const span = b.at - a.at;
|
|
2308
|
+
const fn = EASING_FN[a.easing] ?? EASING_FN.linear;
|
|
2309
|
+
const k = span <= 0 ? 1 : fn(Math.min(1, Math.max(0, (u - a.at) / span)));
|
|
2310
|
+
return a.speed + (b.speed - a.speed) * k;
|
|
2311
|
+
}
|
|
2312
|
+
return last.speed;
|
|
2582
2313
|
}
|
|
2583
|
-
|
|
2584
|
-
function
|
|
2585
|
-
|
|
2314
|
+
/** Source seconds played in `rel` clip seconds: ∫₀ʳᵉˡ speed by the trapezium rule, a fixed step count per key segment. */
|
|
2315
|
+
function rampAdvance(keys, rel) {
|
|
2316
|
+
if (rel <= 0) return 0;
|
|
2317
|
+
const bounds = [
|
|
2318
|
+
0,
|
|
2319
|
+
...keys.map((k) => k.at).filter((at) => at > 0 && at < rel),
|
|
2320
|
+
rel
|
|
2321
|
+
];
|
|
2322
|
+
let total = 0;
|
|
2323
|
+
for (let i = 0; i < bounds.length - 1; i++) {
|
|
2324
|
+
const a = bounds[i], b = bounds[i + 1];
|
|
2325
|
+
if (b <= a) continue;
|
|
2326
|
+
const h = (b - a) / INTEGRATION_STEPS;
|
|
2327
|
+
let sum = (rampSpeed(keys, a) + rampSpeed(keys, b)) / 2;
|
|
2328
|
+
for (let s = 1; s < INTEGRATION_STEPS; s++) sum += rampSpeed(keys, a + s * h);
|
|
2329
|
+
total += sum * h;
|
|
2330
|
+
}
|
|
2331
|
+
return total;
|
|
2586
2332
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
const net = log.net;
|
|
2596
|
-
if (![...net.values()].some((n) => n.presence !== "present" || n.place !== void 0 || n.bake)) return overlayProps(tree, net);
|
|
2597
|
-
const byId = /* @__PURE__ */ new Map();
|
|
2598
|
-
const additions = /* @__PURE__ */ new Map();
|
|
2599
|
-
const movedAway = /* @__PURE__ */ new Set();
|
|
2600
|
-
for (const [id, n] of net) if (n.presence === "inserted" && n.node && n.place) {
|
|
2601
|
-
if (tree.byId.has(id)) movedAway.add(id);
|
|
2602
|
-
push(additions, n.place.parent, {
|
|
2603
|
-
after: n.place.after,
|
|
2604
|
-
node: n.node
|
|
2605
|
-
});
|
|
2606
|
-
} else if (n.presence === "present" && n.place) {
|
|
2607
|
-
const original = tree.byId.get(id);
|
|
2608
|
-
if (original && original.parent !== n.place.parent) {
|
|
2609
|
-
movedAway.add(id);
|
|
2610
|
-
push(additions, n.place.parent, {
|
|
2611
|
-
after: n.place.after,
|
|
2612
|
-
node: original
|
|
2613
|
-
});
|
|
2614
|
-
} else if (original) {
|
|
2615
|
-
movedAway.add(id);
|
|
2616
|
-
push(additions, n.place.parent, {
|
|
2617
|
-
after: n.place.after,
|
|
2618
|
-
node: original
|
|
2619
|
-
});
|
|
2620
|
-
}
|
|
2333
|
+
/** Clip seconds that play `advance` source seconds: the inverse of `rampAdvance`, by bisection (speed ≥ 0.1, so it is monotonic). */
|
|
2334
|
+
function rampInverse(keys, advance) {
|
|
2335
|
+
if (advance <= 0) return 0;
|
|
2336
|
+
let lo = 0, hi = advance / .1;
|
|
2337
|
+
for (let i = 0; i < 60 && hi - lo > 1e-9; i++) {
|
|
2338
|
+
const mid = (lo + hi) / 2;
|
|
2339
|
+
if (rampAdvance(keys, mid) < advance) lo = mid;
|
|
2340
|
+
else hi = mid;
|
|
2621
2341
|
}
|
|
2622
|
-
|
|
2623
|
-
const n = net.get(node.id);
|
|
2624
|
-
if (n?.presence === "removed") return null;
|
|
2625
|
-
let props = node.props;
|
|
2626
|
-
let interior = node.interior;
|
|
2627
|
-
let text = node.text;
|
|
2628
|
-
if (n) {
|
|
2629
|
-
if (n.props.size > 0) ({props, interior} = overlaidProps(node, n));
|
|
2630
|
-
if (n.text !== void 0) text = n.text;
|
|
2631
|
-
}
|
|
2632
|
-
const kept = [];
|
|
2633
|
-
for (const child of node.children) {
|
|
2634
|
-
if (movedAway.has(child.id)) continue;
|
|
2635
|
-
const built = rebuild(child, node.id);
|
|
2636
|
-
if (built) kept.push(built);
|
|
2637
|
-
}
|
|
2638
|
-
const children = insertAdditions(kept, additions.get(node.id) ?? [], node.id, rebuild);
|
|
2639
|
-
const out = {
|
|
2640
|
-
...node,
|
|
2641
|
-
props,
|
|
2642
|
-
children,
|
|
2643
|
-
parent,
|
|
2644
|
-
...text !== void 0 ? { text } : {},
|
|
2645
|
-
...interior ? { interior } : {}
|
|
2646
|
-
};
|
|
2647
|
-
byId.set(out.id, out);
|
|
2648
|
-
return out;
|
|
2649
|
-
};
|
|
2650
|
-
return {
|
|
2651
|
-
root: rebuild(tree.root, null) ?? tree.root,
|
|
2652
|
-
byId,
|
|
2653
|
-
errors: tree.errors
|
|
2654
|
-
};
|
|
2342
|
+
return (lo + hi) / 2;
|
|
2655
2343
|
}
|
|
2656
|
-
/**
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
for (const [id, n] of net) {
|
|
2664
|
-
if (n.props.size === 0 && n.text === void 0) continue;
|
|
2665
|
-
for (let node = tree.byId.get(id); node; node = node.parent === null ? void 0 : tree.byId.get(node.parent)) dirty.add(node.id);
|
|
2344
|
+
/** Element-relative `rel` with the holds before it taken out, or the held source second when `rel` is inside a hold. Holds sorted by `start`. */
|
|
2345
|
+
function freezeShift(holds, rel) {
|
|
2346
|
+
let shift = 0;
|
|
2347
|
+
for (const h of holds) {
|
|
2348
|
+
if (rel < h.start) break;
|
|
2349
|
+
if (rel < h.start + h.duration) return { held: h.at };
|
|
2350
|
+
shift += h.duration;
|
|
2666
2351
|
}
|
|
2667
|
-
|
|
2668
|
-
const byId = new Map(tree.byId);
|
|
2669
|
-
const rebuild = (node) => {
|
|
2670
|
-
if (!dirty.has(node.id)) return node;
|
|
2671
|
-
const n = net.get(node.id);
|
|
2672
|
-
let props = node.props;
|
|
2673
|
-
let interior = node.interior;
|
|
2674
|
-
let text = node.text;
|
|
2675
|
-
if (n && n.props.size > 0) ({props, interior} = overlaidProps(node, n));
|
|
2676
|
-
if (n && n.text !== void 0) text = n.text;
|
|
2677
|
-
const out = {
|
|
2678
|
-
...node,
|
|
2679
|
-
props,
|
|
2680
|
-
children: node.children.map(rebuild),
|
|
2681
|
-
...text !== void 0 ? { text } : {},
|
|
2682
|
-
...interior ? { interior } : {}
|
|
2683
|
-
};
|
|
2684
|
-
byId.set(out.id, out);
|
|
2685
|
-
return out;
|
|
2686
|
-
};
|
|
2687
|
-
return {
|
|
2688
|
-
root: rebuild(tree.root),
|
|
2689
|
-
byId,
|
|
2690
|
-
errors: tree.errors
|
|
2691
|
-
};
|
|
2352
|
+
return { rel: rel - shift };
|
|
2692
2353
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2354
|
+
/** Element-relative time of clip time `u`: the inverse of `freezeShift` outside the holds. A time at a hold's start plays before it. */
|
|
2355
|
+
function freezeUnshift(holds, u) {
|
|
2356
|
+
let rel = u;
|
|
2357
|
+
for (const h of holds) if (h.start < rel - 1e-9) rel += h.duration;
|
|
2358
|
+
return rel;
|
|
2697
2359
|
}
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
for (const { after, node } of additions) {
|
|
2702
|
-
const built = rebuild({
|
|
2703
|
-
...node,
|
|
2704
|
-
parent
|
|
2705
|
-
}, parent);
|
|
2706
|
-
if (!built) continue;
|
|
2707
|
-
const index = after === null ? -1 : result.findIndex((c) => c.id === after);
|
|
2708
|
-
result.splice(index + 1, 0, built);
|
|
2709
|
-
}
|
|
2710
|
-
return result;
|
|
2360
|
+
/** Total seconds the holds add to a clip. */
|
|
2361
|
+
function holdTotal(holds) {
|
|
2362
|
+
return holds.reduce((n, h) => n + h.duration, 0);
|
|
2711
2363
|
}
|
|
2712
|
-
/**
|
|
2713
|
-
function
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
return {
|
|
2723
|
-
props,
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
} : node.interior
|
|
2728
|
-
};
|
|
2364
|
+
/** A clip's `<SpeedKey>` children, sorted by `at`. */
|
|
2365
|
+
function speedKeysOf(children) {
|
|
2366
|
+
return children.filter((c) => c.kind === "speedKey").map((c) => ({
|
|
2367
|
+
at: Number(c.props.at ?? 0),
|
|
2368
|
+
speed: Number(c.props.speed ?? 1),
|
|
2369
|
+
easing: c.props.easing
|
|
2370
|
+
})).sort((a, b) => a.at - b.at);
|
|
2371
|
+
}
|
|
2372
|
+
/** A clip's `<Freeze>` children, sorted by `start`. */
|
|
2373
|
+
function freezeHoldsOf(children) {
|
|
2374
|
+
return children.filter((c) => c.kind === "freeze").map((c) => ({
|
|
2375
|
+
at: Number(c.props.at ?? 0),
|
|
2376
|
+
start: Number(c.props.start ?? 0),
|
|
2377
|
+
duration: Number(c.props.duration ?? 0)
|
|
2378
|
+
})).sort((a, b) => a.start - b.start);
|
|
2729
2379
|
}
|
|
2730
2380
|
// src/resolve/captions.ts
|
|
2731
2381
|
const DEFAULT_CUE_OPTIONS = {
|
|
@@ -2737,7 +2387,7 @@ const SENTENCE_END = /[.!?…]["')\]]?$/;
|
|
|
2737
2387
|
/** Composition time of an asset second, through the first play that covers it; null when it is not on the timeline. */
|
|
2738
2388
|
function onTimeline(sec, plays) {
|
|
2739
2389
|
const EPS = 1e-6;
|
|
2740
|
-
for (const p of plays) if (sec >= p.in - EPS && sec <= p.out + EPS) return ms(playTime(p, sec));
|
|
2390
|
+
for (const p of plays) if (sec >= p.in - EPS && sec <= p.out + EPS) return ms$1(playTime(p, sec));
|
|
2741
2391
|
return null;
|
|
2742
2392
|
}
|
|
2743
2393
|
/** The asset's words at the times they play, in timeline order; words outside every play are left out. */
|
|
@@ -2760,6 +2410,8 @@ function timelineWords(asset, plays) {
|
|
|
2760
2410
|
* with a capital.
|
|
2761
2411
|
*/
|
|
2762
2412
|
function planCues(words, options = DEFAULT_CUE_OPTIONS) {
|
|
2413
|
+
/** A cue ends after this word, whatever the limits and the merging below would do. */
|
|
2414
|
+
const forced = (w) => w !== void 0 && options.breakAfter?.has(w.i) === true;
|
|
2763
2415
|
const lines = [];
|
|
2764
2416
|
let current = [];
|
|
2765
2417
|
for (const w of words) {
|
|
@@ -2775,12 +2427,16 @@ function planCues(words, options = DEFAULT_CUE_OPTIONS) {
|
|
|
2775
2427
|
}
|
|
2776
2428
|
}
|
|
2777
2429
|
current.push(w);
|
|
2430
|
+
if (forced(w)) {
|
|
2431
|
+
lines.push(current);
|
|
2432
|
+
current = [];
|
|
2433
|
+
}
|
|
2778
2434
|
}
|
|
2779
2435
|
if (current.length > 0) lines.push(current);
|
|
2780
2436
|
const merged = [];
|
|
2781
2437
|
for (const line of lines) {
|
|
2782
2438
|
const prev = merged[merged.length - 1], last = prev?.[prev.length - 1], first = line[0];
|
|
2783
|
-
if (!(prev && last && span(line) < MIN_CUE && last.play === first.play && !SENTENCE_END.test(last.text) && first.start - last.end <= options.maxGap)) {
|
|
2439
|
+
if (!(prev && last && !forced(last) && span(line) < MIN_CUE && last.play === first.play && !SENTENCE_END.test(last.text) && first.start - last.end <= options.maxGap)) {
|
|
2784
2440
|
merged.push([...line]);
|
|
2785
2441
|
continue;
|
|
2786
2442
|
}
|
|
@@ -2796,14 +2452,14 @@ function planCues(words, options = DEFAULT_CUE_OPTIONS) {
|
|
|
2796
2452
|
for (let n = merged.length - 2; n >= 0; n--) {
|
|
2797
2453
|
const line = merged[n], next = merged[n + 1];
|
|
2798
2454
|
const last = line[line.length - 1], first = next[0];
|
|
2799
|
-
if (span(line) >= MIN_CUE || SENTENCE_END.test(last.text) || last.play !== first.play || first.start - last.end > options.maxGap) continue;
|
|
2455
|
+
if (span(line) >= MIN_CUE || forced(last) || SENTENCE_END.test(last.text) || last.play !== first.play || first.start - last.end > options.maxGap) continue;
|
|
2800
2456
|
if (chars(line) + 1 + chars(next) <= 2 * options.maxChars) merged.splice(n, 2, [...line, ...next]);
|
|
2801
2457
|
}
|
|
2802
2458
|
return merged.map((line, n) => {
|
|
2803
2459
|
const first = line[0], last = line[line.length - 1];
|
|
2804
2460
|
const next = merged[n + 1]?.[0];
|
|
2805
2461
|
const end = last.end - first.start < MIN_CUE && next ? Math.min(first.start + MIN_CUE, next.start) : last.end;
|
|
2806
|
-
const opens = n === 0 || SENTENCE_END.test(
|
|
2462
|
+
const opens = n === 0 || SENTENCE_END.test(merged[n - 1].at(-1).text);
|
|
2807
2463
|
const cueWords = line.map((w, i) => ({
|
|
2808
2464
|
text: i === 0 && opens ? capitalise(w.text) : w.text,
|
|
2809
2465
|
start: w.start,
|
|
@@ -2811,7 +2467,7 @@ function planCues(words, options = DEFAULT_CUE_OPTIONS) {
|
|
|
2811
2467
|
}));
|
|
2812
2468
|
return {
|
|
2813
2469
|
start: first.start,
|
|
2814
|
-
end: ms(end),
|
|
2470
|
+
end: ms$1(end),
|
|
2815
2471
|
text: cueWords.map((w) => w.text).join(" "),
|
|
2816
2472
|
words: cueWords
|
|
2817
2473
|
};
|
|
@@ -2934,12 +2590,12 @@ function sameAsset(a, b) {
|
|
|
2934
2590
|
return assetIdOf(a) === assetIdOf(b);
|
|
2935
2591
|
}
|
|
2936
2592
|
/** Composition times are written into the file, so they are kept to the millisecond. */
|
|
2937
|
-
function ms(x) {
|
|
2593
|
+
function ms$1(x) {
|
|
2938
2594
|
return Math.round(x * 1e3) / 1e3 + 0;
|
|
2939
2595
|
}
|
|
2940
2596
|
function wordOn(w, play) {
|
|
2941
|
-
const start = ms(playTime(play, Math.max(w.start, play.in)));
|
|
2942
|
-
const end = ms(playTime(play, Math.min(w.end, play.out)));
|
|
2597
|
+
const start = ms$1(playTime(play, Math.max(w.start, play.in)));
|
|
2598
|
+
const end = ms$1(playTime(play, Math.min(w.end, play.out)));
|
|
2943
2599
|
return {
|
|
2944
2600
|
i: w.i,
|
|
2945
2601
|
text: w.text,
|
|
@@ -2949,220 +2605,165 @@ function wordOn(w, play) {
|
|
|
2949
2605
|
...w.speaker !== void 0 ? { speaker: w.speaker } : {}
|
|
2950
2606
|
};
|
|
2951
2607
|
}
|
|
2952
|
-
/** Composition time at which asset second `sec` plays in `play`: through the speed ramp, then past the holds before it. */
|
|
2953
|
-
function playTime(play, sec) {
|
|
2954
|
-
const advance = Math.max(0, sec - play.in);
|
|
2955
|
-
const rel = play.keys ? rampInverse(play.keys, advance) : advance / play.speed;
|
|
2956
|
-
return play.start + (play.holds ? freezeUnshift(play.holds, rel) : rel);
|
|
2957
|
-
}
|
|
2958
|
-
// src/
|
|
2959
|
-
/**
|
|
2960
|
-
const
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
"text",
|
|
2970
|
-
"content",
|
|
2971
|
-
"src",
|
|
2972
|
-
"label",
|
|
2973
|
-
"name",
|
|
2974
|
-
"category",
|
|
2975
|
-
"theme"
|
|
2976
|
-
]);
|
|
2977
|
-
const byInput = /* @__PURE__ */ new WeakMap();
|
|
2978
|
-
let undeclared = null;
|
|
2979
|
-
/** The theme a composition's raw `theme` prop describes. Memoised per object; an invalid theme falls back to its base (the schema reports why). */
|
|
2980
|
-
function resolveTheme(raw) {
|
|
2981
|
-
if (raw === void 0 || raw === null || typeof raw !== "object") return undeclared ??= build(void 0, false);
|
|
2982
|
-
const hit = byInput.get(raw);
|
|
2983
|
-
if (hit) return hit;
|
|
2984
|
-
const parsed = themeSchema.safeParse(raw);
|
|
2985
|
-
const theme = build(parsed.success ? parsed.data : baseOnly(raw), true);
|
|
2986
|
-
byInput.set(raw, theme);
|
|
2987
|
-
return theme;
|
|
2988
|
-
}
|
|
2989
|
-
/** The value of `$name`, or undefined. */
|
|
2990
|
-
function themeToken(theme, name) {
|
|
2991
|
-
return Object.hasOwn(theme.tokens, name) ? theme.tokens[name] : void 0;
|
|
2608
|
+
/** Composition time at which asset second `sec` plays in `play`: through the speed ramp, then past the holds before it. */
|
|
2609
|
+
function playTime(play, sec) {
|
|
2610
|
+
const advance = Math.max(0, sec - play.in);
|
|
2611
|
+
const rel = play.keys ? rampInverse(play.keys, advance) : advance / play.speed;
|
|
2612
|
+
return play.start + (play.holds ? freezeUnshift(play.holds, rel) : rel);
|
|
2613
|
+
}
|
|
2614
|
+
// src/resolve/tree.ts
|
|
2615
|
+
/** Prop keys under this prefix address a Layer's handles rather than the Layer's own props. */
|
|
2616
|
+
const INTERIOR_PREFIX = "interior.";
|
|
2617
|
+
function walk(node, visit, depth = 0) {
|
|
2618
|
+
visit(node, depth);
|
|
2619
|
+
for (const child of node.children) walk(child, visit, depth + 1);
|
|
2620
|
+
}
|
|
2621
|
+
function findParentOf(tree, id) {
|
|
2622
|
+
const node = tree.byId.get(id);
|
|
2623
|
+
if (!node || node.parent === null) return null;
|
|
2624
|
+
return tree.byId.get(node.parent) ?? null;
|
|
2992
2625
|
}
|
|
2626
|
+
// src/resolve/overlay.ts
|
|
2993
2627
|
/**
|
|
2994
|
-
*
|
|
2995
|
-
*
|
|
2996
|
-
* the
|
|
2997
|
-
* type and duration), and whole-string `$token` values become theme values. The
|
|
2998
|
-
* element's own props always win. Returns the input object when nothing was themed.
|
|
2628
|
+
* The collected tree with the patch log's net effect applied: removed nodes
|
|
2629
|
+
* dropped, inserted nodes placed, moved nodes relocated, props and text
|
|
2630
|
+
* overlaid. Returns the same tree when the log is empty.
|
|
2999
2631
|
*/
|
|
3000
|
-
function
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
} else if (named !== null) {
|
|
3027
|
-
const known = Object.keys(presets ?? {});
|
|
3028
|
-
issues.push({
|
|
3029
|
-
code: "UNKNOWN_STYLE",
|
|
3030
|
-
prop: "style",
|
|
3031
|
-
message: `no ${kind} style "${named}" in the theme${known.length ? ` (${known.join(", ")})` : ""}`
|
|
2632
|
+
function overlay(tree, log) {
|
|
2633
|
+
if (!log || log.net.size === 0) return tree;
|
|
2634
|
+
const net = log.net;
|
|
2635
|
+
if (![...net.values()].some((n) => n.presence !== "present" || n.place !== void 0 || n.bake)) return overlayProps(tree, net);
|
|
2636
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2637
|
+
const additions = /* @__PURE__ */ new Map();
|
|
2638
|
+
const movedAway = /* @__PURE__ */ new Set();
|
|
2639
|
+
for (const [id, n] of net) if (n.presence === "inserted" && n.node && n.place) {
|
|
2640
|
+
if (tree.byId.has(id)) movedAway.add(id);
|
|
2641
|
+
push(additions, n.place.parent, {
|
|
2642
|
+
after: n.place.after,
|
|
2643
|
+
node: n.node
|
|
2644
|
+
});
|
|
2645
|
+
} else if (n.presence === "present" && n.place) {
|
|
2646
|
+
const original = tree.byId.get(id);
|
|
2647
|
+
if (original && original.parent !== n.place.parent) {
|
|
2648
|
+
movedAway.add(id);
|
|
2649
|
+
push(additions, n.place.parent, {
|
|
2650
|
+
after: n.place.after,
|
|
2651
|
+
node: original
|
|
2652
|
+
});
|
|
2653
|
+
} else if (original) {
|
|
2654
|
+
movedAway.add(id);
|
|
2655
|
+
push(additions, n.place.parent, {
|
|
2656
|
+
after: n.place.after,
|
|
2657
|
+
node: original
|
|
3032
2658
|
});
|
|
3033
|
-
if (kind === "captions") delete out.style;
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
const motion = theme.declared ? theme.motion : null;
|
|
3037
|
-
if (motion && MOTION_KINDS.has(kind) && !(kind === "title" && out.animation !== void 0)) {
|
|
3038
|
-
if (out.enter === void 0 && motion.enter) {
|
|
3039
|
-
out.enter = motion.enter;
|
|
3040
|
-
from.enter = "motion";
|
|
3041
|
-
}
|
|
3042
|
-
if (out.exit === void 0 && motion.exit) {
|
|
3043
|
-
out.exit = motion.exit;
|
|
3044
|
-
from.exit = "motion";
|
|
3045
2659
|
}
|
|
3046
2660
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
2661
|
+
const rebuild = (node, parent) => {
|
|
2662
|
+
const n = net.get(node.id);
|
|
2663
|
+
if (n?.presence === "removed") return null;
|
|
2664
|
+
let props = node.props;
|
|
2665
|
+
let interior = node.interior;
|
|
2666
|
+
let text = node.text;
|
|
2667
|
+
if (n) {
|
|
2668
|
+
if (n.props.size > 0) ({props, interior} = overlaidProps(node, n));
|
|
2669
|
+
if (n.text !== void 0) text = n.text;
|
|
3051
2670
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
2671
|
+
const kept = [];
|
|
2672
|
+
for (const child of node.children) {
|
|
2673
|
+
if (movedAway.has(child.id)) continue;
|
|
2674
|
+
const built = rebuild(child, node.id);
|
|
2675
|
+
if (built) kept.push(built);
|
|
3055
2676
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
2677
|
+
const children = insertAdditions(kept, additions.get(node.id) ?? [], node.id, rebuild);
|
|
2678
|
+
const out = {
|
|
2679
|
+
...node,
|
|
2680
|
+
props,
|
|
2681
|
+
children,
|
|
2682
|
+
parent,
|
|
2683
|
+
...text !== void 0 ? { text } : {},
|
|
2684
|
+
...interior ? { interior } : {}
|
|
3061
2685
|
};
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
for (const key of Object.keys(out)) {
|
|
3065
|
-
if (CONTENT_PROPS.has(key)) continue;
|
|
3066
|
-
out[key] = substitute(out[key], key, theme, from, issues);
|
|
3067
|
-
}
|
|
3068
|
-
if (Object.keys(from).length === 0 && issues.length === 0) return {
|
|
3069
|
-
props: raw,
|
|
3070
|
-
from: EMPTY,
|
|
3071
|
-
issues
|
|
2686
|
+
byId.set(out.id, out);
|
|
2687
|
+
return out;
|
|
3072
2688
|
};
|
|
3073
2689
|
return {
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
2690
|
+
root: rebuild(tree.root, null) ?? tree.root,
|
|
2691
|
+
byId,
|
|
2692
|
+
errors: tree.errors
|
|
3077
2693
|
};
|
|
3078
2694
|
}
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
prop: path,
|
|
3090
|
-
message: `unknown theme token ${value}`
|
|
3091
|
-
});
|
|
3092
|
-
return value;
|
|
3093
|
-
}
|
|
3094
|
-
from[path] = value;
|
|
3095
|
-
return resolved;
|
|
3096
|
-
}
|
|
3097
|
-
if (Array.isArray(value)) {
|
|
3098
|
-
let changed = null;
|
|
3099
|
-
value.forEach((v, i) => {
|
|
3100
|
-
const s = substitute(v, `${path}.${i}`, theme, from, issues);
|
|
3101
|
-
if (s !== v) (changed ??= [...value])[i] = s;
|
|
3102
|
-
});
|
|
3103
|
-
return changed ?? value;
|
|
3104
|
-
}
|
|
3105
|
-
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
3106
|
-
let changed = null;
|
|
3107
|
-
for (const [k, v] of Object.entries(value)) {
|
|
3108
|
-
const s = substitute(v, `${path}.${k}`, theme, from, issues);
|
|
3109
|
-
if (s !== v) (changed ??= { ...value })[k] = s;
|
|
3110
|
-
}
|
|
3111
|
-
return changed ?? value;
|
|
3112
|
-
}
|
|
3113
|
-
return value;
|
|
3114
|
-
}
|
|
3115
|
-
function hasToken(value) {
|
|
3116
|
-
if (typeof value === "string") return value.startsWith("$");
|
|
3117
|
-
if (Array.isArray(value)) return value.some(hasToken);
|
|
3118
|
-
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
3119
|
-
for (const [k, v] of Object.entries(value)) if (!CONTENT_PROPS.has(k) && hasToken(v)) return true;
|
|
2695
|
+
/**
|
|
2696
|
+
* The common case during a drag: only props or text changed. Rebuild the
|
|
2697
|
+
* patched nodes and their ancestors; every other node keeps its identity,
|
|
2698
|
+
* and with it its props object, so parsed props stay cached downstream.
|
|
2699
|
+
*/
|
|
2700
|
+
function overlayProps(tree, net) {
|
|
2701
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
2702
|
+
for (const [id, n] of net) {
|
|
2703
|
+
if (n.props.size === 0 && n.text === void 0) continue;
|
|
2704
|
+
for (let node = tree.byId.get(id); node; node = node.parent === null ? void 0 : tree.byId.get(node.parent)) dirty.add(node.id);
|
|
3120
2705
|
}
|
|
3121
|
-
return
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
2706
|
+
if (dirty.size === 0) return tree;
|
|
2707
|
+
const byId = new Map(tree.byId);
|
|
2708
|
+
const rebuild = (node) => {
|
|
2709
|
+
if (!dirty.has(node.id)) return node;
|
|
2710
|
+
const n = net.get(node.id);
|
|
2711
|
+
let props = node.props;
|
|
2712
|
+
let interior = node.interior;
|
|
2713
|
+
let text = node.text;
|
|
2714
|
+
if (n && n.props.size > 0) ({props, interior} = overlaidProps(node, n));
|
|
2715
|
+
if (n && n.text !== void 0) text = n.text;
|
|
2716
|
+
const out = {
|
|
2717
|
+
...node,
|
|
2718
|
+
props,
|
|
2719
|
+
children: node.children.map(rebuild),
|
|
2720
|
+
...text !== void 0 ? { text } : {},
|
|
2721
|
+
...interior ? { interior } : {}
|
|
2722
|
+
};
|
|
2723
|
+
byId.set(out.id, out);
|
|
2724
|
+
return out;
|
|
3135
2725
|
};
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
2726
|
+
return {
|
|
2727
|
+
root: rebuild(tree.root),
|
|
2728
|
+
byId,
|
|
2729
|
+
errors: tree.errors
|
|
3139
2730
|
};
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
const
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
2731
|
+
}
|
|
2732
|
+
function push(map, key, value) {
|
|
2733
|
+
const list = map.get(key);
|
|
2734
|
+
if (list) list.push(value);
|
|
2735
|
+
else map.set(key, [value]);
|
|
2736
|
+
}
|
|
2737
|
+
function insertAdditions(children, additions, parent, rebuild) {
|
|
2738
|
+
if (additions.length === 0) return children;
|
|
2739
|
+
const result = [...children];
|
|
2740
|
+
for (const { after, node } of additions) {
|
|
2741
|
+
const built = rebuild({
|
|
2742
|
+
...node,
|
|
2743
|
+
parent
|
|
2744
|
+
}, parent);
|
|
2745
|
+
if (!built) continue;
|
|
2746
|
+
const index = after === null ? -1 : result.findIndex((c) => c.id === after);
|
|
2747
|
+
result.splice(index + 1, 0, built);
|
|
2748
|
+
}
|
|
2749
|
+
return result;
|
|
2750
|
+
}
|
|
2751
|
+
/** Apply a net's prop writes: `interior.<name>` keys go to the Layer's handles, the rest to the node's own props. */
|
|
2752
|
+
function overlaidProps(node, n) {
|
|
2753
|
+
const props = { ...node.props };
|
|
2754
|
+
let handles = null;
|
|
2755
|
+
for (const [k, v] of n.props) {
|
|
2756
|
+
const target = k.startsWith("interior.") ? handles ??= { ...node.interior?.props ?? {} } : props;
|
|
2757
|
+
const key = k.startsWith("interior.") ? k.slice(9) : k;
|
|
2758
|
+
if (v === void 0) delete target[key];
|
|
2759
|
+
else target[key] = v;
|
|
3152
2760
|
}
|
|
3153
2761
|
return {
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
motion,
|
|
3160
|
-
styles,
|
|
3161
|
-
tokens: {
|
|
3162
|
-
...palette,
|
|
3163
|
-
...fonts,
|
|
3164
|
-
radius
|
|
3165
|
-
}
|
|
2762
|
+
props,
|
|
2763
|
+
interior: handles && node.interior ? {
|
|
2764
|
+
component: node.interior.component,
|
|
2765
|
+
props: handles
|
|
2766
|
+
} : node.interior
|
|
3166
2767
|
};
|
|
3167
2768
|
}
|
|
3168
2769
|
// src/resolve/resolve.ts
|
|
@@ -3194,6 +2795,7 @@ function resolve(tree, manifest, log) {
|
|
|
3194
2795
|
*/
|
|
3195
2796
|
function compute(original, manifest, log, previous) {
|
|
3196
2797
|
const tree = overlay(original, log);
|
|
2798
|
+
fileShaders = original.shaders ?? [];
|
|
3197
2799
|
const errors = [...tree.errors];
|
|
3198
2800
|
const root = tree.root;
|
|
3199
2801
|
const theme = resolveTheme(root.props.theme);
|
|
@@ -3386,7 +2988,9 @@ function compute(original, manifest, log, previous) {
|
|
|
3386
2988
|
tree,
|
|
3387
2989
|
manifest,
|
|
3388
2990
|
log,
|
|
3389
|
-
theme
|
|
2991
|
+
theme,
|
|
2992
|
+
fonts: original.fonts ?? [],
|
|
2993
|
+
shaders: original.shaders ?? []
|
|
3390
2994
|
};
|
|
3391
2995
|
}
|
|
3392
2996
|
/**
|
|
@@ -3399,6 +3003,40 @@ function compute(original, manifest, log, previous) {
|
|
|
3399
3003
|
const parsedByProps = /* @__PURE__ */ new WeakMap();
|
|
3400
3004
|
/** Which parsed props the theme supplied, keyed by the parsed `data`. */
|
|
3401
3005
|
const themedByData = /* @__PURE__ */ new WeakMap();
|
|
3006
|
+
/** The shaders of the file being resolved (`defineEffect`, `defineTransition`); set for the length of one `compute`. */
|
|
3007
|
+
let fileShaders = [];
|
|
3008
|
+
/**
|
|
3009
|
+
* An `<Effect>` or `<Transition>` of a type the file declares: its params checked and filled by the declared schema.
|
|
3010
|
+
* An unknown type is an error naming the built-in ones; null for a built-in type or any other element.
|
|
3011
|
+
*/
|
|
3012
|
+
function declaredShader(node, data, own) {
|
|
3013
|
+
if (node.kind !== "effect" && node.kind !== "transition") return null;
|
|
3014
|
+
const type = String(data.type);
|
|
3015
|
+
const builtIn = node.kind === "effect" ? VIDEO_EFFECT_TYPES : TRANSITION_TYPES;
|
|
3016
|
+
if (builtIn.includes(type)) return null;
|
|
3017
|
+
const def = shaderFor(fileShaders, node.kind, type);
|
|
3018
|
+
if (!def) {
|
|
3019
|
+
own.push({
|
|
3020
|
+
code: "SCHEMA",
|
|
3021
|
+
id: node.id,
|
|
3022
|
+
prop: "type",
|
|
3023
|
+
message: `unknown ${node.kind} "${type}": use one of ${builtIn.join(", ")}, or declare it with ${node.kind === "effect" ? "defineEffect" : "defineTransition"}()`
|
|
3024
|
+
});
|
|
3025
|
+
return null;
|
|
3026
|
+
}
|
|
3027
|
+
const result = def.params.safeParse(data.params ?? {});
|
|
3028
|
+
if (result.success) return {
|
|
3029
|
+
...data,
|
|
3030
|
+
params: result.data
|
|
3031
|
+
};
|
|
3032
|
+
for (const issue of result.error.issues) own.push({
|
|
3033
|
+
code: "SCHEMA",
|
|
3034
|
+
id: node.id,
|
|
3035
|
+
prop: ["params", ...issue.path.map(String)].join("."),
|
|
3036
|
+
message: issue.message
|
|
3037
|
+
});
|
|
3038
|
+
return null;
|
|
3039
|
+
}
|
|
3402
3040
|
function parseProps(node, errors, theme) {
|
|
3403
3041
|
const cached = parsedByProps.get(node.props);
|
|
3404
3042
|
if (cached && cached.theme === theme) {
|
|
@@ -3406,7 +3044,7 @@ function parseProps(node, errors, theme) {
|
|
|
3406
3044
|
return cached.data;
|
|
3407
3045
|
}
|
|
3408
3046
|
const themed = applyTheme(node.kind, node.props, theme);
|
|
3409
|
-
if (cached
|
|
3047
|
+
if (cached?.unthemed && themed.props === node.props) {
|
|
3410
3048
|
parsedByProps.set(node.props, {
|
|
3411
3049
|
...cached,
|
|
3412
3050
|
theme
|
|
@@ -3420,14 +3058,17 @@ function parseProps(node, errors, theme) {
|
|
|
3420
3058
|
prop: issue.prop,
|
|
3421
3059
|
message: issue.message
|
|
3422
3060
|
}));
|
|
3423
|
-
const
|
|
3061
|
+
const engine = node.kind === "layer" && typeof node.props.engine === "string" ? node.props.engine : null;
|
|
3062
|
+
const schema = engine ? ENGINE_SCHEMAS[engine] : SCHEMAS_BY_KIND[node.kind];
|
|
3424
3063
|
let data;
|
|
3425
3064
|
if (!schema) data = { ...themed.props };
|
|
3426
3065
|
else {
|
|
3427
3066
|
const result = schema.safeParse(themed.props);
|
|
3428
3067
|
if (result.success) {
|
|
3429
3068
|
data = result.data;
|
|
3430
|
-
|
|
3069
|
+
const declared = declaredShader(node, data, own);
|
|
3070
|
+
if (declared) data = declared;
|
|
3071
|
+
else if (node.kind === "transition" && isBuiltInTransition(String(data.type))) data = {
|
|
3431
3072
|
...data,
|
|
3432
3073
|
params: {
|
|
3433
3074
|
...defaultTransitionParams(data.type),
|
|
@@ -3612,14 +3253,19 @@ function framed(props, asset, comp) {
|
|
|
3612
3253
|
});
|
|
3613
3254
|
return out;
|
|
3614
3255
|
}
|
|
3256
|
+
/** The seconds a time reference resolved to, or `fallback` with its error recorded. */
|
|
3257
|
+
function secondsOr(p, resolved, fallback) {
|
|
3258
|
+
if (typeof resolved === "number") return resolved;
|
|
3259
|
+
p.errors.push(resolved);
|
|
3260
|
+
return fallback;
|
|
3261
|
+
}
|
|
3615
3262
|
function sourceRange(p, asset, markers) {
|
|
3616
3263
|
const { node, props } = p;
|
|
3617
3264
|
const speed = Number(props.speed ?? 1);
|
|
3618
|
-
const
|
|
3265
|
+
const inSec = secondsOr(p, resolveTimeRef(props.in ?? 0, asset, markers, {
|
|
3619
3266
|
id: node.id,
|
|
3620
3267
|
prop: "in"
|
|
3621
|
-
});
|
|
3622
|
-
const inSec = typeof inT === "number" ? inT : (p.errors.push(inT), 0);
|
|
3268
|
+
}), 0);
|
|
3623
3269
|
const dur = props.duration;
|
|
3624
3270
|
const keys = speedKeysOf(node.children);
|
|
3625
3271
|
const holds = holdTotal(freezeHoldsOf(node.children));
|
|
@@ -3632,13 +3278,11 @@ function sourceRange(p, asset, markers) {
|
|
|
3632
3278
|
prop: "duration",
|
|
3633
3279
|
message: "give either out or duration, not both"
|
|
3634
3280
|
});
|
|
3635
|
-
if (props.out !== void 0) {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
outSec = typeof o === "number" ? o : (p.errors.push(o), inSec);
|
|
3641
|
-
} else if (typeof dur === "number") {
|
|
3281
|
+
if (props.out !== void 0) outSec = secondsOr(p, resolveTimeRef(props.out, asset, markers, {
|
|
3282
|
+
id: node.id,
|
|
3283
|
+
prop: "out"
|
|
3284
|
+
}), inSec);
|
|
3285
|
+
else if (typeof dur === "number") {
|
|
3642
3286
|
played = dur;
|
|
3643
3287
|
outSec = inSec + (keys.length > 0 ? rampAdvance(keys, dur) : dur * speed);
|
|
3644
3288
|
} else if (dur === "fill" || typeof dur === "object" && dur !== null) {
|
|
@@ -4095,7 +3739,8 @@ function placeCaptions(root, pending, tracks, manifest, markers) {
|
|
|
4095
3739
|
const options = {
|
|
4096
3740
|
maxChars: Number(cp.props.maxChars ?? lineChars(root, cp.props.style)),
|
|
4097
3741
|
maxDuration: Number(cp.props.maxDuration ?? 5),
|
|
4098
|
-
maxGap: Number(cp.props.maxGap ?? .8)
|
|
3742
|
+
maxGap: Number(cp.props.maxGap ?? .8),
|
|
3743
|
+
...Array.isArray(cp.props.breakAfter) ? { breakAfter: new Set(cp.props.breakAfter.map((ref) => Number(ref.slice(5)))) } : {}
|
|
4099
3744
|
};
|
|
4100
3745
|
nodes = cueNodes(node, planCues(timelineWords(asset, plays), options), (id) => pending.has(id));
|
|
4101
3746
|
cuesByCaptions.set(node, {
|
|
@@ -4160,172 +3805,6 @@ function captionTime(ref, asset, plays, markers, ctx) {
|
|
|
4160
3805
|
};
|
|
4161
3806
|
return at;
|
|
4162
3807
|
}
|
|
4163
|
-
// src/resolve/scene.ts
|
|
4164
|
-
/**
|
|
4165
|
-
* Resolve a `<Nested of={Scene}>` interior: the scene is called with the
|
|
4166
|
-
* nested element's props (minus `of` and `id`) and what it returns is
|
|
4167
|
-
* resolved inside a composition of the outer size. The player renders the
|
|
4168
|
-
* result; `nested/extract` writes it into the file.
|
|
4169
|
-
*/
|
|
4170
|
-
function resolveScene(of, props, outer, manifest) {
|
|
4171
|
-
const { of: _of, id: _id, ...rest } = props;
|
|
4172
|
-
const inner = of(rest);
|
|
4173
|
-
return resolve(collect(createElement(Composition, {
|
|
4174
|
-
fps: outer.fps,
|
|
4175
|
-
width: outer.width,
|
|
4176
|
-
height: outer.height
|
|
4177
|
-
}, inner)), manifest);
|
|
4178
|
-
}
|
|
4179
|
-
// src/theme/fonts.ts
|
|
4180
|
-
const BUNDLED_FONTS = [
|
|
4181
|
-
{
|
|
4182
|
-
family: "Inter",
|
|
4183
|
-
id: "inter",
|
|
4184
|
-
italic: true
|
|
4185
|
-
},
|
|
4186
|
-
{
|
|
4187
|
-
family: "Space Grotesk",
|
|
4188
|
-
id: "space-grotesk",
|
|
4189
|
-
italic: false
|
|
4190
|
-
},
|
|
4191
|
-
{
|
|
4192
|
-
family: "Playfair Display",
|
|
4193
|
-
id: "playfair-display",
|
|
4194
|
-
italic: true
|
|
4195
|
-
},
|
|
4196
|
-
{
|
|
4197
|
-
family: "DM Sans",
|
|
4198
|
-
id: "dm-sans",
|
|
4199
|
-
italic: true
|
|
4200
|
-
},
|
|
4201
|
-
{
|
|
4202
|
-
family: "JetBrains Mono",
|
|
4203
|
-
id: "jetbrains-mono",
|
|
4204
|
-
italic: true
|
|
4205
|
-
}
|
|
4206
|
-
];
|
|
4207
|
-
const BUNDLED_FONT_WEIGHTS = [400, 700];
|
|
4208
|
-
function isBundledFont(family) {
|
|
4209
|
-
return BUNDLED_FONTS.some((f) => f.family === family);
|
|
4210
|
-
}
|
|
4211
|
-
// src/patches/log.ts
|
|
4212
|
-
var Log = class {
|
|
4213
|
-
list = [];
|
|
4214
|
-
reduced = /* @__PURE__ */ new Map();
|
|
4215
|
-
v = 0;
|
|
4216
|
-
isSource;
|
|
4217
|
-
constructor(options) {
|
|
4218
|
-
this.isSource = options.isSource ?? (() => false);
|
|
4219
|
-
}
|
|
4220
|
-
get entries() {
|
|
4221
|
-
return this.list;
|
|
4222
|
-
}
|
|
4223
|
-
get net() {
|
|
4224
|
-
return this.reduced;
|
|
4225
|
-
}
|
|
4226
|
-
get version() {
|
|
4227
|
-
return this.v;
|
|
4228
|
-
}
|
|
4229
|
-
append(patch) {
|
|
4230
|
-
this.list.push(patch);
|
|
4231
|
-
reduceInto(this.reduced, patch, this.isSource);
|
|
4232
|
-
this.v += 1;
|
|
4233
|
-
}
|
|
4234
|
-
mark() {
|
|
4235
|
-
return this.list.length;
|
|
4236
|
-
}
|
|
4237
|
-
truncate(mark) {
|
|
4238
|
-
if (mark >= this.list.length) return;
|
|
4239
|
-
this.list.length = Math.max(0, mark);
|
|
4240
|
-
this.reduced.clear();
|
|
4241
|
-
for (const p of this.list) reduceInto(this.reduced, p, this.isSource);
|
|
4242
|
-
this.v += 1;
|
|
4243
|
-
}
|
|
4244
|
-
clear() {
|
|
4245
|
-
this.list.length = 0;
|
|
4246
|
-
this.reduced.clear();
|
|
4247
|
-
this.v += 1;
|
|
4248
|
-
}
|
|
4249
|
-
};
|
|
4250
|
-
function createPatchLog(options = {}) {
|
|
4251
|
-
return new Log(options);
|
|
4252
|
-
}
|
|
4253
|
-
function entry(net, id) {
|
|
4254
|
-
let e = net.get(id);
|
|
4255
|
-
if (!e) {
|
|
4256
|
-
e = {
|
|
4257
|
-
id,
|
|
4258
|
-
presence: "present",
|
|
4259
|
-
props: /* @__PURE__ */ new Map(),
|
|
4260
|
-
bake: false
|
|
4261
|
-
};
|
|
4262
|
-
net.set(id, e);
|
|
4263
|
-
}
|
|
4264
|
-
return e;
|
|
4265
|
-
}
|
|
4266
|
-
/** Keep `net` reduced: last write wins per prop, structural ops collapse, removal of an insert vanishes. */
|
|
4267
|
-
function reduceInto(net, patch, isSource = () => false) {
|
|
4268
|
-
switch (patch.op) {
|
|
4269
|
-
case "set": {
|
|
4270
|
-
const e = entry(net, patch.id);
|
|
4271
|
-
if (e.presence === "removed") throw new Error(`patch set on removed element ${patch.id}`);
|
|
4272
|
-
e.props.set(patch.prop, patch.value);
|
|
4273
|
-
return;
|
|
4274
|
-
}
|
|
4275
|
-
case "text": {
|
|
4276
|
-
const e = entry(net, patch.id);
|
|
4277
|
-
if (e.presence === "removed") throw new Error(`patch text on removed element ${patch.id}`);
|
|
4278
|
-
e.text = patch.text;
|
|
4279
|
-
return;
|
|
4280
|
-
}
|
|
4281
|
-
case "move": {
|
|
4282
|
-
const e = entry(net, patch.id);
|
|
4283
|
-
if (e.presence === "removed") throw new Error(`patch move on removed element ${patch.id}`);
|
|
4284
|
-
e.place = {
|
|
4285
|
-
parent: patch.parent,
|
|
4286
|
-
after: patch.after
|
|
4287
|
-
};
|
|
4288
|
-
return;
|
|
4289
|
-
}
|
|
4290
|
-
case "remove": {
|
|
4291
|
-
if (net.get(patch.id)?.presence === "inserted" && !isSource(patch.id)) {
|
|
4292
|
-
net.delete(patch.id);
|
|
4293
|
-
return;
|
|
4294
|
-
}
|
|
4295
|
-
const r = entry(net, patch.id);
|
|
4296
|
-
r.presence = "removed";
|
|
4297
|
-
r.props.clear();
|
|
4298
|
-
delete r.text;
|
|
4299
|
-
delete r.place;
|
|
4300
|
-
delete r.node;
|
|
4301
|
-
return;
|
|
4302
|
-
}
|
|
4303
|
-
case "insert": {
|
|
4304
|
-
const e = entry(net, patch.id);
|
|
4305
|
-
e.presence = e.presence === "removed" && e.node !== void 0 && nodesEqual(e.node, patch.node) ? "present" : "inserted";
|
|
4306
|
-
e.props.clear();
|
|
4307
|
-
e.node = patch.node;
|
|
4308
|
-
e.place = {
|
|
4309
|
-
parent: patch.parent,
|
|
4310
|
-
after: patch.after
|
|
4311
|
-
};
|
|
4312
|
-
return;
|
|
4313
|
-
}
|
|
4314
|
-
case "bake":
|
|
4315
|
-
entry(net, patch.id).bake = true;
|
|
4316
|
-
return;
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
function nodesEqual(a, b) {
|
|
4320
|
-
return a.id === b.id && a.kind === b.kind && stableJson(a.props) === stableJson(b.props) && a.text === b.text && a.children.length === b.children.length && a.children.every((c, i) => nodesEqual(c, b.children[i]));
|
|
4321
|
-
}
|
|
4322
|
-
function stableJson(value) {
|
|
4323
|
-
return JSON.stringify(value, (_k, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a < b ? -1 : 1)) : v);
|
|
4324
|
-
}
|
|
4325
|
-
function asJson(value) {
|
|
4326
|
-
if (value === void 0) return void 0;
|
|
4327
|
-
return JSON.parse(JSON.stringify(value));
|
|
4328
|
-
}
|
|
4329
3808
|
// src/actions/types/base.ts
|
|
4330
3809
|
const ok = {
|
|
4331
3810
|
valid: true,
|
|
@@ -4366,7 +3845,6 @@ function nodeOfSpec(spec, parent) {
|
|
|
4366
3845
|
const AUTO_GROUP_TYPES = /* @__PURE__ */ new Set([
|
|
4367
3846
|
"transform/update",
|
|
4368
3847
|
"effect/update",
|
|
4369
|
-
"audioEffect/update",
|
|
4370
3848
|
"audio/setVolume",
|
|
4371
3849
|
"audio/setFade",
|
|
4372
3850
|
"audio/setDuck",
|
|
@@ -4379,8 +3857,6 @@ const AUTO_GROUP_TYPES = /* @__PURE__ */ new Set([
|
|
|
4379
3857
|
"clip/slide",
|
|
4380
3858
|
"clip/roll",
|
|
4381
3859
|
"clip/setSpeed",
|
|
4382
|
-
"clip/setChromaKey",
|
|
4383
|
-
"clip/setStabilization",
|
|
4384
3860
|
"keyframe/move",
|
|
4385
3861
|
"keyframe/update",
|
|
4386
3862
|
"composition/updateSettings",
|
|
@@ -4414,33 +3890,12 @@ function targetIdOf(action) {
|
|
|
4414
3890
|
const value = params[key];
|
|
4415
3891
|
if (typeof value === "string" && value.length > 0) return value;
|
|
4416
3892
|
}
|
|
4417
|
-
return null;
|
|
4418
|
-
}
|
|
4419
|
-
function describeAction(action) {
|
|
4420
|
-
const [ns, name] = action.type.split("/");
|
|
4421
|
-
const words = name.replace(/([A-Z])/g, " $1").toLowerCase();
|
|
4422
|
-
return `${words.charAt(0).toUpperCase()}${words.slice(1)} ${ns}`;
|
|
4423
|
-
}
|
|
4424
|
-
// src/actions/registry.ts
|
|
4425
|
-
const registry = /* @__PURE__ */ new Map();
|
|
4426
|
-
function registerHandler(handler) {
|
|
4427
|
-
if (registry.has(handler.type)) throw new Error(`handler for ${handler.type} registered twice`);
|
|
4428
|
-
registry.set(handler.type, handler);
|
|
4429
|
-
}
|
|
4430
|
-
function getHandler(type) {
|
|
4431
|
-
return registry.get(type);
|
|
4432
|
-
}
|
|
4433
|
-
function listHandlers() {
|
|
4434
|
-
return [...registry.keys()].sort();
|
|
4435
|
-
}
|
|
4436
|
-
function inverseOf(action, before) {
|
|
4437
|
-
const handler = getHandler(action.type);
|
|
4438
|
-
if (!handler) throw new Error(`no handler for ${action.type}`);
|
|
4439
|
-
const inv = handler.invert(action, before);
|
|
4440
|
-
return Array.isArray(inv) ? {
|
|
4441
|
-
type: "batch/apply",
|
|
4442
|
-
params: { actions: inv }
|
|
4443
|
-
} : inv;
|
|
3893
|
+
return null;
|
|
3894
|
+
}
|
|
3895
|
+
function describeAction(action) {
|
|
3896
|
+
const [ns, name] = action.type.split("/");
|
|
3897
|
+
const words = name.replace(/([A-Z])/g, " $1").toLowerCase();
|
|
3898
|
+
return `${words.charAt(0).toUpperCase()}${words.slice(1)} ${ns}`;
|
|
4444
3899
|
}
|
|
4445
3900
|
// src/actions/history.ts
|
|
4446
3901
|
/**
|
|
@@ -4564,6 +4019,27 @@ var ActionHistory = class {
|
|
|
4564
4019
|
this.groupDepth = 0;
|
|
4565
4020
|
}
|
|
4566
4021
|
};
|
|
4022
|
+
// src/actions/registry.ts
|
|
4023
|
+
const registry = /* @__PURE__ */ new Map();
|
|
4024
|
+
function registerHandler(handler) {
|
|
4025
|
+
if (registry.has(handler.type)) throw new Error(`handler for ${handler.type} registered twice`);
|
|
4026
|
+
registry.set(handler.type, handler);
|
|
4027
|
+
}
|
|
4028
|
+
function getHandler(type) {
|
|
4029
|
+
return registry.get(type);
|
|
4030
|
+
}
|
|
4031
|
+
function listHandlers() {
|
|
4032
|
+
return [...registry.keys()].sort();
|
|
4033
|
+
}
|
|
4034
|
+
function inverseOf(action, before) {
|
|
4035
|
+
const handler = getHandler(action.type);
|
|
4036
|
+
if (!handler) throw new Error(`no handler for ${action.type}`);
|
|
4037
|
+
const inv = handler.invert(action, before);
|
|
4038
|
+
return Array.isArray(inv) ? {
|
|
4039
|
+
type: "batch/apply",
|
|
4040
|
+
params: { actions: inv }
|
|
4041
|
+
} : inv;
|
|
4042
|
+
}
|
|
4567
4043
|
// src/actions/executor.ts
|
|
4568
4044
|
/**
|
|
4569
4045
|
* Runs actions against `{ tree, patches }`: validate on the state before,
|
|
@@ -4700,221 +4176,905 @@ var NotImplemented = class extends Error {
|
|
|
4700
4176
|
this.name = "NotImplemented";
|
|
4701
4177
|
}
|
|
4702
4178
|
};
|
|
4703
|
-
function requireElement(state, id, path = "id") {
|
|
4704
|
-
if (typeof id !== "string") return {
|
|
4705
|
-
code: "INVALID_PARAMS",
|
|
4706
|
-
message: `${path} must be a string`,
|
|
4707
|
-
path
|
|
4708
|
-
};
|
|
4709
|
-
return state.items.get(id) ?? {
|
|
4710
|
-
code: "ELEMENT_NOT_FOUND",
|
|
4711
|
-
message: `element "${id}" does not exist`,
|
|
4712
|
-
path
|
|
4179
|
+
function requireElement(state, id, path = "id") {
|
|
4180
|
+
if (typeof id !== "string") return {
|
|
4181
|
+
code: "INVALID_PARAMS",
|
|
4182
|
+
message: `${path} must be a string`,
|
|
4183
|
+
path
|
|
4184
|
+
};
|
|
4185
|
+
return state.items.get(id) ?? {
|
|
4186
|
+
code: "ELEMENT_NOT_FOUND",
|
|
4187
|
+
message: `element "${id}" does not exist`,
|
|
4188
|
+
path
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
function requireUnlocked(state, item) {
|
|
4192
|
+
const track = item.track ? state.tracks.find((t) => t.id === item.track) : void 0;
|
|
4193
|
+
if (track?.locked) return {
|
|
4194
|
+
code: "TRACK_LOCKED",
|
|
4195
|
+
message: `track "${track.id}" is locked`
|
|
4196
|
+
};
|
|
4197
|
+
return null;
|
|
4198
|
+
}
|
|
4199
|
+
function requireNotOpaque(item) {
|
|
4200
|
+
return item.editable === "opaque" ? {
|
|
4201
|
+
code: "OPAQUE_TARGET",
|
|
4202
|
+
message: `"${item.id}" is model-only code`
|
|
4203
|
+
} : null;
|
|
4204
|
+
}
|
|
4205
|
+
function requireLiteralProp(item, prop) {
|
|
4206
|
+
return item.lockedProps.includes(prop) ? {
|
|
4207
|
+
code: "LOCKED_PROP",
|
|
4208
|
+
message: `"${item.id}.${prop}" is an expression; bake it first`,
|
|
4209
|
+
path: prop
|
|
4210
|
+
} : null;
|
|
4211
|
+
}
|
|
4212
|
+
function requireLayout(state, item, layout) {
|
|
4213
|
+
const track = state.tracks.find((t) => t.id === item.track);
|
|
4214
|
+
if (!track || track.layout !== layout) return {
|
|
4215
|
+
code: "UNSUPPORTED_ON_LAYOUT",
|
|
4216
|
+
message: `needs a ${layout} track`
|
|
4217
|
+
};
|
|
4218
|
+
return null;
|
|
4219
|
+
}
|
|
4220
|
+
function collectErrors(...errors) {
|
|
4221
|
+
return invalid(...errors.filter((e) => Boolean(e) && "code" in e && "message" in e));
|
|
4222
|
+
}
|
|
4223
|
+
function isError(value) {
|
|
4224
|
+
return "code" in value && "message" in value && !("kind" in value);
|
|
4225
|
+
}
|
|
4226
|
+
/** Standard checks for a prop write on an element. */
|
|
4227
|
+
function validateTarget(state, id, props = []) {
|
|
4228
|
+
const item = requireElement(state, id);
|
|
4229
|
+
if (isError(item)) return invalid(item);
|
|
4230
|
+
const errors = [];
|
|
4231
|
+
const locked = requireUnlocked(state, item);
|
|
4232
|
+
if (locked) errors.push(locked);
|
|
4233
|
+
const opaque = requireNotOpaque(item);
|
|
4234
|
+
if (opaque) errors.push(opaque);
|
|
4235
|
+
for (const p of props) {
|
|
4236
|
+
const e = requireLiteralProp(item, p);
|
|
4237
|
+
if (e) errors.push(e);
|
|
4238
|
+
}
|
|
4239
|
+
return invalid(...errors);
|
|
4240
|
+
}
|
|
4241
|
+
function rawNode(state, id) {
|
|
4242
|
+
return state.tree.byId.get(id);
|
|
4243
|
+
}
|
|
4244
|
+
function rawProp(state, id, prop) {
|
|
4245
|
+
const node = rawNode(state, id);
|
|
4246
|
+
if (prop.startsWith("interior.")) return asJson(node?.interior?.props[prop.slice(9)]);
|
|
4247
|
+
return asJson(node?.props[prop]);
|
|
4248
|
+
}
|
|
4249
|
+
function siblingAfter(state, id) {
|
|
4250
|
+
const node = rawNode(state, id);
|
|
4251
|
+
const parent = node?.parent ? rawNode(state, node.parent) : void 0;
|
|
4252
|
+
if (!node || !parent) return null;
|
|
4253
|
+
const index = parent.children.findIndex((c) => c.id === id);
|
|
4254
|
+
return index > 0 ? parent.children[index - 1].id : null;
|
|
4255
|
+
}
|
|
4256
|
+
function placementProps(place) {
|
|
4257
|
+
if ("start" in place) return {
|
|
4258
|
+
start: place.start,
|
|
4259
|
+
anchor: void 0,
|
|
4260
|
+
offset: void 0,
|
|
4261
|
+
at: void 0
|
|
4262
|
+
};
|
|
4263
|
+
if ("anchor" in place) return {
|
|
4264
|
+
anchor: place.anchor,
|
|
4265
|
+
offset: place.offset,
|
|
4266
|
+
start: void 0
|
|
4267
|
+
};
|
|
4268
|
+
return {
|
|
4269
|
+
start: void 0,
|
|
4270
|
+
anchor: void 0,
|
|
4271
|
+
offset: void 0,
|
|
4272
|
+
at: void 0
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
function placementOf(state, id) {
|
|
4276
|
+
const node = rawNode(state, id);
|
|
4277
|
+
if (!node) return { after: null };
|
|
4278
|
+
if (typeof node.props.anchor === "string") return typeof node.props.offset === "number" ? {
|
|
4279
|
+
anchor: node.props.anchor,
|
|
4280
|
+
offset: node.props.offset
|
|
4281
|
+
} : { anchor: node.props.anchor };
|
|
4282
|
+
if (typeof node.props.start === "number") return { start: node.props.start };
|
|
4283
|
+
return { after: siblingAfter(state, id) };
|
|
4284
|
+
}
|
|
4285
|
+
/** `base`, or `base-2`, `base-3`, … until it names nothing in the tree, the resolved items or `taken`. */
|
|
4286
|
+
function uniqueId(state, base, taken = /* @__PURE__ */ new Set()) {
|
|
4287
|
+
let id = base;
|
|
4288
|
+
for (let n = 2; state.tree.byId.has(id) || state.items.has(id) || taken.has(id); n++) id = `${base}-${n}`;
|
|
4289
|
+
return id;
|
|
4290
|
+
}
|
|
4291
|
+
function setProps(log, id, props) {
|
|
4292
|
+
for (const [prop, value] of Object.entries(props)) log.append({
|
|
4293
|
+
op: "set",
|
|
4294
|
+
id,
|
|
4295
|
+
prop,
|
|
4296
|
+
value
|
|
4297
|
+
});
|
|
4298
|
+
}
|
|
4299
|
+
function insertNode(log, parent, after, spec) {
|
|
4300
|
+
log.append({
|
|
4301
|
+
op: "insert",
|
|
4302
|
+
id: spec.id,
|
|
4303
|
+
parent,
|
|
4304
|
+
after,
|
|
4305
|
+
node: nodeOfSpec(spec, parent)
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
function placeNode(log, state, parent, place, spec, afterOverride) {
|
|
4309
|
+
const after = afterOverride !== void 0 ? afterOverride : "after" in place ? place.after : lastChildId(state, parent);
|
|
4310
|
+
const props = {
|
|
4311
|
+
...spec.props,
|
|
4312
|
+
...placementProps(place)
|
|
4313
|
+
};
|
|
4314
|
+
const cleaned = Object.fromEntries(Object.entries(props).filter(([, v]) => v !== void 0));
|
|
4315
|
+
insertNode(log, parent, after, {
|
|
4316
|
+
...spec,
|
|
4317
|
+
props: cleaned
|
|
4318
|
+
});
|
|
4319
|
+
}
|
|
4320
|
+
function lastChildId(state, parent) {
|
|
4321
|
+
const node = rawNode(state, parent);
|
|
4322
|
+
const last = node?.children[node.children.length - 1];
|
|
4323
|
+
return last ? last.id : null;
|
|
4324
|
+
}
|
|
4325
|
+
function childIndex(state, parent, childId) {
|
|
4326
|
+
const node = rawNode(state, parent);
|
|
4327
|
+
return node ? node.children.findIndex((c) => c.id === childId) : -1;
|
|
4328
|
+
}
|
|
4329
|
+
function childAt(state, parent, index) {
|
|
4330
|
+
const child = rawNode(state, parent)?.children[index];
|
|
4331
|
+
return child ? child.id : null;
|
|
4332
|
+
}
|
|
4333
|
+
/** An action that sets literal props on one element; the inverse sets the prior values. */
|
|
4334
|
+
function makePropHandler(spec) {
|
|
4335
|
+
const target = spec.target ?? ((params) => params.id);
|
|
4336
|
+
return {
|
|
4337
|
+
type: spec.type,
|
|
4338
|
+
validate(action, state) {
|
|
4339
|
+
const id = target(action.params);
|
|
4340
|
+
const base = validateTarget(state, id, Object.keys(spec.write(action.params, state)));
|
|
4341
|
+
if (!base.valid) return base;
|
|
4342
|
+
const item = state.items.get(id);
|
|
4343
|
+
if (spec.kinds && item && !spec.kinds.includes(item.kind)) return invalid({
|
|
4344
|
+
code: "INVALID_PARAMS",
|
|
4345
|
+
message: `${spec.type} does not apply to <${item.kind}>`
|
|
4346
|
+
});
|
|
4347
|
+
return spec.validate ? spec.validate(action, state) : ok;
|
|
4348
|
+
},
|
|
4349
|
+
apply(action, log, ctx) {
|
|
4350
|
+
setProps(log, target(action.params), spec.write(action.params, ctx.before));
|
|
4351
|
+
},
|
|
4352
|
+
invert(action, before) {
|
|
4353
|
+
const id = target(action.params);
|
|
4354
|
+
const prior = {};
|
|
4355
|
+
for (const prop of Object.keys(spec.write(action.params, before))) prior[prop] = rawProp(before, id, prop);
|
|
4356
|
+
return {
|
|
4357
|
+
type: "source/setProps",
|
|
4358
|
+
params: {
|
|
4359
|
+
id,
|
|
4360
|
+
props: prior
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
4363
|
+
},
|
|
4364
|
+
fold: () => []
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4367
|
+
/** Register a handler and return it, for one-line namespace files. */
|
|
4368
|
+
function define(handler) {
|
|
4369
|
+
registerHandler(handler);
|
|
4370
|
+
return handler;
|
|
4371
|
+
}
|
|
4372
|
+
function stub(type, validate, invert) {
|
|
4373
|
+
return define({
|
|
4374
|
+
type,
|
|
4375
|
+
validate,
|
|
4376
|
+
apply: () => {
|
|
4377
|
+
throw new NotImplemented(type);
|
|
4378
|
+
},
|
|
4379
|
+
invert,
|
|
4380
|
+
fold: () => []
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4383
|
+
// src/jsx/animate.ts
|
|
4384
|
+
/**
|
|
4385
|
+
* Maps `input` from `inputRange` onto `outputRange`, piecewise: `interpolate(elapsed, [0, 0.5, 2], [0, 1, 0.8])`.
|
|
4386
|
+
* The input range must increase. Beyond the ends the value extends linearly, unless clamped; easing applies only inside.
|
|
4387
|
+
*/
|
|
4388
|
+
function interpolate(input, inputRange, outputRange, options = {}) {
|
|
4389
|
+
if (inputRange.length < 2 || inputRange.length !== outputRange.length) throw new Error("interpolate() wants input and output ranges of the same length, at least two");
|
|
4390
|
+
for (let i = 1; i < inputRange.length; i++) if (!(inputRange[i] > inputRange[i - 1])) throw new Error(`interpolate() wants an increasing input range, got [${inputRange.join(", ")}]`);
|
|
4391
|
+
let i = 1;
|
|
4392
|
+
while (i < inputRange.length - 1 && input > inputRange[i]) i++;
|
|
4393
|
+
const a = inputRange[i - 1], b = inputRange[i];
|
|
4394
|
+
let x = (input - a) / (b - a);
|
|
4395
|
+
if (x < 0 && options.extrapolateLeft === "clamp") x = 0;
|
|
4396
|
+
if (x > 1 && options.extrapolateRight === "clamp") x = 1;
|
|
4397
|
+
const k = x >= 0 && x <= 1 && options.easing ? EASING_FN[options.easing](x) : x;
|
|
4398
|
+
const lo = outputRange[i - 1];
|
|
4399
|
+
return lo + (outputRange[i] - lo) * k;
|
|
4400
|
+
}
|
|
4401
|
+
/** A physical spring from `from` (default 0) to `to` (default 1) at `time` seconds: mass 1, damping 10, stiffness 100 unless given. */
|
|
4402
|
+
function spring({ time, from = 0, to = 1, overshootClamping = false, ...config }) {
|
|
4403
|
+
const v = springAt(time, from, to, config);
|
|
4404
|
+
return overshootClamping ? to >= from ? Math.min(v, to) : Math.max(v, to) : v;
|
|
4405
|
+
}
|
|
4406
|
+
/** A number in [0, 1) fixed by `seed`: the same seed gives the same number every frame, in the preview and the export. */
|
|
4407
|
+
function random$1(seed) {
|
|
4408
|
+
return random(hash(String(seed)))();
|
|
4409
|
+
}
|
|
4410
|
+
// src/jsx/engines.ts
|
|
4411
|
+
/** On `globalThis`, like the layer clock: a file loaded with its own copy of the kit still finds the host's engines. */
|
|
4412
|
+
const engines = globalThis[Symbol.for("@cutcraft/kit/layer-engines")] ??= /* @__PURE__ */ new Map();
|
|
4413
|
+
/** Install (or with `null`, remove) the engine that draws `<Layer engine={name}>` tags such as `<Three>`. */
|
|
4414
|
+
function installLayerEngine(name, engine) {
|
|
4415
|
+
if (engine) engines.set(name, engine);
|
|
4416
|
+
else engines.delete(name);
|
|
4417
|
+
}
|
|
4418
|
+
function layerEngine(name) {
|
|
4419
|
+
return engines.get(name) ?? null;
|
|
4420
|
+
}
|
|
4421
|
+
/**
|
|
4422
|
+
* Frames an engine is still preparing (a three.js renderer being created, say). The browser export waits for none
|
|
4423
|
+
* before it captures a frame; the player ignores them. On `globalThis` for the same reason as the engines.
|
|
4424
|
+
*/
|
|
4425
|
+
const pending = globalThis[Symbol.for("@cutcraft/kit/layer-frames-pending")] ??= { count: 0 };
|
|
4426
|
+
/** Hold the frame until the returned release is called (once; later calls do nothing). */
|
|
4427
|
+
function holdLayerFrame() {
|
|
4428
|
+
pending.count++;
|
|
4429
|
+
let released = false;
|
|
4430
|
+
return () => {
|
|
4431
|
+
if (released) return;
|
|
4432
|
+
released = true;
|
|
4433
|
+
pending.count--;
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4436
|
+
/** How many holds are open: 0 when every engine has drawn the frame it was given. */
|
|
4437
|
+
function layerFramesPending() {
|
|
4438
|
+
return pending.count;
|
|
4439
|
+
}
|
|
4440
|
+
// src/jsx/rendering.ts
|
|
4441
|
+
/**
|
|
4442
|
+
* Where the renderers draw less than a file can say. Every type and enum value the schemas accept is drawn (a type the
|
|
4443
|
+
* player and the export do not draw is not offered); these are the remaining limits, which the `not-rendered` lint
|
|
4444
|
+
* reports, so a file that checks clean renders as written.
|
|
4445
|
+
*/
|
|
4446
|
+
/** Masks render on these kinds, one per element. */
|
|
4447
|
+
const MASKED_KINDS = ["clip", "nested"];
|
|
4448
|
+
const RENDERED_VALUES = [...[
|
|
4449
|
+
"title",
|
|
4450
|
+
"shape",
|
|
4451
|
+
"svg",
|
|
4452
|
+
"sticker",
|
|
4453
|
+
"graphic",
|
|
4454
|
+
"layer"
|
|
4455
|
+
].map((kind) => ({
|
|
4456
|
+
kind,
|
|
4457
|
+
prop: "blend",
|
|
4458
|
+
values: ["normal"],
|
|
4459
|
+
reason: "is drawn unblended over the footage, in the preview and the export"
|
|
4460
|
+
}))];
|
|
4461
|
+
const NOT_RENDERED = [
|
|
4462
|
+
{
|
|
4463
|
+
kind: "speedKey",
|
|
4464
|
+
parent: "nested",
|
|
4465
|
+
reason: "is ignored inside <Nested>: the sequence plays at its constant speed"
|
|
4466
|
+
},
|
|
4467
|
+
{
|
|
4468
|
+
kind: "freeze",
|
|
4469
|
+
parent: "nested",
|
|
4470
|
+
reason: "is ignored inside <Nested>: the sequence plays at its constant speed"
|
|
4471
|
+
},
|
|
4472
|
+
{
|
|
4473
|
+
kind: "effect",
|
|
4474
|
+
prop: "params.gamma",
|
|
4475
|
+
reason: "is ignored: levels render their black and white points only"
|
|
4476
|
+
},
|
|
4477
|
+
{
|
|
4478
|
+
kind: "composition",
|
|
4479
|
+
prop: "background.mode",
|
|
4480
|
+
value: "blur",
|
|
4481
|
+
reason: "draws as the plain colour when an overlay sits beneath the footage"
|
|
4482
|
+
},
|
|
4483
|
+
{
|
|
4484
|
+
kind: "captions",
|
|
4485
|
+
prop: "animation",
|
|
4486
|
+
value: "typewriter",
|
|
4487
|
+
reason: "types out only cues without word timings; cues with words show as karaoke"
|
|
4488
|
+
}
|
|
4489
|
+
];
|
|
4490
|
+
const MASK_NOT_RENDERED = {
|
|
4491
|
+
/** On a kind outside MASKED_KINDS. */
|
|
4492
|
+
kind: "is ignored: masks render on clips and nested sequences only, the element shows unmasked",
|
|
4493
|
+
second: "is ignored: only the first <Mask> on an element renders"
|
|
4494
|
+
};
|
|
4495
|
+
/** The composition's motion blur (`<Composition motionBlur>`), or null when it has none. */
|
|
4496
|
+
function motionBlurOf(state) {
|
|
4497
|
+
const blur = state.items.get(state.composition.id)?.props.motionBlur;
|
|
4498
|
+
if (!blur) return null;
|
|
4499
|
+
return {
|
|
4500
|
+
shutterAngle: blur.shutterAngle ?? 180,
|
|
4501
|
+
samples: blur.samples ?? 8
|
|
4713
4502
|
};
|
|
4714
4503
|
}
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4504
|
+
/**
|
|
4505
|
+
* The composition seconds a frame at `t` averages: `samples` moments spread evenly across the open shutter, which
|
|
4506
|
+
* opens at `t` and stays open `shutterAngle / 360` of a frame. Never past the composition's end.
|
|
4507
|
+
*/
|
|
4508
|
+
function shutterTimes(t, fps, blur, duration) {
|
|
4509
|
+
const open = blur.shutterAngle / 360 / fps;
|
|
4510
|
+
const last = Math.max(0, duration - 1e-6);
|
|
4511
|
+
return Array.from({ length: blur.samples }, (_, k) => Math.min(last, t + open * k / blur.samples));
|
|
4722
4512
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4513
|
+
// src/jsx/use-t.ts
|
|
4514
|
+
/**
|
|
4515
|
+
* The installed clock lives on `globalThis`, not in this module: a cut file loaded by a tool may carry its own
|
|
4516
|
+
* copy of the kit (a private module graph, a second install), and its `useT()` must still find the player's clock.
|
|
4517
|
+
*/
|
|
4518
|
+
const slot = globalThis[Symbol.for("@cutcraft/kit/layer-clock")] ??= { impl: null };
|
|
4519
|
+
/** The layer's clock: progress, seconds elapsed and remaining, its duration, the composition time. */
|
|
4520
|
+
function useLayer() {
|
|
4521
|
+
const impl = slot.impl;
|
|
4522
|
+
if (!impl) throw new Error("useLayer() needs a Player. The kit does not render; install one with installLayerClock().");
|
|
4523
|
+
return impl();
|
|
4728
4524
|
}
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
} : null;
|
|
4525
|
+
/** Normalised time (0..1) inside a `<Layer>`; `useLayer().t`. */
|
|
4526
|
+
function useT() {
|
|
4527
|
+
const impl = slot.impl;
|
|
4528
|
+
if (!impl) throw new Error("useT() needs a Player. The kit does not render; install one with installLayerClock().");
|
|
4529
|
+
return impl().t;
|
|
4735
4530
|
}
|
|
4736
|
-
function
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4531
|
+
function installLayerClock(fn) {
|
|
4532
|
+
slot.impl = fn;
|
|
4533
|
+
}
|
|
4534
|
+
// src/manifest/transcript.ts
|
|
4535
|
+
/**
|
|
4536
|
+
* Reads a transcript into the manifest's word list (`assets.<id>.transcript.words`), sniffing the format when it is
|
|
4537
|
+
* not given. Subtitle cues time whole lines, so their words share the cue's time by length and are marked `estimated`;
|
|
4538
|
+
* JSON with word times keeps them exactly.
|
|
4539
|
+
*
|
|
4540
|
+
* const { words } = parseTranscript(await (await fetch("/talk.vtt")).text());
|
|
4541
|
+
* manifest.assets.talk = { ...manifest.assets.talk, transcript: { words } };
|
|
4542
|
+
*/
|
|
4543
|
+
function parseTranscript(text, format = sniff(text)) {
|
|
4544
|
+
const raw = format === "json" ? fromJson(text) : fromCues(text, format);
|
|
4545
|
+
const words = [];
|
|
4546
|
+
for (const w of raw) {
|
|
4547
|
+
const t = w.text.trim();
|
|
4548
|
+
if (!t || !Number.isFinite(w.start) || !Number.isFinite(w.end)) continue;
|
|
4549
|
+
const start = ms(Math.max(0, w.start));
|
|
4550
|
+
words.push({
|
|
4551
|
+
i: words.length,
|
|
4552
|
+
text: t,
|
|
4553
|
+
start,
|
|
4554
|
+
end: Math.max(start, ms(w.end)),
|
|
4555
|
+
...w.speaker ? { speaker: w.speaker } : {},
|
|
4556
|
+
...w.estimated ? { estimated: true } : {}
|
|
4557
|
+
});
|
|
4558
|
+
}
|
|
4559
|
+
return {
|
|
4560
|
+
words,
|
|
4561
|
+
estimated: words.some((w) => w.estimated)
|
|
4741
4562
|
};
|
|
4742
|
-
return null;
|
|
4743
4563
|
}
|
|
4744
|
-
function
|
|
4745
|
-
|
|
4564
|
+
function sniff(text) {
|
|
4565
|
+
const head = text.replace(/^/, "").trimStart();
|
|
4566
|
+
if (head.startsWith("{") || head.startsWith("[")) return "json";
|
|
4567
|
+
if (head.startsWith("WEBVTT")) return "vtt";
|
|
4568
|
+
if (TIMING.test(text)) return "srt";
|
|
4569
|
+
throw new Error("not a transcript: expected SRT, WebVTT or JSON");
|
|
4746
4570
|
}
|
|
4747
|
-
|
|
4748
|
-
|
|
4571
|
+
const ms = (s) => Math.round(s * 1e3) / 1e3;
|
|
4572
|
+
const STAMP = String.raw`(?:(\d+):)?(\d{1,2}):(\d{2})[.,](\d{1,3})`;
|
|
4573
|
+
const TIMING = new RegExp(`${STAMP}\\s*-->\\s*${STAMP}`);
|
|
4574
|
+
function seconds(h, m, s, f) {
|
|
4575
|
+
return Number(h ?? 0) * 3600 + Number(m) * 60 + Number(s) + Number(f.padEnd(3, "0")) / 1e3;
|
|
4749
4576
|
}
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
const
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
const
|
|
4761
|
-
|
|
4577
|
+
function fromCues(text, format) {
|
|
4578
|
+
const out = [];
|
|
4579
|
+
const blocks = text.replace(/^/, "").replace(/\r\n?/g, "\n").split(/\n{2,}/);
|
|
4580
|
+
for (const block of blocks) {
|
|
4581
|
+
const lines = block.split("\n");
|
|
4582
|
+
const at = lines.findIndex((l) => TIMING.test(l));
|
|
4583
|
+
if (at < 0) continue;
|
|
4584
|
+
const m = TIMING.exec(lines[at]);
|
|
4585
|
+
const start = seconds(m[1], m[2], m[3], m[4]);
|
|
4586
|
+
const end = seconds(m[5], m[6], m[7], m[8]);
|
|
4587
|
+
const body = lines.slice(at + 1).map((l) => l.replace(/^-\s+/, "")).join(" ");
|
|
4588
|
+
out.push(...format === "vtt" ? vttCue(body, start, end) : shareOut(clean(body), start, end, void 0));
|
|
4762
4589
|
}
|
|
4763
|
-
return
|
|
4590
|
+
return out;
|
|
4764
4591
|
}
|
|
4765
|
-
|
|
4766
|
-
|
|
4592
|
+
/** Text without markup: tags (`<i>`, `<c.x>`, `<v Ann>`), SSA overrides (`{\an8}`) and the common entities. */
|
|
4593
|
+
function clean(text) {
|
|
4594
|
+
return text.replace(/<[^>]*>/g, "").replace(/\{\\[^}]*\}/g, "").replace(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
4767
4595
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
return
|
|
4596
|
+
/** A cue's words, each given a share of [start, end] by its length: an estimate, marked as one. */
|
|
4597
|
+
function shareOut(text, start, end, speaker, estimated = true) {
|
|
4598
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
4599
|
+
if (words.length === 0) return [];
|
|
4600
|
+
const total = words.reduce((n, w) => n + w.length, 0);
|
|
4601
|
+
const out = [];
|
|
4602
|
+
let at = start;
|
|
4603
|
+
for (const w of words) {
|
|
4604
|
+
const next = at + (end - start) * w.length / total;
|
|
4605
|
+
out.push({
|
|
4606
|
+
text: w,
|
|
4607
|
+
start: at,
|
|
4608
|
+
end: next,
|
|
4609
|
+
...speaker ? { speaker } : {},
|
|
4610
|
+
...estimated ? { estimated } : {}
|
|
4611
|
+
});
|
|
4612
|
+
at = next;
|
|
4613
|
+
}
|
|
4614
|
+
return out;
|
|
4772
4615
|
}
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4616
|
+
const INLINE = new RegExp(`<${STAMP}>`, "g");
|
|
4617
|
+
/**
|
|
4618
|
+
* A WebVTT cue: `<v Ann>` names the speaker; inline timestamps (`<00:01.500>`), where a captioner wrote them, start
|
|
4619
|
+
* the words after them. A stretch between two timestamps that holds one word is measured, not estimated.
|
|
4620
|
+
*/
|
|
4621
|
+
function vttCue(body, start, end) {
|
|
4622
|
+
const speaker = /<v(?:\.[^\s>]*)?\s+([^>]+)>/.exec(body)?.[1]?.trim();
|
|
4623
|
+
const stamps = [];
|
|
4624
|
+
for (const m of body.matchAll(INLINE)) stamps.push({
|
|
4625
|
+
at: seconds(m[1], m[2], m[3], m[4]),
|
|
4626
|
+
index: m.index,
|
|
4627
|
+
length: m[0].length
|
|
4628
|
+
});
|
|
4629
|
+
if (stamps.length === 0) return shareOut(clean(body), start, end, speaker);
|
|
4630
|
+
const out = [];
|
|
4631
|
+
const edges = [{
|
|
4632
|
+
at: start,
|
|
4633
|
+
index: 0,
|
|
4634
|
+
length: 0
|
|
4635
|
+
}, ...stamps];
|
|
4636
|
+
edges.forEach((edge, n) => {
|
|
4637
|
+
const next = edges[n + 1];
|
|
4638
|
+
const text = clean(body.slice(edge.index + edge.length, next ? next.index : body.length));
|
|
4639
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
4640
|
+
out.push(...shareOut(text, edge.at, next ? next.at : end, speaker, words.length > 1));
|
|
4641
|
+
});
|
|
4642
|
+
return out;
|
|
4779
4643
|
}
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4644
|
+
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4645
|
+
const num = (v) => typeof v === "number" ? v : NaN;
|
|
4646
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
4647
|
+
const SHAPES = "{ words: [{ word | text, start, end }] } (OpenAI, ElevenLabs, CutCraft), { segments: [{ words | text, start, end }] } (openai-whisper, faster-whisper, WhisperX) or { transcription: [{ tokens, offsets }] } (whisper.cpp)";
|
|
4648
|
+
function fromJson(text) {
|
|
4649
|
+
let data;
|
|
4650
|
+
try {
|
|
4651
|
+
data = JSON.parse(text);
|
|
4652
|
+
} catch (error) {
|
|
4653
|
+
throw new Error(`not a transcript: ${error instanceof Error ? error.message : String(error)}`);
|
|
4654
|
+
}
|
|
4655
|
+
if (Array.isArray(data)) data = { segments: data };
|
|
4656
|
+
if (!isObject(data)) throw new Error(`not a transcript: expected ${SHAPES}`);
|
|
4657
|
+
if (Array.isArray(data.words)) return words(data.words);
|
|
4658
|
+
if (Array.isArray(data.segments)) return data.segments.filter(isObject).flatMap(segment);
|
|
4659
|
+
if (Array.isArray(data.transcription)) return data.transcription.filter(isObject).flatMap(whisperCpp);
|
|
4660
|
+
throw new Error(`not a transcript: expected ${SHAPES}`);
|
|
4661
|
+
}
|
|
4662
|
+
/** Word objects in seconds: `word` (OpenAI, whisper) or `text` (ElevenLabs, CutCraft); ElevenLabs' spacing dropped. */
|
|
4663
|
+
function words(list, segmentSpeaker) {
|
|
4664
|
+
const out = [];
|
|
4665
|
+
for (const w of list) {
|
|
4666
|
+
if (!isObject(w)) continue;
|
|
4667
|
+
if (typeof w.type === "string" && w.type !== "word") continue;
|
|
4668
|
+
const speaker = str(w.speaker) ?? str(w.speaker_id) ?? segmentSpeaker;
|
|
4669
|
+
out.push({
|
|
4670
|
+
text: str(w.word) ?? str(w.text) ?? "",
|
|
4671
|
+
start: num(w.start),
|
|
4672
|
+
end: num(w.end),
|
|
4673
|
+
...speaker ? { speaker } : {},
|
|
4674
|
+
...w.estimated === true ? { estimated: true } : {}
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
return out;
|
|
4678
|
+
}
|
|
4679
|
+
/** A segment: its words when it has word times, else its text shared out over its span. */
|
|
4680
|
+
function segment(s) {
|
|
4681
|
+
const speaker = str(s.speaker);
|
|
4682
|
+
if (Array.isArray(s.words) && s.words.length > 0) return words(s.words, speaker);
|
|
4683
|
+
return shareOut(str(s.text) ?? "", num(s.start), num(s.end), speaker);
|
|
4684
|
+
}
|
|
4685
|
+
/** whisper.cpp's `-ojf` segment: sub-word tokens with offsets in ms, joined into words on a leading space. */
|
|
4686
|
+
function whisperCpp(s) {
|
|
4687
|
+
const offsets = isObject(s.offsets) ? s.offsets : {};
|
|
4688
|
+
const tokens = Array.isArray(s.tokens) ? s.tokens.filter(isObject) : [];
|
|
4689
|
+
if (tokens.length === 0) return shareOut(str(s.text) ?? "", num(offsets.from) / 1e3, num(offsets.to) / 1e3, void 0);
|
|
4690
|
+
const out = [];
|
|
4691
|
+
for (const t of tokens) {
|
|
4692
|
+
const text = str(t.text) ?? "";
|
|
4693
|
+
if (/^\s*\[_[^\]]*\]\s*$/.test(text) || /^\s*<\|[^|]*\|>\s*$/.test(text)) continue;
|
|
4694
|
+
const o = isObject(t.offsets) ? t.offsets : {};
|
|
4695
|
+
const from = num(o.from) / 1e3;
|
|
4696
|
+
const to = num(o.to) / 1e3;
|
|
4697
|
+
const last = out[out.length - 1];
|
|
4698
|
+
if (!last || /^\s/.test(text)) out.push({
|
|
4699
|
+
text: text.trim(),
|
|
4700
|
+
start: from,
|
|
4701
|
+
end: to
|
|
4702
|
+
});
|
|
4703
|
+
else {
|
|
4704
|
+
last.text += text;
|
|
4705
|
+
last.end = to;
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
return out;
|
|
4709
|
+
}
|
|
4710
|
+
// src/theme/fonts.ts
|
|
4711
|
+
const BUNDLED_FONTS = [
|
|
4712
|
+
{
|
|
4713
|
+
family: "Inter",
|
|
4714
|
+
id: "inter",
|
|
4715
|
+
italic: true
|
|
4716
|
+
},
|
|
4717
|
+
{
|
|
4718
|
+
family: "Space Grotesk",
|
|
4719
|
+
id: "space-grotesk",
|
|
4720
|
+
italic: false
|
|
4721
|
+
},
|
|
4722
|
+
{
|
|
4723
|
+
family: "Playfair Display",
|
|
4724
|
+
id: "playfair-display",
|
|
4725
|
+
italic: true
|
|
4726
|
+
},
|
|
4727
|
+
{
|
|
4728
|
+
family: "DM Sans",
|
|
4729
|
+
id: "dm-sans",
|
|
4730
|
+
italic: true
|
|
4731
|
+
},
|
|
4732
|
+
{
|
|
4733
|
+
family: "JetBrains Mono",
|
|
4734
|
+
id: "jetbrains-mono",
|
|
4735
|
+
italic: true
|
|
4736
|
+
}
|
|
4737
|
+
];
|
|
4738
|
+
const BUNDLED_FONT_WEIGHTS = [400, 700];
|
|
4739
|
+
function isBundledFont(family) {
|
|
4740
|
+
return BUNDLED_FONTS.some((f) => f.family === family);
|
|
4741
|
+
}
|
|
4742
|
+
const STORE_KEY = Symbol.for("cutcraft.fonts.store");
|
|
4743
|
+
const store = globalThis[STORE_KEY] ??= {
|
|
4744
|
+
scope: null,
|
|
4745
|
+
global: /* @__PURE__ */ new Map()
|
|
4746
|
+
};
|
|
4747
|
+
/** The faces a module loaded by `loadModule` declared, stored on its exports under this key. */
|
|
4748
|
+
const MODULE_FONTS = Symbol.for("cutcraft.fonts");
|
|
4749
|
+
function declare(face) {
|
|
4750
|
+
if (!(Number.isInteger(face.weight) && face.weight >= 100 && face.weight <= 900 && face.weight % 100 === 0)) throw new Error(`font "${face.family}": weight must be 100, 200 … 900, got ${face.weight}`);
|
|
4751
|
+
if (face.style !== "normal" && face.style !== "italic") throw new Error(`font "${face.family}": style must be "normal" or "italic", got "${face.style}"`);
|
|
4752
|
+
(store.scope ?? store.global).set(`${face.family}|${face.weight}|${face.style}`, face);
|
|
4753
|
+
return face.family;
|
|
4754
|
+
}
|
|
4755
|
+
/**
|
|
4756
|
+
* Declares a face from a font file and returns its family, to pass as a `font` prop, a theme font or a CSS
|
|
4757
|
+
* `fontFamily`. Call it at the top of the file with literal arguments, as the editor and lint read them from the source.
|
|
4758
|
+
*
|
|
4759
|
+
* const brand = font({ family: "Brand Sans", src: "asset:brand", weight: 700 });
|
|
4760
|
+
* <Title id="hello" font={brand}>Hello</Title>
|
|
4761
|
+
*
|
|
4762
|
+
* `src: "asset:brand"` finds `brand.ttf` (or `.otf`, `.woff`, `.woff2`) where the export finds media, and asks the
|
|
4763
|
+
* player's `media` for `brand`.
|
|
4764
|
+
*/
|
|
4765
|
+
function font(options) {
|
|
4766
|
+
if (!options.src.startsWith("asset:") || options.src.length === 6) throw new Error(`font "${options.family}": src must be "asset:<id>", got "${options.src}"`);
|
|
4767
|
+
return declare({
|
|
4768
|
+
family: options.family,
|
|
4769
|
+
weight: options.weight ?? 400,
|
|
4770
|
+
style: options.style ?? "normal",
|
|
4771
|
+
source: {
|
|
4772
|
+
kind: "asset",
|
|
4773
|
+
id: options.src.slice(6)
|
|
4774
|
+
}
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
/**
|
|
4778
|
+
* Declares a Google Fonts family, in each weight and style given (default 400, normal), and returns the family. The
|
|
4779
|
+
* export downloads the files once into `~/.cache/cutcraft/fonts` (`CUTCRAFT_FONT_CACHE` moves it) and reads them from
|
|
4780
|
+
* there; the player loads them from Google.
|
|
4781
|
+
*
|
|
4782
|
+
* const titan = googleFont("Titan One");
|
|
4783
|
+
* const inter = googleFont("Inter Tight", { weights: [400, 700], styles: ["normal", "italic"] });
|
|
4784
|
+
*/
|
|
4785
|
+
function googleFont(family, options = {}) {
|
|
4786
|
+
for (const weight of options.weights ?? [400]) for (const style of options.styles ?? ["normal"]) declare({
|
|
4787
|
+
family,
|
|
4788
|
+
weight,
|
|
4789
|
+
style,
|
|
4790
|
+
source: { kind: "google" }
|
|
4791
|
+
});
|
|
4792
|
+
return family;
|
|
4793
|
+
}
|
|
4794
|
+
/** Runs `load` and returns the faces declared while it ran: how `loadModule` keeps each file's faces apart. */
|
|
4795
|
+
function collectFonts(load) {
|
|
4796
|
+
const outer = store.scope;
|
|
4797
|
+
const scope = /* @__PURE__ */ new Map();
|
|
4798
|
+
store.scope = scope;
|
|
4799
|
+
try {
|
|
4800
|
+
load();
|
|
4801
|
+
} finally {
|
|
4802
|
+
store.scope = outer;
|
|
4803
|
+
}
|
|
4804
|
+
return [...scope.values()];
|
|
4805
|
+
}
|
|
4806
|
+
/** The faces a module declared: those `loadModule` recorded for it, or else every face declared outside a load. */
|
|
4807
|
+
function fontsOf(mod) {
|
|
4808
|
+
return mod[MODULE_FONTS] ?? [...store.global.values()];
|
|
4809
|
+
}
|
|
4810
|
+
// src/resolve/collect.ts
|
|
4811
|
+
const NO_BINDINGS = Object.freeze({});
|
|
4812
|
+
/**
|
|
4813
|
+
* Walk the element tree the file's default export returned and index every
|
|
4814
|
+
* vocabulary element by id. Pure helper components are expanded by calling
|
|
4815
|
+
* them; a `<Layer>` interior is not entered.
|
|
4816
|
+
*/
|
|
4817
|
+
function collect(element, options = {}) {
|
|
4818
|
+
const walker = {
|
|
4819
|
+
byId: /* @__PURE__ */ new Map(),
|
|
4820
|
+
errors: [],
|
|
4821
|
+
index: options.index,
|
|
4822
|
+
helperUses: /* @__PURE__ */ new Map()
|
|
4791
4823
|
};
|
|
4824
|
+
const roots = collectChildren(element, null, walker, null);
|
|
4825
|
+
const root = roots.find((n) => n.kind === "composition") ?? roots[0];
|
|
4826
|
+
if (!root) {
|
|
4827
|
+
const empty = {
|
|
4828
|
+
id: "composition",
|
|
4829
|
+
kind: "composition",
|
|
4830
|
+
props: {},
|
|
4831
|
+
children: [],
|
|
4832
|
+
parent: null,
|
|
4833
|
+
editable: "literal",
|
|
4834
|
+
lockedProps: [],
|
|
4835
|
+
boundProps: {}
|
|
4836
|
+
};
|
|
4837
|
+
walker.errors.push({
|
|
4838
|
+
code: "SCHEMA",
|
|
4839
|
+
id: "composition",
|
|
4840
|
+
message: "the file must render a <Composition>"
|
|
4841
|
+
});
|
|
4842
|
+
return {
|
|
4843
|
+
root: empty,
|
|
4844
|
+
byId: walker.byId,
|
|
4845
|
+
errors: walker.errors
|
|
4846
|
+
};
|
|
4847
|
+
}
|
|
4848
|
+
const multi = new Set([...walker.helperUses].filter(([, n]) => n > 1).map(([name]) => name));
|
|
4849
|
+
if (multi.size > 0) {
|
|
4850
|
+
for (const node of walker.byId.values()) if (node.origin && multi.has(node.origin.helper) && node.editable === "literal") node.editable = "generated";
|
|
4851
|
+
}
|
|
4792
4852
|
return {
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
at: void 0
|
|
4853
|
+
root,
|
|
4854
|
+
byId: walker.byId,
|
|
4855
|
+
errors: walker.errors
|
|
4797
4856
|
};
|
|
4798
4857
|
}
|
|
4799
|
-
function
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4858
|
+
function collectModule(mod, overrides = {}, options = {}) {
|
|
4859
|
+
return {
|
|
4860
|
+
...collect(mod.default({
|
|
4861
|
+
...mod.defaults ?? {},
|
|
4862
|
+
...overrides
|
|
4863
|
+
}) ?? Fragment({ children: null }), options),
|
|
4864
|
+
fonts: fontsOf(mod),
|
|
4865
|
+
shaders: shadersOf(mod)
|
|
4866
|
+
};
|
|
4808
4867
|
}
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4868
|
+
function collectChildren(node, parent, walker, origin) {
|
|
4869
|
+
const out = [];
|
|
4870
|
+
Children.forEach(node, (child) => {
|
|
4871
|
+
if (!isValidElement(child)) return;
|
|
4872
|
+
const element = child;
|
|
4873
|
+
const type = element.type;
|
|
4874
|
+
if (type === Fragment) {
|
|
4875
|
+
out.push(...collectChildren(element.props.children, parent, walker, origin));
|
|
4876
|
+
return;
|
|
4877
|
+
}
|
|
4878
|
+
if (isVocabulary(type)) {
|
|
4879
|
+
if (type.kind === "layer") {
|
|
4880
|
+
const engine = engineOf(type);
|
|
4881
|
+
const node = makeNode(engine ? withEngine(element, engine) : element, type.kind, parent, walker, origin, [], true);
|
|
4882
|
+
const all = Children.toArray(element.props.children);
|
|
4883
|
+
const own = all.filter(isLayerChild);
|
|
4884
|
+
const children = own.length > 0 ? collectChildren(own, node.id, walker, origin) : [];
|
|
4885
|
+
const interior = engine ? null : interiorOf(all.filter((c) => !isLayerChild(c)));
|
|
4886
|
+
const full = {
|
|
4887
|
+
...node,
|
|
4888
|
+
children,
|
|
4889
|
+
...interior ? { interior } : {}
|
|
4890
|
+
};
|
|
4891
|
+
if (full !== node) walker.byId.set(node.id, full);
|
|
4892
|
+
out.push(full);
|
|
4893
|
+
return;
|
|
4894
|
+
}
|
|
4895
|
+
const placeholder = makeNode(element, type.kind, parent, walker, origin, [], false);
|
|
4896
|
+
const children = collectChildren(element.props.children, placeholder.id, walker, origin);
|
|
4897
|
+
const node = {
|
|
4898
|
+
...placeholder,
|
|
4899
|
+
children
|
|
4900
|
+
};
|
|
4901
|
+
walker.byId.set(node.id, node);
|
|
4902
|
+
out.push(node);
|
|
4903
|
+
return;
|
|
4904
|
+
}
|
|
4905
|
+
if (typeof type === "function") {
|
|
4906
|
+
const helper = type;
|
|
4907
|
+
const name = helper.name || "anonymous";
|
|
4908
|
+
const uses = (walker.helperUses.get(name) ?? 0) + 1;
|
|
4909
|
+
walker.helperUses.set(name, uses);
|
|
4910
|
+
let produced;
|
|
4911
|
+
try {
|
|
4912
|
+
produced = helper(element.props);
|
|
4913
|
+
} catch (error) {
|
|
4914
|
+
walker.errors.push({
|
|
4915
|
+
code: "SCHEMA",
|
|
4916
|
+
id: parent ?? "composition",
|
|
4917
|
+
message: `helper ${name} threw while expanding: ${String(error)}`
|
|
4918
|
+
});
|
|
4919
|
+
return;
|
|
4920
|
+
}
|
|
4921
|
+
const childOrigin = origin ?? {
|
|
4922
|
+
helper: name,
|
|
4923
|
+
callerId: parent,
|
|
4924
|
+
callIndex: uses - 1
|
|
4925
|
+
};
|
|
4926
|
+
out.push(...collectChildren(produced, parent, walker, childOrigin));
|
|
4927
|
+
return;
|
|
4928
|
+
}
|
|
4929
|
+
out.push(...collectChildren(element.props.children, parent, walker, origin));
|
|
4930
|
+
});
|
|
4931
|
+
return out;
|
|
4814
4932
|
}
|
|
4815
|
-
function
|
|
4816
|
-
|
|
4817
|
-
|
|
4933
|
+
function makeNode(element, kind, parent, walker, origin, children, opaqueInterior) {
|
|
4934
|
+
const { children: rawChildren, ...props } = element.props;
|
|
4935
|
+
const idValue = props.id ?? (kind === "composition" ? "composition" : void 0);
|
|
4936
|
+
const id = typeof idValue === "string" && idValue.length > 0 ? idValue : `${kind}-${walker.byId.size + 1}`;
|
|
4937
|
+
if (typeof idValue !== "string" || idValue.length === 0) walker.errors.push({
|
|
4938
|
+
code: "SCHEMA",
|
|
4818
4939
|
id,
|
|
4819
|
-
prop,
|
|
4820
|
-
|
|
4940
|
+
prop: "id",
|
|
4941
|
+
message: `<${kind}> needs a literal id`
|
|
4821
4942
|
});
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
id: spec.id,
|
|
4827
|
-
parent,
|
|
4828
|
-
after,
|
|
4829
|
-
node: nodeOfSpec(spec, parent)
|
|
4943
|
+
if (walker.byId.has(id)) walker.errors.push({
|
|
4944
|
+
code: "DUPLICATE_ID",
|
|
4945
|
+
id,
|
|
4946
|
+
message: `id "${id}" is used more than once`
|
|
4830
4947
|
});
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4948
|
+
const text = textOf(rawChildren);
|
|
4949
|
+
const indexed = walker.index?.elements.get(id);
|
|
4950
|
+
let editable = "literal";
|
|
4951
|
+
if (walker.index && (!indexed || indexed.generated)) editable = "generated";
|
|
4952
|
+
const lockedProps = indexed?.lockedProps ?? [];
|
|
4953
|
+
const boundProps = indexed?.boundProps ?? NO_BINDINGS;
|
|
4954
|
+
const node = {
|
|
4955
|
+
id,
|
|
4956
|
+
kind,
|
|
4957
|
+
props,
|
|
4958
|
+
children,
|
|
4959
|
+
parent,
|
|
4960
|
+
editable,
|
|
4961
|
+
lockedProps,
|
|
4962
|
+
boundProps,
|
|
4963
|
+
...text !== void 0 ? { text } : {},
|
|
4964
|
+
...origin ? { origin } : {}
|
|
4837
4965
|
};
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4966
|
+
if (opaqueInterior) {
|
|
4967
|
+
const opaque = {
|
|
4968
|
+
...node,
|
|
4969
|
+
editable: editable === "generated" ? "generated" : "literal"
|
|
4970
|
+
};
|
|
4971
|
+
walker.byId.set(id, opaque);
|
|
4972
|
+
return opaque;
|
|
4973
|
+
}
|
|
4974
|
+
walker.byId.set(id, node);
|
|
4975
|
+
return node;
|
|
4843
4976
|
}
|
|
4844
|
-
function
|
|
4845
|
-
const
|
|
4846
|
-
|
|
4847
|
-
|
|
4977
|
+
function textOf(children) {
|
|
4978
|
+
const parts = [];
|
|
4979
|
+
let onlyText = true;
|
|
4980
|
+
Children.forEach(children, (child) => {
|
|
4981
|
+
if (typeof child === "string" || typeof child === "number") parts.push(String(child));
|
|
4982
|
+
else if (isValidElement(child) && isVocabulary(child.type)) return;
|
|
4983
|
+
else if (child !== null && child !== void 0 && typeof child !== "boolean") onlyText = false;
|
|
4984
|
+
});
|
|
4985
|
+
if (!onlyText || parts.length === 0) return void 0;
|
|
4986
|
+
return parts.join("");
|
|
4848
4987
|
}
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
return
|
|
4988
|
+
/** The element with its engine named in its props, where the resolver, the Player and the export read it. */
|
|
4989
|
+
function withEngine(element, engine) {
|
|
4990
|
+
return {
|
|
4991
|
+
...element,
|
|
4992
|
+
props: {
|
|
4993
|
+
...element.props,
|
|
4994
|
+
engine
|
|
4995
|
+
}
|
|
4996
|
+
};
|
|
4852
4997
|
}
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4998
|
+
/** The timeline children a Layer may hold in its JSX, beside its free code. */
|
|
4999
|
+
const LAYER_CHILD_KINDS = /* @__PURE__ */ new Set(["effect", "keyframe"]);
|
|
5000
|
+
function isLayerChild(child) {
|
|
5001
|
+
return isValidElement(child) && isVocabulary(child.type) && LAYER_CHILD_KINDS.has(child.type.kind);
|
|
4856
5002
|
}
|
|
4857
|
-
/**
|
|
4858
|
-
function
|
|
4859
|
-
const
|
|
5003
|
+
/** A Layer's handles: exactly one child element whose type is a function component (not a vocabulary element). */
|
|
5004
|
+
function interiorOf(children) {
|
|
5005
|
+
const only = Children.toArray(children).filter((c) => c !== null && c !== void 0 && typeof c !== "boolean" && c !== "");
|
|
5006
|
+
const child = only.length === 1 ? only[0] : null;
|
|
5007
|
+
if (!isValidElement(child) || typeof child.type !== "function" || isVocabulary(child.type)) return null;
|
|
5008
|
+
const { children: _children, ...props } = child.props;
|
|
4860
5009
|
return {
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
const id = target(action.params);
|
|
4864
|
-
const base = validateTarget(state, id, Object.keys(spec.write(action.params, state)));
|
|
4865
|
-
if (!base.valid) return base;
|
|
4866
|
-
const item = state.items.get(id);
|
|
4867
|
-
if (spec.kinds && item && !spec.kinds.includes(item.kind)) return invalid({
|
|
4868
|
-
code: "INVALID_PARAMS",
|
|
4869
|
-
message: `${spec.type} does not apply to <${item.kind}>`
|
|
4870
|
-
});
|
|
4871
|
-
return spec.validate ? spec.validate(action, state) : ok;
|
|
4872
|
-
},
|
|
4873
|
-
apply(action, log, ctx) {
|
|
4874
|
-
setProps(log, target(action.params), spec.write(action.params, ctx.before));
|
|
4875
|
-
},
|
|
4876
|
-
invert(action, before) {
|
|
4877
|
-
const id = target(action.params);
|
|
4878
|
-
const prior = {};
|
|
4879
|
-
for (const prop of Object.keys(spec.write(action.params, before))) prior[prop] = rawProp(before, id, prop);
|
|
4880
|
-
return {
|
|
4881
|
-
type: "source/setProps",
|
|
4882
|
-
params: {
|
|
4883
|
-
id,
|
|
4884
|
-
props: prior
|
|
4885
|
-
}
|
|
4886
|
-
};
|
|
4887
|
-
},
|
|
4888
|
-
fold: () => []
|
|
5010
|
+
component: child.type.name || "anonymous",
|
|
5011
|
+
props
|
|
4889
5012
|
};
|
|
4890
5013
|
}
|
|
4891
|
-
|
|
4892
|
-
function
|
|
4893
|
-
|
|
4894
|
-
return handler;
|
|
5014
|
+
// src/resolve/errors.ts
|
|
5015
|
+
function isResolveError(value) {
|
|
5016
|
+
return typeof value === "object" && value !== null && "code" in value && "message" in value && "id" in value;
|
|
4895
5017
|
}
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
5018
|
+
// src/resolve/scene.ts
|
|
5019
|
+
/**
|
|
5020
|
+
* Resolve a `<Nested of={Scene}>` interior: the scene is called with the
|
|
5021
|
+
* nested element's props (minus `of` and `id`) and what it returns is
|
|
5022
|
+
* resolved inside a composition of the outer size. The player renders the
|
|
5023
|
+
* result; `nested/extract` writes it into the file.
|
|
5024
|
+
*/
|
|
5025
|
+
function resolveScene(of, props, outer, manifest) {
|
|
5026
|
+
const { of: _of, id: _id, ...rest } = props;
|
|
5027
|
+
const inner = of(rest);
|
|
5028
|
+
return resolve(collect(createElement(Composition, {
|
|
5029
|
+
fps: outer.fps,
|
|
5030
|
+
width: outer.width,
|
|
5031
|
+
height: outer.height
|
|
5032
|
+
}, inner)), manifest);
|
|
5033
|
+
}
|
|
5034
|
+
// src/resolve/subtitles.ts
|
|
5035
|
+
/**
|
|
5036
|
+
* The captions the cut plays, as an SRT or WebVTT file: every `<Caption>` under a `<Captions>`, written in the file
|
|
5037
|
+
* or generated from a transcript, at the times the video shows them. With `from`/`to` (an export of part of the
|
|
5038
|
+
* composition), cues are clipped to that range and timed from its start, so they line up with that file.
|
|
5039
|
+
*/
|
|
5040
|
+
function subtitles(state, options) {
|
|
5041
|
+
const from = Math.max(0, options.from ?? 0);
|
|
5042
|
+
const to = Math.min(state.composition.duration, options.to ?? state.composition.duration);
|
|
5043
|
+
const lines = [];
|
|
5044
|
+
for (const p of state.items.values()) {
|
|
5045
|
+
if (p.kind !== "caption" || state.items.get(p.parent ?? "")?.kind !== "captions") continue;
|
|
5046
|
+
const start = Math.max(p.start, from);
|
|
5047
|
+
const end = Math.min(p.end, to);
|
|
5048
|
+
const text = String(p.props.text ?? "").trim();
|
|
5049
|
+
if (end <= start || !text) continue;
|
|
5050
|
+
lines.push({
|
|
5051
|
+
start: start - from,
|
|
5052
|
+
end: end - from,
|
|
5053
|
+
text
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5056
|
+
lines.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
5057
|
+
if (options.format === "vtt") return `WEBVTT\n\n${lines.map((l) => `${stamp(l.start, ".")} --> ${stamp(l.end, ".")}\n${l.text}\n`).join("\n")}`;
|
|
5058
|
+
return lines.map((l, n) => `${n + 1}\n${stamp(l.start, ",")} --> ${stamp(l.end, ",")}\n${l.text}\n`).join("\n");
|
|
5059
|
+
}
|
|
5060
|
+
/** `HH:MM:SS,mmm` (SRT) or `HH:MM:SS.mmm` (WebVTT). */
|
|
5061
|
+
function stamp(seconds, mark) {
|
|
5062
|
+
const total = Math.round(seconds * 1e3);
|
|
5063
|
+
const h = Math.floor(total / 36e5);
|
|
5064
|
+
const m = Math.floor(total / 6e4) % 60;
|
|
5065
|
+
const s = Math.floor(total / 1e3) % 60;
|
|
5066
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
5067
|
+
return `${pad(h)}:${pad(m)}:${pad(s)}${mark}${pad(total % 1e3, 3)}`;
|
|
4906
5068
|
}
|
|
4907
5069
|
// src/index.ts
|
|
4908
5070
|
var src_exports = /* @__PURE__ */ __exportAll({
|
|
4909
5071
|
ANIMATION_UNITS: () => ANIMATION_UNITS,
|
|
4910
|
-
AUDIO_EFFECT_TYPES: () => AUDIO_EFFECT_TYPES,
|
|
4911
5072
|
AUTOMATION_LANES: () => AUTOMATION_LANES,
|
|
4912
5073
|
AUTO_GROUP_TYPES: () => AUTO_GROUP_TYPES,
|
|
4913
5074
|
ActionHistory: () => ActionHistory,
|
|
4914
5075
|
Adjustment: () => Adjustment,
|
|
4915
5076
|
Angle: () => Angle,
|
|
4916
5077
|
Audio: () => Audio,
|
|
4917
|
-
AudioEffect: () => AudioEffect,
|
|
4918
5078
|
BLEND_MODES: () => BLEND_MODES,
|
|
4919
5079
|
BUNDLED_FONTS: () => BUNDLED_FONTS,
|
|
4920
5080
|
BUNDLED_FONT_WEIGHTS: () => BUNDLED_FONT_WEIGHTS,
|
|
@@ -4931,8 +5091,9 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
4931
5091
|
EASINGS: () => EASINGS,
|
|
4932
5092
|
EASING_FN: () => EASING_FN,
|
|
4933
5093
|
ELEMENTS: () => ELEMENTS,
|
|
4934
|
-
EMPHASIS_ANIMATIONS: () => EMPHASIS_ANIMATIONS,
|
|
4935
5094
|
EMPTY_MANIFEST: () => EMPTY_MANIFEST,
|
|
5095
|
+
ENGINE_LAYERS: () => ENGINE_LAYERS,
|
|
5096
|
+
ENGINE_SCHEMAS: () => ENGINE_SCHEMAS,
|
|
4936
5097
|
Effect: () => Effect,
|
|
4937
5098
|
Executor: () => Executor,
|
|
4938
5099
|
FIT_MODES: () => FIT_MODES,
|
|
@@ -4942,7 +5103,6 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
4942
5103
|
Gap: () => Gap,
|
|
4943
5104
|
Graphic: () => Graphic,
|
|
4944
5105
|
INTERIOR_PREFIX: () => INTERIOR_PREFIX,
|
|
4945
|
-
INTERPOLATION_QUALITIES: () => INTERPOLATION_QUALITIES,
|
|
4946
5106
|
KEYFRAME_PROPERTIES: () => KEYFRAME_PROPERTIES,
|
|
4947
5107
|
KEYFRAME_PROPERTY_PATTERN: () => KEYFRAME_PROPERTY_PATTERN,
|
|
4948
5108
|
KIND_CAPABILITIES: () => KIND_CAPABILITIES,
|
|
@@ -4952,27 +5112,21 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
4952
5112
|
MASK_EASINGS: () => MASK_EASINGS,
|
|
4953
5113
|
MASK_NOT_RENDERED: () => MASK_NOT_RENDERED,
|
|
4954
5114
|
MASK_TYPES: () => MASK_TYPES,
|
|
4955
|
-
|
|
5115
|
+
MODULE_FONTS: () => MODULE_FONTS,
|
|
5116
|
+
MODULE_SHADERS: () => MODULE_SHADERS,
|
|
4956
5117
|
MOTIONS: () => MOTIONS,
|
|
4957
5118
|
MOTION_NAMES: () => MOTION_NAMES,
|
|
4958
5119
|
Marker: () => Marker,
|
|
4959
5120
|
Mask: () => Mask,
|
|
5121
|
+
Motion: () => Motion,
|
|
4960
5122
|
Multicam: () => Multicam,
|
|
4961
5123
|
NOT_RENDERED: () => NOT_RENDERED,
|
|
4962
5124
|
Nested: () => Nested,
|
|
4963
5125
|
NotImplemented: () => NotImplemented,
|
|
4964
5126
|
PALETTE_KEYS: () => PALETTE_KEYS,
|
|
4965
|
-
RENDERED_AUDIO_EFFECTS: () => RENDERED_AUDIO_EFFECTS,
|
|
4966
|
-
RENDERED_CAPTION_ANIMATIONS: () => RENDERED_CAPTION_ANIMATIONS,
|
|
4967
|
-
RENDERED_COLOR_GRADE: () => RENDERED_COLOR_GRADE,
|
|
4968
|
-
RENDERED_EFFECTS: () => RENDERED_EFFECTS,
|
|
4969
|
-
RENDERED_MASK_TYPES: () => RENDERED_MASK_TYPES,
|
|
4970
|
-
RENDERED_TITLE_ANIMATIONS: () => RENDERED_TITLE_ANIMATIONS,
|
|
4971
|
-
RENDERED_TRANSITIONS: () => RENDERED_TRANSITIONS,
|
|
4972
5127
|
RENDERED_VALUES: () => RENDERED_VALUES,
|
|
4973
5128
|
SCHEMAS_BY_KIND: () => SCHEMAS_BY_KIND,
|
|
4974
5129
|
SHAPE_TYPES: () => SHAPE_TYPES,
|
|
4975
|
-
STABILIZE_CROP_MODES: () => STABILIZE_CROP_MODES,
|
|
4976
5130
|
STYLED_KINDS: () => STYLED_KINDS,
|
|
4977
5131
|
SVG_COLOR_MODES: () => SVG_COLOR_MODES,
|
|
4978
5132
|
Shape: () => Shape,
|
|
@@ -4992,6 +5146,7 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
4992
5146
|
TRACK_ROLES: () => TRACK_ROLES,
|
|
4993
5147
|
TRANSITION_PARAMS: () => TRANSITION_PARAMS,
|
|
4994
5148
|
TRANSITION_TYPES: () => TRANSITION_TYPES,
|
|
5149
|
+
Three: () => Three,
|
|
4995
5150
|
Title: () => Title,
|
|
4996
5151
|
Track: () => Track,
|
|
4997
5152
|
Transition: () => Transition,
|
|
@@ -5004,22 +5159,32 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
5004
5159
|
childIndex: () => childIndex,
|
|
5005
5160
|
collect: () => collect,
|
|
5006
5161
|
collectErrors: () => collectErrors,
|
|
5162
|
+
collectFonts: () => collectFonts,
|
|
5007
5163
|
collectModule: () => collectModule,
|
|
5164
|
+
collectShaders: () => collectShaders,
|
|
5008
5165
|
createPatchLog: () => createPatchLog,
|
|
5009
5166
|
cueNodes: () => cueNodes,
|
|
5010
5167
|
defaultTransitionParams: () => defaultTransitionParams,
|
|
5011
5168
|
define: () => define,
|
|
5169
|
+
defineEffect: () => defineEffect,
|
|
5012
5170
|
defineElement: () => defineElement,
|
|
5171
|
+
defineTransition: () => defineTransition,
|
|
5013
5172
|
describeAction: () => describeAction,
|
|
5173
|
+
engineOf: () => engineOf,
|
|
5014
5174
|
findParentOf: () => findParentOf,
|
|
5175
|
+
font: () => font,
|
|
5176
|
+
fontsOf: () => fontsOf,
|
|
5015
5177
|
freezeHoldsOf: () => freezeHoldsOf,
|
|
5016
5178
|
freezeShift: () => freezeShift,
|
|
5017
5179
|
freezeUnshift: () => freezeUnshift,
|
|
5018
5180
|
getHandler: () => getHandler,
|
|
5181
|
+
googleFont: () => googleFont,
|
|
5019
5182
|
graphicControls: () => graphicControls,
|
|
5183
|
+
holdLayerFrame: () => holdLayerFrame,
|
|
5020
5184
|
holdTotal: () => holdTotal,
|
|
5021
5185
|
insertNode: () => insertNode,
|
|
5022
5186
|
installLayerClock: () => installLayerClock,
|
|
5187
|
+
installLayerEngine: () => installLayerEngine,
|
|
5023
5188
|
interpolate: () => interpolate,
|
|
5024
5189
|
invalid: () => invalid,
|
|
5025
5190
|
inverseOf: () => inverseOf,
|
|
@@ -5031,9 +5196,12 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
5031
5196
|
isVocabulary: () => isVocabulary,
|
|
5032
5197
|
kindOf: () => kindOf,
|
|
5033
5198
|
lastChildId: () => lastChildId,
|
|
5199
|
+
layerEngine: () => layerEngine,
|
|
5200
|
+
layerFramesPending: () => layerFramesPending,
|
|
5034
5201
|
listHandlers: () => listHandlers,
|
|
5035
5202
|
lookupAsset: () => lookupAsset,
|
|
5036
5203
|
makePropHandler: () => makePropHandler,
|
|
5204
|
+
motionBlurOf: () => motionBlurOf,
|
|
5037
5205
|
motionStyleSchema: () => motionStyleSchema,
|
|
5038
5206
|
nodeOfSpec: () => nodeOfSpec,
|
|
5039
5207
|
nodeSpecOf: () => nodeSpecOf,
|
|
@@ -5041,6 +5209,7 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
5041
5209
|
ok: () => ok,
|
|
5042
5210
|
onTimeline: () => onTimeline,
|
|
5043
5211
|
parseTimeRef: () => parseTimeRef,
|
|
5212
|
+
parseTranscript: () => parseTranscript,
|
|
5044
5213
|
placeNode: () => placeNode,
|
|
5045
5214
|
placementOf: () => placementOf,
|
|
5046
5215
|
placementProps: () => placementProps,
|
|
@@ -5066,12 +5235,16 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
5066
5235
|
sameAsset: () => sameAsset,
|
|
5067
5236
|
samePlacement: () => samePlacement,
|
|
5068
5237
|
setProps: () => setProps,
|
|
5238
|
+
shaderFor: () => shaderFor,
|
|
5239
|
+
shadersOf: () => shadersOf,
|
|
5240
|
+
shutterTimes: () => shutterTimes,
|
|
5069
5241
|
siblingAfter: () => siblingAfter,
|
|
5070
5242
|
speedKeysOf: () => speedKeysOf,
|
|
5071
5243
|
spring: () => spring,
|
|
5072
5244
|
springAt: () => springAt,
|
|
5073
5245
|
stableJson: () => stableJson,
|
|
5074
5246
|
stub: () => stub,
|
|
5247
|
+
subtitles: () => subtitles,
|
|
5075
5248
|
targetIdOf: () => targetIdOf,
|
|
5076
5249
|
themeSchema: () => themeSchema,
|
|
5077
5250
|
themeToken: () => themeToken,
|
|
@@ -5080,13 +5253,12 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
5080
5253
|
uniqueId: () => uniqueId,
|
|
5081
5254
|
useLayer: () => useLayer,
|
|
5082
5255
|
useT: () => useT,
|
|
5256
|
+
usesShaders: () => usesShaders,
|
|
5083
5257
|
validateTarget: () => validateTarget,
|
|
5084
5258
|
walk: () => walk,
|
|
5085
|
-
zAudioEffectType: () => zAudioEffectType,
|
|
5086
5259
|
zBlendMode: () => zBlendMode,
|
|
5087
5260
|
zCaptionAnimation: () => zCaptionAnimation,
|
|
5088
5261
|
zEasing: () => zEasing,
|
|
5089
|
-
zEmphasisAnimation: () => zEmphasisAnimation,
|
|
5090
5262
|
zFitMode: () => zFitMode,
|
|
5091
5263
|
zGraphicAnimation: () => zGraphicAnimation,
|
|
5092
5264
|
zKeyframeProperty: () => zKeyframeProperty,
|
|
@@ -5098,4 +5270,4 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
5098
5270
|
zTransitionType: () => zTransitionType,
|
|
5099
5271
|
zVideoEffectType: () => zVideoEffectType
|
|
5100
5272
|
});
|
|
5101
|
-
export {
|
|
5273
|
+
export { siblingAfter as $, EASINGS as $n, shadersOf as $t, interpolate as A, Title as An, zVideoEffectType as Ar, freezeHoldsOf as At, lastChildId as B, tokenSchema as Bn, stableJson as Br, resolveTheme as Bt, RENDERED_VALUES as C, Nested as Cn, zKeyframeProperty as Cr, cueNodes as Ct, installLayerEngine as D, Sticker as Dn, zTrackLayout as Dr, sameAsset as Dt, holdLayerFrame as E, SpeedKey as En, zTextAnimationPreset as Er, playsKey as Et, childIndex as F, defaultTransitionParams as Fn, capabilitiesOf as Fr, rampInverse as Ft, rawNode as G, PALETTE_KEYS as Gn, isTimeRefString as Gt, placeNode as H, FONT_KEYS as Hn, EMPTY_MANIFEST as Ht, collectErrors as I, STYLED_KINDS as In, asJson as Ir, rampSpeed as It, requireLayout as J, ANIMATION_UNITS as Jn, MODULE_SHADERS as Jt, rawProp as K, THEMES as Kn, parseTimeRef as Kt, define as L, TOKEN_PATTERN as Ln, createPatchLog as Lr, speedKeysOf as Lt, spring as M, Transition as Mn, isVocabulary as Mr, freezeUnshift as Mt, NotImplemented as N, engineOf as Nn, kindOf as Nr, holdTotal as Nt, layerEngine as O, Svg as On, zTrackRole as Or, timelineWords as Ot, childAt as P, TRANSITION_PARAMS as Pn, KIND_CAPABILITIES as Pr, rampAdvance as Pt, setProps as Q, CAPTION_POSITIONS as Qn, shaderFor as Qt, insertNode as R, motionStyleSchema as Rn, nodesEqual as Rr, springAt as Rt, NOT_RENDERED as S, Multicam as Sn, zGraphicAnimation as Sr, DEFAULT_CUE_OPTIONS as St, shutterTimes as T, Shape as Tn, zShapeType as Tr, planCues as Tt, placementOf as U, MOTIONS as Un, assetIdOf as Ut, makePropHandler as V, DEFAULT_THEME as Vn, themeToken as Vt, placementProps as W, MOTION_NAMES as Wn, lookupAsset as Wt, requireNotOpaque as X, BLEND_MODES as Xn, defineEffect as Xt, requireLiteralProp as Y, AUTOMATION_LANES as Yn, collectShaders as Yt, requireUnlocked as Z, CAPTION_ANIMATIONS as Zn, defineTransition as Zt, installLayerClock as _, Keyframe as _n, isKeyframeProperty as _r, resolve as _t, isResolveError as a, Captions as an, MASK_TYPES as ar, inverseOf as at, MASKED_KINDS as b, Mask as bn, zEasing as br, findParentOf as bt, BUNDLED_FONTS as c, Composition as cn, TEXT_ALIGNS as cr, ActionHistory as ct, collectFonts as d, ENGINE_LAYERS as dn, TEXT_VALIGNS as dr, describeAction as dt, usesShaders as en, FIT_MODES as er, stub as et, font as f, ENGINE_SCHEMAS as fn, TRACK_FLAGS as fr, targetIdOf as ft, parseTranscript as g, Graphic as gn, VIDEO_EFFECT_TYPES as gr, ok as gt, isBundledFont as h, Gap as hn, TRANSITION_TYPES as hr, nodeSpecOf as ht, resolveScene as i, Caption as in, MASK_EASINGS as ir, getHandler as it, random$1 as j, Track as jn, defineElement as jr, freezeShift as jt, layerFramesPending as k, Three as kn, zTransitionType as kr, EASING_FN as kt, BUNDLED_FONT_WEIGHTS as l, Cut as ln, TEXT_ANIMATION_PRESETS as lr, AUTO_GROUP_TYPES as lt, googleFont as m, Freeze as mn, TRACK_ROLES as mr, nodeOfSpec as mt, src_exports as n, Angle as nn, KEYFRAME_PROPERTIES as nr, validateTarget as nt, collect as o, Clip as on, SHAPE_TYPES as or, listHandlers as ot, fontsOf as p, Effect as pn, TRACK_LAYOUTS as pr, invalid as pt, requireElement as q, THEME_NAMES as qn, resolveTimeRef as qt, subtitles as r, Audio as rn, KEYFRAME_PROPERTY_PATTERN as rr, Executor as rt, collectModule as s, ColorGrade as sn, SVG_COLOR_MODES as sr, registerHandler as st, graphicControls as t, Adjustment as tn, GRAPHIC_ANIMATIONS as tr, uniqueId as tt, MODULE_FONTS as u, ELEMENTS as un, TEXT_DECORATIONS as ur, TARGET_PARAM_KEYS as ut, useLayer as v, Layer as vn, zBlendMode as vr, samePlacement as vt, motionBlurOf as w, SCHEMAS_BY_KIND as wn, zMaskEasing as wr, onTimeline as wt, MASK_NOT_RENDERED as x, Motion as xn, zFitMode as xr, walk as xt, useT as y, Marker as yn, zCaptionAnimation as yr, INTERIOR_PREFIX as yt, isError as z, themeSchema as zn, reduceInto as zr, applyTheme as zt };
|