reframe-video 0.1.1 → 0.1.2
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/analyze.js +44 -1
- package/dist/bin.js +262 -7
- package/dist/browserEntry.js +259 -3
- package/dist/cli.js +222 -5
- package/dist/index.js +510 -16
- package/dist/renderer-canvas.js +36 -0
- package/dist/trace-cli.js +677 -0
- package/dist/types/compile.d.ts +15 -1
- package/dist/types/compose.d.ts +12 -2
- package/dist/types/dsl.d.ts +27 -1
- package/dist/types/evaluate.d.ts +9 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/ir.d.ts +67 -1
- package/dist/types/motion.d.ts +54 -0
- package/dist/types/path.d.ts +15 -0
- package/dist/types/presets.d.ts +41 -0
- package/guides/edsl-guide.md +19 -0
- package/package.json +1 -1
- package/preview/src/main.ts +62 -0
- package/preview/src/panel.ts +37 -3
- package/preview/src/store.ts +28 -2
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,88 @@ import { basename, dirname as dirname6, join as join5, resolve as resolve4 } fro
|
|
|
8
8
|
// ../core/src/ir.ts
|
|
9
9
|
var DEFAULT_TO_DURATION = 0.5;
|
|
10
10
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
11
|
+
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
12
|
+
|
|
13
|
+
// ../core/src/path.ts
|
|
14
|
+
function locate(segCount, u) {
|
|
15
|
+
if (segCount <= 0) return { i: 0, t: 0 };
|
|
16
|
+
const clamped = Math.max(0, Math.min(1, u));
|
|
17
|
+
const scaled = clamped * segCount;
|
|
18
|
+
let i = Math.floor(scaled);
|
|
19
|
+
if (i >= segCount) i = segCount - 1;
|
|
20
|
+
return { i, t: scaled - i };
|
|
21
|
+
}
|
|
22
|
+
function controls(points, closed, i) {
|
|
23
|
+
const n = points.length;
|
|
24
|
+
const at = (k) => {
|
|
25
|
+
if (closed) return points[(k % n + n) % n];
|
|
26
|
+
return points[Math.max(0, Math.min(n - 1, k))];
|
|
27
|
+
};
|
|
28
|
+
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
29
|
+
}
|
|
30
|
+
function segCountOf(points, closed) {
|
|
31
|
+
const n = points.length;
|
|
32
|
+
if (n < 2) return 0;
|
|
33
|
+
return closed ? n : n - 1;
|
|
34
|
+
}
|
|
35
|
+
function pathPoint(points, closed, u) {
|
|
36
|
+
const n = points.length;
|
|
37
|
+
if (n === 0) return [0, 0];
|
|
38
|
+
if (n === 1) return [points[0][0], points[0][1]];
|
|
39
|
+
const segs = segCountOf(points, closed);
|
|
40
|
+
const { i, t } = locate(segs, u);
|
|
41
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
42
|
+
const t2 = t * t;
|
|
43
|
+
const t3 = t2 * t;
|
|
44
|
+
const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
|
|
45
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
46
|
+
}
|
|
47
|
+
function pathTangentAngle(points, closed, u) {
|
|
48
|
+
const n = points.length;
|
|
49
|
+
if (n < 2) return 0;
|
|
50
|
+
const segs = segCountOf(points, closed);
|
|
51
|
+
const { i, t } = locate(segs, u);
|
|
52
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
53
|
+
const t2 = t * t;
|
|
54
|
+
const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
|
|
55
|
+
const dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
56
|
+
const dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
57
|
+
if (dx === 0 && dy === 0) return 0;
|
|
58
|
+
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
59
|
+
}
|
|
11
60
|
|
|
12
61
|
// ../core/src/compile.ts
|
|
13
62
|
var key = (target, prop) => `${target}.${prop}`;
|
|
63
|
+
function scaleTimeline(tl, k) {
|
|
64
|
+
switch (tl.kind) {
|
|
65
|
+
case "seq":
|
|
66
|
+
case "par":
|
|
67
|
+
return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
68
|
+
case "stagger":
|
|
69
|
+
return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
70
|
+
case "wait":
|
|
71
|
+
return { ...tl, duration: tl.duration * k };
|
|
72
|
+
case "tween":
|
|
73
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
|
|
74
|
+
case "motionPath":
|
|
75
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
|
|
76
|
+
case "to":
|
|
77
|
+
return {
|
|
78
|
+
...tl,
|
|
79
|
+
duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
|
|
80
|
+
...tl.stagger !== void 0 && { stagger: tl.stagger * k }
|
|
81
|
+
};
|
|
82
|
+
case "beat":
|
|
83
|
+
return {
|
|
84
|
+
...tl,
|
|
85
|
+
children: tl.children.map((c) => scaleTimeline(c, k)),
|
|
86
|
+
...tl.gap !== void 0 && { gap: tl.gap * k }
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function orderBeats(children) {
|
|
91
|
+
return children.map((c, i) => ({ c, i, key: c.kind === "beat" && c.order !== void 0 ? c.order : i })).sort((a, b) => a.key - b.key || a.i - b.i).map((x) => x.c);
|
|
92
|
+
}
|
|
14
93
|
function compileScene(ir) {
|
|
15
94
|
const nodeById = /* @__PURE__ */ new Map();
|
|
16
95
|
const nodeOrder = [];
|
|
@@ -39,6 +118,7 @@ function compileScene(ir) {
|
|
|
39
118
|
}
|
|
40
119
|
}
|
|
41
120
|
const segments = /* @__PURE__ */ new Map();
|
|
121
|
+
const motionPaths = /* @__PURE__ */ new Map();
|
|
42
122
|
const current = new Map(initialValues);
|
|
43
123
|
const pushSegment = (seg) => {
|
|
44
124
|
const k = key(seg.target, seg.prop);
|
|
@@ -55,6 +135,50 @@ function compileScene(ir) {
|
|
|
55
135
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
56
136
|
};
|
|
57
137
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
138
|
+
const beatTimes = /* @__PURE__ */ new Map();
|
|
139
|
+
const durationOf = (tl, start) => {
|
|
140
|
+
switch (tl.kind) {
|
|
141
|
+
case "seq": {
|
|
142
|
+
let t = start;
|
|
143
|
+
for (const child of orderBeats(tl.children)) t = durationOf(child, t);
|
|
144
|
+
return t;
|
|
145
|
+
}
|
|
146
|
+
case "par": {
|
|
147
|
+
let end = start;
|
|
148
|
+
for (const child of tl.children) end = Math.max(end, durationOf(child, start));
|
|
149
|
+
return end;
|
|
150
|
+
}
|
|
151
|
+
case "stagger": {
|
|
152
|
+
let end = start;
|
|
153
|
+
tl.children.forEach((child, i) => {
|
|
154
|
+
end = Math.max(end, durationOf(child, start + i * tl.interval));
|
|
155
|
+
});
|
|
156
|
+
return end;
|
|
157
|
+
}
|
|
158
|
+
case "wait":
|
|
159
|
+
return start + tl.duration;
|
|
160
|
+
case "tween":
|
|
161
|
+
return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
162
|
+
case "motionPath":
|
|
163
|
+
return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
164
|
+
case "to": {
|
|
165
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
166
|
+
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
167
|
+
const si = tl.stagger ?? 0;
|
|
168
|
+
const targets = nodeOrder.filter(
|
|
169
|
+
(id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
|
|
170
|
+
);
|
|
171
|
+
return start + duration + Math.max(0, targets.length - 1) * si;
|
|
172
|
+
}
|
|
173
|
+
case "beat": {
|
|
174
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
175
|
+
const natural = durationOf(grouping, 0);
|
|
176
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
177
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
178
|
+
return beatStart + k * natural;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
58
182
|
const walk = (tl, start) => {
|
|
59
183
|
const end = walkInner(tl, start);
|
|
60
184
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -64,9 +188,19 @@ function compileScene(ir) {
|
|
|
64
188
|
switch (tl.kind) {
|
|
65
189
|
case "seq": {
|
|
66
190
|
let t = start;
|
|
67
|
-
for (const child of tl.children) t = walk(child, t);
|
|
191
|
+
for (const child of orderBeats(tl.children)) t = walk(child, t);
|
|
68
192
|
return t;
|
|
69
193
|
}
|
|
194
|
+
case "beat": {
|
|
195
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
196
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
197
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
198
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
199
|
+
const end = walk(inner, beatStart);
|
|
200
|
+
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
201
|
+
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
202
|
+
return end;
|
|
203
|
+
}
|
|
70
204
|
case "par": {
|
|
71
205
|
let end = start;
|
|
72
206
|
for (const child of tl.children) end = Math.max(end, walk(child, start));
|
|
@@ -96,6 +230,23 @@ function compileScene(ir) {
|
|
|
96
230
|
}
|
|
97
231
|
return start + duration;
|
|
98
232
|
}
|
|
233
|
+
case "motionPath": {
|
|
234
|
+
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
235
|
+
const points = tl.points;
|
|
236
|
+
const closed = tl.closed ?? false;
|
|
237
|
+
const autoRotate = tl.autoRotate ?? false;
|
|
238
|
+
const rotateOffset = tl.rotateOffset ?? 0;
|
|
239
|
+
let list = motionPaths.get(tl.target);
|
|
240
|
+
if (!list) motionPaths.set(tl.target, list = []);
|
|
241
|
+
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
242
|
+
if (points.length > 0) {
|
|
243
|
+
const [ex, ey] = pathPoint(points, closed, 1);
|
|
244
|
+
current.set(key(tl.target, "x"), ex);
|
|
245
|
+
current.set(key(tl.target, "y"), ey);
|
|
246
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
247
|
+
}
|
|
248
|
+
return start + duration;
|
|
249
|
+
}
|
|
99
250
|
case "to": {
|
|
100
251
|
const override = ir.states?.[tl.state] ?? {};
|
|
101
252
|
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
@@ -124,14 +275,17 @@ function compileScene(ir) {
|
|
|
124
275
|
};
|
|
125
276
|
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
126
277
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
278
|
+
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
127
279
|
return {
|
|
128
280
|
ir,
|
|
129
281
|
duration: ir.duration ?? inferredEnd,
|
|
130
282
|
segments,
|
|
283
|
+
motionPaths,
|
|
131
284
|
initialValues,
|
|
132
285
|
nodeById,
|
|
133
286
|
nodeOrder,
|
|
134
|
-
labelTimes
|
|
287
|
+
labelTimes,
|
|
288
|
+
beatTimes
|
|
135
289
|
};
|
|
136
290
|
}
|
|
137
291
|
|
|
@@ -143,6 +297,7 @@ var PROPS_BY_TYPE = {
|
|
|
143
297
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
144
298
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
145
299
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
300
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
146
301
|
group: COMMON_PROPS
|
|
147
302
|
};
|
|
148
303
|
var SceneValidationError = class extends Error {
|
|
@@ -233,9 +388,39 @@ function validateScene(ir) {
|
|
|
233
388
|
problems.push(`${path}: tween duration must be > 0`);
|
|
234
389
|
}
|
|
235
390
|
break;
|
|
391
|
+
case "motionPath": {
|
|
392
|
+
const node = nodeById.get(tl.target);
|
|
393
|
+
if (!node) {
|
|
394
|
+
problems.push(
|
|
395
|
+
`${path}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
396
|
+
);
|
|
397
|
+
} else if (node.type === "line") {
|
|
398
|
+
problems.push(`${path}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
|
|
399
|
+
}
|
|
400
|
+
if (tl.points.length < 1) problems.push(`${path}: motionPath "${tl.target}" needs at least 1 point`);
|
|
401
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
402
|
+
problems.push(`${path}: motionPath "${tl.target}" duration must be > 0`);
|
|
403
|
+
}
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
236
406
|
case "wait":
|
|
237
407
|
if (tl.duration < 0) problems.push(`${path}: wait duration must be >= 0`);
|
|
238
408
|
break;
|
|
409
|
+
case "beat":
|
|
410
|
+
if (labels.has(tl.name)) {
|
|
411
|
+
problems.push(
|
|
412
|
+
`${path}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
labels.add(tl.name);
|
|
416
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
417
|
+
problems.push(`${path}: beat "${tl.name}" duration must be > 0`);
|
|
418
|
+
}
|
|
419
|
+
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
420
|
+
problems.push(`${path}: beat "${tl.name}" scale must be > 0`);
|
|
421
|
+
}
|
|
422
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path}.beat(${tl.name})[${i}]`));
|
|
423
|
+
break;
|
|
239
424
|
}
|
|
240
425
|
};
|
|
241
426
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
@@ -399,13 +584,16 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
399
584
|
const byLabel = /* @__PURE__ */ new Map();
|
|
400
585
|
const walkTimeline = (tl) => {
|
|
401
586
|
if ("label" in tl && tl.label !== void 0) byLabel.set(tl.label, tl);
|
|
587
|
+
if (tl.kind === "beat") byLabel.set(tl.name, tl);
|
|
402
588
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
403
589
|
};
|
|
404
590
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
405
591
|
const PATCHABLE = {
|
|
406
592
|
to: ["duration", "ease", "stagger"],
|
|
407
593
|
tween: ["duration", "ease"],
|
|
408
|
-
wait: ["duration"]
|
|
594
|
+
wait: ["duration"],
|
|
595
|
+
motionPath: ["points", "duration", "ease"],
|
|
596
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
409
597
|
};
|
|
410
598
|
let timingPatched = false;
|
|
411
599
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
@@ -429,7 +617,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
429
617
|
}
|
|
430
618
|
step[key2] = value;
|
|
431
619
|
applied(`timeline.${label}.${key2}`, "set");
|
|
432
|
-
if (
|
|
620
|
+
if (["duration", "stagger", "at", "gap", "scale", "order"].includes(key2)) timingPatched = true;
|
|
433
621
|
}
|
|
434
622
|
}
|
|
435
623
|
if (timingPatched && overlay.scene?.duration === void 0) {
|
|
@@ -454,6 +642,9 @@ function formatComposeReport(report) {
|
|
|
454
642
|
return lines.join("\n");
|
|
455
643
|
}
|
|
456
644
|
|
|
645
|
+
// ../core/src/presets.ts
|
|
646
|
+
var SET = 1 / 120;
|
|
647
|
+
|
|
457
648
|
// ../core/src/audio.ts
|
|
458
649
|
var SFX_DURATION = {
|
|
459
650
|
whoosh: 0.35,
|
|
@@ -526,6 +717,19 @@ function resolveAudioPlan(compiled) {
|
|
|
526
717
|
}
|
|
527
718
|
|
|
528
719
|
// ../core/src/interpolate.ts
|
|
720
|
+
var BACK_C1 = 1.70158;
|
|
721
|
+
var BACK_C2 = BACK_C1 * 1.525;
|
|
722
|
+
var BACK_C3 = BACK_C1 + 1;
|
|
723
|
+
var ELASTIC_C4 = 2 * Math.PI / 3;
|
|
724
|
+
var ELASTIC_C5 = 2 * Math.PI / 4.5;
|
|
725
|
+
function easeOutBounce(u) {
|
|
726
|
+
const n1 = 7.5625;
|
|
727
|
+
const d1 = 2.75;
|
|
728
|
+
if (u < 1 / d1) return n1 * u * u;
|
|
729
|
+
if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
|
|
730
|
+
if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
|
|
731
|
+
return n1 * (u -= 2.625 / d1) * u + 0.984375;
|
|
732
|
+
}
|
|
529
733
|
var EASE_TABLE = {
|
|
530
734
|
linear: (u) => u,
|
|
531
735
|
easeInQuad: (u) => u * u,
|
|
@@ -539,7 +743,20 @@ var EASE_TABLE = {
|
|
|
539
743
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
540
744
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
541
745
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
542
|
-
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
|
|
746
|
+
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
|
|
747
|
+
// --- expressive eases (GSAP's signature feel) — standard Penner equations ---
|
|
748
|
+
// back: overshoots past the target then settles (pop / snap)
|
|
749
|
+
easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
|
|
750
|
+
easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
|
|
751
|
+
easeInOutBack: (u) => u < 0.5 ? (2 * u) ** 2 * ((BACK_C2 + 1) * 2 * u - BACK_C2) / 2 : ((2 * u - 2) ** 2 * ((BACK_C2 + 1) * (2 * u - 2) + BACK_C2) + 2) / 2,
|
|
752
|
+
// elastic: rings around the target before settling (playful spring)
|
|
753
|
+
easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
|
|
754
|
+
easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
|
|
755
|
+
easeInOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? -(2 ** (20 * u - 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5)) / 2 : 2 ** (-20 * u + 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5) / 2 + 1,
|
|
756
|
+
// bounce: drops and bounces to rest (lands without overshoot)
|
|
757
|
+
easeInBounce: (u) => 1 - easeOutBounce(1 - u),
|
|
758
|
+
easeOutBounce,
|
|
759
|
+
easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
|
|
543
760
|
};
|
|
544
761
|
var EASE_NAMES = Object.keys(EASE_TABLE);
|
|
545
762
|
|