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 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 (key2 === "duration" || key2 === "stagger") timingPatched = true;
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
- var EASE_TABLE, EASE_NAMES;
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");