reframe-video 0.6.16 → 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 CHANGED
@@ -327,6 +327,7 @@ function compileScene(ir) {
327
327
  for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
328
328
  const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
329
329
  const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
330
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
330
331
  return {
331
332
  ir,
332
333
  duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
@@ -338,7 +339,8 @@ function compileScene(ir) {
338
339
  labelTimes,
339
340
  beatTimes,
340
341
  hasCamera,
341
- hasPerspective
342
+ hasPerspective,
343
+ zSort
342
344
  };
343
345
  }
344
346
  var key;
@@ -544,8 +546,10 @@ function validateScene(ir) {
544
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)`);
545
547
  }
546
548
  for (const [key2, value] of Object.entries(ir.camera)) {
547
- if (!CAMERA_PROPS.includes(key2)) {
548
- problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}`);
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`);
549
553
  } else if (typeof value !== "number") {
550
554
  problems.push(`camera.${key2} must be a number`);
551
555
  } else if (key2 === "perspective" && value <= 0) {
@@ -324,6 +324,7 @@
324
324
  for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
325
325
  const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
326
326
  const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
327
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
327
328
  return {
328
329
  ir,
329
330
  duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
@@ -335,7 +336,8 @@
335
336
  labelTimes,
336
337
  beatTimes,
337
338
  hasCamera,
338
- hasPerspective
339
+ hasPerspective,
340
+ zSort
339
341
  };
340
342
  }
341
343
 
@@ -750,6 +752,9 @@
750
752
  if (extra <= 0) return fx;
751
753
  return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
752
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));
753
758
  const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
754
759
  const id = node.id;
755
760
  const clipSpread = clips.length > 0 ? { clips } : void 0;
@@ -824,7 +829,8 @@
824
829
  for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips, depth, project);
825
830
  ops.push({ type: "matte-pop", id, transform: matrix, opacity });
826
831
  } else {
827
- for (const child of node.children) walk(child, matrix, opacity, childClips, depth, project);
832
+ const kids = zSort && project ? depthOrder(node.children, depth) : node.children;
833
+ for (const child of kids) walk(child, matrix, opacity, childClips, depth, project);
828
834
  }
829
835
  if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
830
836
  return;
@@ -966,7 +972,12 @@
966
972
  },
967
973
  compiled2.ir.size
968
974
  ) : IDENTITY;
969
- for (const node of compiled2.ir.nodes) {
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) {
970
981
  const root = compiled2.hasCamera && node.props.fixed ? IDENTITY : cameraRoot;
971
982
  const project = persp && !(node.props.fixed && compiled2.hasCamera);
972
983
  walk(node, root, 1, [], 0, project);
package/dist/cli.js CHANGED
@@ -314,6 +314,7 @@ function compileScene(ir) {
314
314
  for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
315
315
  const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
316
316
  const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
317
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
317
318
  return {
318
319
  ir,
319
320
  duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
@@ -325,7 +326,8 @@ function compileScene(ir) {
325
326
  labelTimes,
326
327
  beatTimes,
327
328
  hasCamera,
328
- hasPerspective
329
+ hasPerspective,
330
+ zSort
329
331
  };
330
332
  }
331
333
 
@@ -558,8 +560,10 @@ function validateScene(ir) {
558
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)`);
559
561
  }
560
562
  for (const [key2, value] of Object.entries(ir.camera)) {
561
- if (!CAMERA_PROPS.includes(key2)) {
562
- problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}`);
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`);
563
567
  } else if (typeof value !== "number") {
564
568
  problems.push(`camera.${key2} must be a number`);
565
569
  } else if (key2 === "perspective" && value <= 0) {
package/dist/diff.js CHANGED
@@ -320,6 +320,7 @@ function compileScene(ir) {
320
320
  for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
321
321
  const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
322
322
  const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
323
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
323
324
  return {
324
325
  ir,
325
326
  duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
@@ -331,7 +332,8 @@ function compileScene(ir) {
331
332
  labelTimes,
332
333
  beatTimes,
333
334
  hasCamera,
334
- hasPerspective
335
+ hasPerspective,
336
+ zSort
335
337
  };
336
338
  }
337
339
 
@@ -564,8 +566,10 @@ function validateScene(ir) {
564
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)`);
565
567
  }
566
568
  for (const [key2, value] of Object.entries(ir.camera)) {
567
- if (!CAMERA_PROPS.includes(key2)) {
568
- problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}`);
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`);
569
573
  } else if (typeof value !== "number") {
570
574
  problems.push(`camera.${key2} must be a number`);
571
575
  } else if (key2 === "perspective" && value <= 0) {
package/dist/index.js CHANGED
@@ -324,6 +324,7 @@ function compileScene(ir) {
324
324
  for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
325
325
  const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
326
326
  const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
327
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
327
328
  return {
328
329
  ir,
329
330
  duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
@@ -335,7 +336,8 @@ function compileScene(ir) {
335
336
  labelTimes,
336
337
  beatTimes,
337
338
  hasCamera,
338
- hasPerspective
339
+ hasPerspective,
340
+ zSort
339
341
  };
340
342
  }
341
343
 
@@ -568,8 +570,10 @@ function validateScene(ir) {
568
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)`);
569
571
  }
570
572
  for (const [key2, value] of Object.entries(ir.camera)) {
571
- if (!CAMERA_PROPS.includes(key2)) {
572
- problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}`);
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`);
573
577
  } else if (typeof value !== "number") {
574
578
  problems.push(`camera.${key2} must be a number`);
575
579
  } else if (key2 === "perspective" && value <= 0) {
@@ -3226,6 +3230,9 @@ function evaluate(compiled, t) {
3226
3230
  if (extra <= 0) return fx;
3227
3231
  return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
3228
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));
3229
3236
  const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
3230
3237
  const id = node.id;
3231
3238
  const clipSpread = clips.length > 0 ? { clips } : void 0;
@@ -3300,7 +3307,8 @@ function evaluate(compiled, t) {
3300
3307
  for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips, depth, project);
3301
3308
  ops.push({ type: "matte-pop", id, transform: matrix, opacity });
3302
3309
  } else {
3303
- for (const child of node.children) walk(child, matrix, opacity, childClips, depth, project);
3310
+ const kids = zSort && project ? depthOrder(node.children, depth) : node.children;
3311
+ for (const child of kids) walk(child, matrix, opacity, childClips, depth, project);
3304
3312
  }
3305
3313
  if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
3306
3314
  return;
@@ -3442,7 +3450,12 @@ function evaluate(compiled, t) {
3442
3450
  },
3443
3451
  compiled.ir.size
3444
3452
  ) : IDENTITY;
3445
- for (const node of compiled.ir.nodes) {
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) {
3446
3459
  const root = compiled.hasCamera && node.props.fixed ? IDENTITY : cameraRoot;
3447
3460
  const project = persp && !(node.props.fixed && compiled.hasCamera);
3448
3461
  walk(node, root, 1, [], 0, project);
package/dist/labels.js CHANGED
@@ -308,6 +308,7 @@ function compileScene(ir) {
308
308
  for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
309
309
  const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
310
310
  const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
311
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
311
312
  return {
312
313
  ir,
313
314
  duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
@@ -319,7 +320,8 @@ function compileScene(ir) {
319
320
  labelTimes,
320
321
  beatTimes,
321
322
  hasCamera,
322
- hasPerspective
323
+ hasPerspective,
324
+ zSort
323
325
  };
324
326
  }
325
327
 
@@ -552,8 +554,10 @@ function validateScene(ir) {
552
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)`);
553
555
  }
554
556
  for (const [key2, value] of Object.entries(ir.camera)) {
555
- if (!CAMERA_PROPS.includes(key2)) {
556
- problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}`);
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`);
557
561
  } else if (typeof value !== "number") {
558
562
  problems.push(`camera.${key2} must be a number`);
559
563
  } else if (key2 === "perspective" && value <= 0) {
@@ -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;
@@ -472,6 +472,14 @@ export interface CameraIR {
472
472
  */
473
473
  focus?: number;
474
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;
475
483
  }
476
484
  export interface SceneIR {
477
485
  version: 1;
@@ -221,11 +221,17 @@ scene({
221
221
  `aperture` for an iris pull. Absent/`0` ⇒ no blur. HUD/UI text should be `fixed` so it stays
222
222
  crisp (a `fixed` node opts out of DOF too). It feeds the same `blur` op, so it composes with an
223
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`.
224
230
  - **Limits (honest):** `rotateX`/`rotateY` are an affine approximation (cos-foreshorten +
225
231
  keystone skew) — a single rotated quad is really a trapezoid Canvas 2D can't draw, so it
226
232
  reads as a flip/tilt, not a pixel-true 3D face (that needs WebGL). Depth positioning
227
- (parallax, convergence, dolly) IS exact. `z` does NOT reorder drawingpaint stays array
228
- order, so order your nodes back-to-front yourself. No GPU 3D, no z-buffer.
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.
229
235
 
230
236
  See `examples/scenes/perspective-cards.ts`.
231
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.16",
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",