reframe-video 0.1.0 → 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/assets/sfx/LICENSE.md +5 -0
- package/assets/sfx/bgm-song21.mp3 +0 -0
- package/assets/sfx/pop.wav +0 -0
- package/assets/sfx/rise.wav +0 -0
- package/assets/sfx/shimmer.wav +0 -0
- package/assets/sfx/thud.wav +0 -0
- package/assets/sfx/tick.wav +0 -0
- package/assets/sfx/whoosh.wav +0 -0
- package/dist/analyze.js +44 -1
- package/dist/bin.js +371 -35
- package/dist/browserEntry.js +311 -8
- package/dist/cli.js +305 -21
- package/dist/index.js +566 -16
- package/dist/renderer-canvas.js +58 -3
- package/dist/trace-cli.js +677 -0
- package/dist/types/assets.d.ts +9 -0
- package/dist/types/compile.d.ts +15 -1
- package/dist/types/compose.d.ts +12 -2
- package/dist/types/dsl.d.ts +30 -1
- package/dist/types/evaluate.d.ts +17 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/ir.d.ts +82 -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 +24 -0
- package/package.json +1 -1
- package/preview/src/main.ts +111 -6
- package/preview/src/panel.ts +37 -3
- package/preview/src/store.ts +28 -2
- package/preview/src/virtual.d.ts +9 -1
- package/preview/vite.config.ts +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,95 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
|
|
3
3
|
// ../render-cli/src/cli.ts
|
|
4
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
4
|
+
import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm2 } from "node:fs/promises";
|
|
5
5
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
6
|
-
import { basename, join as join5, resolve as
|
|
6
|
+
import { basename, dirname as dirname6, join as join5, resolve as resolve4 } from "node:path";
|
|
7
7
|
|
|
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
|
|
|
@@ -142,6 +296,8 @@ var PROPS_BY_TYPE = {
|
|
|
142
296
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
143
297
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
144
298
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
299
|
+
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
300
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
145
301
|
group: COMMON_PROPS
|
|
146
302
|
};
|
|
147
303
|
var SceneValidationError = class extends Error {
|
|
@@ -232,9 +388,39 @@ function validateScene(ir) {
|
|
|
232
388
|
problems.push(`${path}: tween duration must be > 0`);
|
|
233
389
|
}
|
|
234
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
|
+
}
|
|
235
406
|
case "wait":
|
|
236
407
|
if (tl.duration < 0) problems.push(`${path}: wait duration must be >= 0`);
|
|
237
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;
|
|
238
424
|
}
|
|
239
425
|
};
|
|
240
426
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
@@ -398,13 +584,16 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
398
584
|
const byLabel = /* @__PURE__ */ new Map();
|
|
399
585
|
const walkTimeline = (tl) => {
|
|
400
586
|
if ("label" in tl && tl.label !== void 0) byLabel.set(tl.label, tl);
|
|
587
|
+
if (tl.kind === "beat") byLabel.set(tl.name, tl);
|
|
401
588
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
402
589
|
};
|
|
403
590
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
404
591
|
const PATCHABLE = {
|
|
405
592
|
to: ["duration", "ease", "stagger"],
|
|
406
593
|
tween: ["duration", "ease"],
|
|
407
|
-
wait: ["duration"]
|
|
594
|
+
wait: ["duration"],
|
|
595
|
+
motionPath: ["points", "duration", "ease"],
|
|
596
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
408
597
|
};
|
|
409
598
|
let timingPatched = false;
|
|
410
599
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
@@ -428,7 +617,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
428
617
|
}
|
|
429
618
|
step[key2] = value;
|
|
430
619
|
applied(`timeline.${label}.${key2}`, "set");
|
|
431
|
-
if (
|
|
620
|
+
if (["duration", "stagger", "at", "gap", "scale", "order"].includes(key2)) timingPatched = true;
|
|
432
621
|
}
|
|
433
622
|
}
|
|
434
623
|
if (timingPatched && overlay.scene?.duration === void 0) {
|
|
@@ -453,6 +642,9 @@ function formatComposeReport(report) {
|
|
|
453
642
|
return lines.join("\n");
|
|
454
643
|
}
|
|
455
644
|
|
|
645
|
+
// ../core/src/presets.ts
|
|
646
|
+
var SET = 1 / 120;
|
|
647
|
+
|
|
456
648
|
// ../core/src/audio.ts
|
|
457
649
|
var SFX_DURATION = {
|
|
458
650
|
whoosh: 0.35,
|
|
@@ -525,6 +717,19 @@ function resolveAudioPlan(compiled) {
|
|
|
525
717
|
}
|
|
526
718
|
|
|
527
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
|
+
}
|
|
528
733
|
var EASE_TABLE = {
|
|
529
734
|
linear: (u) => u,
|
|
530
735
|
easeInQuad: (u) => u * u,
|
|
@@ -538,10 +743,55 @@ var EASE_TABLE = {
|
|
|
538
743
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
539
744
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
540
745
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
541
|
-
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
|
|
542
760
|
};
|
|
543
761
|
var EASE_NAMES = Object.keys(EASE_TABLE);
|
|
544
762
|
|
|
763
|
+
// ../core/src/assets.ts
|
|
764
|
+
function collectImageSrcs(ir) {
|
|
765
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
766
|
+
const imageIds = /* @__PURE__ */ new Set();
|
|
767
|
+
const walkNodes = (nodes) => {
|
|
768
|
+
for (const node of nodes) {
|
|
769
|
+
if (node.type === "image") {
|
|
770
|
+
imageIds.add(node.id);
|
|
771
|
+
srcs.add(node.props.src);
|
|
772
|
+
}
|
|
773
|
+
if (node.type === "group") walkNodes(node.children);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
walkNodes(ir.nodes);
|
|
777
|
+
for (const overrides of Object.values(ir.states ?? {})) {
|
|
778
|
+
for (const [nodeId, props] of Object.entries(overrides)) {
|
|
779
|
+
if (imageIds.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const walkTimeline = (step) => {
|
|
783
|
+
if (!step) return;
|
|
784
|
+
if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
|
|
785
|
+
for (const child of step.children) walkTimeline(child);
|
|
786
|
+
} else if (step.kind === "tween" && imageIds.has(step.target)) {
|
|
787
|
+
const src = step.props.src;
|
|
788
|
+
if (typeof src === "string") srcs.add(src);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
walkTimeline(ir.timeline);
|
|
792
|
+
return [...srcs];
|
|
793
|
+
}
|
|
794
|
+
|
|
545
795
|
// ../render-cli/src/audio/index.ts
|
|
546
796
|
import { dirname as dirname2 } from "node:path";
|
|
547
797
|
|
|
@@ -881,12 +1131,12 @@ async function encodeMp4(framesDir, fps, outFile) {
|
|
|
881
1131
|
"+faststart",
|
|
882
1132
|
outFile
|
|
883
1133
|
];
|
|
884
|
-
await new Promise((
|
|
1134
|
+
await new Promise((resolve5, reject) => {
|
|
885
1135
|
const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
886
1136
|
let stderr = "";
|
|
887
1137
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
888
1138
|
proc.on("close", (code) => {
|
|
889
|
-
if (code === 0)
|
|
1139
|
+
if (code === 0) resolve5();
|
|
890
1140
|
else reject(new Error(`ffmpeg exited with ${code}:
|
|
891
1141
|
${stderr.slice(-2e3)}`));
|
|
892
1142
|
});
|
|
@@ -925,6 +1175,38 @@ async function fontFaceCss() {
|
|
|
925
1175
|
return cssCache;
|
|
926
1176
|
}
|
|
927
1177
|
|
|
1178
|
+
// ../render-cli/src/images.ts
|
|
1179
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1180
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1181
|
+
import { extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
1182
|
+
var MIME = {
|
|
1183
|
+
".png": "image/png",
|
|
1184
|
+
".jpg": "image/jpeg",
|
|
1185
|
+
".jpeg": "image/jpeg",
|
|
1186
|
+
".webp": "image/webp"
|
|
1187
|
+
};
|
|
1188
|
+
async function buildImageAssets(ir, sceneDir) {
|
|
1189
|
+
const assets = {};
|
|
1190
|
+
for (const src of collectImageSrcs(ir)) {
|
|
1191
|
+
const mime = MIME[extname(src).toLowerCase()];
|
|
1192
|
+
if (!mime) {
|
|
1193
|
+
throw new Error(
|
|
1194
|
+
`image "${src}": unsupported format "${extname(src)}" \u2014 supported: ${Object.keys(MIME).join(" ")}`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
|
|
1198
|
+
(c) => c !== null
|
|
1199
|
+
);
|
|
1200
|
+
const found = candidates.find((c) => existsSync2(c));
|
|
1201
|
+
if (!found) {
|
|
1202
|
+
throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
1203
|
+
}
|
|
1204
|
+
const data = await readFile2(found);
|
|
1205
|
+
assets[src] = `data:${mime};base64,${data.toString("base64")}`;
|
|
1206
|
+
}
|
|
1207
|
+
return assets;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
928
1210
|
// ../render-cli/src/vclock.ts
|
|
929
1211
|
var VCLOCK_SOURCE = String.raw`
|
|
930
1212
|
(() => {
|
|
@@ -1014,8 +1296,8 @@ var bundleCache = null;
|
|
|
1014
1296
|
async function browserBundle() {
|
|
1015
1297
|
if (bundleCache) return bundleCache;
|
|
1016
1298
|
if (true) {
|
|
1017
|
-
const { readFile:
|
|
1018
|
-
bundleCache = await
|
|
1299
|
+
const { readFile: readFile5 } = await import("node:fs/promises");
|
|
1300
|
+
bundleCache = await readFile5(
|
|
1019
1301
|
join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
1020
1302
|
"utf8"
|
|
1021
1303
|
);
|
|
@@ -1034,6 +1316,7 @@ async function browserBundle() {
|
|
|
1034
1316
|
}
|
|
1035
1317
|
async function captureIr(ir, opts) {
|
|
1036
1318
|
await mkdir2(opts.framesDir, { recursive: true });
|
|
1319
|
+
const assets = await buildImageAssets(ir, opts.sceneDir ?? process.cwd());
|
|
1037
1320
|
const bundle = await browserBundle();
|
|
1038
1321
|
return withPage(ir.size, async (page) => {
|
|
1039
1322
|
await page.setContent(
|
|
@@ -1042,8 +1325,8 @@ async function captureIr(ir, opts) {
|
|
|
1042
1325
|
await injectFonts(page);
|
|
1043
1326
|
await page.addScriptTag({ content: bundle });
|
|
1044
1327
|
const info = await page.evaluate(
|
|
1045
|
-
(sceneIr) => window.__reframe.init(sceneIr),
|
|
1046
|
-
ir
|
|
1328
|
+
([sceneIr, imageAssets]) => window.__reframe.init(sceneIr, imageAssets),
|
|
1329
|
+
[ir, assets]
|
|
1047
1330
|
);
|
|
1048
1331
|
const fps = opts.fps ?? info.fps;
|
|
1049
1332
|
const duration = opts.duration ?? info.duration;
|
|
@@ -1080,14 +1363,14 @@ async function captureHtml(htmlPath, opts) {
|
|
|
1080
1363
|
|
|
1081
1364
|
// ../render-cli/src/loadScene.ts
|
|
1082
1365
|
import { build as build2 } from "esbuild";
|
|
1083
|
-
import { readFile as
|
|
1084
|
-
import { dirname as dirname5, resolve as
|
|
1366
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1367
|
+
import { dirname as dirname5, resolve as resolve3 } from "node:path";
|
|
1085
1368
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1086
1369
|
var HERE = dirname5(fileURLToPath4(import.meta.url));
|
|
1087
|
-
var CORE_ENTRY = true ?
|
|
1370
|
+
var CORE_ENTRY = true ? resolve3(HERE, "index.js") : resolve3(HERE, "..", "..", "core", "src", "index.ts");
|
|
1088
1371
|
async function loadScene(path) {
|
|
1089
1372
|
if (path.endsWith(".json")) {
|
|
1090
|
-
const ir = JSON.parse(await
|
|
1373
|
+
const ir = JSON.parse(await readFile3(path, "utf8"));
|
|
1091
1374
|
validateScene(ir);
|
|
1092
1375
|
return ir;
|
|
1093
1376
|
}
|
|
@@ -1128,7 +1411,7 @@ function parseArgs(argv) {
|
|
|
1128
1411
|
}
|
|
1129
1412
|
const args = {
|
|
1130
1413
|
mode,
|
|
1131
|
-
input:
|
|
1414
|
+
input: resolve4(input),
|
|
1132
1415
|
out: `${basename(input).replace(/\.[^.]+$/, "")}.mp4`,
|
|
1133
1416
|
keepFrames: false,
|
|
1134
1417
|
overlays: [],
|
|
@@ -1140,8 +1423,8 @@ function parseArgs(argv) {
|
|
|
1140
1423
|
else if (a === "--fps") args.fps = Number(rest[++i]);
|
|
1141
1424
|
else if (a === "--duration") args.duration = Number(rest[++i]);
|
|
1142
1425
|
else if (a === "--keep-frames") args.keepFrames = true;
|
|
1143
|
-
else if (a === "--frames-dir") args.framesDir =
|
|
1144
|
-
else if (a === "--overlay") args.overlays.push(
|
|
1426
|
+
else if (a === "--frames-dir") args.framesDir = resolve4(rest[++i]);
|
|
1427
|
+
else if (a === "--overlay") args.overlays.push(resolve4(rest[++i]));
|
|
1145
1428
|
else if (a === "--no-audio") args.noAudio = true;
|
|
1146
1429
|
else {
|
|
1147
1430
|
console.error(`unknown flag ${a}`);
|
|
@@ -1159,7 +1442,7 @@ async function main() {
|
|
|
1159
1442
|
let ir = await loadScene(args.input);
|
|
1160
1443
|
if (args.overlays.length > 0) {
|
|
1161
1444
|
const docs = await Promise.all(
|
|
1162
|
-
args.overlays.map(async (p) => JSON.parse(await
|
|
1445
|
+
args.overlays.map(async (p) => JSON.parse(await readFile4(p, "utf8")))
|
|
1163
1446
|
);
|
|
1164
1447
|
const composed = composeScene(ir, ...docs);
|
|
1165
1448
|
console.error(formatComposeReport(composed.report));
|
|
@@ -1174,6 +1457,7 @@ async function main() {
|
|
|
1174
1457
|
}
|
|
1175
1458
|
result = await captureIr(ir, {
|
|
1176
1459
|
framesDir,
|
|
1460
|
+
sceneDir: dirname6(args.input),
|
|
1177
1461
|
...args.fps !== void 0 && { fps: args.fps },
|
|
1178
1462
|
...args.duration !== void 0 && { duration: args.duration }
|
|
1179
1463
|
});
|