reframe-video 0.6.15 → 0.6.16

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 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();
@@ -548,6 +550,8 @@ function validateScene(ir) {
548
550
  problems.push(`camera.${key2} must be a number`);
549
551
  } else if (key2 === "perspective" && value <= 0) {
550
552
  problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
553
+ } else if (key2 === "aperture" && value < 0) {
554
+ problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
551
555
  }
552
556
  }
553
557
  }
@@ -629,7 +633,7 @@ var init_validate = __esm({
629
633
  ]);
630
634
  IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
631
635
  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"];
636
+ CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
633
637
  PROPS_BY_TYPE = {
634
638
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
635
639
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
@@ -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();
@@ -740,6 +742,14 @@
740
742
  const dPersp = persp ? num("camera", "perspective", 0) : 0;
741
743
  const vx = persp ? compiled2.ir.size.width / 2 : 0;
742
744
  const vy = persp ? compiled2.ir.size.height / 2 : 0;
745
+ const aperture = persp ? num("camera", "aperture", 0) : 0;
746
+ const focus = persp ? num("camera", "focus", 0) : 0;
747
+ const dofFx = (fx, depth, project) => {
748
+ if (!project || aperture <= 0) return fx;
749
+ const extra = aperture * Math.abs(depth - focus);
750
+ if (extra <= 0) return fx;
751
+ return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
752
+ };
743
753
  const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
744
754
  const id = node.id;
745
755
  const clipSpread = clips.length > 0 ? { clips } : void 0;
@@ -762,7 +772,8 @@
762
772
  y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
763
773
  stroke: str(id, "stroke", node.props.stroke),
764
774
  strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
765
- ...fx,
775
+ // a line carries no z of its own — DOF uses the inherited subtree depth
776
+ ...dofFx(fx, zAcc, project),
766
777
  ...clipSpread
767
778
  });
768
779
  return;
@@ -799,6 +810,7 @@
799
810
  const tilted = rotX !== 0 || rotY !== 0 ? tiltSkew(m, rotX, rotY, hw, hh, dPersp) : m;
800
811
  return projectDepth(tilted, depth, vx, vy, dPersp);
801
812
  };
813
+ const leafFx = dofFx(fx, depth, project);
802
814
  switch (node.type) {
803
815
  case "group": {
804
816
  const clipTf = projDraw(matrix, 0, 0);
@@ -839,7 +851,7 @@
839
851
  ...fill !== void 0 && { fill },
840
852
  ...stroke !== void 0 && { stroke, strokeWidth },
841
853
  ...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
842
- ...fx,
854
+ ...leafFx,
843
855
  ...clipSpread
844
856
  });
845
857
  return;
@@ -859,7 +871,7 @@
859
871
  offsetX: -width * ax,
860
872
  offsetY: -height * ay,
861
873
  ...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
862
- ...fx,
874
+ ...leafFx,
863
875
  ...clipSpread
864
876
  });
865
877
  return;
@@ -886,7 +898,7 @@
886
898
  offsetY: -height * ay,
887
899
  frame,
888
900
  ...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
889
- ...fx,
901
+ ...leafFx,
890
902
  ...clipSpread
891
903
  });
892
904
  return;
@@ -911,7 +923,7 @@
911
923
  ...fill !== void 0 && { fill },
912
924
  ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
913
925
  ...needsBox && { bbox: pathBBox(dStr) },
914
- ...fx,
926
+ ...leafFx,
915
927
  ...clipSpread
916
928
  });
917
929
  return;
@@ -938,7 +950,7 @@
938
950
  letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
939
951
  align: TEXT_ALIGN[ax] ?? "left",
940
952
  baseline: TEXT_BASELINE[ay] ?? "top",
941
- ...fx,
953
+ ...leafFx,
942
954
  ...clipSpread
943
955
  });
944
956
  return;
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();
@@ -344,7 +346,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
344
346
  ]);
345
347
  var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
346
348
  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"];
349
+ var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
348
350
  var PROPS_BY_TYPE = {
349
351
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
350
352
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
@@ -562,6 +564,8 @@ function validateScene(ir) {
562
564
  problems.push(`camera.${key2} must be a number`);
563
565
  } else if (key2 === "perspective" && value <= 0) {
564
566
  problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
567
+ } else if (key2 === "aperture" && value < 0) {
568
+ problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
565
569
  }
566
570
  }
567
571
  }
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();
@@ -350,7 +352,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
350
352
  ]);
351
353
  var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
352
354
  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"];
355
+ var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
354
356
  var PROPS_BY_TYPE = {
355
357
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
356
358
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
@@ -568,6 +570,8 @@ function validateScene(ir) {
568
570
  problems.push(`camera.${key2} must be a number`);
569
571
  } else if (key2 === "perspective" && value <= 0) {
570
572
  problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
573
+ } else if (key2 === "aperture" && value < 0) {
574
+ problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
571
575
  }
572
576
  }
573
577
  }
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();
@@ -354,7 +356,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
354
356
  ]);
355
357
  var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
356
358
  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"];
359
+ var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
358
360
  var PROPS_BY_TYPE = {
359
361
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
360
362
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
@@ -572,6 +574,8 @@ function validateScene(ir) {
572
574
  problems.push(`camera.${key2} must be a number`);
573
575
  } else if (key2 === "perspective" && value <= 0) {
574
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`);
575
579
  }
576
580
  }
577
581
  }
@@ -3214,6 +3218,14 @@ function evaluate(compiled, t) {
3214
3218
  const dPersp = persp ? num("camera", "perspective", 0) : 0;
3215
3219
  const vx = persp ? compiled.ir.size.width / 2 : 0;
3216
3220
  const vy = persp ? compiled.ir.size.height / 2 : 0;
3221
+ const aperture = persp ? num("camera", "aperture", 0) : 0;
3222
+ const focus = persp ? num("camera", "focus", 0) : 0;
3223
+ const dofFx = (fx, depth, project) => {
3224
+ if (!project || aperture <= 0) return fx;
3225
+ const extra = aperture * Math.abs(depth - focus);
3226
+ if (extra <= 0) return fx;
3227
+ return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
3228
+ };
3217
3229
  const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
3218
3230
  const id = node.id;
3219
3231
  const clipSpread = clips.length > 0 ? { clips } : void 0;
@@ -3236,7 +3248,8 @@ function evaluate(compiled, t) {
3236
3248
  y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
3237
3249
  stroke: str(id, "stroke", node.props.stroke),
3238
3250
  strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
3239
- ...fx,
3251
+ // a line carries no z of its own — DOF uses the inherited subtree depth
3252
+ ...dofFx(fx, zAcc, project),
3240
3253
  ...clipSpread
3241
3254
  });
3242
3255
  return;
@@ -3273,6 +3286,7 @@ function evaluate(compiled, t) {
3273
3286
  const tilted = rotX !== 0 || rotY !== 0 ? tiltSkew(m, rotX, rotY, hw, hh, dPersp) : m;
3274
3287
  return projectDepth(tilted, depth, vx, vy, dPersp);
3275
3288
  };
3289
+ const leafFx = dofFx(fx, depth, project);
3276
3290
  switch (node.type) {
3277
3291
  case "group": {
3278
3292
  const clipTf = projDraw(matrix, 0, 0);
@@ -3313,7 +3327,7 @@ function evaluate(compiled, t) {
3313
3327
  ...fill !== void 0 && { fill },
3314
3328
  ...stroke !== void 0 && { stroke, strokeWidth },
3315
3329
  ...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
3316
- ...fx,
3330
+ ...leafFx,
3317
3331
  ...clipSpread
3318
3332
  });
3319
3333
  return;
@@ -3333,7 +3347,7 @@ function evaluate(compiled, t) {
3333
3347
  offsetX: -width * ax,
3334
3348
  offsetY: -height * ay,
3335
3349
  ...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
3336
- ...fx,
3350
+ ...leafFx,
3337
3351
  ...clipSpread
3338
3352
  });
3339
3353
  return;
@@ -3360,7 +3374,7 @@ function evaluate(compiled, t) {
3360
3374
  offsetY: -height * ay,
3361
3375
  frame,
3362
3376
  ...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
3363
- ...fx,
3377
+ ...leafFx,
3364
3378
  ...clipSpread
3365
3379
  });
3366
3380
  return;
@@ -3385,7 +3399,7 @@ function evaluate(compiled, t) {
3385
3399
  ...fill !== void 0 && { fill },
3386
3400
  ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
3387
3401
  ...needsBox && { bbox: pathBBox(dStr) },
3388
- ...fx,
3402
+ ...leafFx,
3389
3403
  ...clipSpread
3390
3404
  });
3391
3405
  return;
@@ -3412,7 +3426,7 @@ function evaluate(compiled, t) {
3412
3426
  letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
3413
3427
  align: TEXT_ALIGN[ax] ?? "left",
3414
3428
  baseline: TEXT_BASELINE[ay] ?? "top",
3415
- ...fx,
3429
+ ...leafFx,
3416
3430
  ...clipSpread
3417
3431
  });
3418
3432
  return;
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();
@@ -338,7 +340,7 @@ var BLEND_MODES = /* @__PURE__ */ new Set([
338
340
  ]);
339
341
  var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
340
342
  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"];
343
+ var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
342
344
  var PROPS_BY_TYPE = {
343
345
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
344
346
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
@@ -556,6 +558,8 @@ function validateScene(ir) {
556
558
  problems.push(`camera.${key2} must be a number`);
557
559
  } else if (key2 === "perspective" && value <= 0) {
558
560
  problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
561
+ } else if (key2 === "aperture" && value < 0) {
562
+ problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
559
563
  }
560
564
  }
561
565
  }
@@ -461,6 +461,17 @@ 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;
464
475
  }
465
476
  export interface SceneIR {
466
477
  version: 1;
@@ -215,6 +215,12 @@ 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`.
218
224
  - **Limits (honest):** `rotateX`/`rotateY` are an affine approximation (cos-foreshorten +
219
225
  keystone skew) — a single rotated quad is really a trapezoid Canvas 2D can't draw, so it
220
226
  reads as a flip/tilt, not a pixel-true 3D face (that needs WebGL). Depth positioning
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.15",
3
+ "version": "0.6.16",
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",