@kitsra/kavio-builder 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1670 @@
1
+ import { validateComposition } from "@kitsra/kavio-schema";
2
+ const propPattern = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
3
+ const animatableProperties = ["opacity", "x", "y", "scale", "rotation"];
4
+ export class PropReference {
5
+ name;
6
+ metadata;
7
+ constructor(name, metadata) {
8
+ if (!propPattern.test(name)) {
9
+ throw new Error(`Invalid Kavio prop name "${name}".`);
10
+ }
11
+ this.name = name;
12
+ if (metadata !== undefined) {
13
+ this.metadata = metadata;
14
+ }
15
+ }
16
+ toString() {
17
+ return interpolationFor(this.name);
18
+ }
19
+ toJSON() {
20
+ return interpolationFor(this.name);
21
+ }
22
+ }
23
+ export class AssetReference {
24
+ id;
25
+ type;
26
+ definition;
27
+ constructor(id, type, src, options = {}) {
28
+ assertId(id, "asset");
29
+ this.id = id;
30
+ this.type = type;
31
+ this.definition = compactObject({
32
+ type,
33
+ src,
34
+ ...options
35
+ });
36
+ }
37
+ toDefinition() {
38
+ return { ...this.definition };
39
+ }
40
+ toString() {
41
+ return this.id;
42
+ }
43
+ toJSON() {
44
+ return this.id;
45
+ }
46
+ }
47
+ export class LayerBuilder {
48
+ layer;
49
+ constructor(layer) {
50
+ this.layer = layer;
51
+ }
52
+ animate(property, frames) {
53
+ return this.motion({ [property]: frames });
54
+ }
55
+ motion(frames) {
56
+ const existing = isRecord(this.layer.keyframes) ? this.layer.keyframes : {};
57
+ const next = { ...existing };
58
+ for (const property of animatableProperties) {
59
+ const propertyFrames = frames[property];
60
+ if (propertyFrames !== undefined) {
61
+ next[property] = propertyFrames.map((frame) => ({ ...frame }));
62
+ }
63
+ }
64
+ this.layer.keyframes = next;
65
+ return this;
66
+ }
67
+ transitionIn(definition) {
68
+ this.layer.transitionIn = { ...definition };
69
+ return this;
70
+ }
71
+ transitionOut(definition) {
72
+ this.layer.transitionOut = { ...definition };
73
+ return this;
74
+ }
75
+ enter(definition) {
76
+ return this.transitionIn(definition);
77
+ }
78
+ exit(definition) {
79
+ return this.transitionOut(definition);
80
+ }
81
+ toInput() {
82
+ return cloneAuthorObject(this.layer);
83
+ }
84
+ toJSON() {
85
+ return normalizeStandalone(this.layer);
86
+ }
87
+ get id() {
88
+ return String(this.layer.id);
89
+ }
90
+ }
91
+ export class TrackBuilder {
92
+ definition;
93
+ constructor(id, clips = []) {
94
+ assertId(id, "track");
95
+ this.definition = {
96
+ id,
97
+ clips: clips.map((clip) => cloneTrackClip(clip))
98
+ };
99
+ }
100
+ clip(id, options) {
101
+ this.definition.clips.push(trackClip(id, options));
102
+ return this;
103
+ }
104
+ toInput() {
105
+ return {
106
+ id: this.definition.id,
107
+ clips: this.definition.clips.map((clip) => cloneTrackClip(clip))
108
+ };
109
+ }
110
+ toJSON() {
111
+ return normalizeStandalone(this.toInput());
112
+ }
113
+ }
114
+ export class VideoBuilder {
115
+ composition;
116
+ metadata;
117
+ propDefinitions = new Map();
118
+ assetDefinitions = new Map();
119
+ layers = [];
120
+ trackDefinitions = [];
121
+ audioTracks = [];
122
+ exportDefinitions = [];
123
+ constructor(composition, options = {}) {
124
+ this.composition = { ...composition };
125
+ if (options.metadata !== undefined) {
126
+ this.metadata = options.metadata;
127
+ }
128
+ }
129
+ prop(reference) {
130
+ this.registerProp(reference);
131
+ return this;
132
+ }
133
+ props(...references) {
134
+ for (const reference of references) {
135
+ this.registerProp(reference);
136
+ }
137
+ return this;
138
+ }
139
+ asset(reference) {
140
+ this.registerAsset(reference);
141
+ return this;
142
+ }
143
+ assets(...references) {
144
+ for (const reference of references) {
145
+ this.registerAsset(reference);
146
+ }
147
+ return this;
148
+ }
149
+ add(...layers) {
150
+ for (const layer of layers) {
151
+ this.layers.push(layer.toInput());
152
+ }
153
+ return this;
154
+ }
155
+ addTrack(...tracks) {
156
+ for (const entry of tracks) {
157
+ this.trackDefinitions.push(entry instanceof TrackBuilder ? entry.toInput() : cloneTrack(entry));
158
+ }
159
+ return this;
160
+ }
161
+ tracks(...tracks) {
162
+ return this.addTrack(...tracks);
163
+ }
164
+ audio(options) {
165
+ const entry = compactObject({
166
+ ...options,
167
+ id: options.id ?? (options.asset instanceof AssetReference ? options.asset.id : undefined)
168
+ });
169
+ this.audioTracks.push(entry);
170
+ return this;
171
+ }
172
+ addExport(definition) {
173
+ this.exportDefinitions.push(compactObject(definition));
174
+ return this;
175
+ }
176
+ exports(...definitions) {
177
+ for (const definition of definitions) {
178
+ this.addExport(definition);
179
+ }
180
+ return this;
181
+ }
182
+ toJSON() {
183
+ const props = this.normalizeProps();
184
+ const document = {
185
+ version: "0.1",
186
+ ...(this.metadata === undefined ? {} : { metadata: this.normalize(this.metadata) }),
187
+ composition: this.normalize(this.composition),
188
+ ...(Object.keys(props).length === 0 ? {} : { props }),
189
+ assets: this.normalizeAssets(),
190
+ layers: this.layers.map((layer) => this.normalize(layer)),
191
+ ...(this.trackDefinitions.length === 0
192
+ ? {}
193
+ : { tracks: this.trackDefinitions.map((track) => this.normalize(track)) }),
194
+ audio: this.audioTracks.map((track) => this.normalize(track)),
195
+ exports: this.exportDefinitions.map((definition) => this.normalize(definition))
196
+ };
197
+ return structuredClone(document);
198
+ }
199
+ validate() {
200
+ return validateComposition(this.toJSON());
201
+ }
202
+ registerAsset(reference) {
203
+ this.assetDefinitions.set(reference.id, reference.toDefinition());
204
+ }
205
+ registerProp(reference) {
206
+ if (reference.metadata === undefined) {
207
+ return;
208
+ }
209
+ const existing = this.propDefinitions.get(reference.name) ?? {};
210
+ this.propDefinitions.set(reference.name, { ...existing, ...reference.metadata });
211
+ }
212
+ normalizeProps() {
213
+ const output = {};
214
+ for (const [name, metadata] of this.propDefinitions) {
215
+ output[name] = this.normalize(metadata);
216
+ }
217
+ return output;
218
+ }
219
+ normalizeAssets() {
220
+ const output = {};
221
+ for (const [id, definition] of this.assetDefinitions) {
222
+ output[id] = this.normalize(definition);
223
+ }
224
+ return output;
225
+ }
226
+ normalize(value) {
227
+ if (value instanceof PropReference) {
228
+ this.registerProp(value);
229
+ return interpolationFor(value.name);
230
+ }
231
+ if (value instanceof AssetReference) {
232
+ this.registerAsset(value);
233
+ return value.id;
234
+ }
235
+ if (Array.isArray(value)) {
236
+ return value.map((item) => this.normalize(item));
237
+ }
238
+ if (isRecord(value)) {
239
+ const output = {};
240
+ for (const [key, nested] of Object.entries(value)) {
241
+ if (nested !== undefined) {
242
+ output[key] = this.normalize(nested);
243
+ }
244
+ }
245
+ return output;
246
+ }
247
+ return value;
248
+ }
249
+ }
250
+ export function video(composition, options) {
251
+ return new VideoBuilder(composition, options);
252
+ }
253
+ export function prop(name, metadata) {
254
+ return new PropReference(name, metadata);
255
+ }
256
+ export function validate(input) {
257
+ return validateComposition(input instanceof VideoBuilder ? input.toJSON() : input);
258
+ }
259
+ export const asset = {
260
+ video(id, src, options) {
261
+ return new AssetReference(id, "video", src, options);
262
+ },
263
+ image(id, src, options) {
264
+ return new AssetReference(id, "image", src, options);
265
+ },
266
+ audio(id, src, options) {
267
+ return new AssetReference(id, "audio", src, options);
268
+ },
269
+ font(id, src, options) {
270
+ return new AssetReference(id, "font", src, options);
271
+ }
272
+ };
273
+ export function clip(id, options) {
274
+ return buildLayer(id, "video", options);
275
+ }
276
+ export function videoLayer(id, options) {
277
+ return clip(id, options);
278
+ }
279
+ export function image(id, options) {
280
+ return buildLayer(id, "image", options);
281
+ }
282
+ export function text(id, options) {
283
+ return buildLayer(id, "text", options);
284
+ }
285
+ export function shape(id, options) {
286
+ return buildLayer(id, "shape", { shape: "rect", ...options });
287
+ }
288
+ export function caption(id, options) {
289
+ return buildLayer(id, "caption", options);
290
+ }
291
+ export function trackClip(id, options) {
292
+ assertId(id, "track clip");
293
+ const definition = {
294
+ id,
295
+ layerId: layerIdFor(options.layerId),
296
+ startFrame: options.startFrame,
297
+ durationFrames: options.durationFrames
298
+ };
299
+ if (options.transitionFromPrevious !== undefined) {
300
+ definition.transitionFromPrevious = transitionSeries.fromPrevious(options.transitionFromPrevious);
301
+ }
302
+ return definition;
303
+ }
304
+ export function track(id, clips = []) {
305
+ return new TrackBuilder(id, clips);
306
+ }
307
+ export const layers = {
308
+ video: videoLayer,
309
+ clip,
310
+ image,
311
+ text,
312
+ shape,
313
+ caption
314
+ };
315
+ export const transition = {
316
+ fade(options) {
317
+ return transitionDefinition("fade", options);
318
+ },
319
+ slide(options) {
320
+ return transitionDefinition("slide", options);
321
+ },
322
+ wipe(options) {
323
+ return transitionDefinition("wipe", options);
324
+ },
325
+ crossfade(options) {
326
+ return transitionDefinition("crossfade", options);
327
+ },
328
+ zoom(options = { durationFrames: 12 }) {
329
+ return transitionDefinition("zoom", options);
330
+ },
331
+ push(options) {
332
+ return transitionDefinition("push", options);
333
+ },
334
+ spin(options = { durationFrames: 12 }) {
335
+ return transitionDefinition("spin", options);
336
+ },
337
+ rotate(options = { durationFrames: 12 }) {
338
+ return transitionDefinition("rotate", options);
339
+ },
340
+ flip(options = { durationFrames: 12 }) {
341
+ return transitionDefinition("flip", options);
342
+ },
343
+ blurDissolve(options = { durationFrames: 12 }) {
344
+ return transitionDefinition("blurDissolve", options);
345
+ },
346
+ colorDissolve(options = { durationFrames: 12 }) {
347
+ return transitionDefinition("colorDissolve", options);
348
+ },
349
+ dip(options = { durationFrames: 12 }) {
350
+ return transitionDefinition("dip", options);
351
+ },
352
+ iris(options = { durationFrames: 12 }) {
353
+ return transitionDefinition("iris", options);
354
+ },
355
+ stretch(options = { durationFrames: 12 }) {
356
+ return transitionDefinition("stretch", options);
357
+ },
358
+ squeeze(options = { durationFrames: 12 }) {
359
+ return transitionDefinition("squeeze", options);
360
+ },
361
+ clockWipe(options = { durationFrames: 12 }) {
362
+ return transitionDefinition("clockWipe", options);
363
+ },
364
+ barWipe(options = { durationFrames: 12 }) {
365
+ return transitionDefinition("barWipe", options);
366
+ },
367
+ gridWipe(options = { durationFrames: 12 }) {
368
+ return transitionDefinition("gridWipe", options);
369
+ },
370
+ tileReveal(options = { durationFrames: 12 }) {
371
+ return transitionDefinition("tileReveal", options);
372
+ },
373
+ radialBlur(options = { durationFrames: 12 }) {
374
+ return transitionDefinition("radialBlur", options);
375
+ },
376
+ zoomBlur(options = { durationFrames: 12 }) {
377
+ return transitionDefinition("zoomBlur", options);
378
+ },
379
+ bookFlip(options = { durationFrames: 12 }) {
380
+ return transitionDefinition("bookFlip", options);
381
+ },
382
+ pageCurlLite(options = { durationFrames: 12 }) {
383
+ return transitionDefinition("pageCurlLite", options);
384
+ },
385
+ skewSlide(options = { durationFrames: 12 }) {
386
+ return transitionDefinition("skewSlide", options);
387
+ },
388
+ expandMask(options = { durationFrames: 12 }) {
389
+ return transitionDefinition("expandMask", options);
390
+ },
391
+ letterboxReveal(options = { durationFrames: 12 }) {
392
+ return transitionDefinition("letterboxReveal", options);
393
+ },
394
+ filmFlash(options = { durationFrames: 6 }) {
395
+ return transitionDefinition("filmFlash", options);
396
+ },
397
+ cameraWhip(options = { durationFrames: 8 }) {
398
+ return transitionDefinition("cameraWhip", options);
399
+ }
400
+ };
401
+ export const transitions = transition;
402
+ export const transitionSeries = {
403
+ fromPrevious(definition) {
404
+ if (isTransitionSeriesDefinition(definition)) {
405
+ return cloneTransitionSeriesDefinition(definition);
406
+ }
407
+ return transitionSeriesDefinition(definition);
408
+ }
409
+ };
410
+ export const camera = {
411
+ kenBurns(options) {
412
+ const amount = cameraTravelAmount(options, 36);
413
+ const direction = options.direction ?? "right";
414
+ const frames = zoomMotion(options, 1, 1 + cameraIntensity(options, 0.08));
415
+ if (direction === "left" || direction === "right") {
416
+ frames.x = cameraAxisFrames({
417
+ durationFrames: options.durationFrames,
418
+ easing: options.easing,
419
+ axis: "x",
420
+ direction,
421
+ amount,
422
+ restingValue: options.restingX ?? 0,
423
+ subjectAnchor: options.subjectAnchor
424
+ });
425
+ }
426
+ else if (direction === "up" || direction === "down") {
427
+ frames.y = cameraAxisFrames({
428
+ durationFrames: options.durationFrames,
429
+ easing: options.easing,
430
+ axis: "y",
431
+ direction,
432
+ amount,
433
+ restingValue: options.restingY ?? 0,
434
+ subjectAnchor: options.subjectAnchor
435
+ });
436
+ }
437
+ return frames;
438
+ },
439
+ pushIn(options) {
440
+ return zoomMotion(options, options.fromScale ?? 1, options.toScale ?? 1 + cameraIntensity(options, 0.08));
441
+ },
442
+ pullBack(options) {
443
+ return zoomMotion(options, options.fromScale ?? 1 + cameraIntensity(options, 0.08), options.toScale ?? 1);
444
+ },
445
+ pan(options) {
446
+ const direction = options.direction ?? "left";
447
+ const amount = cameraTravelAmount(options, 72);
448
+ const scale = options.scale ?? 1 + cameraIntensity(options, 0.04);
449
+ const frames = {
450
+ x: cameraAxisFrames({
451
+ durationFrames: options.durationFrames,
452
+ easing: options.easing,
453
+ axis: "x",
454
+ direction,
455
+ amount,
456
+ restingValue: options.restingX ?? 0,
457
+ fromValue: options.fromX,
458
+ toValue: options.toX,
459
+ subjectAnchor: options.subjectAnchor
460
+ })
461
+ };
462
+ if (scale !== 1) {
463
+ frames.scale = cameraTrack(options.durationFrames, scale, scale);
464
+ }
465
+ return frames;
466
+ },
467
+ tilt(options) {
468
+ const direction = options.direction ?? "up";
469
+ const amount = cameraTravelAmount(options, 72);
470
+ const scale = options.scale ?? 1 + cameraIntensity(options, 0.04);
471
+ const frames = {
472
+ y: cameraAxisFrames({
473
+ durationFrames: options.durationFrames,
474
+ easing: options.easing,
475
+ axis: "y",
476
+ direction,
477
+ amount,
478
+ restingValue: options.restingY ?? 0,
479
+ fromValue: options.fromY,
480
+ toValue: options.toY,
481
+ subjectAnchor: options.subjectAnchor
482
+ })
483
+ };
484
+ if (scale !== 1) {
485
+ frames.scale = cameraTrack(options.durationFrames, scale, scale);
486
+ }
487
+ return frames;
488
+ },
489
+ parallax(options) {
490
+ const direction = options.direction ?? "right";
491
+ const amount = cameraTravelAmount(options, 48);
492
+ const frames = zoomMotion(options, options.fromScale ?? 1.02, options.toScale ?? 1.02 + cameraIntensity(options, 0.04));
493
+ if (direction === "left" || direction === "right") {
494
+ frames.x = cameraAxisFrames({
495
+ durationFrames: options.durationFrames,
496
+ easing: options.easing,
497
+ axis: "x",
498
+ direction,
499
+ amount,
500
+ restingValue: options.restingX ?? 0,
501
+ fromValue: options.fromX,
502
+ toValue: options.toX,
503
+ subjectAnchor: options.subjectAnchor
504
+ });
505
+ }
506
+ else if (direction === "up" || direction === "down") {
507
+ frames.y = cameraAxisFrames({
508
+ durationFrames: options.durationFrames,
509
+ easing: options.easing,
510
+ axis: "y",
511
+ direction,
512
+ amount,
513
+ restingValue: options.restingY ?? 0,
514
+ fromValue: options.fromY,
515
+ toValue: options.toY,
516
+ subjectAnchor: options.subjectAnchor
517
+ });
518
+ }
519
+ return frames;
520
+ },
521
+ orbitLite(options) {
522
+ const endFrame = cameraEndFrame(options.durationFrames);
523
+ const middleFrame = Math.floor(endFrame / 2);
524
+ const amount = cameraTravelAmount(options, 42);
525
+ const halfAmount = amount / 2;
526
+ const verticalAmount = cameraTravelAmount({ ...options, amount: options.verticalAmount ?? 18 }, 18);
527
+ const rotationAmount = options.rotationAmount ?? 1.8 * (1 + cameraIntensity(options, 0));
528
+ const directionSign = (options.direction ?? "right") === "left" ? -1 : 1;
529
+ const restingX = options.restingX ?? 0;
530
+ const restingY = options.restingY ?? 0;
531
+ const restingRotation = options.restingRotation ?? 0;
532
+ const frames = {
533
+ x: cameraThreePointTrack(options.durationFrames, restingX - directionSign * halfAmount, restingX, restingX + directionSign * halfAmount, options.easing),
534
+ scale: cameraThreePointTrack(options.durationFrames, options.fromScale ?? 1.02, Math.max(options.fromScale ?? 1.02, options.toScale ?? 1.05), options.toScale ?? 1.02, options.easing),
535
+ rotation: cameraTrack(options.durationFrames, restingRotation - directionSign * rotationAmount, restingRotation + directionSign * rotationAmount, options.easing)
536
+ };
537
+ if (endFrame > 1 && verticalAmount !== 0) {
538
+ frames.y = [
539
+ compactObject({ frame: 0, value: restingY + verticalAmount / 2, easing: options.easing }),
540
+ compactObject({ frame: middleFrame, value: restingY - verticalAmount / 2, easing: options.easing }),
541
+ { frame: endFrame, value: restingY + verticalAmount / 2 }
542
+ ];
543
+ }
544
+ return frames;
545
+ },
546
+ handheld(options) {
547
+ const amount = cameraTravelAmount(options, 8);
548
+ const rotationAmount = options.rotationAmount ?? 0.8 * (1 + cameraIntensity(options, 0));
549
+ const scale = options.scale ?? 1 + cameraIntensity(options, 0.02);
550
+ const frames = {
551
+ x: cameraJitterTrack({
552
+ durationFrames: options.durationFrames,
553
+ seed: options.seed ?? 1,
554
+ intervalFrames: options.intervalFrames ?? 6,
555
+ restingValue: options.restingX ?? 0,
556
+ amount,
557
+ easing: options.easing ?? "inOutQuad"
558
+ }),
559
+ y: cameraJitterTrack({
560
+ durationFrames: options.durationFrames,
561
+ seed: (options.seed ?? 1) + 101,
562
+ intervalFrames: options.intervalFrames ?? 6,
563
+ restingValue: options.restingY ?? 0,
564
+ amount: amount * 0.65,
565
+ easing: options.easing ?? "inOutQuad"
566
+ }),
567
+ rotation: cameraJitterTrack({
568
+ durationFrames: options.durationFrames,
569
+ seed: (options.seed ?? 1) + 211,
570
+ intervalFrames: options.intervalFrames ?? 6,
571
+ restingValue: options.restingRotation ?? 0,
572
+ amount: rotationAmount,
573
+ easing: options.easing ?? "inOutQuad"
574
+ })
575
+ };
576
+ if (scale !== 1) {
577
+ frames.scale = cameraTrack(options.durationFrames, scale, scale);
578
+ }
579
+ return frames;
580
+ },
581
+ crashZoom(options) {
582
+ const direction = options.direction ?? "in";
583
+ const intensityValue = cameraIntensity(options, 0.28);
584
+ const fromScale = options.fromScale ?? (direction === "in" ? 1 : 1 + intensityValue);
585
+ const toScale = options.toScale ?? (direction === "in" ? 1 + intensityValue : 1);
586
+ const overshootDirection = direction === "in" ? 1 : -1;
587
+ const overshootScale = options.overshootScale ?? toScale + overshootDirection * intensityValue * 0.2;
588
+ return {
589
+ scale: cameraCrashZoomTrack({
590
+ durationFrames: options.durationFrames,
591
+ easing: options.easing ?? "outCubic",
592
+ fromScale,
593
+ overshootScale,
594
+ toScale,
595
+ impactFrame: options.impactFrame
596
+ })
597
+ };
598
+ },
599
+ dollyZoomLite(options) {
600
+ const direction = options.direction ?? "out";
601
+ const intensityValue = cameraIntensity(options, 0.12);
602
+ const fromScale = options.fromScale ?? (direction === "out" ? 1 + intensityValue : 1);
603
+ const toScale = options.toScale ?? (direction === "out" ? 1 : 1 + intensityValue);
604
+ const amount = cameraTravelAmount(options, 28);
605
+ const xBias = anchorBias(options.subjectAnchor, "x");
606
+ const yBias = anchorBias(options.subjectAnchor, "y");
607
+ const directionSign = direction === "out" ? 1 : -1;
608
+ const frames = zoomMotion(options, fromScale, toScale);
609
+ if (options.fromX !== undefined || options.toX !== undefined || xBias !== 0) {
610
+ const restingX = options.restingX ?? 0;
611
+ frames.x = cameraTrack(options.durationFrames, options.fromX ?? restingX - xBias * amount * directionSign, options.toX ?? restingX + xBias * amount * directionSign, options.easing);
612
+ }
613
+ if (options.fromY !== undefined || options.toY !== undefined || yBias !== 0) {
614
+ const restingY = options.restingY ?? 0;
615
+ frames.y = cameraTrack(options.durationFrames, options.fromY ?? restingY - yBias * amount * directionSign, options.toY ?? restingY + yBias * amount * directionSign, options.easing);
616
+ }
617
+ return frames;
618
+ }
619
+ };
620
+ export const cinematic = {
621
+ zoomPush(options = {}) {
622
+ const durationFrames = options.durationFrames ?? 14;
623
+ return compactObject({
624
+ transitionIn: transition.zoom({
625
+ durationFrames,
626
+ amount: options.intensity ?? 0.18,
627
+ easing: options.easing ?? "outCubic"
628
+ }),
629
+ transitionOut: transition.push({
630
+ direction: options.direction ?? "left",
631
+ durationFrames,
632
+ easing: options.easing ?? "inCubic"
633
+ })
634
+ });
635
+ },
636
+ whipPan(options = {}) {
637
+ return compactObject({
638
+ transitionIn: transition.push({
639
+ direction: options.direction ?? "left",
640
+ durationFrames: options.durationFrames ?? 8,
641
+ easing: options.easing ?? "inOutCubic",
642
+ intensity: options.intensity
643
+ })
644
+ });
645
+ },
646
+ filmFlash(options = {}) {
647
+ return compactObject({
648
+ transitionIn: transition.colorDissolve({
649
+ color: options.color ?? "#ffffff",
650
+ durationFrames: options.durationFrames ?? 6,
651
+ amount: options.intensity ?? 1,
652
+ easing: options.easing ?? "outQuad"
653
+ })
654
+ });
655
+ },
656
+ dreamyBlur(options = {}) {
657
+ return compactObject({
658
+ transitionIn: transition.blurDissolve({
659
+ durationFrames: options.durationFrames ?? 18,
660
+ amount: options.intensity ?? 18,
661
+ easing: options.easing ?? "outCubic"
662
+ })
663
+ });
664
+ },
665
+ broadcastDip(options = {}) {
666
+ return compactObject({
667
+ transitionIn: transition.dip({
668
+ color: options.color ?? "#05070a",
669
+ durationFrames: options.durationFrames ?? 10,
670
+ amount: options.intensity ?? 1,
671
+ easing: options.easing ?? "inOutCubic"
672
+ })
673
+ });
674
+ },
675
+ irisOpen(options = {}) {
676
+ return compactObject({
677
+ transitionIn: transition.iris({
678
+ shape: options.shape ?? "circle",
679
+ durationFrames: options.durationFrames ?? 16,
680
+ intensity: options.intensity,
681
+ easing: options.easing ?? "outCubic"
682
+ })
683
+ });
684
+ },
685
+ flipCard(options = {}) {
686
+ return compactObject({
687
+ transitionIn: transition.flip({
688
+ axis: options.axis ?? "y",
689
+ direction: options.direction,
690
+ durationFrames: options.durationFrames ?? 14,
691
+ amount: options.intensity ?? 90,
692
+ easing: options.easing ?? "outCubic"
693
+ })
694
+ });
695
+ },
696
+ glitchCut(options = {}) {
697
+ const durationFrames = options.durationFrames ?? 8;
698
+ const endFrame = cameraEndFrame(durationFrames);
699
+ const direction = options.direction ?? "left";
700
+ const directionSign = direction === "right" || direction === "down" ? 1 : -1;
701
+ const axis = direction === "up" || direction === "down" ? "y" : "x";
702
+ const offset = roundMotionValue((options.intensity ?? 1) * 14 * directionSign);
703
+ const jitter = roundMotionValue((seedToUnit(normalizeSeed(options.seed ?? 17)) * 2 - 1) * 6 * (options.intensity ?? 1));
704
+ const settleFrame = Math.min(endFrame, 3);
705
+ return compactObject({
706
+ transitionIn: transition.skewSlide({
707
+ direction,
708
+ durationFrames,
709
+ intensity: 12 * (options.intensity ?? 1),
710
+ easing: options.easing ?? "outExpo"
711
+ }),
712
+ keyframes: compactObject({
713
+ [axis]: keyframes([
714
+ { frame: 0, value: offset, timing: timing.steps({ steps: 2, direction: "end" }) },
715
+ { frame: 1, value: roundMotionValue(-offset * 0.45 + jitter), timing: timing.steps({ steps: 2, direction: "end" }) },
716
+ { frame: settleFrame, value: 0, easing: options.easing ?? "outExpo" }
717
+ ]),
718
+ opacity: keyframes([
719
+ { frame: 0, value: 0.72, timing: timing.steps({ steps: 2, direction: "end" }) },
720
+ { frame: settleFrame, value: 1 }
721
+ ])
722
+ })
723
+ });
724
+ },
725
+ lightLeak(options = {}) {
726
+ const durationFrames = options.durationFrames ?? 14;
727
+ const easingValue = options.easing ?? "outQuad";
728
+ const drift = roundMotionValue(10 * (options.intensity ?? 1));
729
+ return compactObject({
730
+ transitionIn: transition.colorDissolve({
731
+ color: options.color ?? "#fff1b8",
732
+ durationFrames,
733
+ amount: options.intensity ?? 0.72,
734
+ easing: easingValue
735
+ }),
736
+ keyframes: compactObject({
737
+ x: cameraTrack(durationFrames, -drift, drift, easingValue),
738
+ opacity: keyframes([
739
+ [0, 0.82, easingValue],
740
+ [Math.max(1, durationFrames - 1), 1]
741
+ ])
742
+ })
743
+ });
744
+ },
745
+ kenBurns(options = {}) {
746
+ const durationFrames = options.durationFrames ?? 90;
747
+ const subjectStart = options.subject === undefined
748
+ ? undefined
749
+ : options.easing === undefined
750
+ ? { frame: 0, x: options.subject.fromX, y: options.subject.fromY }
751
+ : { frame: 0, x: options.subject.fromX, y: options.subject.fromY, easing: options.easing };
752
+ const subjectCropOptions = options.subject === undefined
753
+ ? undefined
754
+ : {
755
+ keyframes: [subjectStart, { frame: durationFrames, x: options.subject.toX, y: options.subject.toY }],
756
+ ...(options.subject.smoothingFrames === undefined ? {} : { smoothingFrames: options.subject.smoothingFrames }),
757
+ ...(options.subject.source === undefined ? {} : { source: options.subject.source })
758
+ };
759
+ return compactObject({
760
+ keyframes: compactObject({
761
+ scale: keyframes([
762
+ [0, options.fromScale ?? 1.04, options.easing ?? "outCubic"],
763
+ [durationFrames, options.toScale ?? 1.14]
764
+ ]),
765
+ x: options.fromX === undefined && options.toX === undefined
766
+ ? undefined
767
+ : keyframes([
768
+ [0, options.fromX ?? 0, options.easing ?? "outCubic"],
769
+ [durationFrames, options.toX ?? 0]
770
+ ]),
771
+ y: options.fromY === undefined && options.toY === undefined
772
+ ? undefined
773
+ : keyframes([
774
+ [0, options.fromY ?? 0, options.easing ?? "outCubic"],
775
+ [durationFrames, options.toY ?? 0]
776
+ ])
777
+ }),
778
+ crop: subjectCropOptions === undefined ? undefined : crop.subject(subjectCropOptions)
779
+ });
780
+ },
781
+ logoSting(options = {}) {
782
+ const durationFrames = options.durationFrames ?? 18;
783
+ const easingValue = options.easing ?? "outBack";
784
+ const rotationDirection = options.direction === "right" || options.direction === "down" ? 1 : -1;
785
+ return compactObject({
786
+ transitionIn: transition.zoom({
787
+ durationFrames: Math.max(1, Math.round(durationFrames * 0.6)),
788
+ amount: options.intensity ?? 0.12,
789
+ easing: easingValue
790
+ }),
791
+ transitionOut: transition.fade({
792
+ durationFrames: Math.max(1, Math.round(durationFrames * 0.45)),
793
+ easing: "inCubic"
794
+ }),
795
+ keyframes: compactObject({
796
+ scale: cameraThreePointTrack(durationFrames, 0.92, 1 + cameraIntensity(options, 0.04), 1, easingValue),
797
+ rotation: cameraTrack(durationFrames, rotationDirection * -2, 0, options.easing ?? "outCubic")
798
+ })
799
+ });
800
+ },
801
+ productReveal(options = {}) {
802
+ const durationFrames = options.durationFrames ?? 24;
803
+ const easingValue = options.easing ?? "outCubic";
804
+ return compactObject({
805
+ transitionIn: transition.wipe({
806
+ direction: options.direction ?? "up",
807
+ durationFrames,
808
+ easing: easingValue
809
+ }),
810
+ keyframes: compactObject({
811
+ scale: cameraTrack(durationFrames, 0.96, 1 + cameraIntensity(options, 0.02), easingValue),
812
+ opacity: entranceOpacityKeyframes(Math.max(1, Math.round(durationFrames * 0.6)), easingValue)
813
+ })
814
+ });
815
+ },
816
+ socialHook(options = {}) {
817
+ const durationFrames = options.durationFrames ?? 16;
818
+ const easingValue = options.easing ?? "outCubic";
819
+ return compactObject({
820
+ transitionIn: transition.colorDissolve({
821
+ color: options.color ?? "#ffffff",
822
+ durationFrames: Math.max(1, Math.round(durationFrames * 0.45)),
823
+ amount: options.intensity ?? 0.7,
824
+ easing: "outQuad"
825
+ }),
826
+ transitionOut: transition.push({
827
+ direction: options.direction ?? "left",
828
+ durationFrames: Math.max(1, Math.round(durationFrames * 0.5)),
829
+ easing: "inCubic"
830
+ }),
831
+ keyframes: compactObject({
832
+ scale: cameraCrashZoomTrack({
833
+ durationFrames,
834
+ easing: easingValue,
835
+ fromScale: 1 + cameraIntensity(options, 0.16),
836
+ overshootScale: 0.98,
837
+ toScale: 1
838
+ })
839
+ })
840
+ });
841
+ },
842
+ titleSequence(options = {}) {
843
+ const durationFrames = options.durationFrames ?? 30;
844
+ const easingValue = options.easing ?? "outCubic";
845
+ return compactObject({
846
+ transitionIn: transition.slide({
847
+ direction: options.direction ?? "up",
848
+ durationFrames,
849
+ easing: easingValue
850
+ }),
851
+ transitionOut: transition.slide({
852
+ direction: options.exitDirection ?? "down",
853
+ durationFrames: Math.max(1, Math.round(durationFrames * 0.55)),
854
+ easing: "inCubic"
855
+ }),
856
+ keyframes: compactObject({
857
+ opacity: entranceOpacityKeyframes(durationFrames, easingValue)
858
+ })
859
+ });
860
+ },
861
+ endCard(options = {}) {
862
+ const durationFrames = options.durationFrames ?? 20;
863
+ const easingValue = options.easing ?? "outCubic";
864
+ const horizontalDirection = options.direction === "left" || options.direction === "right" ? options.direction : undefined;
865
+ const verticalDirection = options.direction === "up" || options.direction === "down" ? options.direction : undefined;
866
+ return compactObject({
867
+ transitionIn: transition.dip({
868
+ color: options.color ?? "#05070a",
869
+ durationFrames,
870
+ amount: options.intensity ?? 1,
871
+ easing: "inOutCubic"
872
+ }),
873
+ transitionOut: transition.fade({
874
+ durationFrames: Math.max(1, Math.round(durationFrames * 0.5)),
875
+ easing: "inCubic"
876
+ }),
877
+ keyframes: compactObject({
878
+ scale: cameraTrack(durationFrames, 0.98, 1, easingValue),
879
+ x: horizontalDirection === undefined
880
+ ? undefined
881
+ : cameraAxisFrames({
882
+ durationFrames,
883
+ easing: easingValue,
884
+ axis: "x",
885
+ direction: horizontalDirection,
886
+ amount: 20 * (1 + cameraIntensity(options, 0)),
887
+ restingValue: 0
888
+ }),
889
+ y: verticalDirection === undefined
890
+ ? undefined
891
+ : cameraAxisFrames({
892
+ durationFrames,
893
+ easing: easingValue,
894
+ axis: "y",
895
+ direction: verticalDirection,
896
+ amount: 20 * (1 + cameraIntensity(options, 0)),
897
+ restingValue: 0
898
+ })
899
+ })
900
+ });
901
+ }
902
+ };
903
+ export const cinematicPresets = cinematic;
904
+ function textMotionPreset(type, options, defaults) {
905
+ return {
906
+ textMotion: compactObject({
907
+ type,
908
+ split: options.split ?? defaults.split,
909
+ durationFrames: options.durationFrames ?? defaults.durationFrames,
910
+ easing: options.easing ?? defaults.easing,
911
+ staggerFrames: options.staggerFrames ?? defaults.staggerFrames,
912
+ origin: options.origin,
913
+ seed: options.seed,
914
+ preserveLayout: options.preserveLayout ?? true,
915
+ restingBox: options.restingBox,
916
+ direction: options.direction,
917
+ amount: options.amount,
918
+ intensity: options.intensity,
919
+ color: options.color
920
+ })
921
+ };
922
+ }
923
+ export const textMotion = {
924
+ rise(options = {}) {
925
+ const durationFrames = options.durationFrames ?? 14;
926
+ const easingValue = options.easing ?? "outCubic";
927
+ return {
928
+ transitionIn: transition.slide({
929
+ direction: options.direction ?? "up",
930
+ durationFrames,
931
+ easing: easingValue
932
+ }),
933
+ keyframes: {
934
+ opacity: entranceOpacityKeyframes(durationFrames, easingValue)
935
+ }
936
+ };
937
+ },
938
+ blurIn(options = {}) {
939
+ return {
940
+ transitionIn: transition.blurDissolve({
941
+ durationFrames: options.durationFrames ?? 12,
942
+ amount: options.amount,
943
+ intensity: options.intensity,
944
+ easing: options.easing ?? "outCubic"
945
+ })
946
+ };
947
+ },
948
+ typeOn(options = {}) {
949
+ return textMotionPreset("typeOn", options, {
950
+ split: "char",
951
+ durationFrames: 18,
952
+ easing: "linear",
953
+ staggerFrames: 1
954
+ });
955
+ },
956
+ cascade(options = {}) {
957
+ return textMotionPreset("cascade", options, {
958
+ split: "word",
959
+ durationFrames: 14,
960
+ easing: "outCubic",
961
+ staggerFrames: 2
962
+ });
963
+ },
964
+ scramble(options = {}) {
965
+ return textMotionPreset("scramble", options, {
966
+ split: "char",
967
+ durationFrames: 18,
968
+ easing: "outCubic",
969
+ staggerFrames: 1
970
+ });
971
+ },
972
+ highlightSweep(options = {}) {
973
+ return textMotionPreset("highlightSweep", options, {
974
+ split: "word",
975
+ durationFrames: 18,
976
+ easing: "outCubic",
977
+ staggerFrames: 0
978
+ });
979
+ },
980
+ trackingIn(options = {}) {
981
+ return textMotionPreset("trackingIn", options, {
982
+ split: "char",
983
+ durationFrames: 16,
984
+ easing: "outCubic",
985
+ staggerFrames: 1
986
+ });
987
+ }
988
+ };
989
+ export const textMotions = textMotion;
990
+ export const effect = {};
991
+ export const presetNamespaces = {
992
+ transition,
993
+ cinematic,
994
+ textMotion,
995
+ camera,
996
+ effect
997
+ };
998
+ export function keyframes(frames) {
999
+ return frames.map((frame) => {
1000
+ if (isKeyframeTuple(frame)) {
1001
+ const [frameNumber, value, ease] = frame;
1002
+ return compactObject({
1003
+ frame: frameNumber,
1004
+ value,
1005
+ easing: ease
1006
+ });
1007
+ }
1008
+ return compactObject({
1009
+ frame: frame.frame,
1010
+ value: frame.value,
1011
+ easing: frame.easing,
1012
+ timing: frame.timing
1013
+ });
1014
+ });
1015
+ }
1016
+ export const easing = {
1017
+ linear: "linear",
1018
+ inQuad: "inQuad",
1019
+ outQuad: "outQuad",
1020
+ inOutQuad: "inOutQuad",
1021
+ inCubic: "inCubic",
1022
+ outCubic: "outCubic",
1023
+ inOutCubic: "inOutCubic",
1024
+ inCirc: "inCirc",
1025
+ outCirc: "outCirc",
1026
+ inOutCirc: "inOutCirc",
1027
+ inExpo: "inExpo",
1028
+ outExpo: "outExpo",
1029
+ inOutExpo: "inOutExpo",
1030
+ anticipate: "anticipate",
1031
+ back: "back",
1032
+ inBack: "inBack",
1033
+ outBack: "outBack",
1034
+ inOutBack: "inOutBack",
1035
+ inElastic: "inElastic",
1036
+ outElastic: "outElastic",
1037
+ inOutElastic: "inOutElastic",
1038
+ inBounce: "inBounce",
1039
+ outBounce: "outBounce",
1040
+ inOutBounce: "inOutBounce",
1041
+ cubicBezier(x1, y1, x2, y2) {
1042
+ return `cubic-bezier(${x1},${y1},${x2},${y2})`;
1043
+ }
1044
+ };
1045
+ export const timing = {
1046
+ tween(options = {}) {
1047
+ assertOptionalPositiveInteger(options.durationFrames, "tween durationFrames");
1048
+ return compactObject({
1049
+ type: "tween",
1050
+ durationFrames: options.durationFrames,
1051
+ easing: options.easing
1052
+ });
1053
+ },
1054
+ spring(options = {}) {
1055
+ assertOptionalPositiveInteger(options.durationFrames, "spring durationFrames");
1056
+ assertOptionalPositiveNumber(options.stiffness, "spring stiffness");
1057
+ assertOptionalPositiveNumber(options.damping, "spring damping");
1058
+ assertOptionalPositiveNumber(options.mass, "spring mass");
1059
+ assertOptionalNonNegativeNumber(options.restSpeed, "spring restSpeed");
1060
+ assertOptionalRange(options.bounce, "spring bounce", 0, 1);
1061
+ return compactObject({
1062
+ type: "spring",
1063
+ durationFrames: options.durationFrames,
1064
+ stiffness: options.stiffness,
1065
+ damping: options.damping,
1066
+ mass: options.mass,
1067
+ restSpeed: options.restSpeed,
1068
+ bounce: options.bounce
1069
+ });
1070
+ },
1071
+ steps(options) {
1072
+ assertOptionalPositiveInteger(options.durationFrames, "steps durationFrames");
1073
+ assertPositiveInteger(options.steps, "steps");
1074
+ return compactObject({
1075
+ type: "steps",
1076
+ durationFrames: options.durationFrames,
1077
+ steps: options.steps,
1078
+ direction: options.direction
1079
+ });
1080
+ },
1081
+ sequence(segments) {
1082
+ if (segments.length === 0) {
1083
+ throw new Error("sequence timing requires at least one segment.");
1084
+ }
1085
+ return {
1086
+ type: "sequence",
1087
+ segments: segments.map((segment) => {
1088
+ assertPositiveInteger(segment.durationFrames, "sequence segment durationFrames");
1089
+ return compactObject({
1090
+ durationFrames: segment.durationFrames,
1091
+ timing: segment.timing,
1092
+ from: segment.from,
1093
+ to: segment.to
1094
+ });
1095
+ })
1096
+ };
1097
+ },
1098
+ stagger(options) {
1099
+ assertPositiveInteger(options.childCount, "stagger childCount");
1100
+ assertNonNegativeInteger(options.eachFrames, "stagger eachFrames");
1101
+ assertOptionalNonNegativeInteger(options.childIndex, "stagger childIndex");
1102
+ if (options.childIndex !== undefined && options.childIndex >= options.childCount) {
1103
+ throw new Error("stagger childIndex must be lower than childCount.");
1104
+ }
1105
+ return compactObject({
1106
+ type: "stagger",
1107
+ timing: options.timing,
1108
+ childCount: options.childCount,
1109
+ eachFrames: options.eachFrames,
1110
+ childIndex: options.childIndex,
1111
+ from: options.from
1112
+ });
1113
+ }
1114
+ };
1115
+ export const exportPreset = {
1116
+ vertical(options = {}) {
1117
+ return exportDefinition({
1118
+ name: "vertical-9x16",
1119
+ format: "mp4",
1120
+ codec: "h264",
1121
+ width: 1080,
1122
+ height: 1920,
1123
+ ...options
1124
+ });
1125
+ },
1126
+ reels(options = {}) {
1127
+ return exportDefinition({
1128
+ name: "reels",
1129
+ format: "mp4",
1130
+ codec: "h264",
1131
+ width: 1080,
1132
+ height: 1920,
1133
+ ...options
1134
+ });
1135
+ },
1136
+ instagramReels(options = {}) {
1137
+ return exportDefinition({
1138
+ name: "instagram-reels-9x16",
1139
+ format: "mp4",
1140
+ codec: "h264",
1141
+ width: 1080,
1142
+ height: 1920,
1143
+ ...options
1144
+ });
1145
+ },
1146
+ tiktok(options = {}) {
1147
+ return exportDefinition({
1148
+ name: "tiktok-9x16",
1149
+ format: "mp4",
1150
+ codec: "h264",
1151
+ width: 1080,
1152
+ height: 1920,
1153
+ ...options
1154
+ });
1155
+ },
1156
+ youtubeShorts(options = {}) {
1157
+ return exportDefinition({
1158
+ name: "youtube-shorts-9x16",
1159
+ format: "mp4",
1160
+ codec: "h264",
1161
+ width: 1080,
1162
+ height: 1920,
1163
+ ...options
1164
+ });
1165
+ },
1166
+ facebookReels(options = {}) {
1167
+ return exportDefinition({
1168
+ name: "facebook-reels-9x16",
1169
+ format: "mp4",
1170
+ codec: "h264",
1171
+ width: 1080,
1172
+ height: 1920,
1173
+ ...options
1174
+ });
1175
+ },
1176
+ square(options = {}) {
1177
+ return exportDefinition({
1178
+ name: "square",
1179
+ format: "mp4",
1180
+ codec: "h264",
1181
+ width: 1080,
1182
+ height: 1080,
1183
+ ...options
1184
+ });
1185
+ },
1186
+ portrait(options = {}) {
1187
+ return exportDefinition({
1188
+ name: "portrait-4x5",
1189
+ format: "mp4",
1190
+ codec: "h264",
1191
+ width: 1080,
1192
+ height: 1350,
1193
+ ...options
1194
+ });
1195
+ },
1196
+ landscape(options = {}) {
1197
+ return exportDefinition({
1198
+ name: "landscape",
1199
+ format: "mp4",
1200
+ codec: "h264",
1201
+ width: 1920,
1202
+ height: 1080,
1203
+ ...options
1204
+ });
1205
+ },
1206
+ social(options = {}) {
1207
+ return compactArray([
1208
+ options.instagramReels === false ? undefined : exportPreset.instagramReels(options.instagramReels ?? {}),
1209
+ options.tiktok === false ? undefined : exportPreset.tiktok(options.tiktok ?? {}),
1210
+ options.youtubeShorts === false ? undefined : exportPreset.youtubeShorts(options.youtubeShorts ?? {}),
1211
+ options.facebookReels === false ? undefined : exportPreset.facebookReels(options.facebookReels ?? {}),
1212
+ options.reels === undefined || options.reels === false
1213
+ ? undefined
1214
+ : exportPreset.reels({ name: "reels-9x16", ...options.reels }),
1215
+ options.square === false ? undefined : exportPreset.square({ name: "square-1x1", ...(options.square ?? {}) }),
1216
+ options.portrait === false ? undefined : exportPreset.portrait(options.portrait ?? {}),
1217
+ options.landscape === false
1218
+ ? undefined
1219
+ : exportPreset.landscape({ name: "landscape-16x9", ...(options.landscape ?? {}) })
1220
+ ]);
1221
+ },
1222
+ custom(options) {
1223
+ return exportDefinition({
1224
+ format: "mp4",
1225
+ codec: "h264",
1226
+ ...options
1227
+ });
1228
+ }
1229
+ };
1230
+ export const exportPresets = exportPreset;
1231
+ export const vertical = exportPreset.vertical;
1232
+ export const reels = exportPreset.reels;
1233
+ export const instagramReels = exportPreset.instagramReels;
1234
+ export const tiktok = exportPreset.tiktok;
1235
+ export const youtubeShorts = exportPreset.youtubeShorts;
1236
+ export const facebookReels = exportPreset.facebookReels;
1237
+ export const square = exportPreset.square;
1238
+ export const portrait = exportPreset.portrait;
1239
+ export const landscape = exportPreset.landscape;
1240
+ export const social = exportPreset.social;
1241
+ export const customExport = exportPreset.custom;
1242
+ export const socialMediaPresets = [
1243
+ socialPresetDefinition("instagram-reels", "Instagram Reels", "Instagram", "9:16", exportPreset.instagramReels()),
1244
+ socialPresetDefinition("tiktok", "TikTok", "TikTok", "9:16", exportPreset.tiktok()),
1245
+ socialPresetDefinition("youtube-shorts", "YouTube Shorts", "YouTube", "9:16", exportPreset.youtubeShorts()),
1246
+ socialPresetDefinition("facebook-reels", "Facebook Reels", "Facebook", "9:16", exportPreset.facebookReels()),
1247
+ socialPresetDefinition("instagram-feed-portrait", "Instagram Feed Portrait", "Instagram", "4:5", exportPreset.portrait()),
1248
+ socialPresetDefinition("square-feed", "Square Feed", "Generic social", "1:1", exportPreset.square({ name: "square-1x1" })),
1249
+ socialPresetDefinition("landscape-feed", "Landscape Feed", "Generic social", "16:9", exportPreset.landscape({ name: "landscape-16x9" }))
1250
+ ];
1251
+ export const crop = {
1252
+ center() {
1253
+ return { mode: "center" };
1254
+ },
1255
+ subject(options) {
1256
+ return compactObject({
1257
+ mode: "subject",
1258
+ x: options.x,
1259
+ y: options.y,
1260
+ keyframes: options.keyframes === undefined ? undefined : options.keyframes.map((frame) => ({ ...frame })),
1261
+ smoothingFrames: options.smoothingFrames,
1262
+ source: options.source
1263
+ });
1264
+ }
1265
+ };
1266
+ function buildLayer(id, type, options) {
1267
+ assertId(id, "layer");
1268
+ const { x, y, width, height, ...rest } = options;
1269
+ const layer = compactObject({
1270
+ id,
1271
+ type,
1272
+ ...rest
1273
+ });
1274
+ if (layer.position === undefined && (x !== undefined || y !== undefined)) {
1275
+ layer.position = compactObject({ x, y });
1276
+ }
1277
+ if (layer.size === undefined && (width !== undefined || height !== undefined)) {
1278
+ layer.size = compactObject({ width, height });
1279
+ }
1280
+ return new LayerBuilder(layer);
1281
+ }
1282
+ function transitionDefinition(type, options) {
1283
+ return compactObject({
1284
+ type,
1285
+ durationFrames: options.durationFrames,
1286
+ direction: options.direction,
1287
+ axis: options.axis,
1288
+ shape: options.shape,
1289
+ color: options.color,
1290
+ amount: options.amount,
1291
+ intensity: options.intensity,
1292
+ easing: options.easing,
1293
+ timing: options.timing
1294
+ });
1295
+ }
1296
+ function transitionSeriesDefinition(definition) {
1297
+ return {
1298
+ presentation: compactObject({
1299
+ type: definition.type,
1300
+ direction: definition.direction,
1301
+ axis: definition.axis,
1302
+ shape: definition.shape,
1303
+ color: definition.color,
1304
+ amount: definition.amount,
1305
+ intensity: definition.intensity,
1306
+ rows: definition.rows,
1307
+ columns: definition.columns
1308
+ }),
1309
+ timing: compactObject({
1310
+ type: "tween",
1311
+ durationFrames: definition.durationFrames,
1312
+ easing: definition.easing
1313
+ })
1314
+ };
1315
+ }
1316
+ function entranceOpacityKeyframes(durationFrames, easingValue) {
1317
+ if (durationFrames <= 1) {
1318
+ return [{ frame: 0, value: 1 }];
1319
+ }
1320
+ return keyframes([
1321
+ [0, 0, easingValue],
1322
+ [durationFrames - 1, 1]
1323
+ ]);
1324
+ }
1325
+ function zoomMotion(options, fallbackFromScale, fallbackToScale) {
1326
+ return {
1327
+ scale: cameraTrack(options.durationFrames, options.fromScale ?? fallbackFromScale, options.toScale ?? fallbackToScale, options.easing)
1328
+ };
1329
+ }
1330
+ function cameraTrack(durationFrames, fromValue, toValue, easing) {
1331
+ const endFrame = cameraEndFrame(durationFrames);
1332
+ if (endFrame === 0) {
1333
+ return [{ frame: 0, value: fromValue }];
1334
+ }
1335
+ const firstFrame = compactObject({
1336
+ frame: 0,
1337
+ value: fromValue,
1338
+ easing
1339
+ });
1340
+ return [firstFrame, { frame: endFrame, value: toValue }];
1341
+ }
1342
+ function cameraThreePointTrack(durationFrames, fromValue, middleValue, toValue, easing) {
1343
+ const endFrame = cameraEndFrame(durationFrames);
1344
+ if (endFrame === 0) {
1345
+ return [{ frame: 0, value: fromValue }];
1346
+ }
1347
+ const middleFrame = Math.floor(endFrame / 2);
1348
+ if (middleFrame === 0 || middleFrame === endFrame) {
1349
+ return cameraTrack(durationFrames, fromValue, toValue, easing);
1350
+ }
1351
+ return [
1352
+ compactObject({ frame: 0, value: fromValue, easing }),
1353
+ compactObject({ frame: middleFrame, value: middleValue, easing }),
1354
+ { frame: endFrame, value: toValue }
1355
+ ];
1356
+ }
1357
+ function cameraCrashZoomTrack(options) {
1358
+ const endFrame = cameraEndFrame(options.durationFrames);
1359
+ if (endFrame === 0) {
1360
+ return [{ frame: 0, value: options.fromScale }];
1361
+ }
1362
+ const fallbackImpactFrame = Math.max(1, Math.floor(endFrame * 0.35));
1363
+ const impactFrame = clampInteger(options.impactFrame ?? fallbackImpactFrame, 1, endFrame);
1364
+ if (impactFrame === endFrame) {
1365
+ return keyframes([
1366
+ [0, options.fromScale, options.easing],
1367
+ [endFrame, options.toScale]
1368
+ ]);
1369
+ }
1370
+ return keyframes([
1371
+ [0, options.fromScale, options.easing],
1372
+ [impactFrame, options.overshootScale, "outBack"],
1373
+ [endFrame, options.toScale]
1374
+ ]);
1375
+ }
1376
+ function cameraJitterTrack(options) {
1377
+ const endFrame = cameraEndFrame(options.durationFrames);
1378
+ const intervalFrames = clampInteger(options.intervalFrames, 1, Math.max(1, endFrame));
1379
+ const frames = [];
1380
+ let state = normalizeSeed(options.seed);
1381
+ for (let frame = 0; frame <= endFrame; frame += intervalFrames) {
1382
+ state = nextSeed(state);
1383
+ const value = roundMotionValue(options.restingValue + (seedToUnit(state) * 2 - 1) * options.amount);
1384
+ frames.push(compactObject({
1385
+ frame,
1386
+ value,
1387
+ easing: frame === endFrame ? undefined : options.easing
1388
+ }));
1389
+ }
1390
+ if (frames[frames.length - 1]?.frame !== endFrame) {
1391
+ state = nextSeed(state);
1392
+ frames.push({
1393
+ frame: endFrame,
1394
+ value: roundMotionValue(options.restingValue + (seedToUnit(state) * 2 - 1) * options.amount)
1395
+ });
1396
+ }
1397
+ if (frames.length === 1) {
1398
+ frames[0] = { frame: 0, value: options.restingValue };
1399
+ }
1400
+ return frames;
1401
+ }
1402
+ function cameraAxisFrames(options) {
1403
+ const halfAmount = options.amount / 2;
1404
+ const directionSign = options.direction === "left" || options.direction === "up" ? -1 : 1;
1405
+ const subjectBias = anchorBias(options.subjectAnchor, options.axis) * halfAmount * 0.5;
1406
+ const fromValue = options.fromValue ?? options.restingValue - directionSign * halfAmount + subjectBias;
1407
+ const toValue = options.toValue ?? options.restingValue + directionSign * halfAmount + subjectBias;
1408
+ return cameraTrack(options.durationFrames, fromValue, toValue, options.easing);
1409
+ }
1410
+ function cameraIntensity(options, fallback) {
1411
+ return Math.max(0, options.intensity ?? fallback);
1412
+ }
1413
+ function cameraTravelAmount(options, fallback) {
1414
+ const baseAmount = options.amount ?? fallback * (1 + cameraIntensity(options, 0));
1415
+ return baseAmount * safeAreaMultiplier(options.safeArea);
1416
+ }
1417
+ function cameraEndFrame(durationFrames) {
1418
+ if (!Number.isInteger(durationFrames) || durationFrames < 1) {
1419
+ throw new Error("Camera motion durationFrames must be a positive integer.");
1420
+ }
1421
+ return durationFrames - 1;
1422
+ }
1423
+ function safeAreaMultiplier(safeArea) {
1424
+ if (safeArea === undefined) {
1425
+ return 1;
1426
+ }
1427
+ if (typeof safeArea === "number") {
1428
+ return 1 - clamp(safeArea, 0, 0.45) * 2;
1429
+ }
1430
+ const largestInset = Math.max(safeArea.top ?? 0, safeArea.right ?? 0, safeArea.bottom ?? 0, safeArea.left ?? 0);
1431
+ return 1 - clamp(largestInset, 0, 0.45) * 2;
1432
+ }
1433
+ function anchorBias(anchor, axis) {
1434
+ if (anchor === undefined) {
1435
+ return 0;
1436
+ }
1437
+ if (typeof anchor === "object") {
1438
+ const value = axis === "x" ? anchor.x : anchor.y;
1439
+ return clamp(value, 0, 1) * 2 - 1;
1440
+ }
1441
+ if (axis === "x") {
1442
+ if (anchor.endsWith("left")) {
1443
+ return -1;
1444
+ }
1445
+ if (anchor.endsWith("right")) {
1446
+ return 1;
1447
+ }
1448
+ return 0;
1449
+ }
1450
+ if (anchor.startsWith("top")) {
1451
+ return -1;
1452
+ }
1453
+ if (anchor.startsWith("bottom")) {
1454
+ return 1;
1455
+ }
1456
+ return 0;
1457
+ }
1458
+ function clamp(value, min, max) {
1459
+ return Math.min(max, Math.max(min, value));
1460
+ }
1461
+ function clampInteger(value, min, max) {
1462
+ return Math.min(max, Math.max(min, Math.round(value)));
1463
+ }
1464
+ function normalizeSeed(seed) {
1465
+ const normalized = Math.trunc(seed) % 2147483647;
1466
+ return normalized <= 0 ? normalized + 2147483646 : normalized;
1467
+ }
1468
+ function nextSeed(seed) {
1469
+ return (seed * 16807) % 2147483647;
1470
+ }
1471
+ function seedToUnit(seed) {
1472
+ return (seed - 1) / 2147483646;
1473
+ }
1474
+ function roundMotionValue(value) {
1475
+ return Math.round(value * 1000) / 1000;
1476
+ }
1477
+ function exportDefinition(definition) {
1478
+ return compactObject(definition);
1479
+ }
1480
+ function socialPresetDefinition(id, label, platform, aspectRatio, preset) {
1481
+ const definition = {
1482
+ id,
1483
+ label,
1484
+ platform,
1485
+ aspectRatio,
1486
+ width: preset.width ?? 0,
1487
+ height: preset.height ?? 0,
1488
+ defaultName: preset.name ?? id,
1489
+ preset
1490
+ };
1491
+ if (preset.fps !== undefined) {
1492
+ definition.fps = preset.fps;
1493
+ }
1494
+ return definition;
1495
+ }
1496
+ function isKeyframeTuple(frame) {
1497
+ return Array.isArray(frame);
1498
+ }
1499
+ function interpolationFor(name) {
1500
+ return `{{${name}}}`;
1501
+ }
1502
+ function assertId(id, kind) {
1503
+ if (id.trim() === "") {
1504
+ throw new Error(`Kavio ${kind} id must not be empty.`);
1505
+ }
1506
+ }
1507
+ function assertPositiveInteger(value, name) {
1508
+ if (!Number.isInteger(value) || value < 1) {
1509
+ throw new Error(`${name} must be a positive integer.`);
1510
+ }
1511
+ }
1512
+ function assertOptionalPositiveInteger(value, name) {
1513
+ if (value !== undefined) {
1514
+ assertPositiveInteger(value, name);
1515
+ }
1516
+ }
1517
+ function assertNonNegativeInteger(value, name) {
1518
+ if (!Number.isInteger(value) || value < 0) {
1519
+ throw new Error(`${name} must be a non-negative integer.`);
1520
+ }
1521
+ }
1522
+ function assertOptionalNonNegativeInteger(value, name) {
1523
+ if (value !== undefined) {
1524
+ assertNonNegativeInteger(value, name);
1525
+ }
1526
+ }
1527
+ function assertOptionalPositiveNumber(value, name) {
1528
+ if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
1529
+ throw new Error(`${name} must be a positive number.`);
1530
+ }
1531
+ }
1532
+ function assertOptionalNonNegativeNumber(value, name) {
1533
+ if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
1534
+ throw new Error(`${name} must be a non-negative number.`);
1535
+ }
1536
+ }
1537
+ function assertOptionalRange(value, name, min, max) {
1538
+ if (value !== undefined && (!Number.isFinite(value) || value < min || value > max)) {
1539
+ throw new Error(`${name} must be between ${min} and ${max}.`);
1540
+ }
1541
+ }
1542
+ function compactObject(input) {
1543
+ const output = {};
1544
+ for (const [key, value] of Object.entries(input)) {
1545
+ if (value !== undefined) {
1546
+ output[key] = value;
1547
+ }
1548
+ }
1549
+ return output;
1550
+ }
1551
+ function compactArray(input) {
1552
+ return input.filter((item) => item !== undefined);
1553
+ }
1554
+ function layerIdFor(value) {
1555
+ return value instanceof LayerBuilder ? value.id : value;
1556
+ }
1557
+ function cloneTrack(track) {
1558
+ return {
1559
+ id: track.id,
1560
+ clips: track.clips.map((clip) => cloneTrackClip(clip))
1561
+ };
1562
+ }
1563
+ function cloneTrackClip(clip) {
1564
+ const output = {
1565
+ id: clip.id,
1566
+ layerId: clip.layerId,
1567
+ startFrame: clip.startFrame,
1568
+ durationFrames: clip.durationFrames
1569
+ };
1570
+ if (clip.transitionFromPrevious !== undefined) {
1571
+ output.transitionFromPrevious = cloneTransitionSeriesDefinition(clip.transitionFromPrevious);
1572
+ }
1573
+ return output;
1574
+ }
1575
+ function cloneTransitionSeriesDefinition(definition) {
1576
+ const output = {
1577
+ presentation: {
1578
+ type: definition.presentation.type
1579
+ },
1580
+ timing: {
1581
+ type: "tween",
1582
+ durationFrames: definition.timing.durationFrames
1583
+ }
1584
+ };
1585
+ if (definition.presentation.direction !== undefined) {
1586
+ output.presentation.direction = definition.presentation.direction;
1587
+ }
1588
+ if (definition.presentation.axis !== undefined) {
1589
+ output.presentation.axis = definition.presentation.axis;
1590
+ }
1591
+ if (definition.presentation.shape !== undefined) {
1592
+ output.presentation.shape = definition.presentation.shape;
1593
+ }
1594
+ if (definition.presentation.color !== undefined) {
1595
+ output.presentation.color = definition.presentation.color;
1596
+ }
1597
+ if (definition.presentation.amount !== undefined) {
1598
+ output.presentation.amount = definition.presentation.amount;
1599
+ }
1600
+ if (definition.presentation.intensity !== undefined) {
1601
+ output.presentation.intensity = definition.presentation.intensity;
1602
+ }
1603
+ if (definition.presentation.rows !== undefined) {
1604
+ output.presentation.rows = definition.presentation.rows;
1605
+ }
1606
+ if (definition.presentation.columns !== undefined) {
1607
+ output.presentation.columns = definition.presentation.columns;
1608
+ }
1609
+ if (definition.timing.easing !== undefined) {
1610
+ output.timing.easing = definition.timing.easing;
1611
+ }
1612
+ return output;
1613
+ }
1614
+ function isTransitionSeriesDefinition(value) {
1615
+ return isRecord(value) && isRecord(value.presentation) && isRecord(value.timing);
1616
+ }
1617
+ function cloneAuthorObject(input) {
1618
+ const output = {};
1619
+ for (const [key, value] of Object.entries(input)) {
1620
+ if (value instanceof PropReference || value instanceof AssetReference || value === undefined) {
1621
+ output[key] = value;
1622
+ }
1623
+ else if (Array.isArray(value)) {
1624
+ output[key] = value.map((item) => cloneAuthorValue(item));
1625
+ }
1626
+ else if (isRecord(value)) {
1627
+ output[key] = cloneAuthorObject(value);
1628
+ }
1629
+ else {
1630
+ output[key] = value;
1631
+ }
1632
+ }
1633
+ return output;
1634
+ }
1635
+ function cloneAuthorValue(value) {
1636
+ if (value instanceof PropReference || value instanceof AssetReference || value === undefined) {
1637
+ return value;
1638
+ }
1639
+ if (Array.isArray(value)) {
1640
+ return value.map((item) => cloneAuthorValue(item));
1641
+ }
1642
+ if (isRecord(value)) {
1643
+ return cloneAuthorObject(value);
1644
+ }
1645
+ return value;
1646
+ }
1647
+ function normalizeStandalone(value) {
1648
+ if (value instanceof PropReference) {
1649
+ return interpolationFor(value.name);
1650
+ }
1651
+ if (value instanceof AssetReference) {
1652
+ return value.id;
1653
+ }
1654
+ if (Array.isArray(value)) {
1655
+ return value.map((item) => normalizeStandalone(item));
1656
+ }
1657
+ if (isRecord(value)) {
1658
+ const output = {};
1659
+ for (const [key, nested] of Object.entries(value)) {
1660
+ if (nested !== undefined) {
1661
+ output[key] = normalizeStandalone(nested);
1662
+ }
1663
+ }
1664
+ return output;
1665
+ }
1666
+ return value;
1667
+ }
1668
+ function isRecord(value) {
1669
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1670
+ }