reframe-video 0.6.15 → 0.6.17
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/bin.js +12 -4
- package/dist/browserEntry.js +32 -9
- package/dist/cli.js +12 -4
- package/dist/diff.js +12 -4
- package/dist/index.js +39 -12
- package/dist/labels.js +12 -4
- package/dist/types/compile.d.ts +2 -0
- package/dist/types/ir.d.ts +19 -0
- package/guides/edsl-guide.md +14 -2
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -162,6 +162,8 @@ function compileScene(ir) {
|
|
|
162
162
|
initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
|
|
163
163
|
initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
|
|
164
164
|
if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
|
|
165
|
+
if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
|
|
166
|
+
if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
|
|
165
167
|
}
|
|
166
168
|
const segments = /* @__PURE__ */ new Map();
|
|
167
169
|
const motionPaths = /* @__PURE__ */ new Map();
|
|
@@ -325,6 +327,7 @@ function compileScene(ir) {
|
|
|
325
327
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
326
328
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
327
329
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
330
|
+
const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
|
|
328
331
|
return {
|
|
329
332
|
ir,
|
|
330
333
|
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
@@ -336,7 +339,8 @@ function compileScene(ir) {
|
|
|
336
339
|
labelTimes,
|
|
337
340
|
beatTimes,
|
|
338
341
|
hasCamera,
|
|
339
|
-
hasPerspective
|
|
342
|
+
hasPerspective,
|
|
343
|
+
zSort
|
|
340
344
|
};
|
|
341
345
|
}
|
|
342
346
|
var key;
|
|
@@ -542,12 +546,16 @@ function validateScene(ir) {
|
|
|
542
546
|
problems.push(`camera: a node is already named "camera" \u2014 rename that node or drop the scene camera (the id "camera" can't be both)`);
|
|
543
547
|
}
|
|
544
548
|
for (const [key2, value] of Object.entries(ir.camera)) {
|
|
545
|
-
if (
|
|
546
|
-
problems.push(`camera
|
|
549
|
+
if (key2 === "zSort") {
|
|
550
|
+
if (typeof value !== "boolean") problems.push(`camera.zSort must be a boolean`);
|
|
551
|
+
} else if (!CAMERA_PROPS.includes(key2)) {
|
|
552
|
+
problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}, zSort`);
|
|
547
553
|
} else if (typeof value !== "number") {
|
|
548
554
|
problems.push(`camera.${key2} must be a number`);
|
|
549
555
|
} else if (key2 === "perspective" && value <= 0) {
|
|
550
556
|
problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
|
|
557
|
+
} else if (key2 === "aperture" && value < 0) {
|
|
558
|
+
problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
|
|
551
559
|
}
|
|
552
560
|
}
|
|
553
561
|
}
|
|
@@ -629,7 +637,7 @@ var init_validate = __esm({
|
|
|
629
637
|
]);
|
|
630
638
|
IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
|
|
631
639
|
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "z", "rotateX", "rotateY", "anchor", "fixed", ...FX_PROPS];
|
|
632
|
-
CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective"];
|
|
640
|
+
CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
|
|
633
641
|
PROPS_BY_TYPE = {
|
|
634
642
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
635
643
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
package/dist/browserEntry.js
CHANGED
|
@@ -159,6 +159,8 @@
|
|
|
159
159
|
initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
|
|
160
160
|
initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
|
|
161
161
|
if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
|
|
162
|
+
if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
|
|
163
|
+
if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
|
|
162
164
|
}
|
|
163
165
|
const segments = /* @__PURE__ */ new Map();
|
|
164
166
|
const motionPaths = /* @__PURE__ */ new Map();
|
|
@@ -322,6 +324,7 @@
|
|
|
322
324
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
323
325
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
324
326
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
327
|
+
const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
|
|
325
328
|
return {
|
|
326
329
|
ir,
|
|
327
330
|
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
@@ -333,7 +336,8 @@
|
|
|
333
336
|
labelTimes,
|
|
334
337
|
beatTimes,
|
|
335
338
|
hasCamera,
|
|
336
|
-
hasPerspective
|
|
339
|
+
hasPerspective,
|
|
340
|
+
zSort
|
|
337
341
|
};
|
|
338
342
|
}
|
|
339
343
|
|
|
@@ -740,6 +744,17 @@
|
|
|
740
744
|
const dPersp = persp ? num("camera", "perspective", 0) : 0;
|
|
741
745
|
const vx = persp ? compiled2.ir.size.width / 2 : 0;
|
|
742
746
|
const vy = persp ? compiled2.ir.size.height / 2 : 0;
|
|
747
|
+
const aperture = persp ? num("camera", "aperture", 0) : 0;
|
|
748
|
+
const focus = persp ? num("camera", "focus", 0) : 0;
|
|
749
|
+
const dofFx = (fx, depth, project) => {
|
|
750
|
+
if (!project || aperture <= 0) return fx;
|
|
751
|
+
const extra = aperture * Math.abs(depth - focus);
|
|
752
|
+
if (extra <= 0) return fx;
|
|
753
|
+
return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
|
|
754
|
+
};
|
|
755
|
+
const zSort = compiled2.zSort;
|
|
756
|
+
const depthOf = (node, zAcc) => zAcc + num(node.id, "z", node.props.z ?? 0);
|
|
757
|
+
const depthOrder = (children, zAcc) => [...children].sort((a, b) => depthOf(b, zAcc) - depthOf(a, zAcc));
|
|
743
758
|
const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
|
|
744
759
|
const id = node.id;
|
|
745
760
|
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
@@ -762,7 +777,8 @@
|
|
|
762
777
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
763
778
|
stroke: str(id, "stroke", node.props.stroke),
|
|
764
779
|
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
765
|
-
|
|
780
|
+
// a line carries no z of its own — DOF uses the inherited subtree depth
|
|
781
|
+
...dofFx(fx, zAcc, project),
|
|
766
782
|
...clipSpread
|
|
767
783
|
});
|
|
768
784
|
return;
|
|
@@ -799,6 +815,7 @@
|
|
|
799
815
|
const tilted = rotX !== 0 || rotY !== 0 ? tiltSkew(m, rotX, rotY, hw, hh, dPersp) : m;
|
|
800
816
|
return projectDepth(tilted, depth, vx, vy, dPersp);
|
|
801
817
|
};
|
|
818
|
+
const leafFx = dofFx(fx, depth, project);
|
|
802
819
|
switch (node.type) {
|
|
803
820
|
case "group": {
|
|
804
821
|
const clipTf = projDraw(matrix, 0, 0);
|
|
@@ -812,7 +829,8 @@
|
|
|
812
829
|
for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips, depth, project);
|
|
813
830
|
ops.push({ type: "matte-pop", id, transform: matrix, opacity });
|
|
814
831
|
} else {
|
|
815
|
-
|
|
832
|
+
const kids = zSort && project ? depthOrder(node.children, depth) : node.children;
|
|
833
|
+
for (const child of kids) walk(child, matrix, opacity, childClips, depth, project);
|
|
816
834
|
}
|
|
817
835
|
if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
|
|
818
836
|
return;
|
|
@@ -839,7 +857,7 @@
|
|
|
839
857
|
...fill !== void 0 && { fill },
|
|
840
858
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
841
859
|
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
842
|
-
...
|
|
860
|
+
...leafFx,
|
|
843
861
|
...clipSpread
|
|
844
862
|
});
|
|
845
863
|
return;
|
|
@@ -859,7 +877,7 @@
|
|
|
859
877
|
offsetX: -width * ax,
|
|
860
878
|
offsetY: -height * ay,
|
|
861
879
|
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
862
|
-
...
|
|
880
|
+
...leafFx,
|
|
863
881
|
...clipSpread
|
|
864
882
|
});
|
|
865
883
|
return;
|
|
@@ -886,7 +904,7 @@
|
|
|
886
904
|
offsetY: -height * ay,
|
|
887
905
|
frame,
|
|
888
906
|
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
889
|
-
...
|
|
907
|
+
...leafFx,
|
|
890
908
|
...clipSpread
|
|
891
909
|
});
|
|
892
910
|
return;
|
|
@@ -911,7 +929,7 @@
|
|
|
911
929
|
...fill !== void 0 && { fill },
|
|
912
930
|
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
913
931
|
...needsBox && { bbox: pathBBox(dStr) },
|
|
914
|
-
...
|
|
932
|
+
...leafFx,
|
|
915
933
|
...clipSpread
|
|
916
934
|
});
|
|
917
935
|
return;
|
|
@@ -938,7 +956,7 @@
|
|
|
938
956
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
939
957
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
940
958
|
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
941
|
-
...
|
|
959
|
+
...leafFx,
|
|
942
960
|
...clipSpread
|
|
943
961
|
});
|
|
944
962
|
return;
|
|
@@ -954,7 +972,12 @@
|
|
|
954
972
|
},
|
|
955
973
|
compiled2.ir.size
|
|
956
974
|
) : IDENTITY;
|
|
957
|
-
|
|
975
|
+
let roots = compiled2.ir.nodes;
|
|
976
|
+
if (zSort) {
|
|
977
|
+
const isHud = (n) => !!(n.props.fixed && compiled2.hasCamera);
|
|
978
|
+
roots = [...depthOrder(compiled2.ir.nodes.filter((n) => !isHud(n)), 0), ...compiled2.ir.nodes.filter(isHud)];
|
|
979
|
+
}
|
|
980
|
+
for (const node of roots) {
|
|
958
981
|
const root = compiled2.hasCamera && node.props.fixed ? IDENTITY : cameraRoot;
|
|
959
982
|
const project = persp && !(node.props.fixed && compiled2.hasCamera);
|
|
960
983
|
walk(node, root, 1, [], 0, project);
|
package/dist/cli.js
CHANGED
|
@@ -149,6 +149,8 @@ function compileScene(ir) {
|
|
|
149
149
|
initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
|
|
150
150
|
initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
|
|
151
151
|
if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
|
|
152
|
+
if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
|
|
153
|
+
if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
|
|
152
154
|
}
|
|
153
155
|
const segments = /* @__PURE__ */ new Map();
|
|
154
156
|
const motionPaths = /* @__PURE__ */ new Map();
|
|
@@ -312,6 +314,7 @@ function compileScene(ir) {
|
|
|
312
314
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
313
315
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
314
316
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
317
|
+
const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
|
|
315
318
|
return {
|
|
316
319
|
ir,
|
|
317
320
|
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
@@ -323,7 +326,8 @@ function compileScene(ir) {
|
|
|
323
326
|
labelTimes,
|
|
324
327
|
beatTimes,
|
|
325
328
|
hasCamera,
|
|
326
|
-
hasPerspective
|
|
329
|
+
hasPerspective,
|
|
330
|
+
zSort
|
|
327
331
|
};
|
|
328
332
|
}
|
|
329
333
|
|
|
@@ -344,7 +348,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
|
344
348
|
]);
|
|
345
349
|
var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
|
|
346
350
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "z", "rotateX", "rotateY", "anchor", "fixed", ...FX_PROPS];
|
|
347
|
-
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective"];
|
|
351
|
+
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
|
|
348
352
|
var PROPS_BY_TYPE = {
|
|
349
353
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
350
354
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
@@ -556,12 +560,16 @@ function validateScene(ir) {
|
|
|
556
560
|
problems.push(`camera: a node is already named "camera" \u2014 rename that node or drop the scene camera (the id "camera" can't be both)`);
|
|
557
561
|
}
|
|
558
562
|
for (const [key2, value] of Object.entries(ir.camera)) {
|
|
559
|
-
if (
|
|
560
|
-
problems.push(`camera
|
|
563
|
+
if (key2 === "zSort") {
|
|
564
|
+
if (typeof value !== "boolean") problems.push(`camera.zSort must be a boolean`);
|
|
565
|
+
} else if (!CAMERA_PROPS.includes(key2)) {
|
|
566
|
+
problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}, zSort`);
|
|
561
567
|
} else if (typeof value !== "number") {
|
|
562
568
|
problems.push(`camera.${key2} must be a number`);
|
|
563
569
|
} else if (key2 === "perspective" && value <= 0) {
|
|
564
570
|
problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
|
|
571
|
+
} else if (key2 === "aperture" && value < 0) {
|
|
572
|
+
problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
|
|
565
573
|
}
|
|
566
574
|
}
|
|
567
575
|
}
|
package/dist/diff.js
CHANGED
|
@@ -155,6 +155,8 @@ function compileScene(ir) {
|
|
|
155
155
|
initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
|
|
156
156
|
initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
|
|
157
157
|
if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
|
|
158
|
+
if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
|
|
159
|
+
if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
|
|
158
160
|
}
|
|
159
161
|
const segments = /* @__PURE__ */ new Map();
|
|
160
162
|
const motionPaths = /* @__PURE__ */ new Map();
|
|
@@ -318,6 +320,7 @@ function compileScene(ir) {
|
|
|
318
320
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
319
321
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
320
322
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
323
|
+
const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
|
|
321
324
|
return {
|
|
322
325
|
ir,
|
|
323
326
|
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
@@ -329,7 +332,8 @@ function compileScene(ir) {
|
|
|
329
332
|
labelTimes,
|
|
330
333
|
beatTimes,
|
|
331
334
|
hasCamera,
|
|
332
|
-
hasPerspective
|
|
335
|
+
hasPerspective,
|
|
336
|
+
zSort
|
|
333
337
|
};
|
|
334
338
|
}
|
|
335
339
|
|
|
@@ -350,7 +354,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
|
350
354
|
]);
|
|
351
355
|
var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
|
|
352
356
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "z", "rotateX", "rotateY", "anchor", "fixed", ...FX_PROPS];
|
|
353
|
-
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective"];
|
|
357
|
+
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
|
|
354
358
|
var PROPS_BY_TYPE = {
|
|
355
359
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
356
360
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
@@ -562,12 +566,16 @@ function validateScene(ir) {
|
|
|
562
566
|
problems.push(`camera: a node is already named "camera" \u2014 rename that node or drop the scene camera (the id "camera" can't be both)`);
|
|
563
567
|
}
|
|
564
568
|
for (const [key2, value] of Object.entries(ir.camera)) {
|
|
565
|
-
if (
|
|
566
|
-
problems.push(`camera
|
|
569
|
+
if (key2 === "zSort") {
|
|
570
|
+
if (typeof value !== "boolean") problems.push(`camera.zSort must be a boolean`);
|
|
571
|
+
} else if (!CAMERA_PROPS.includes(key2)) {
|
|
572
|
+
problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}, zSort`);
|
|
567
573
|
} else if (typeof value !== "number") {
|
|
568
574
|
problems.push(`camera.${key2} must be a number`);
|
|
569
575
|
} else if (key2 === "perspective" && value <= 0) {
|
|
570
576
|
problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
|
|
577
|
+
} else if (key2 === "aperture" && value < 0) {
|
|
578
|
+
problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
|
|
571
579
|
}
|
|
572
580
|
}
|
|
573
581
|
}
|
package/dist/index.js
CHANGED
|
@@ -159,6 +159,8 @@ function compileScene(ir) {
|
|
|
159
159
|
initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
|
|
160
160
|
initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
|
|
161
161
|
if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
|
|
162
|
+
if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
|
|
163
|
+
if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
|
|
162
164
|
}
|
|
163
165
|
const segments = /* @__PURE__ */ new Map();
|
|
164
166
|
const motionPaths = /* @__PURE__ */ new Map();
|
|
@@ -322,6 +324,7 @@ function compileScene(ir) {
|
|
|
322
324
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
323
325
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
324
326
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
327
|
+
const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
|
|
325
328
|
return {
|
|
326
329
|
ir,
|
|
327
330
|
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
@@ -333,7 +336,8 @@ function compileScene(ir) {
|
|
|
333
336
|
labelTimes,
|
|
334
337
|
beatTimes,
|
|
335
338
|
hasCamera,
|
|
336
|
-
hasPerspective
|
|
339
|
+
hasPerspective,
|
|
340
|
+
zSort
|
|
337
341
|
};
|
|
338
342
|
}
|
|
339
343
|
|
|
@@ -354,7 +358,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
|
354
358
|
]);
|
|
355
359
|
var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
|
|
356
360
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "z", "rotateX", "rotateY", "anchor", "fixed", ...FX_PROPS];
|
|
357
|
-
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective"];
|
|
361
|
+
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
|
|
358
362
|
var PROPS_BY_TYPE = {
|
|
359
363
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
360
364
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
@@ -566,12 +570,16 @@ function validateScene(ir) {
|
|
|
566
570
|
problems.push(`camera: a node is already named "camera" \u2014 rename that node or drop the scene camera (the id "camera" can't be both)`);
|
|
567
571
|
}
|
|
568
572
|
for (const [key2, value] of Object.entries(ir.camera)) {
|
|
569
|
-
if (
|
|
570
|
-
problems.push(`camera
|
|
573
|
+
if (key2 === "zSort") {
|
|
574
|
+
if (typeof value !== "boolean") problems.push(`camera.zSort must be a boolean`);
|
|
575
|
+
} else if (!CAMERA_PROPS.includes(key2)) {
|
|
576
|
+
problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}, zSort`);
|
|
571
577
|
} else if (typeof value !== "number") {
|
|
572
578
|
problems.push(`camera.${key2} must be a number`);
|
|
573
579
|
} else if (key2 === "perspective" && value <= 0) {
|
|
574
580
|
problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
|
|
581
|
+
} else if (key2 === "aperture" && value < 0) {
|
|
582
|
+
problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
|
|
575
583
|
}
|
|
576
584
|
}
|
|
577
585
|
}
|
|
@@ -3214,6 +3222,17 @@ function evaluate(compiled, t) {
|
|
|
3214
3222
|
const dPersp = persp ? num("camera", "perspective", 0) : 0;
|
|
3215
3223
|
const vx = persp ? compiled.ir.size.width / 2 : 0;
|
|
3216
3224
|
const vy = persp ? compiled.ir.size.height / 2 : 0;
|
|
3225
|
+
const aperture = persp ? num("camera", "aperture", 0) : 0;
|
|
3226
|
+
const focus = persp ? num("camera", "focus", 0) : 0;
|
|
3227
|
+
const dofFx = (fx, depth, project) => {
|
|
3228
|
+
if (!project || aperture <= 0) return fx;
|
|
3229
|
+
const extra = aperture * Math.abs(depth - focus);
|
|
3230
|
+
if (extra <= 0) return fx;
|
|
3231
|
+
return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
|
|
3232
|
+
};
|
|
3233
|
+
const zSort = compiled.zSort;
|
|
3234
|
+
const depthOf = (node, zAcc) => zAcc + num(node.id, "z", node.props.z ?? 0);
|
|
3235
|
+
const depthOrder = (children, zAcc) => [...children].sort((a, b) => depthOf(b, zAcc) - depthOf(a, zAcc));
|
|
3217
3236
|
const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
|
|
3218
3237
|
const id = node.id;
|
|
3219
3238
|
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
@@ -3236,7 +3255,8 @@ function evaluate(compiled, t) {
|
|
|
3236
3255
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
3237
3256
|
stroke: str(id, "stroke", node.props.stroke),
|
|
3238
3257
|
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
3239
|
-
|
|
3258
|
+
// a line carries no z of its own — DOF uses the inherited subtree depth
|
|
3259
|
+
...dofFx(fx, zAcc, project),
|
|
3240
3260
|
...clipSpread
|
|
3241
3261
|
});
|
|
3242
3262
|
return;
|
|
@@ -3273,6 +3293,7 @@ function evaluate(compiled, t) {
|
|
|
3273
3293
|
const tilted = rotX !== 0 || rotY !== 0 ? tiltSkew(m, rotX, rotY, hw, hh, dPersp) : m;
|
|
3274
3294
|
return projectDepth(tilted, depth, vx, vy, dPersp);
|
|
3275
3295
|
};
|
|
3296
|
+
const leafFx = dofFx(fx, depth, project);
|
|
3276
3297
|
switch (node.type) {
|
|
3277
3298
|
case "group": {
|
|
3278
3299
|
const clipTf = projDraw(matrix, 0, 0);
|
|
@@ -3286,7 +3307,8 @@ function evaluate(compiled, t) {
|
|
|
3286
3307
|
for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips, depth, project);
|
|
3287
3308
|
ops.push({ type: "matte-pop", id, transform: matrix, opacity });
|
|
3288
3309
|
} else {
|
|
3289
|
-
|
|
3310
|
+
const kids = zSort && project ? depthOrder(node.children, depth) : node.children;
|
|
3311
|
+
for (const child of kids) walk(child, matrix, opacity, childClips, depth, project);
|
|
3290
3312
|
}
|
|
3291
3313
|
if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
|
|
3292
3314
|
return;
|
|
@@ -3313,7 +3335,7 @@ function evaluate(compiled, t) {
|
|
|
3313
3335
|
...fill !== void 0 && { fill },
|
|
3314
3336
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
3315
3337
|
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
3316
|
-
...
|
|
3338
|
+
...leafFx,
|
|
3317
3339
|
...clipSpread
|
|
3318
3340
|
});
|
|
3319
3341
|
return;
|
|
@@ -3333,7 +3355,7 @@ function evaluate(compiled, t) {
|
|
|
3333
3355
|
offsetX: -width * ax,
|
|
3334
3356
|
offsetY: -height * ay,
|
|
3335
3357
|
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
3336
|
-
...
|
|
3358
|
+
...leafFx,
|
|
3337
3359
|
...clipSpread
|
|
3338
3360
|
});
|
|
3339
3361
|
return;
|
|
@@ -3360,7 +3382,7 @@ function evaluate(compiled, t) {
|
|
|
3360
3382
|
offsetY: -height * ay,
|
|
3361
3383
|
frame,
|
|
3362
3384
|
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
3363
|
-
...
|
|
3385
|
+
...leafFx,
|
|
3364
3386
|
...clipSpread
|
|
3365
3387
|
});
|
|
3366
3388
|
return;
|
|
@@ -3385,7 +3407,7 @@ function evaluate(compiled, t) {
|
|
|
3385
3407
|
...fill !== void 0 && { fill },
|
|
3386
3408
|
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
3387
3409
|
...needsBox && { bbox: pathBBox(dStr) },
|
|
3388
|
-
...
|
|
3410
|
+
...leafFx,
|
|
3389
3411
|
...clipSpread
|
|
3390
3412
|
});
|
|
3391
3413
|
return;
|
|
@@ -3412,7 +3434,7 @@ function evaluate(compiled, t) {
|
|
|
3412
3434
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
3413
3435
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
3414
3436
|
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
3415
|
-
...
|
|
3437
|
+
...leafFx,
|
|
3416
3438
|
...clipSpread
|
|
3417
3439
|
});
|
|
3418
3440
|
return;
|
|
@@ -3428,7 +3450,12 @@ function evaluate(compiled, t) {
|
|
|
3428
3450
|
},
|
|
3429
3451
|
compiled.ir.size
|
|
3430
3452
|
) : IDENTITY;
|
|
3431
|
-
|
|
3453
|
+
let roots = compiled.ir.nodes;
|
|
3454
|
+
if (zSort) {
|
|
3455
|
+
const isHud = (n3) => !!(n3.props.fixed && compiled.hasCamera);
|
|
3456
|
+
roots = [...depthOrder(compiled.ir.nodes.filter((n3) => !isHud(n3)), 0), ...compiled.ir.nodes.filter(isHud)];
|
|
3457
|
+
}
|
|
3458
|
+
for (const node of roots) {
|
|
3432
3459
|
const root = compiled.hasCamera && node.props.fixed ? IDENTITY : cameraRoot;
|
|
3433
3460
|
const project = persp && !(node.props.fixed && compiled.hasCamera);
|
|
3434
3461
|
walk(node, root, 1, [], 0, project);
|
package/dist/labels.js
CHANGED
|
@@ -143,6 +143,8 @@ function compileScene(ir) {
|
|
|
143
143
|
initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
|
|
144
144
|
initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
|
|
145
145
|
if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
|
|
146
|
+
if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
|
|
147
|
+
if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
|
|
146
148
|
}
|
|
147
149
|
const segments = /* @__PURE__ */ new Map();
|
|
148
150
|
const motionPaths = /* @__PURE__ */ new Map();
|
|
@@ -306,6 +308,7 @@ function compileScene(ir) {
|
|
|
306
308
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
307
309
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
308
310
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
311
|
+
const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
|
|
309
312
|
return {
|
|
310
313
|
ir,
|
|
311
314
|
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
@@ -317,7 +320,8 @@ function compileScene(ir) {
|
|
|
317
320
|
labelTimes,
|
|
318
321
|
beatTimes,
|
|
319
322
|
hasCamera,
|
|
320
|
-
hasPerspective
|
|
323
|
+
hasPerspective,
|
|
324
|
+
zSort
|
|
321
325
|
};
|
|
322
326
|
}
|
|
323
327
|
|
|
@@ -338,7 +342,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
|
338
342
|
]);
|
|
339
343
|
var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
|
|
340
344
|
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "z", "rotateX", "rotateY", "anchor", "fixed", ...FX_PROPS];
|
|
341
|
-
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective"];
|
|
345
|
+
var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
|
|
342
346
|
var PROPS_BY_TYPE = {
|
|
343
347
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
344
348
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
@@ -550,12 +554,16 @@ function validateScene(ir) {
|
|
|
550
554
|
problems.push(`camera: a node is already named "camera" \u2014 rename that node or drop the scene camera (the id "camera" can't be both)`);
|
|
551
555
|
}
|
|
552
556
|
for (const [key2, value] of Object.entries(ir.camera)) {
|
|
553
|
-
if (
|
|
554
|
-
problems.push(`camera
|
|
557
|
+
if (key2 === "zSort") {
|
|
558
|
+
if (typeof value !== "boolean") problems.push(`camera.zSort must be a boolean`);
|
|
559
|
+
} else if (!CAMERA_PROPS.includes(key2)) {
|
|
560
|
+
problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}, zSort`);
|
|
555
561
|
} else if (typeof value !== "number") {
|
|
556
562
|
problems.push(`camera.${key2} must be a number`);
|
|
557
563
|
} else if (key2 === "perspective" && value <= 0) {
|
|
558
564
|
problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
|
|
565
|
+
} else if (key2 === "aperture" && value < 0) {
|
|
566
|
+
problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
|
|
559
567
|
}
|
|
560
568
|
}
|
|
561
569
|
}
|
package/dist/types/compile.d.ts
CHANGED
|
@@ -53,5 +53,7 @@ export interface CompiledScene {
|
|
|
53
53
|
hasCamera: boolean;
|
|
54
54
|
/** True iff the scene sets/animates `camera.perspective` (gates depth projection). */
|
|
55
55
|
hasPerspective: boolean;
|
|
56
|
+
/** True iff `camera.zSort` is on (gates depth-ordered paint; needs perspective). */
|
|
57
|
+
zSort: boolean;
|
|
56
58
|
}
|
|
57
59
|
export declare function compileScene(ir: SceneIR): CompiledScene;
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -461,6 +461,25 @@ export interface CameraIR {
|
|
|
461
461
|
* focal pull). A node BEHIND the camera (`perspective + z <= 0`) is culled.
|
|
462
462
|
*/
|
|
463
463
|
perspective?: number;
|
|
464
|
+
/**
|
|
465
|
+
* Depth of field (requires `perspective`). `aperture` is the blur strength —
|
|
466
|
+
* screen-pixels of gaussian blur added per unit of depth away from the focal
|
|
467
|
+
* plane; absent / 0 ⇒ no DOF (byte-identical). `focus` is the in-focus depth
|
|
468
|
+
* (same units as a node's world `z`, default 0 = the camera plane). A drawn op
|
|
469
|
+
* at depth `d` gains `aperture · |d − focus|` blur on top of any authored blur,
|
|
470
|
+
* so far (and near) layers soften while the focal plane stays sharp. Both are
|
|
471
|
+
* keyframable — animate `focus` for a rack focus, `aperture` for an iris pull.
|
|
472
|
+
*/
|
|
473
|
+
focus?: number;
|
|
474
|
+
aperture?: number;
|
|
475
|
+
/**
|
|
476
|
+
* Paint order by depth (requires `perspective`). Off by default — drawing stays
|
|
477
|
+
* array order. When `true`, siblings at each level are drawn far-to-near (larger
|
|
478
|
+
* world `z` first) so nearer nodes occlude farther ones without hand-ordering the
|
|
479
|
+
* tree. A `fixed` HUD stays on top; a track-matte group keeps its child order (the
|
|
480
|
+
* first child is the mask). Discrete flag, not animatable.
|
|
481
|
+
*/
|
|
482
|
+
zSort?: boolean;
|
|
464
483
|
}
|
|
465
484
|
export interface SceneIR {
|
|
466
485
|
version: 1;
|
package/guides/edsl-guide.md
CHANGED
|
@@ -215,11 +215,23 @@ scene({
|
|
|
215
215
|
- A node needs a base value to tween (`rotateY: 0` on the card before tweening it to 360).
|
|
216
216
|
- A tilted **group** foreshortens its whole subtree (cos folds into children). Clips project
|
|
217
217
|
by the group's depth. A `fixed` HUD ignores depth (perspective is part of the camera).
|
|
218
|
+
- **Depth of field** (needs `perspective`): add `camera.aperture` (blur px per unit depth) and
|
|
219
|
+
`camera.focus` (the in-focus `z`, default 0). A layer at depth `d` softens by
|
|
220
|
+
`aperture·|d − focus|` while the focal plane stays sharp; keyframe `focus` for a **rack focus**,
|
|
221
|
+
`aperture` for an iris pull. Absent/`0` ⇒ no blur. HUD/UI text should be `fixed` so it stays
|
|
222
|
+
crisp (a `fixed` node opts out of DOF too). It feeds the same `blur` op, so it composes with an
|
|
223
|
+
authored `blur`.
|
|
224
|
+
- **Occlusion by depth** is opt-in: set `camera.zSort: true` and siblings paint far→near
|
|
225
|
+
(larger `z` first) so nearer nodes cover farther ones without hand-ordering the tree (a
|
|
226
|
+
`fixed` HUD stays on top; a track-matte group keeps its child order). Off by default — paint
|
|
227
|
+
stays array order. Gotcha: with `zSort`, a full-screen background rect at `z: 0` is the
|
|
228
|
+
NEAREST plane and paints on top — use the scene `background` color instead, or give the
|
|
229
|
+
backdrop a large `z`.
|
|
218
230
|
- **Limits (honest):** `rotateX`/`rotateY` are an affine approximation (cos-foreshorten +
|
|
219
231
|
keystone skew) — a single rotated quad is really a trapezoid Canvas 2D can't draw, so it
|
|
220
232
|
reads as a flip/tilt, not a pixel-true 3D face (that needs WebGL). Depth positioning
|
|
221
|
-
(parallax, convergence, dolly) IS exact.
|
|
222
|
-
|
|
233
|
+
(parallax, convergence, dolly) IS exact. No GPU 3D, no z-buffer (per-pixel) — `zSort` orders
|
|
234
|
+
whole nodes, so two INTERSECTING planes can't visually cross.
|
|
223
235
|
|
|
224
236
|
See `examples/scenes/perspective-cards.ts`.
|
|
225
237
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.17",
|
|
4
4
|
"description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"motion-graphics",
|