reframe-video 0.6.34 → 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 +178 -31
- package/dist/browserEntry.js +62 -3
- package/dist/cli.js +93 -14
- 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 +1528 -1107
- 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/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)) {
|
|
@@ -1443,13 +1521,14 @@ var SFX_DURATION = {
|
|
|
1443
1521
|
camera: 0.18
|
|
1444
1522
|
};
|
|
1445
1523
|
var FILE_CUE_DURATION = 0.4;
|
|
1446
|
-
function collectClipAudio(ir, duration, warnings) {
|
|
1524
|
+
function collectClipAudio(ir, duration, labelTimes, warnings) {
|
|
1447
1525
|
const out = [];
|
|
1448
1526
|
const walk = (nodes) => {
|
|
1449
1527
|
for (const node of nodes) {
|
|
1450
1528
|
if (node.type === "video") {
|
|
1451
1529
|
const gain = node.props.volume ?? 1;
|
|
1452
|
-
const
|
|
1530
|
+
const startRaw = node.props.start;
|
|
1531
|
+
const start = typeof startRaw === "string" ? labelTimes.get(startRaw)?.t0 ?? 0 : startRaw ?? 0;
|
|
1453
1532
|
if (gain <= 0) continue;
|
|
1454
1533
|
if (start >= duration) {
|
|
1455
1534
|
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
@@ -1467,7 +1546,7 @@ function resolveAudioPlan(compiled) {
|
|
|
1467
1546
|
const audio = compiled.ir.audio;
|
|
1468
1547
|
const warnings = [];
|
|
1469
1548
|
const duration = compiled.duration;
|
|
1470
|
-
const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
|
|
1549
|
+
const clipAudio = collectClipAudio(compiled.ir, duration, compiled.labelTimes, warnings);
|
|
1471
1550
|
const autoCues = audio?.autoFoley ? autoFoley(compiled, audio.autoFoley === true ? {} : audio.autoFoley) : [];
|
|
1472
1551
|
const manualCues = [...audio?.cues ?? [], ...autoCues];
|
|
1473
1552
|
if (!audio || !audio.bgm && manualCues.length === 0) {
|
|
@@ -2664,7 +2743,7 @@ ${stderr.slice(-2e3)}`))
|
|
|
2664
2743
|
});
|
|
2665
2744
|
}
|
|
2666
2745
|
function neededSeconds(node, duration) {
|
|
2667
|
-
const start = node.props.start ?? 0;
|
|
2746
|
+
const start = typeof node.props.start === "string" ? 0 : node.props.start ?? 0;
|
|
2668
2747
|
const rate = node.props.rate ?? 1;
|
|
2669
2748
|
const clipStart = node.props.clipStart ?? 0;
|
|
2670
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;
|
package/dist/frame.js
CHANGED
|
@@ -278,11 +278,68 @@ function compileScene(ir) {
|
|
|
278
278
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
279
279
|
const natural = durationOf(grouping, 0);
|
|
280
280
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
281
|
-
const
|
|
281
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
282
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
282
283
|
return beatStart + k * natural;
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
};
|
|
287
|
+
let labelClock;
|
|
288
|
+
const anyAnchor = (tl) => tl.kind === "beat" && typeof tl.at === "string" || "children" in tl && tl.children.some(anyAnchor);
|
|
289
|
+
if (ir.timeline && anyAnchor(ir.timeline)) {
|
|
290
|
+
const clock = /* @__PURE__ */ new Map();
|
|
291
|
+
const clockWalk = (tl, start) => {
|
|
292
|
+
let end = start;
|
|
293
|
+
switch (tl.kind) {
|
|
294
|
+
case "seq": {
|
|
295
|
+
let t = start;
|
|
296
|
+
for (const c of orderBeats(tl.children)) t = clockWalk(c, t);
|
|
297
|
+
end = t;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "par": {
|
|
301
|
+
for (const c of tl.children) end = Math.max(end, clockWalk(c, start));
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "stagger": {
|
|
305
|
+
tl.children.forEach((c, i) => {
|
|
306
|
+
end = Math.max(end, clockWalk(c, start + i * tl.interval));
|
|
307
|
+
});
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "wait":
|
|
311
|
+
end = start + tl.duration;
|
|
312
|
+
break;
|
|
313
|
+
case "tween":
|
|
314
|
+
end = start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
315
|
+
break;
|
|
316
|
+
case "motionPath":
|
|
317
|
+
end = start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
318
|
+
break;
|
|
319
|
+
case "to": {
|
|
320
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
321
|
+
const si = tl.stagger ?? 0;
|
|
322
|
+
const targets = nodeOrder.filter((id) => id in override && (tl.filter === void 0 || tl.filter.includes(id)));
|
|
323
|
+
end = start + (tl.duration ?? DEFAULT_TO_DURATION) + Math.max(0, targets.length - 1) * si;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case "beat": {
|
|
327
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
328
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
329
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
330
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
331
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
332
|
+
end = clockWalk(inner, beatStart);
|
|
333
|
+
clock.set(tl.name, { t0: beatStart, t1: end });
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if ("label" in tl && tl.label !== void 0) clock.set(tl.label, { t0: start, t1: end });
|
|
338
|
+
return end;
|
|
339
|
+
};
|
|
340
|
+
clockWalk(ir.timeline, 0);
|
|
341
|
+
labelClock = clock;
|
|
342
|
+
}
|
|
286
343
|
const walk = (tl, start) => {
|
|
287
344
|
const end = walkInner(tl, start);
|
|
288
345
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -299,7 +356,8 @@ function compileScene(ir) {
|
|
|
299
356
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
300
357
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
301
358
|
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
302
|
-
const
|
|
359
|
+
const anchored = typeof tl.at === "string" ? labelClock?.get(tl.at)?.t0 : tl.at;
|
|
360
|
+
const beatStart = anchored !== void 0 ? anchored + (typeof tl.at === "string" ? tl.gap ?? 0 : 0) : start + (tl.gap ?? 0);
|
|
303
361
|
const end = walk(inner, beatStart);
|
|
304
362
|
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
305
363
|
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
@@ -500,6 +558,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
500
558
|
function validateScene(ir) {
|
|
501
559
|
const problems = [];
|
|
502
560
|
const nodeById = /* @__PURE__ */ new Map();
|
|
561
|
+
const startAnchors = [];
|
|
503
562
|
const checkPaint = (where, value) => {
|
|
504
563
|
if (typeof value !== "object" || value === null) return;
|
|
505
564
|
const g = value;
|
|
@@ -532,6 +591,7 @@ function validateScene(ir) {
|
|
|
532
591
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
533
592
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
534
593
|
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
594
|
+
if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
|
|
535
595
|
if (node.type === "group") {
|
|
536
596
|
const clip = node.props.clip;
|
|
537
597
|
if (clip) {
|
|
@@ -592,6 +652,7 @@ function validateScene(ir) {
|
|
|
592
652
|
);
|
|
593
653
|
}
|
|
594
654
|
const labels = /* @__PURE__ */ new Set();
|
|
655
|
+
const beatAnchors = [];
|
|
595
656
|
const checkEase = (path2, ease) => {
|
|
596
657
|
if (ease === void 0) return;
|
|
597
658
|
if (typeof ease === "string") {
|
|
@@ -683,6 +744,7 @@ function validateScene(ir) {
|
|
|
683
744
|
);
|
|
684
745
|
}
|
|
685
746
|
labels.add(tl.name);
|
|
747
|
+
if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path2 });
|
|
686
748
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
687
749
|
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
688
750
|
}
|
|
@@ -701,6 +763,22 @@ function validateScene(ir) {
|
|
|
701
763
|
}
|
|
702
764
|
};
|
|
703
765
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
766
|
+
for (const a of beatAnchors) {
|
|
767
|
+
if (a.at === a.name) {
|
|
768
|
+
problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
|
|
769
|
+
} else if (!labels.has(a.at)) {
|
|
770
|
+
problems.push(
|
|
771
|
+
`${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
for (const a of startAnchors) {
|
|
776
|
+
if (!labels.has(a.at)) {
|
|
777
|
+
problems.push(
|
|
778
|
+
`video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
704
782
|
for (const [i, b] of (ir.behaviors ?? []).entries()) {
|
|
705
783
|
checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
|
|
706
784
|
}
|
|
@@ -891,7 +969,7 @@ ${stderr.slice(-2e3)}`))
|
|
|
891
969
|
});
|
|
892
970
|
}
|
|
893
971
|
function neededSeconds(node, duration) {
|
|
894
|
-
const start = node.props.start ?? 0;
|
|
972
|
+
const start = typeof node.props.start === "string" ? 0 : node.props.start ?? 0;
|
|
895
973
|
const rate = node.props.rate ?? 1;
|
|
896
974
|
const clipStart = node.props.clipStart ?? 0;
|
|
897
975
|
return clipStart + Math.max(0, duration - start) * Math.max(0, rate) + 1 / 30;
|