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/analyze.js
CHANGED
|
@@ -155,6 +155,43 @@ async function* frameStream(source, opts) {
|
|
|
155
155
|
if (pending.length !== 0) throw new Error(`trailing partial frame (${pending.length} bytes)`);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// ../../benchmark/harness/motion/grid.ts
|
|
159
|
+
var DEFAULT_GRID = { cols: 8, rows: 5 };
|
|
160
|
+
var cellOf = (x, y, w, h, spec) => {
|
|
161
|
+
const c = Math.min(spec.cols - 1, Math.floor(x * spec.cols / w));
|
|
162
|
+
const r = Math.min(spec.rows - 1, Math.floor(y * spec.rows / h));
|
|
163
|
+
return r * spec.cols + c;
|
|
164
|
+
};
|
|
165
|
+
function cellDiff(prev, next, w, h, spec) {
|
|
166
|
+
const n = spec.cols * spec.rows;
|
|
167
|
+
const sum = new Array(n).fill(0);
|
|
168
|
+
const cnt = new Array(n).fill(0);
|
|
169
|
+
for (let y = 0; y < h; y++) {
|
|
170
|
+
for (let x = 0; x < w; x++) {
|
|
171
|
+
const idx = cellOf(x, y, w, h, spec);
|
|
172
|
+
sum[idx] += Math.abs(next[y * w + x] - prev[y * w + x]);
|
|
173
|
+
cnt[idx]++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return sum.map((s, i) => cnt[i] ? s / cnt[i] : 0);
|
|
177
|
+
}
|
|
178
|
+
function frameOccupancy(frame, w, h, spec) {
|
|
179
|
+
const n = spec.cols * spec.rows;
|
|
180
|
+
const sum = new Array(n).fill(0);
|
|
181
|
+
const cnt = new Array(n).fill(0);
|
|
182
|
+
for (let y = 0; y < h; y++) {
|
|
183
|
+
for (let x = 0; x < w; x++) {
|
|
184
|
+
const here = frame[y * w + x];
|
|
185
|
+
const gx = x + 1 < w ? Math.abs(frame[y * w + x + 1] - here) : 0;
|
|
186
|
+
const gy = y + 1 < h ? Math.abs(frame[(y + 1) * w + x] - here) : 0;
|
|
187
|
+
const idx = cellOf(x, y, w, h, spec);
|
|
188
|
+
sum[idx] += gx + gy;
|
|
189
|
+
cnt[idx]++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return sum.map((s, i) => cnt[i] ? s / cnt[i] : 0);
|
|
193
|
+
}
|
|
194
|
+
|
|
158
195
|
// ../../benchmark/harness/motion/analyze.ts
|
|
159
196
|
var ANALYSIS_WIDTH = 480;
|
|
160
197
|
var ANALYSIS_HEIGHT = 270;
|
|
@@ -243,6 +280,7 @@ async function analyzeMotion(inputPath, opts = {}) {
|
|
|
243
280
|
height: ANALYSIS_HEIGHT,
|
|
244
281
|
...opts.changedThreshold !== void 0 && { changedThreshold: opts.changedThreshold }
|
|
245
282
|
};
|
|
283
|
+
const gridSpec = opts.grid ? typeof opts.grid === "object" ? opts.grid : DEFAULT_GRID : null;
|
|
246
284
|
const source = await resolveSource(resolve(inputPath));
|
|
247
285
|
const keys = [
|
|
248
286
|
"blockSpeedMean",
|
|
@@ -256,13 +294,17 @@ async function analyzeMotion(inputPath, opts = {}) {
|
|
|
256
294
|
"activeBlocks"
|
|
257
295
|
];
|
|
258
296
|
const series = Object.fromEntries([...keys.map((k) => [k, []]), ["t", []]]);
|
|
297
|
+
const gridDiff = [];
|
|
298
|
+
const gridOcc = [];
|
|
259
299
|
let prev = null;
|
|
260
300
|
let pair = 0;
|
|
261
301
|
for await (const frame of frameStream(source, blockOpts)) {
|
|
302
|
+
if (gridSpec) gridOcc.push(frameOccupancy(frame, ANALYSIS_WIDTH, ANALYSIS_HEIGHT, gridSpec));
|
|
262
303
|
if (prev) {
|
|
263
304
|
const stats = analyzePair(prev, frame, blockOpts);
|
|
264
305
|
for (const k of keys) series[k].push(stats[k]);
|
|
265
306
|
series.t.push((pair + 0.5) / fps);
|
|
307
|
+
if (gridSpec) gridDiff.push(cellDiff(prev, frame, ANALYSIS_WIDTH, ANALYSIS_HEIGHT, gridSpec));
|
|
266
308
|
pair++;
|
|
267
309
|
}
|
|
268
310
|
prev = frame;
|
|
@@ -310,7 +352,8 @@ async function analyzeMotion(inputPath, opts = {}) {
|
|
|
310
352
|
saturatedPairs: series.saturatedFraction.map((s, i) => s > 0.3 ? i : -1).filter((i) => i >= 0),
|
|
311
353
|
diffPeriodicityHz: dominantPeriodicity(d, fps)
|
|
312
354
|
},
|
|
313
|
-
segments
|
|
355
|
+
segments,
|
|
356
|
+
...gridSpec && { grid: { spec: gridSpec, diff: gridDiff, occupancy: gridOcc } }
|
|
314
357
|
};
|
|
315
358
|
}
|
|
316
359
|
async function main() {
|
package/dist/bin.js
CHANGED
|
@@ -10,16 +10,100 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// ../core/src/ir.ts
|
|
13
|
-
var DEFAULT_TO_DURATION, DEFAULT_TWEEN_DURATION;
|
|
13
|
+
var DEFAULT_TO_DURATION, DEFAULT_TWEEN_DURATION, DEFAULT_MOTIONPATH_DURATION;
|
|
14
14
|
var init_ir = __esm({
|
|
15
15
|
"../core/src/ir.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
DEFAULT_TO_DURATION = 0.5;
|
|
18
18
|
DEFAULT_TWEEN_DURATION = 0.5;
|
|
19
|
+
DEFAULT_MOTIONPATH_DURATION = 1;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ../core/src/path.ts
|
|
24
|
+
function locate(segCount, u) {
|
|
25
|
+
if (segCount <= 0) return { i: 0, t: 0 };
|
|
26
|
+
const clamped = Math.max(0, Math.min(1, u));
|
|
27
|
+
const scaled = clamped * segCount;
|
|
28
|
+
let i = Math.floor(scaled);
|
|
29
|
+
if (i >= segCount) i = segCount - 1;
|
|
30
|
+
return { i, t: scaled - i };
|
|
31
|
+
}
|
|
32
|
+
function controls(points, closed, i) {
|
|
33
|
+
const n = points.length;
|
|
34
|
+
const at = (k) => {
|
|
35
|
+
if (closed) return points[(k % n + n) % n];
|
|
36
|
+
return points[Math.max(0, Math.min(n - 1, k))];
|
|
37
|
+
};
|
|
38
|
+
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
39
|
+
}
|
|
40
|
+
function segCountOf(points, closed) {
|
|
41
|
+
const n = points.length;
|
|
42
|
+
if (n < 2) return 0;
|
|
43
|
+
return closed ? n : n - 1;
|
|
44
|
+
}
|
|
45
|
+
function pathPoint(points, closed, u) {
|
|
46
|
+
const n = points.length;
|
|
47
|
+
if (n === 0) return [0, 0];
|
|
48
|
+
if (n === 1) return [points[0][0], points[0][1]];
|
|
49
|
+
const segs = segCountOf(points, closed);
|
|
50
|
+
const { i, t } = locate(segs, u);
|
|
51
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
52
|
+
const t2 = t * t;
|
|
53
|
+
const t3 = t2 * t;
|
|
54
|
+
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);
|
|
55
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
56
|
+
}
|
|
57
|
+
function pathTangentAngle(points, closed, u) {
|
|
58
|
+
const n = points.length;
|
|
59
|
+
if (n < 2) return 0;
|
|
60
|
+
const segs = segCountOf(points, closed);
|
|
61
|
+
const { i, t } = locate(segs, u);
|
|
62
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
63
|
+
const t2 = t * t;
|
|
64
|
+
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);
|
|
65
|
+
const dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
66
|
+
const dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
67
|
+
if (dx === 0 && dy === 0) return 0;
|
|
68
|
+
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
69
|
+
}
|
|
70
|
+
var init_path = __esm({
|
|
71
|
+
"../core/src/path.ts"() {
|
|
72
|
+
"use strict";
|
|
19
73
|
}
|
|
20
74
|
});
|
|
21
75
|
|
|
22
76
|
// ../core/src/compile.ts
|
|
77
|
+
function scaleTimeline(tl, k) {
|
|
78
|
+
switch (tl.kind) {
|
|
79
|
+
case "seq":
|
|
80
|
+
case "par":
|
|
81
|
+
return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
82
|
+
case "stagger":
|
|
83
|
+
return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
84
|
+
case "wait":
|
|
85
|
+
return { ...tl, duration: tl.duration * k };
|
|
86
|
+
case "tween":
|
|
87
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
|
|
88
|
+
case "motionPath":
|
|
89
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
|
|
90
|
+
case "to":
|
|
91
|
+
return {
|
|
92
|
+
...tl,
|
|
93
|
+
duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
|
|
94
|
+
...tl.stagger !== void 0 && { stagger: tl.stagger * k }
|
|
95
|
+
};
|
|
96
|
+
case "beat":
|
|
97
|
+
return {
|
|
98
|
+
...tl,
|
|
99
|
+
children: tl.children.map((c) => scaleTimeline(c, k)),
|
|
100
|
+
...tl.gap !== void 0 && { gap: tl.gap * k }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function orderBeats(children) {
|
|
105
|
+
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);
|
|
106
|
+
}
|
|
23
107
|
function compileScene(ir) {
|
|
24
108
|
const nodeById = /* @__PURE__ */ new Map();
|
|
25
109
|
const nodeOrder = [];
|
|
@@ -48,6 +132,7 @@ function compileScene(ir) {
|
|
|
48
132
|
}
|
|
49
133
|
}
|
|
50
134
|
const segments = /* @__PURE__ */ new Map();
|
|
135
|
+
const motionPaths = /* @__PURE__ */ new Map();
|
|
51
136
|
const current = new Map(initialValues);
|
|
52
137
|
const pushSegment = (seg) => {
|
|
53
138
|
const k = key(seg.target, seg.prop);
|
|
@@ -64,6 +149,50 @@ function compileScene(ir) {
|
|
|
64
149
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
65
150
|
};
|
|
66
151
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
152
|
+
const beatTimes = /* @__PURE__ */ new Map();
|
|
153
|
+
const durationOf = (tl, start) => {
|
|
154
|
+
switch (tl.kind) {
|
|
155
|
+
case "seq": {
|
|
156
|
+
let t = start;
|
|
157
|
+
for (const child of orderBeats(tl.children)) t = durationOf(child, t);
|
|
158
|
+
return t;
|
|
159
|
+
}
|
|
160
|
+
case "par": {
|
|
161
|
+
let end = start;
|
|
162
|
+
for (const child of tl.children) end = Math.max(end, durationOf(child, start));
|
|
163
|
+
return end;
|
|
164
|
+
}
|
|
165
|
+
case "stagger": {
|
|
166
|
+
let end = start;
|
|
167
|
+
tl.children.forEach((child, i) => {
|
|
168
|
+
end = Math.max(end, durationOf(child, start + i * tl.interval));
|
|
169
|
+
});
|
|
170
|
+
return end;
|
|
171
|
+
}
|
|
172
|
+
case "wait":
|
|
173
|
+
return start + tl.duration;
|
|
174
|
+
case "tween":
|
|
175
|
+
return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
176
|
+
case "motionPath":
|
|
177
|
+
return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
178
|
+
case "to": {
|
|
179
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
180
|
+
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
181
|
+
const si = tl.stagger ?? 0;
|
|
182
|
+
const targets = nodeOrder.filter(
|
|
183
|
+
(id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
|
|
184
|
+
);
|
|
185
|
+
return start + duration + Math.max(0, targets.length - 1) * si;
|
|
186
|
+
}
|
|
187
|
+
case "beat": {
|
|
188
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
189
|
+
const natural = durationOf(grouping, 0);
|
|
190
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
191
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
192
|
+
return beatStart + k * natural;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
67
196
|
const walk = (tl, start) => {
|
|
68
197
|
const end = walkInner(tl, start);
|
|
69
198
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -73,9 +202,19 @@ function compileScene(ir) {
|
|
|
73
202
|
switch (tl.kind) {
|
|
74
203
|
case "seq": {
|
|
75
204
|
let t = start;
|
|
76
|
-
for (const child of tl.children) t = walk(child, t);
|
|
205
|
+
for (const child of orderBeats(tl.children)) t = walk(child, t);
|
|
77
206
|
return t;
|
|
78
207
|
}
|
|
208
|
+
case "beat": {
|
|
209
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
210
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
211
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
212
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
213
|
+
const end = walk(inner, beatStart);
|
|
214
|
+
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
215
|
+
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
216
|
+
return end;
|
|
217
|
+
}
|
|
79
218
|
case "par": {
|
|
80
219
|
let end = start;
|
|
81
220
|
for (const child of tl.children) end = Math.max(end, walk(child, start));
|
|
@@ -105,6 +244,23 @@ function compileScene(ir) {
|
|
|
105
244
|
}
|
|
106
245
|
return start + duration;
|
|
107
246
|
}
|
|
247
|
+
case "motionPath": {
|
|
248
|
+
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
249
|
+
const points = tl.points;
|
|
250
|
+
const closed = tl.closed ?? false;
|
|
251
|
+
const autoRotate = tl.autoRotate ?? false;
|
|
252
|
+
const rotateOffset = tl.rotateOffset ?? 0;
|
|
253
|
+
let list = motionPaths.get(tl.target);
|
|
254
|
+
if (!list) motionPaths.set(tl.target, list = []);
|
|
255
|
+
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
256
|
+
if (points.length > 0) {
|
|
257
|
+
const [ex, ey] = pathPoint(points, closed, 1);
|
|
258
|
+
current.set(key(tl.target, "x"), ex);
|
|
259
|
+
current.set(key(tl.target, "y"), ey);
|
|
260
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
261
|
+
}
|
|
262
|
+
return start + duration;
|
|
263
|
+
}
|
|
108
264
|
case "to": {
|
|
109
265
|
const override = ir.states?.[tl.state] ?? {};
|
|
110
266
|
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
@@ -133,14 +289,17 @@ function compileScene(ir) {
|
|
|
133
289
|
};
|
|
134
290
|
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
135
291
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
292
|
+
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
136
293
|
return {
|
|
137
294
|
ir,
|
|
138
295
|
duration: ir.duration ?? inferredEnd,
|
|
139
296
|
segments,
|
|
297
|
+
motionPaths,
|
|
140
298
|
initialValues,
|
|
141
299
|
nodeById,
|
|
142
300
|
nodeOrder,
|
|
143
|
-
labelTimes
|
|
301
|
+
labelTimes,
|
|
302
|
+
beatTimes
|
|
144
303
|
};
|
|
145
304
|
}
|
|
146
305
|
var key;
|
|
@@ -148,6 +307,7 @@ var init_compile = __esm({
|
|
|
148
307
|
"../core/src/compile.ts"() {
|
|
149
308
|
"use strict";
|
|
150
309
|
init_ir();
|
|
310
|
+
init_path();
|
|
151
311
|
key = (target, prop) => `${target}.${prop}`;
|
|
152
312
|
}
|
|
153
313
|
});
|
|
@@ -232,9 +392,39 @@ function validateScene(ir) {
|
|
|
232
392
|
problems.push(`${path}: tween duration must be > 0`);
|
|
233
393
|
}
|
|
234
394
|
break;
|
|
395
|
+
case "motionPath": {
|
|
396
|
+
const node = nodeById.get(tl.target);
|
|
397
|
+
if (!node) {
|
|
398
|
+
problems.push(
|
|
399
|
+
`${path}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
400
|
+
);
|
|
401
|
+
} else if (node.type === "line") {
|
|
402
|
+
problems.push(`${path}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
|
|
403
|
+
}
|
|
404
|
+
if (tl.points.length < 1) problems.push(`${path}: motionPath "${tl.target}" needs at least 1 point`);
|
|
405
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
406
|
+
problems.push(`${path}: motionPath "${tl.target}" duration must be > 0`);
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
235
410
|
case "wait":
|
|
236
411
|
if (tl.duration < 0) problems.push(`${path}: wait duration must be >= 0`);
|
|
237
412
|
break;
|
|
413
|
+
case "beat":
|
|
414
|
+
if (labels.has(tl.name)) {
|
|
415
|
+
problems.push(
|
|
416
|
+
`${path}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
labels.add(tl.name);
|
|
420
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
421
|
+
problems.push(`${path}: beat "${tl.name}" duration must be > 0`);
|
|
422
|
+
}
|
|
423
|
+
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
424
|
+
problems.push(`${path}: beat "${tl.name}" scale must be > 0`);
|
|
425
|
+
}
|
|
426
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path}.beat(${tl.name})[${i}]`));
|
|
427
|
+
break;
|
|
238
428
|
}
|
|
239
429
|
};
|
|
240
430
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
@@ -284,6 +474,7 @@ var init_validate = __esm({
|
|
|
284
474
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
285
475
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
286
476
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
477
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
287
478
|
group: COMMON_PROPS
|
|
288
479
|
};
|
|
289
480
|
SceneValidationError = class extends Error {
|
|
@@ -430,13 +621,16 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
430
621
|
const byLabel = /* @__PURE__ */ new Map();
|
|
431
622
|
const walkTimeline = (tl) => {
|
|
432
623
|
if ("label" in tl && tl.label !== void 0) byLabel.set(tl.label, tl);
|
|
624
|
+
if (tl.kind === "beat") byLabel.set(tl.name, tl);
|
|
433
625
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
434
626
|
};
|
|
435
627
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
436
628
|
const PATCHABLE = {
|
|
437
629
|
to: ["duration", "ease", "stagger"],
|
|
438
630
|
tween: ["duration", "ease"],
|
|
439
|
-
wait: ["duration"]
|
|
631
|
+
wait: ["duration"],
|
|
632
|
+
motionPath: ["points", "duration", "ease"],
|
|
633
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
440
634
|
};
|
|
441
635
|
let timingPatched = false;
|
|
442
636
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
@@ -460,7 +654,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
460
654
|
}
|
|
461
655
|
step[key2] = value;
|
|
462
656
|
applied(`timeline.${label}.${key2}`, "set");
|
|
463
|
-
if (
|
|
657
|
+
if (["duration", "stagger", "at", "gap", "scale", "order"].includes(key2)) timingPatched = true;
|
|
464
658
|
}
|
|
465
659
|
}
|
|
466
660
|
if (timingPatched && overlay.scene?.duration === void 0) {
|
|
@@ -484,6 +678,16 @@ var init_compose = __esm({
|
|
|
484
678
|
}
|
|
485
679
|
});
|
|
486
680
|
|
|
681
|
+
// ../core/src/presets.ts
|
|
682
|
+
var SET;
|
|
683
|
+
var init_presets = __esm({
|
|
684
|
+
"../core/src/presets.ts"() {
|
|
685
|
+
"use strict";
|
|
686
|
+
init_dsl();
|
|
687
|
+
SET = 1 / 120;
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
487
691
|
// ../core/src/audio.ts
|
|
488
692
|
function resolveAudioPlan(compiled) {
|
|
489
693
|
const audio = compiled.ir.audio;
|
|
@@ -569,10 +773,23 @@ var init_behaviors = __esm({
|
|
|
569
773
|
});
|
|
570
774
|
|
|
571
775
|
// ../core/src/interpolate.ts
|
|
572
|
-
|
|
776
|
+
function easeOutBounce(u) {
|
|
777
|
+
const n1 = 7.5625;
|
|
778
|
+
const d1 = 2.75;
|
|
779
|
+
if (u < 1 / d1) return n1 * u * u;
|
|
780
|
+
if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
|
|
781
|
+
if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
|
|
782
|
+
return n1 * (u -= 2.625 / d1) * u + 0.984375;
|
|
783
|
+
}
|
|
784
|
+
var BACK_C1, BACK_C2, BACK_C3, ELASTIC_C4, ELASTIC_C5, EASE_TABLE, EASE_NAMES;
|
|
573
785
|
var init_interpolate = __esm({
|
|
574
786
|
"../core/src/interpolate.ts"() {
|
|
575
787
|
"use strict";
|
|
788
|
+
BACK_C1 = 1.70158;
|
|
789
|
+
BACK_C2 = BACK_C1 * 1.525;
|
|
790
|
+
BACK_C3 = BACK_C1 + 1;
|
|
791
|
+
ELASTIC_C4 = 2 * Math.PI / 3;
|
|
792
|
+
ELASTIC_C5 = 2 * Math.PI / 4.5;
|
|
576
793
|
EASE_TABLE = {
|
|
577
794
|
linear: (u) => u,
|
|
578
795
|
easeInQuad: (u) => u * u,
|
|
@@ -586,7 +803,20 @@ var init_interpolate = __esm({
|
|
|
586
803
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
587
804
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
588
805
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
589
|
-
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
|
|
806
|
+
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
|
|
807
|
+
// --- expressive eases (GSAP's signature feel) — standard Penner equations ---
|
|
808
|
+
// back: overshoots past the target then settles (pop / snap)
|
|
809
|
+
easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
|
|
810
|
+
easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
|
|
811
|
+
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,
|
|
812
|
+
// elastic: rings around the target before settling (playful spring)
|
|
813
|
+
easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
|
|
814
|
+
easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
|
|
815
|
+
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,
|
|
816
|
+
// bounce: drops and bounces to rest (lands without overshoot)
|
|
817
|
+
easeInBounce: (u) => 1 - easeOutBounce(1 - u),
|
|
818
|
+
easeOutBounce,
|
|
819
|
+
easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
|
|
590
820
|
};
|
|
591
821
|
EASE_NAMES = Object.keys(EASE_TABLE);
|
|
592
822
|
}
|
|
@@ -598,6 +828,7 @@ var init_evaluate = __esm({
|
|
|
598
828
|
"use strict";
|
|
599
829
|
init_behaviors();
|
|
600
830
|
init_interpolate();
|
|
831
|
+
init_path();
|
|
601
832
|
}
|
|
602
833
|
});
|
|
603
834
|
|
|
@@ -638,6 +869,14 @@ var init_assets = __esm({
|
|
|
638
869
|
}
|
|
639
870
|
});
|
|
640
871
|
|
|
872
|
+
// ../core/src/motion.ts
|
|
873
|
+
var init_motion = __esm({
|
|
874
|
+
"../core/src/motion.ts"() {
|
|
875
|
+
"use strict";
|
|
876
|
+
init_dsl();
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
641
880
|
// ../core/src/index.ts
|
|
642
881
|
var init_src = __esm({
|
|
643
882
|
"../core/src/index.ts"() {
|
|
@@ -647,11 +886,14 @@ var init_src = __esm({
|
|
|
647
886
|
init_validate();
|
|
648
887
|
init_compose();
|
|
649
888
|
init_compile();
|
|
889
|
+
init_path();
|
|
890
|
+
init_presets();
|
|
650
891
|
init_audio();
|
|
651
892
|
init_evaluate();
|
|
652
893
|
init_interpolate();
|
|
653
894
|
init_behaviors();
|
|
654
895
|
init_assets();
|
|
896
|
+
init_motion();
|
|
655
897
|
}
|
|
656
898
|
});
|
|
657
899
|
|
|
@@ -1500,6 +1742,7 @@ var ROOT2 = PACKAGED ? resolve4(HERE2, "..") : resolve4(HERE2, "..", "..", "..")
|
|
|
1500
1742
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
1501
1743
|
var RENDER_CLI = PACKAGED ? join6(ROOT2, "dist", "cli.js") : join6(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
1502
1744
|
var ANALYZE = PACKAGED ? join6(ROOT2, "dist", "analyze.js") : join6(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
1745
|
+
var TRACE = PACKAGED ? join6(ROOT2, "dist", "trace-cli.js") : join6(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
1503
1746
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
1504
1747
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
1505
1748
|
|
|
@@ -1509,6 +1752,7 @@ usage:
|
|
|
1509
1752
|
${CMD} preview open the scrub/edit UI (lists scenes in your directory)
|
|
1510
1753
|
${CMD} new <scene-name> scaffold <scene-name>.ts in your directory
|
|
1511
1754
|
${CMD} motion <mp4|framesDir> motion-profile a rendered clip
|
|
1755
|
+
${CMD} trace <ref.mp4> [--apply scene.ts] extract a video's motion structure \u2192 MotionSketch / timeline
|
|
1512
1756
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
1513
1757
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
1514
1758
|
`;
|
|
@@ -1731,6 +1975,17 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1731
1975
|
await (PACKAGED ? run(process.execPath, [ANALYZE, userPath(input), ...rest.slice(1)]) : run("npx", ["tsx", ANALYZE, userPath(input), ...rest.slice(1)]))
|
|
1732
1976
|
);
|
|
1733
1977
|
}
|
|
1978
|
+
case "trace": {
|
|
1979
|
+
const input = rest[0];
|
|
1980
|
+
if (!input || input.startsWith("-")) fail(`usage: ${CMD} trace <ref.mp4> [--apply scene.ts] [-o out.json]`);
|
|
1981
|
+
preflightFfmpeg();
|
|
1982
|
+
const args = rest.slice(1).map(
|
|
1983
|
+
(a, i) => rest.slice(1)[i - 1] === "--apply" || rest.slice(1)[i - 1] === "-o" ? userPath(a) : a
|
|
1984
|
+
);
|
|
1985
|
+
process.exit(
|
|
1986
|
+
await (PACKAGED ? run(process.execPath, [TRACE, userPath(input), ...args]) : run("npx", ["tsx", TRACE, userPath(input), ...args]))
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1734
1989
|
case "guide": {
|
|
1735
1990
|
const file = rest.includes("--regen") ? PACKAGED ? join6(ROOT2, "guides", "regen-contract.md") : join6(ROOT2, "docs", "regen-contract.md") : PACKAGED ? join6(ROOT2, "guides", "edsl-guide.md") : join6(ROOT2, "benchmark", "guides", "edsl-guide.md");
|
|
1736
1991
|
const { readFile: readFile5 } = await import("node:fs/promises");
|