reframe-video 0.6.33 → 0.6.39
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/.claude-plugin/plugin.json +1 -1
- package/dist/assemble.js +138 -0
- package/dist/bin.js +248 -36
- package/dist/browserEntry.js +62 -3
- package/dist/cli.js +162 -18
- package/dist/compile-api.js +20 -0
- package/dist/compile.js +20 -0
- package/dist/diff.js +81 -3
- package/dist/frame.js +81 -3
- package/dist/index.js +1537 -1111
- package/dist/labels.js +80 -2
- package/dist/lint.js +1060 -0
- package/dist/manifest.js +1065 -0
- package/dist/types/compose.d.ts +6 -0
- package/dist/types/devicePreset.d.ts +36 -3
- package/dist/types/dsl.d.ts +4 -3
- package/dist/types/index.d.ts +4 -2
- package/dist/types/ir.d.ts +13 -5
- package/dist/types/manifest.d.ts +92 -0
- package/dist/types/titles.d.ts +71 -0
- package/dist/verifyOverlay.js +1152 -0
- package/guides/edsl-guide.md +58 -0
- package/guides/regen-contract.md +14 -0
- package/package.json +1 -1
- package/skills/reframe/SKILL.md +26 -0
package/dist/browserEntry.js
CHANGED
|
@@ -219,11 +219,68 @@
|
|
|
219
219
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
220
220
|
const natural = durationOf(grouping, 0);
|
|
221
221
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
222
|
-
const
|
|
222
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
223
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
223
224
|
return beatStart + k * natural;
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
};
|
|
228
|
+
let labelClock;
|
|
229
|
+
const anyAnchor = (tl) => tl.kind === "beat" && typeof tl.at === "string" || "children" in tl && tl.children.some(anyAnchor);
|
|
230
|
+
if (ir.timeline && anyAnchor(ir.timeline)) {
|
|
231
|
+
const clock = /* @__PURE__ */ new Map();
|
|
232
|
+
const clockWalk = (tl, start) => {
|
|
233
|
+
let end = start;
|
|
234
|
+
switch (tl.kind) {
|
|
235
|
+
case "seq": {
|
|
236
|
+
let t = start;
|
|
237
|
+
for (const c of orderBeats(tl.children)) t = clockWalk(c, t);
|
|
238
|
+
end = t;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "par": {
|
|
242
|
+
for (const c of tl.children) end = Math.max(end, clockWalk(c, start));
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case "stagger": {
|
|
246
|
+
tl.children.forEach((c, i) => {
|
|
247
|
+
end = Math.max(end, clockWalk(c, start + i * tl.interval));
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "wait":
|
|
252
|
+
end = start + tl.duration;
|
|
253
|
+
break;
|
|
254
|
+
case "tween":
|
|
255
|
+
end = start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
256
|
+
break;
|
|
257
|
+
case "motionPath":
|
|
258
|
+
end = start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
259
|
+
break;
|
|
260
|
+
case "to": {
|
|
261
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
262
|
+
const si = tl.stagger ?? 0;
|
|
263
|
+
const targets = nodeOrder.filter((id) => id in override && (tl.filter === void 0 || tl.filter.includes(id)));
|
|
264
|
+
end = start + (tl.duration ?? DEFAULT_TO_DURATION) + Math.max(0, targets.length - 1) * si;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "beat": {
|
|
268
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
269
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
270
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
271
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
272
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
273
|
+
end = clockWalk(inner, beatStart);
|
|
274
|
+
clock.set(tl.name, { t0: beatStart, t1: end });
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if ("label" in tl && tl.label !== void 0) clock.set(tl.label, { t0: start, t1: end });
|
|
279
|
+
return end;
|
|
280
|
+
};
|
|
281
|
+
clockWalk(ir.timeline, 0);
|
|
282
|
+
labelClock = clock;
|
|
283
|
+
}
|
|
227
284
|
const walk = (tl, start) => {
|
|
228
285
|
const end = walkInner(tl, start);
|
|
229
286
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -240,7 +297,8 @@
|
|
|
240
297
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
241
298
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
242
299
|
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
243
|
-
const
|
|
300
|
+
const anchored = typeof tl.at === "string" ? labelClock?.get(tl.at)?.t0 : tl.at;
|
|
301
|
+
const beatStart = anchored !== void 0 ? anchored + (typeof tl.at === "string" ? tl.gap ?? 0 : 0) : start + (tl.gap ?? 0);
|
|
244
302
|
const end = walk(inner, beatStart);
|
|
245
303
|
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
246
304
|
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
@@ -885,7 +943,8 @@
|
|
|
885
943
|
const height = num(id, "height", node.props.height);
|
|
886
944
|
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
887
945
|
const fps = compiled2.ir.fps ?? 30;
|
|
888
|
-
const
|
|
946
|
+
const startRaw = node.props.start;
|
|
947
|
+
const start = typeof startRaw === "string" ? compiled2.labelTimes.get(startRaw)?.t0 ?? 0 : startRaw ?? 0;
|
|
889
948
|
const rate = node.props.rate ?? 1;
|
|
890
949
|
const clipStart = node.props.clipStart ?? 0;
|
|
891
950
|
const srcT = clipStart + Math.max(0, t - start) * rate;
|
package/dist/cli.js
CHANGED
|
@@ -245,11 +245,68 @@ function compileScene(ir) {
|
|
|
245
245
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
246
246
|
const natural = durationOf(grouping, 0);
|
|
247
247
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
248
|
-
const
|
|
248
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
249
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
249
250
|
return beatStart + k * natural;
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
};
|
|
254
|
+
let labelClock;
|
|
255
|
+
const anyAnchor = (tl) => tl.kind === "beat" && typeof tl.at === "string" || "children" in tl && tl.children.some(anyAnchor);
|
|
256
|
+
if (ir.timeline && anyAnchor(ir.timeline)) {
|
|
257
|
+
const clock = /* @__PURE__ */ new Map();
|
|
258
|
+
const clockWalk = (tl, start) => {
|
|
259
|
+
let end = start;
|
|
260
|
+
switch (tl.kind) {
|
|
261
|
+
case "seq": {
|
|
262
|
+
let t = start;
|
|
263
|
+
for (const c of orderBeats(tl.children)) t = clockWalk(c, t);
|
|
264
|
+
end = t;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "par": {
|
|
268
|
+
for (const c of tl.children) end = Math.max(end, clockWalk(c, start));
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "stagger": {
|
|
272
|
+
tl.children.forEach((c, i) => {
|
|
273
|
+
end = Math.max(end, clockWalk(c, start + i * tl.interval));
|
|
274
|
+
});
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "wait":
|
|
278
|
+
end = start + tl.duration;
|
|
279
|
+
break;
|
|
280
|
+
case "tween":
|
|
281
|
+
end = start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
282
|
+
break;
|
|
283
|
+
case "motionPath":
|
|
284
|
+
end = start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
285
|
+
break;
|
|
286
|
+
case "to": {
|
|
287
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
288
|
+
const si = tl.stagger ?? 0;
|
|
289
|
+
const targets = nodeOrder.filter((id) => id in override && (tl.filter === void 0 || tl.filter.includes(id)));
|
|
290
|
+
end = start + (tl.duration ?? DEFAULT_TO_DURATION) + Math.max(0, targets.length - 1) * si;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case "beat": {
|
|
294
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
295
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
296
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
297
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
298
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
299
|
+
end = clockWalk(inner, beatStart);
|
|
300
|
+
clock.set(tl.name, { t0: beatStart, t1: end });
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if ("label" in tl && tl.label !== void 0) clock.set(tl.label, { t0: start, t1: end });
|
|
305
|
+
return end;
|
|
306
|
+
};
|
|
307
|
+
clockWalk(ir.timeline, 0);
|
|
308
|
+
labelClock = clock;
|
|
309
|
+
}
|
|
253
310
|
const walk = (tl, start) => {
|
|
254
311
|
const end = walkInner(tl, start);
|
|
255
312
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -266,7 +323,8 @@ function compileScene(ir) {
|
|
|
266
323
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
267
324
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
268
325
|
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
269
|
-
const
|
|
326
|
+
const anchored = typeof tl.at === "string" ? labelClock?.get(tl.at)?.t0 : tl.at;
|
|
327
|
+
const beatStart = anchored !== void 0 ? anchored + (typeof tl.at === "string" ? tl.gap ?? 0 : 0) : start + (tl.gap ?? 0);
|
|
270
328
|
const end = walk(inner, beatStart);
|
|
271
329
|
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
272
330
|
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
@@ -586,6 +644,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
586
644
|
function validateScene(ir) {
|
|
587
645
|
const problems = [];
|
|
588
646
|
const nodeById = /* @__PURE__ */ new Map();
|
|
647
|
+
const startAnchors = [];
|
|
589
648
|
const checkPaint = (where, value) => {
|
|
590
649
|
if (typeof value !== "object" || value === null) return;
|
|
591
650
|
const g = value;
|
|
@@ -618,6 +677,7 @@ function validateScene(ir) {
|
|
|
618
677
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
619
678
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
620
679
|
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
680
|
+
if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
|
|
621
681
|
if (node.type === "group") {
|
|
622
682
|
const clip = node.props.clip;
|
|
623
683
|
if (clip) {
|
|
@@ -678,6 +738,7 @@ function validateScene(ir) {
|
|
|
678
738
|
);
|
|
679
739
|
}
|
|
680
740
|
const labels = /* @__PURE__ */ new Set();
|
|
741
|
+
const beatAnchors = [];
|
|
681
742
|
const checkEase = (path2, ease) => {
|
|
682
743
|
if (ease === void 0) return;
|
|
683
744
|
if (typeof ease === "string") {
|
|
@@ -769,6 +830,7 @@ function validateScene(ir) {
|
|
|
769
830
|
);
|
|
770
831
|
}
|
|
771
832
|
labels.add(tl.name);
|
|
833
|
+
if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path2 });
|
|
772
834
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
773
835
|
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
774
836
|
}
|
|
@@ -787,6 +849,22 @@ function validateScene(ir) {
|
|
|
787
849
|
}
|
|
788
850
|
};
|
|
789
851
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
852
|
+
for (const a of beatAnchors) {
|
|
853
|
+
if (a.at === a.name) {
|
|
854
|
+
problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
|
|
855
|
+
} else if (!labels.has(a.at)) {
|
|
856
|
+
problems.push(
|
|
857
|
+
`${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
for (const a of startAnchors) {
|
|
862
|
+
if (!labels.has(a.at)) {
|
|
863
|
+
problems.push(
|
|
864
|
+
`video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
790
868
|
for (const [i, b] of (ir.behaviors ?? []).entries()) {
|
|
791
869
|
checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
|
|
792
870
|
}
|
|
@@ -913,6 +991,13 @@ function compileComposition(comp) {
|
|
|
913
991
|
|
|
914
992
|
// ../core/src/compose.ts
|
|
915
993
|
var SCENE_PATCHABLE = ["background", "duration", "fps"];
|
|
994
|
+
var TIMELINE_PATCHABLE = {
|
|
995
|
+
to: ["duration", "ease", "stagger"],
|
|
996
|
+
tween: ["duration", "ease"],
|
|
997
|
+
wait: ["duration"],
|
|
998
|
+
motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
|
|
999
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
1000
|
+
};
|
|
916
1001
|
function composeScene(base, ...overlays) {
|
|
917
1002
|
const ir = structuredClone(base);
|
|
918
1003
|
const report = { applied: [], orphans: [], warnings: [] };
|
|
@@ -1047,13 +1132,6 @@ function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
|
|
|
1047
1132
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
1048
1133
|
};
|
|
1049
1134
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
1050
|
-
const PATCHABLE = {
|
|
1051
|
-
to: ["duration", "ease", "stagger"],
|
|
1052
|
-
tween: ["duration", "ease"],
|
|
1053
|
-
wait: ["duration"],
|
|
1054
|
-
motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
|
|
1055
|
-
beat: ["at", "gap", "scale", "duration", "order"]
|
|
1056
|
-
};
|
|
1057
1135
|
let timingPatched = false;
|
|
1058
1136
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
1059
1137
|
const step = byLabel.get(label);
|
|
@@ -1064,7 +1142,7 @@ function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
|
|
|
1064
1142
|
);
|
|
1065
1143
|
continue;
|
|
1066
1144
|
}
|
|
1067
|
-
const allowed =
|
|
1145
|
+
const allowed = TIMELINE_PATCHABLE[step.kind] ?? [];
|
|
1068
1146
|
for (const [key2, value] of Object.entries(patch)) {
|
|
1069
1147
|
if (value === void 0) continue;
|
|
1070
1148
|
if (!allowed.includes(key2)) {
|
|
@@ -1174,7 +1252,35 @@ function hash01(n, seed) {
|
|
|
1174
1252
|
}
|
|
1175
1253
|
|
|
1176
1254
|
// ../core/src/evaluate.ts
|
|
1255
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
1256
|
+
function multiply(m, n) {
|
|
1257
|
+
return [
|
|
1258
|
+
m[0] * n[0] + m[2] * n[1],
|
|
1259
|
+
m[1] * n[0] + m[3] * n[1],
|
|
1260
|
+
m[0] * n[2] + m[2] * n[3],
|
|
1261
|
+
m[1] * n[2] + m[3] * n[3],
|
|
1262
|
+
m[0] * n[4] + m[2] * n[5] + m[4],
|
|
1263
|
+
m[1] * n[4] + m[3] * n[5] + m[5]
|
|
1264
|
+
];
|
|
1265
|
+
}
|
|
1177
1266
|
var DEG = Math.PI / 180;
|
|
1267
|
+
function localMatrix(x, y, rotationDeg, scale, scaleX = 1, scaleY = 1, skewXDeg = 0, skewYDeg = 0) {
|
|
1268
|
+
const r = rotationDeg * Math.PI / 180;
|
|
1269
|
+
if (scaleX === 1 && scaleY === 1 && skewXDeg === 0 && skewYDeg === 0) {
|
|
1270
|
+
const cos = Math.cos(r) * scale;
|
|
1271
|
+
const sin = Math.sin(r) * scale;
|
|
1272
|
+
return [cos, sin, -sin, cos, x, y];
|
|
1273
|
+
}
|
|
1274
|
+
const c = Math.cos(r);
|
|
1275
|
+
const s = Math.sin(r);
|
|
1276
|
+
const tx = Math.tan(skewXDeg * Math.PI / 180);
|
|
1277
|
+
const ty = Math.tan(skewYDeg * Math.PI / 180);
|
|
1278
|
+
const R = [c, s, -s, c, 0, 0];
|
|
1279
|
+
const K = [1, ty, tx, 1, 0, 0];
|
|
1280
|
+
const S = [scale * scaleX, 0, 0, scale * scaleY, 0, 0];
|
|
1281
|
+
const m = multiply(R, multiply(K, S));
|
|
1282
|
+
return [m[0], m[1], m[2], m[3], x, y];
|
|
1283
|
+
}
|
|
1178
1284
|
function behaviorEnvelope(b, t) {
|
|
1179
1285
|
const from = b.from ?? Number.NEGATIVE_INFINITY;
|
|
1180
1286
|
const until = b.until ?? Number.POSITIVE_INFINITY;
|
|
@@ -1230,6 +1336,38 @@ function sampleProp(compiled, t, target, prop, fallback) {
|
|
|
1230
1336
|
}
|
|
1231
1337
|
return value;
|
|
1232
1338
|
}
|
|
1339
|
+
function nodeParentMatrix(compiled, id, t) {
|
|
1340
|
+
const num2 = (target, prop, fallback) => {
|
|
1341
|
+
const v = sampleProp(compiled, t, target, prop, fallback);
|
|
1342
|
+
return typeof v === "number" ? v : fallback;
|
|
1343
|
+
};
|
|
1344
|
+
let result = null;
|
|
1345
|
+
const walk = (node, parent) => {
|
|
1346
|
+
if (node.id === id) {
|
|
1347
|
+
result = parent;
|
|
1348
|
+
return true;
|
|
1349
|
+
}
|
|
1350
|
+
if (node.type === "group") {
|
|
1351
|
+
const m = multiply(
|
|
1352
|
+
parent,
|
|
1353
|
+
localMatrix(
|
|
1354
|
+
num2(node.id, "x", node.props.x),
|
|
1355
|
+
num2(node.id, "y", node.props.y),
|
|
1356
|
+
num2(node.id, "rotation", node.props.rotation ?? 0),
|
|
1357
|
+
num2(node.id, "scale", node.props.scale ?? 1),
|
|
1358
|
+
num2(node.id, "scaleX", node.props.scaleX ?? 1),
|
|
1359
|
+
num2(node.id, "scaleY", node.props.scaleY ?? 1),
|
|
1360
|
+
num2(node.id, "skewX", node.props.skewX ?? 0),
|
|
1361
|
+
num2(node.id, "skewY", node.props.skewY ?? 0)
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1364
|
+
for (const child of node.children) if (walk(child, m)) return true;
|
|
1365
|
+
}
|
|
1366
|
+
return false;
|
|
1367
|
+
};
|
|
1368
|
+
for (const node of compiled.ir.nodes) if (walk(node, IDENTITY)) break;
|
|
1369
|
+
return result;
|
|
1370
|
+
}
|
|
1233
1371
|
|
|
1234
1372
|
// ../core/src/autoFoley.ts
|
|
1235
1373
|
var V_MIN = 360;
|
|
@@ -1275,6 +1413,10 @@ function autoFoley(compiled, opts = {}) {
|
|
|
1275
1413
|
os.push(num(compiled, t, id, "opacity", node.props.opacity ?? 1));
|
|
1276
1414
|
}
|
|
1277
1415
|
const speed = (i2) => i2 <= 0 ? 0 : Math.hypot(xs[i2] - xs[i2 - 1], ys[i2] - ys[i2 - 1]) * fps;
|
|
1416
|
+
const worldX = (frame) => {
|
|
1417
|
+
const m = nodeParentMatrix(compiled, id, frame / fps);
|
|
1418
|
+
return m ? m[0] * xs[frame] + m[2] * ys[frame] + m[4] : xs[frame];
|
|
1419
|
+
};
|
|
1278
1420
|
let i = 1;
|
|
1279
1421
|
while (i <= N) {
|
|
1280
1422
|
if (speed(i) <= vMin) {
|
|
@@ -1297,19 +1439,20 @@ function autoFoley(compiled, opts = {}) {
|
|
|
1297
1439
|
if (peakV > vMin && durS >= MIN_DUR && visible) {
|
|
1298
1440
|
if (wantWhoosh) {
|
|
1299
1441
|
const quickFlick = durS < 0.25;
|
|
1300
|
-
cands.push({ t: peak / fps, sfx: quickFlick ? "swish" : "whoosh", gain: master * loud(peakV), pan: panOf(
|
|
1442
|
+
cands.push({ t: peak / fps, sfx: quickFlick ? "swish" : "whoosh", gain: master * loud(peakV), pan: panOf(worldX(peak)), rank: peakV });
|
|
1301
1443
|
}
|
|
1302
1444
|
const stopped = b >= N || speed(b + 1) < vStop && speed(Math.min(N, b + 2)) < vStop;
|
|
1303
|
-
const
|
|
1445
|
+
const wxb = worldX(b);
|
|
1446
|
+
const landsOnScreen = wxb >= 0 && wxb <= W && os[b] > 0.1;
|
|
1304
1447
|
if (wantImpact && peakV > vDecel && stopped && landsOnScreen && b < N) {
|
|
1305
|
-
cands.push({ t: (b + 1) / fps, sfx: size > 220 ? "thud" : "knock", gain: master * loud(peakV), pan: panOf(
|
|
1448
|
+
cands.push({ t: (b + 1) / fps, sfx: size > 220 ? "thud" : "knock", gain: master * loud(peakV), pan: panOf(worldX(b)), rank: peakV * 1.1 });
|
|
1306
1449
|
}
|
|
1307
1450
|
}
|
|
1308
1451
|
}
|
|
1309
1452
|
if (wantPop && ss[0] < 0.25) {
|
|
1310
1453
|
for (let k = 1; k <= N; k++) {
|
|
1311
1454
|
if (ss[k - 1] < 0.5 && ss[k] >= 0.5 && os[k] > 0.05) {
|
|
1312
|
-
cands.push({ t: k / fps, sfx: "pop", gain: master * 0.7, pan: panOf(
|
|
1455
|
+
cands.push({ t: k / fps, sfx: "pop", gain: master * 0.7, pan: panOf(worldX(k)), rank: 600 });
|
|
1313
1456
|
break;
|
|
1314
1457
|
}
|
|
1315
1458
|
}
|
|
@@ -1378,13 +1521,14 @@ var SFX_DURATION = {
|
|
|
1378
1521
|
camera: 0.18
|
|
1379
1522
|
};
|
|
1380
1523
|
var FILE_CUE_DURATION = 0.4;
|
|
1381
|
-
function collectClipAudio(ir, duration, warnings) {
|
|
1524
|
+
function collectClipAudio(ir, duration, labelTimes, warnings) {
|
|
1382
1525
|
const out = [];
|
|
1383
1526
|
const walk = (nodes) => {
|
|
1384
1527
|
for (const node of nodes) {
|
|
1385
1528
|
if (node.type === "video") {
|
|
1386
1529
|
const gain = node.props.volume ?? 1;
|
|
1387
|
-
const
|
|
1530
|
+
const startRaw = node.props.start;
|
|
1531
|
+
const start = typeof startRaw === "string" ? labelTimes.get(startRaw)?.t0 ?? 0 : startRaw ?? 0;
|
|
1388
1532
|
if (gain <= 0) continue;
|
|
1389
1533
|
if (start >= duration) {
|
|
1390
1534
|
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
@@ -1402,7 +1546,7 @@ function resolveAudioPlan(compiled) {
|
|
|
1402
1546
|
const audio = compiled.ir.audio;
|
|
1403
1547
|
const warnings = [];
|
|
1404
1548
|
const duration = compiled.duration;
|
|
1405
|
-
const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
|
|
1549
|
+
const clipAudio = collectClipAudio(compiled.ir, duration, compiled.labelTimes, warnings);
|
|
1406
1550
|
const autoCues = audio?.autoFoley ? autoFoley(compiled, audio.autoFoley === true ? {} : audio.autoFoley) : [];
|
|
1407
1551
|
const manualCues = [...audio?.cues ?? [], ...autoCues];
|
|
1408
1552
|
if (!audio || !audio.bgm && manualCues.length === 0) {
|
|
@@ -2599,7 +2743,7 @@ ${stderr.slice(-2e3)}`))
|
|
|
2599
2743
|
});
|
|
2600
2744
|
}
|
|
2601
2745
|
function neededSeconds(node, duration) {
|
|
2602
|
-
const start = node.props.start ?? 0;
|
|
2746
|
+
const start = typeof node.props.start === "string" ? 0 : node.props.start ?? 0;
|
|
2603
2747
|
const rate = node.props.rate ?? 1;
|
|
2604
2748
|
const clipStart = node.props.clipStart ?? 0;
|
|
2605
2749
|
return clipStart + Math.max(0, duration - start) * Math.max(0, rate) + 1 / 30;
|
package/dist/compile-api.js
CHANGED
|
@@ -142,6 +142,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
142
142
|
function validateScene(ir) {
|
|
143
143
|
const problems = [];
|
|
144
144
|
const nodeById = /* @__PURE__ */ new Map();
|
|
145
|
+
const startAnchors = [];
|
|
145
146
|
const checkPaint = (where, value) => {
|
|
146
147
|
if (typeof value !== "object" || value === null) return;
|
|
147
148
|
const g = value;
|
|
@@ -174,6 +175,7 @@ function validateScene(ir) {
|
|
|
174
175
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
175
176
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
176
177
|
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
178
|
+
if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
|
|
177
179
|
if (node.type === "group") {
|
|
178
180
|
const clip = node.props.clip;
|
|
179
181
|
if (clip) {
|
|
@@ -234,6 +236,7 @@ function validateScene(ir) {
|
|
|
234
236
|
);
|
|
235
237
|
}
|
|
236
238
|
const labels = /* @__PURE__ */ new Set();
|
|
239
|
+
const beatAnchors = [];
|
|
237
240
|
const checkEase = (path2, ease) => {
|
|
238
241
|
if (ease === void 0) return;
|
|
239
242
|
if (typeof ease === "string") {
|
|
@@ -325,6 +328,7 @@ function validateScene(ir) {
|
|
|
325
328
|
);
|
|
326
329
|
}
|
|
327
330
|
labels.add(tl.name);
|
|
331
|
+
if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path2 });
|
|
328
332
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
329
333
|
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
330
334
|
}
|
|
@@ -343,6 +347,22 @@ function validateScene(ir) {
|
|
|
343
347
|
}
|
|
344
348
|
};
|
|
345
349
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
350
|
+
for (const a of beatAnchors) {
|
|
351
|
+
if (a.at === a.name) {
|
|
352
|
+
problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
|
|
353
|
+
} else if (!labels.has(a.at)) {
|
|
354
|
+
problems.push(
|
|
355
|
+
`${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const a of startAnchors) {
|
|
360
|
+
if (!labels.has(a.at)) {
|
|
361
|
+
problems.push(
|
|
362
|
+
`video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
346
366
|
for (const [i, b] of (ir.behaviors ?? []).entries()) {
|
|
347
367
|
checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
|
|
348
368
|
}
|
package/dist/compile.js
CHANGED
|
@@ -147,6 +147,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
147
147
|
function validateScene(ir) {
|
|
148
148
|
const problems = [];
|
|
149
149
|
const nodeById = /* @__PURE__ */ new Map();
|
|
150
|
+
const startAnchors = [];
|
|
150
151
|
const checkPaint = (where, value) => {
|
|
151
152
|
if (typeof value !== "object" || value === null) return;
|
|
152
153
|
const g = value;
|
|
@@ -179,6 +180,7 @@ function validateScene(ir) {
|
|
|
179
180
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
180
181
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
181
182
|
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
183
|
+
if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
|
|
182
184
|
if (node.type === "group") {
|
|
183
185
|
const clip = node.props.clip;
|
|
184
186
|
if (clip) {
|
|
@@ -239,6 +241,7 @@ function validateScene(ir) {
|
|
|
239
241
|
);
|
|
240
242
|
}
|
|
241
243
|
const labels = /* @__PURE__ */ new Set();
|
|
244
|
+
const beatAnchors = [];
|
|
242
245
|
const checkEase = (path2, ease) => {
|
|
243
246
|
if (ease === void 0) return;
|
|
244
247
|
if (typeof ease === "string") {
|
|
@@ -330,6 +333,7 @@ function validateScene(ir) {
|
|
|
330
333
|
);
|
|
331
334
|
}
|
|
332
335
|
labels.add(tl.name);
|
|
336
|
+
if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path2 });
|
|
333
337
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
334
338
|
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
335
339
|
}
|
|
@@ -348,6 +352,22 @@ function validateScene(ir) {
|
|
|
348
352
|
}
|
|
349
353
|
};
|
|
350
354
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
355
|
+
for (const a of beatAnchors) {
|
|
356
|
+
if (a.at === a.name) {
|
|
357
|
+
problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
|
|
358
|
+
} else if (!labels.has(a.at)) {
|
|
359
|
+
problems.push(
|
|
360
|
+
`${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const a of startAnchors) {
|
|
365
|
+
if (!labels.has(a.at)) {
|
|
366
|
+
problems.push(
|
|
367
|
+
`video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
351
371
|
for (const [i, b] of (ir.behaviors ?? []).entries()) {
|
|
352
372
|
checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
|
|
353
373
|
}
|
package/dist/diff.js
CHANGED
|
@@ -251,11 +251,68 @@ function compileScene(ir) {
|
|
|
251
251
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
252
252
|
const natural = durationOf(grouping, 0);
|
|
253
253
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
254
|
-
const
|
|
254
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
255
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
255
256
|
return beatStart + k * natural;
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
};
|
|
260
|
+
let labelClock;
|
|
261
|
+
const anyAnchor = (tl) => tl.kind === "beat" && typeof tl.at === "string" || "children" in tl && tl.children.some(anyAnchor);
|
|
262
|
+
if (ir.timeline && anyAnchor(ir.timeline)) {
|
|
263
|
+
const clock = /* @__PURE__ */ new Map();
|
|
264
|
+
const clockWalk = (tl, start) => {
|
|
265
|
+
let end = start;
|
|
266
|
+
switch (tl.kind) {
|
|
267
|
+
case "seq": {
|
|
268
|
+
let t = start;
|
|
269
|
+
for (const c of orderBeats(tl.children)) t = clockWalk(c, t);
|
|
270
|
+
end = t;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "par": {
|
|
274
|
+
for (const c of tl.children) end = Math.max(end, clockWalk(c, start));
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "stagger": {
|
|
278
|
+
tl.children.forEach((c, i) => {
|
|
279
|
+
end = Math.max(end, clockWalk(c, start + i * tl.interval));
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "wait":
|
|
284
|
+
end = start + tl.duration;
|
|
285
|
+
break;
|
|
286
|
+
case "tween":
|
|
287
|
+
end = start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
288
|
+
break;
|
|
289
|
+
case "motionPath":
|
|
290
|
+
end = start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
291
|
+
break;
|
|
292
|
+
case "to": {
|
|
293
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
294
|
+
const si = tl.stagger ?? 0;
|
|
295
|
+
const targets = nodeOrder.filter((id) => id in override && (tl.filter === void 0 || tl.filter.includes(id)));
|
|
296
|
+
end = start + (tl.duration ?? DEFAULT_TO_DURATION) + Math.max(0, targets.length - 1) * si;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "beat": {
|
|
300
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
301
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
302
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
303
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
304
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
305
|
+
end = clockWalk(inner, beatStart);
|
|
306
|
+
clock.set(tl.name, { t0: beatStart, t1: end });
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if ("label" in tl && tl.label !== void 0) clock.set(tl.label, { t0: start, t1: end });
|
|
311
|
+
return end;
|
|
312
|
+
};
|
|
313
|
+
clockWalk(ir.timeline, 0);
|
|
314
|
+
labelClock = clock;
|
|
315
|
+
}
|
|
259
316
|
const walk = (tl, start) => {
|
|
260
317
|
const end = walkInner(tl, start);
|
|
261
318
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -272,7 +329,8 @@ function compileScene(ir) {
|
|
|
272
329
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
273
330
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
274
331
|
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
275
|
-
const
|
|
332
|
+
const anchored = typeof tl.at === "string" ? labelClock?.get(tl.at)?.t0 : tl.at;
|
|
333
|
+
const beatStart = anchored !== void 0 ? anchored + (typeof tl.at === "string" ? tl.gap ?? 0 : 0) : start + (tl.gap ?? 0);
|
|
276
334
|
const end = walk(inner, beatStart);
|
|
277
335
|
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
278
336
|
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
@@ -473,6 +531,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
473
531
|
function validateScene(ir) {
|
|
474
532
|
const problems = [];
|
|
475
533
|
const nodeById = /* @__PURE__ */ new Map();
|
|
534
|
+
const startAnchors = [];
|
|
476
535
|
const checkPaint = (where, value) => {
|
|
477
536
|
if (typeof value !== "object" || value === null) return;
|
|
478
537
|
const g = value;
|
|
@@ -505,6 +564,7 @@ function validateScene(ir) {
|
|
|
505
564
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
506
565
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
507
566
|
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
567
|
+
if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
|
|
508
568
|
if (node.type === "group") {
|
|
509
569
|
const clip = node.props.clip;
|
|
510
570
|
if (clip) {
|
|
@@ -565,6 +625,7 @@ function validateScene(ir) {
|
|
|
565
625
|
);
|
|
566
626
|
}
|
|
567
627
|
const labels = /* @__PURE__ */ new Set();
|
|
628
|
+
const beatAnchors = [];
|
|
568
629
|
const checkEase = (path2, ease) => {
|
|
569
630
|
if (ease === void 0) return;
|
|
570
631
|
if (typeof ease === "string") {
|
|
@@ -656,6 +717,7 @@ function validateScene(ir) {
|
|
|
656
717
|
);
|
|
657
718
|
}
|
|
658
719
|
labels.add(tl.name);
|
|
720
|
+
if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path2 });
|
|
659
721
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
660
722
|
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
661
723
|
}
|
|
@@ -674,6 +736,22 @@ function validateScene(ir) {
|
|
|
674
736
|
}
|
|
675
737
|
};
|
|
676
738
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
739
|
+
for (const a of beatAnchors) {
|
|
740
|
+
if (a.at === a.name) {
|
|
741
|
+
problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
|
|
742
|
+
} else if (!labels.has(a.at)) {
|
|
743
|
+
problems.push(
|
|
744
|
+
`${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
for (const a of startAnchors) {
|
|
749
|
+
if (!labels.has(a.at)) {
|
|
750
|
+
problems.push(
|
|
751
|
+
`video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
677
755
|
for (const [i, b] of (ir.behaviors ?? []).entries()) {
|
|
678
756
|
checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
|
|
679
757
|
}
|
|
@@ -980,7 +1058,7 @@ ${stderr.slice(-2e3)}`))
|
|
|
980
1058
|
});
|
|
981
1059
|
}
|
|
982
1060
|
function neededSeconds(node, duration) {
|
|
983
|
-
const start = node.props.start ?? 0;
|
|
1061
|
+
const start = typeof node.props.start === "string" ? 0 : node.props.start ?? 0;
|
|
984
1062
|
const rate = node.props.rate ?? 1;
|
|
985
1063
|
const clipStart = node.props.clipStart ?? 0;
|
|
986
1064
|
return clipStart + Math.max(0, duration - start) * Math.max(0, rate) + 1 / 30;
|