hyperframes 0.8.45 → 0.8.46

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/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.8.45" : "0.0.0-dev";
53
+ VERSION = true ? "0.8.46" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -66022,37 +66022,80 @@ function finiteOr(value, fallback) {
66022
66022
  }
66023
66023
  function resolveMotionBlurPlan(options) {
66024
66024
  if (!options) return null;
66025
- const samplesPerFrame = Math.min(
66025
+ const fixedSamplesPerFrame = options.samplesPerFrame === void 0 ? null : Math.min(
66026
66026
  MAX_SAMPLES_PER_FRAME,
66027
66027
  Math.max(1, Math.round(finiteOr(options.samplesPerFrame, DEFAULT_SAMPLES_PER_FRAME)))
66028
66028
  );
66029
66029
  const shutterAngle = finiteOr(options.shutterAngle, DEFAULT_SHUTTER_ANGLE);
66030
66030
  const shutterPhase = finiteOr(options.shutterPhase, DEFAULT_SHUTTER_PHASE);
66031
66031
  const blend = options.blend === "linear" ? "linear" : "srgb";
66032
- const windowStartFrames = shutterPhase / 360;
66033
- const shutterFrames = shutterAngle / 360;
66034
- const sampleTickOffsets = [];
66032
+ return {
66033
+ blend,
66034
+ subFrameDivisions: SUB_FRAME_DIVISIONS,
66035
+ windowStartFrames: shutterPhase / 360,
66036
+ shutterFrames: shutterAngle / 360,
66037
+ fixedSamplesPerFrame
66038
+ };
66039
+ }
66040
+ function sampleTickOffsets(plan2, samplesPerFrame) {
66041
+ const offsets = [];
66035
66042
  for (let k2 = 0; k2 < samplesPerFrame; k2++) {
66036
- const offsetFrames = windowStartFrames + (k2 + 0.5) / samplesPerFrame * shutterFrames;
66037
- sampleTickOffsets.push(Math.round(offsetFrames * SUB_FRAME_DIVISIONS));
66043
+ const offsetFrames = plan2.windowStartFrames + (k2 + 0.5) / samplesPerFrame * plan2.shutterFrames;
66044
+ offsets.push(Math.round(offsetFrames * plan2.subFrameDivisions));
66038
66045
  }
66039
- return { blend, subFrameDivisions: SUB_FRAME_DIVISIONS, sampleTickOffsets };
66046
+ return offsets;
66040
66047
  }
66041
- function motionBlurSampleTimes(plan2, frameIndex, fps) {
66048
+ function motionBlurSampleTimes(plan2, frameIndex, fps, samplesPerFrame) {
66042
66049
  const grid = fps * plan2.subFrameDivisions;
66043
66050
  const frameTicks = frameIndex * plan2.subFrameDivisions;
66044
- return plan2.sampleTickOffsets.map((offset2) => Math.max(0, frameTicks + offset2) / grid);
66051
+ return sampleTickOffsets(plan2, samplesPerFrame).map(
66052
+ (offset2) => Math.max(0, frameTicks + offset2) / grid
66053
+ );
66045
66054
  }
66046
- function motionBlurWindowIsStatic(plan2, frameIndex, staticFrames) {
66047
- const first = Math.floor(
66048
- frameIndex - 1 + Math.min(...plan2.sampleTickOffsets) / plan2.subFrameDivisions
66055
+ function motionBlurProbeTimes(plan2, frameIndex, fps) {
66056
+ const grid = fps * plan2.subFrameDivisions;
66057
+ const frameTicks = frameIndex * plan2.subFrameDivisions;
66058
+ const startTick = Math.round(plan2.windowStartFrames * plan2.subFrameDivisions);
66059
+ const endTick = Math.round(
66060
+ (plan2.windowStartFrames + plan2.shutterFrames) * plan2.subFrameDivisions
66049
66061
  );
66050
- const last = Math.floor(frameIndex + Math.max(...plan2.sampleTickOffsets) / plan2.subFrameDivisions) + 1;
66062
+ return {
66063
+ windowStart: Math.max(0, frameTicks + startTick) / grid,
66064
+ windowEnd: Math.max(0, frameTicks + endTick) / grid
66065
+ };
66066
+ }
66067
+ function motionBlurWindowIsStatic(plan2, frameIndex, staticFrames) {
66068
+ const worstCaseSamples = plan2.fixedSamplesPerFrame ?? MAX_SAMPLES_PER_FRAME;
66069
+ const inset = plan2.shutterFrames / (2 * worstCaseSamples);
66070
+ const minOffsetFrames = plan2.windowStartFrames + inset;
66071
+ const maxOffsetFrames = plan2.windowStartFrames + plan2.shutterFrames - inset;
66072
+ const first = Math.floor(frameIndex - 1 + Math.min(0, minOffsetFrames));
66073
+ const last = Math.floor(frameIndex + Math.max(0, maxOffsetFrames)) + 1;
66051
66074
  for (let frame = first; frame <= last; frame++) {
66052
66075
  if (!staticFrames.has(frame)) return false;
66053
66076
  }
66054
66077
  return true;
66055
66078
  }
66079
+ function adaptiveSampleCount(diff) {
66080
+ for (const step of ADAPTIVE_SAMPLE_STEPS) {
66081
+ if (diff <= step.maxDiff) return step.samples;
66082
+ }
66083
+ return MAX_SAMPLES_PER_FRAME;
66084
+ }
66085
+ function probeDiffMagnitude(a, b2) {
66086
+ const decodedA = decodePng(a);
66087
+ const decodedB = decodePng(b2);
66088
+ if (decodedA.width !== decodedB.width || decodedA.height !== decodedB.height) {
66089
+ throw new Error(
66090
+ `probeDiffMagnitude: sample geometry mismatch (${decodedA.width}x${decodedA.height} vs ${decodedB.width}x${decodedB.height})`
66091
+ );
66092
+ }
66093
+ let sum = 0;
66094
+ for (let i2 = 0; i2 < decodedA.data.length; i2++) {
66095
+ sum += Math.abs(decodedA.data[i2] - decodedB.data[i2]);
66096
+ }
66097
+ return sum / decodedA.data.length;
66098
+ }
66056
66099
  function linearToByte(value) {
66057
66100
  const c3 = value <= 31308e-7 ? value * 12.92 : 1.055 * Math.pow(value, 1 / 2.4) - 0.055;
66058
66101
  return clampByte(c3 * 255);
@@ -66060,7 +66103,7 @@ function linearToByte(value) {
66060
66103
  function clampByte(value) {
66061
66104
  return Math.min(255, Math.max(0, Math.round(value)));
66062
66105
  }
66063
- var MAX_SAMPLES_PER_FRAME, DEFAULT_SAMPLES_PER_FRAME, DEFAULT_SHUTTER_ANGLE, DEFAULT_SHUTTER_PHASE, SUB_FRAME_DIVISIONS, SRGB_TO_LINEAR, SRGB_PASSTHROUGH, MotionBlurAccumulator;
66106
+ var MAX_SAMPLES_PER_FRAME, DEFAULT_SAMPLES_PER_FRAME, DEFAULT_SHUTTER_ANGLE, DEFAULT_SHUTTER_PHASE, SUB_FRAME_DIVISIONS, ADAPTIVE_SAMPLE_STEPS, SPATIAL_TWEEN_PROPERTIES, SRGB_TO_LINEAR, SRGB_PASSTHROUGH, MotionBlurAccumulator;
66064
66107
  var init_motionBlur = __esm({
66065
66108
  "../engine/src/services/motionBlur.ts"() {
66066
66109
  "use strict";
@@ -66070,6 +66113,42 @@ var init_motionBlur = __esm({
66070
66113
  DEFAULT_SHUTTER_ANGLE = 180;
66071
66114
  DEFAULT_SHUTTER_PHASE = -90;
66072
66115
  SUB_FRAME_DIVISIONS = 4096;
66116
+ ADAPTIVE_SAMPLE_STEPS = [
66117
+ { maxDiff: 3, samples: DEFAULT_SAMPLES_PER_FRAME },
66118
+ { maxDiff: 6, samples: 32 },
66119
+ { maxDiff: Infinity, samples: MAX_SAMPLES_PER_FRAME }
66120
+ ];
66121
+ SPATIAL_TWEEN_PROPERTIES = [
66122
+ "x",
66123
+ "y",
66124
+ "z",
66125
+ "xPercent",
66126
+ "yPercent",
66127
+ "rotation",
66128
+ "rotate",
66129
+ "rotationX",
66130
+ "rotationY",
66131
+ "rotationZ",
66132
+ "scale",
66133
+ "scaleX",
66134
+ "scaleY",
66135
+ "scaleZ",
66136
+ "skewX",
66137
+ "skewY",
66138
+ "perspective",
66139
+ "transformPerspective",
66140
+ "top",
66141
+ "left",
66142
+ "right",
66143
+ "bottom",
66144
+ "width",
66145
+ "height",
66146
+ "translate",
66147
+ "translateX",
66148
+ "translateY",
66149
+ "translateZ",
66150
+ "transform"
66151
+ ];
66073
66152
  SRGB_TO_LINEAR = (() => {
66074
66153
  const lut = new Float64Array(256);
66075
66154
  for (let i2 = 0; i2 < 256; i2++) lut[i2] = srgbByteToLinear(i2);
@@ -69691,7 +69770,13 @@ function isStaticDedupFrameAnalysisSafe(totalFrames) {
69691
69770
  return Number.isFinite(totalFrames) && Number.isSafeInteger(totalFrames) && totalFrames > 0 && totalFrames <= MAX_STATIC_DEDUP_ANALYSIS_FRAMES;
69692
69771
  }
69693
69772
  async function computeStaticFrameSet(page, fps) {
69694
- const result = await page.evaluate(() => {
69773
+ const result = await page.evaluate((spatialProps) => {
69774
+ const SPATIAL_PROPS = new Set(spatialProps);
69775
+ function isSpatial(vars) {
69776
+ if (!vars) return false;
69777
+ for (const key2 of Object.keys(vars)) if (SPATIAL_PROPS.has(key2)) return true;
69778
+ return false;
69779
+ }
69695
69780
  const intervals2 = [];
69696
69781
  let tweenCount2 = 0;
69697
69782
  let hasTimelineCall2 = false;
@@ -69699,7 +69784,7 @@ async function computeStaticFrameSet(page, fps) {
69699
69784
  if (typeof tl.getChildren !== "function") return;
69700
69785
  if (typeof tl.vars?.onUpdate === "function") {
69701
69786
  const total = typeof tl.totalDuration === "function" ? tl.totalDuration() : 0;
69702
- if (total > 0) intervals2.push({ start: offset2, end: offset2 + total });
69787
+ if (total > 0) intervals2.push({ start: offset2, end: offset2 + total, spatial: true });
69703
69788
  }
69704
69789
  for (const child of tl.getChildren(false, true, true)) {
69705
69790
  const start = offset2 + (typeof child.startTime === "function" ? child.startTime() : 0);
@@ -69707,14 +69792,14 @@ async function computeStaticFrameSet(page, fps) {
69707
69792
  const total = typeof child.totalDuration === "function" ? child.totalDuration() : single;
69708
69793
  if (typeof child.getChildren === "function") {
69709
69794
  if (total > single + 1e-6) {
69710
- intervals2.push({ start, end: start + total });
69795
+ intervals2.push({ start, end: start + total, spatial: true });
69711
69796
  walk2(child, start);
69712
69797
  } else {
69713
69798
  walk2(child, start);
69714
69799
  }
69715
69800
  } else {
69716
69801
  tweenCount2++;
69717
- intervals2.push({ start, end: start + total });
69802
+ intervals2.push({ start, end: start + total, spatial: isSpatial(child.vars) });
69718
69803
  if (total <= 1e-6 && (typeof child.vars?.onComplete === "function" || typeof child.vars?.onReverseComplete === "function")) {
69719
69804
  hasTimelineCall2 = true;
69720
69805
  }
@@ -69755,7 +69840,7 @@ async function computeStaticFrameSet(page, fps) {
69755
69840
  hasUnresolvableClipStart: hasUnresolvableClipStart2,
69756
69841
  hasTimelineCall: hasTimelineCall2
69757
69842
  };
69758
- });
69843
+ }, SPATIAL_TWEEN_PROPERTIES);
69759
69844
  const {
69760
69845
  intervals,
69761
69846
  tweenCount,
@@ -69771,6 +69856,7 @@ async function computeStaticFrameSet(page, fps) {
69771
69856
  return {
69772
69857
  totalFrames,
69773
69858
  staticFrameSet: /* @__PURE__ */ new Set(),
69859
+ nonSpatialOnlyFrameSet: /* @__PURE__ */ new Set(),
69774
69860
  hasVideo,
69775
69861
  hasCanvas,
69776
69862
  hasNonGsapAnim,
@@ -69780,10 +69866,14 @@ async function computeStaticFrameSet(page, fps) {
69780
69866
  };
69781
69867
  }
69782
69868
  const animated = /* @__PURE__ */ new Set();
69783
- for (const { start, end } of intervals) {
69869
+ const spatialFrameSet = /* @__PURE__ */ new Set();
69870
+ for (const { start, end, spatial } of intervals) {
69784
69871
  const lo = Math.max(0, Math.floor(start * fps));
69785
69872
  const hi = Math.min(totalFrames - 1, Math.ceil(end * fps));
69786
- for (let f3 = lo; f3 <= hi; f3++) animated.add(f3);
69873
+ for (let f3 = lo; f3 <= hi; f3++) {
69874
+ animated.add(f3);
69875
+ if (spatial) spatialFrameSet.add(f3);
69876
+ }
69787
69877
  }
69788
69878
  for (const f3 of await computeClipBoundaryFrames(page, fps)) animated.add(f3);
69789
69879
  const reasons = [];
@@ -69801,9 +69891,14 @@ async function computeStaticFrameSet(page, fps) {
69801
69891
  if (!animated.has(f3) && !animated.has(f3 - 1)) staticFrameSet.add(f3);
69802
69892
  }
69803
69893
  }
69894
+ const nonSpatialOnlyFrameSet = /* @__PURE__ */ new Set();
69895
+ if (eligible) {
69896
+ for (const f3 of animated) if (!spatialFrameSet.has(f3)) nonSpatialOnlyFrameSet.add(f3);
69897
+ }
69804
69898
  return {
69805
69899
  totalFrames,
69806
69900
  staticFrameSet,
69901
+ nonSpatialOnlyFrameSet,
69807
69902
  hasVideo,
69808
69903
  hasCanvas,
69809
69904
  hasNonGsapAnim,
@@ -70013,6 +70108,11 @@ async function verifyStaticFramesSafe(session, page, staticFrames, fps, sampleCo
70013
70108
  }
70014
70109
  }
70015
70110
  async function armStaticDedup(session, page, logInitPhase) {
70111
+ let sharedStaticFrameStats;
70112
+ if (session.options.motionBlur && session.options.motionBlur.samplesPerFrame === void 0 && !session.motionBlurNonSpatialFrames) {
70113
+ sharedStaticFrameStats = await computeStaticFrameSet(page, fpsToNumber(session.options.fps));
70114
+ session.motionBlurNonSpatialFrames = sharedStaticFrameStats.nonSpatialOnlyFrameSet;
70115
+ }
70016
70116
  if (session.staticFrames || session.staticDedupSkipReason) return;
70017
70117
  session.staticDedupEnabled = session.config?.staticFrameDedup !== false;
70018
70118
  if (!session.staticDedupEnabled) return;
@@ -70037,7 +70137,7 @@ async function armStaticDedup(session, page, logInitPhase) {
70037
70137
  return;
70038
70138
  }
70039
70139
  const fps = fpsToNumber(session.options.fps);
70040
- const stats = await computeStaticFrameSet(page, fps);
70140
+ const stats = sharedStaticFrameStats ?? await computeStaticFrameSet(page, fps);
70041
70141
  if (!stats.eligible || stats.staticFrameSet.size === 0) {
70042
70142
  session.staticDedupSkipReason = "ineligible";
70043
70143
  logInitPhase(`static-frame dedup: disabled (${stats.reason})`);
@@ -70283,6 +70383,25 @@ function finalizeSessionInit(session) {
70283
70383
  session.motionBlur = resolveSessionMotionBlur(session);
70284
70384
  session.isInitialized = true;
70285
70385
  }
70386
+ async function resolveAdaptiveSampleCount(session, frameIndex, absFrameIndex, plan2, fps, sampleSeek) {
70387
+ if (session.motionBlurNonSpatialFrames?.has(absFrameIndex)) {
70388
+ return {
70389
+ samplesPerFrame: DEFAULT_SAMPLES_PER_FRAME,
70390
+ seekMs: 0,
70391
+ beforeCaptureMs: 0,
70392
+ screenshotMs: 0
70393
+ };
70394
+ }
70395
+ const { windowStart, windowEnd } = motionBlurProbeTimes(plan2, absFrameIndex, fps);
70396
+ const probeA = await captureFrameSurface(session, frameIndex, windowStart, sampleSeek);
70397
+ const probeB = await captureFrameSurface(session, frameIndex, windowEnd, sampleSeek);
70398
+ return {
70399
+ samplesPerFrame: adaptiveSampleCount(probeDiffMagnitude(probeA.buffer, probeB.buffer)),
70400
+ seekMs: probeA.seekMs + probeB.seekMs,
70401
+ beforeCaptureMs: probeA.beforeCaptureMs + probeB.beforeCaptureMs,
70402
+ screenshotMs: probeA.screenshotMs + probeB.screenshotMs
70403
+ };
70404
+ }
70286
70405
  async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan2) {
70287
70406
  const fps = fpsToNumber(session.options.fps);
70288
70407
  const frameTime = quantizeSeekTime(absFrameIndex / fps, fps);
@@ -70293,8 +70412,23 @@ async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan2
70293
70412
  suppressEvents: true,
70294
70413
  subFrameDivisions: plan2.subFrameDivisions
70295
70414
  };
70415
+ let samplesPerFrame = plan2.fixedSamplesPerFrame;
70416
+ if (samplesPerFrame === null) {
70417
+ const chosen = await resolveAdaptiveSampleCount(
70418
+ session,
70419
+ frameIndex,
70420
+ absFrameIndex,
70421
+ plan2,
70422
+ fps,
70423
+ sampleSeek
70424
+ );
70425
+ samplesPerFrame = chosen.samplesPerFrame;
70426
+ totals.seekMs += chosen.seekMs;
70427
+ totals.beforeCaptureMs += chosen.beforeCaptureMs;
70428
+ totals.screenshotMs += chosen.screenshotMs;
70429
+ }
70296
70430
  const accumulator = new MotionBlurAccumulator(plan2.blend);
70297
- for (const sampleTime of motionBlurSampleTimes(plan2, absFrameIndex, fps)) {
70431
+ for (const sampleTime of motionBlurSampleTimes(plan2, absFrameIndex, fps, samplesPerFrame)) {
70298
70432
  const sample = await captureFrameSurface(session, frameIndex, sampleTime, sampleSeek);
70299
70433
  totals.seekMs += sample.seekMs;
70300
70434
  totals.beforeCaptureMs += sample.beforeCaptureMs;
@@ -56326,37 +56326,80 @@ function finiteOr(value, fallback) {
56326
56326
  }
56327
56327
  function resolveMotionBlurPlan(options) {
56328
56328
  if (!options) return null;
56329
- const samplesPerFrame = Math.min(
56329
+ const fixedSamplesPerFrame = options.samplesPerFrame === void 0 ? null : Math.min(
56330
56330
  MAX_SAMPLES_PER_FRAME,
56331
56331
  Math.max(1, Math.round(finiteOr(options.samplesPerFrame, DEFAULT_SAMPLES_PER_FRAME)))
56332
56332
  );
56333
56333
  const shutterAngle = finiteOr(options.shutterAngle, DEFAULT_SHUTTER_ANGLE);
56334
56334
  const shutterPhase = finiteOr(options.shutterPhase, DEFAULT_SHUTTER_PHASE);
56335
56335
  const blend = options.blend === "linear" ? "linear" : "srgb";
56336
- const windowStartFrames = shutterPhase / 360;
56337
- const shutterFrames = shutterAngle / 360;
56338
- const sampleTickOffsets = [];
56336
+ return {
56337
+ blend,
56338
+ subFrameDivisions: SUB_FRAME_DIVISIONS,
56339
+ windowStartFrames: shutterPhase / 360,
56340
+ shutterFrames: shutterAngle / 360,
56341
+ fixedSamplesPerFrame
56342
+ };
56343
+ }
56344
+ function sampleTickOffsets(plan2, samplesPerFrame) {
56345
+ const offsets = [];
56339
56346
  for (let k = 0; k < samplesPerFrame; k++) {
56340
- const offsetFrames = windowStartFrames + (k + 0.5) / samplesPerFrame * shutterFrames;
56341
- sampleTickOffsets.push(Math.round(offsetFrames * SUB_FRAME_DIVISIONS));
56347
+ const offsetFrames = plan2.windowStartFrames + (k + 0.5) / samplesPerFrame * plan2.shutterFrames;
56348
+ offsets.push(Math.round(offsetFrames * plan2.subFrameDivisions));
56342
56349
  }
56343
- return { blend, subFrameDivisions: SUB_FRAME_DIVISIONS, sampleTickOffsets };
56350
+ return offsets;
56344
56351
  }
56345
- function motionBlurSampleTimes(plan2, frameIndex, fps) {
56352
+ function motionBlurSampleTimes(plan2, frameIndex, fps, samplesPerFrame) {
56346
56353
  const grid = fps * plan2.subFrameDivisions;
56347
56354
  const frameTicks = frameIndex * plan2.subFrameDivisions;
56348
- return plan2.sampleTickOffsets.map((offset2) => Math.max(0, frameTicks + offset2) / grid);
56355
+ return sampleTickOffsets(plan2, samplesPerFrame).map(
56356
+ (offset2) => Math.max(0, frameTicks + offset2) / grid
56357
+ );
56349
56358
  }
56350
- function motionBlurWindowIsStatic(plan2, frameIndex, staticFrames) {
56351
- const first = Math.floor(
56352
- frameIndex - 1 + Math.min(...plan2.sampleTickOffsets) / plan2.subFrameDivisions
56359
+ function motionBlurProbeTimes(plan2, frameIndex, fps) {
56360
+ const grid = fps * plan2.subFrameDivisions;
56361
+ const frameTicks = frameIndex * plan2.subFrameDivisions;
56362
+ const startTick = Math.round(plan2.windowStartFrames * plan2.subFrameDivisions);
56363
+ const endTick = Math.round(
56364
+ (plan2.windowStartFrames + plan2.shutterFrames) * plan2.subFrameDivisions
56353
56365
  );
56354
- const last = Math.floor(frameIndex + Math.max(...plan2.sampleTickOffsets) / plan2.subFrameDivisions) + 1;
56366
+ return {
56367
+ windowStart: Math.max(0, frameTicks + startTick) / grid,
56368
+ windowEnd: Math.max(0, frameTicks + endTick) / grid
56369
+ };
56370
+ }
56371
+ function motionBlurWindowIsStatic(plan2, frameIndex, staticFrames) {
56372
+ const worstCaseSamples = plan2.fixedSamplesPerFrame ?? MAX_SAMPLES_PER_FRAME;
56373
+ const inset = plan2.shutterFrames / (2 * worstCaseSamples);
56374
+ const minOffsetFrames = plan2.windowStartFrames + inset;
56375
+ const maxOffsetFrames = plan2.windowStartFrames + plan2.shutterFrames - inset;
56376
+ const first = Math.floor(frameIndex - 1 + Math.min(0, minOffsetFrames));
56377
+ const last = Math.floor(frameIndex + Math.max(0, maxOffsetFrames)) + 1;
56355
56378
  for (let frame = first; frame <= last; frame++) {
56356
56379
  if (!staticFrames.has(frame)) return false;
56357
56380
  }
56358
56381
  return true;
56359
56382
  }
56383
+ function adaptiveSampleCount(diff) {
56384
+ for (const step of ADAPTIVE_SAMPLE_STEPS) {
56385
+ if (diff <= step.maxDiff) return step.samples;
56386
+ }
56387
+ return MAX_SAMPLES_PER_FRAME;
56388
+ }
56389
+ function probeDiffMagnitude(a, b) {
56390
+ const decodedA = decodePng(a);
56391
+ const decodedB = decodePng(b);
56392
+ if (decodedA.width !== decodedB.width || decodedA.height !== decodedB.height) {
56393
+ throw new Error(
56394
+ `probeDiffMagnitude: sample geometry mismatch (${decodedA.width}x${decodedA.height} vs ${decodedB.width}x${decodedB.height})`
56395
+ );
56396
+ }
56397
+ let sum = 0;
56398
+ for (let i = 0; i < decodedA.data.length; i++) {
56399
+ sum += Math.abs(decodedA.data[i] - decodedB.data[i]);
56400
+ }
56401
+ return sum / decodedA.data.length;
56402
+ }
56360
56403
  function linearToByte(value) {
56361
56404
  const c = value <= 31308e-7 ? value * 12.92 : 1.055 * Math.pow(value, 1 / 2.4) - 0.055;
56362
56405
  return clampByte(c * 255);
@@ -56364,7 +56407,7 @@ function linearToByte(value) {
56364
56407
  function clampByte(value) {
56365
56408
  return Math.min(255, Math.max(0, Math.round(value)));
56366
56409
  }
56367
- var MAX_SAMPLES_PER_FRAME, DEFAULT_SAMPLES_PER_FRAME, DEFAULT_SHUTTER_ANGLE, DEFAULT_SHUTTER_PHASE, SUB_FRAME_DIVISIONS, SRGB_TO_LINEAR, SRGB_PASSTHROUGH, MotionBlurAccumulator;
56410
+ var MAX_SAMPLES_PER_FRAME, DEFAULT_SAMPLES_PER_FRAME, DEFAULT_SHUTTER_ANGLE, DEFAULT_SHUTTER_PHASE, SUB_FRAME_DIVISIONS, ADAPTIVE_SAMPLE_STEPS, SPATIAL_TWEEN_PROPERTIES, SRGB_TO_LINEAR, SRGB_PASSTHROUGH, MotionBlurAccumulator;
56368
56411
  var init_motionBlur = __esm({
56369
56412
  "../engine/src/services/motionBlur.ts"() {
56370
56413
  "use strict";
@@ -56374,6 +56417,42 @@ var init_motionBlur = __esm({
56374
56417
  DEFAULT_SHUTTER_ANGLE = 180;
56375
56418
  DEFAULT_SHUTTER_PHASE = -90;
56376
56419
  SUB_FRAME_DIVISIONS = 4096;
56420
+ ADAPTIVE_SAMPLE_STEPS = [
56421
+ { maxDiff: 3, samples: DEFAULT_SAMPLES_PER_FRAME },
56422
+ { maxDiff: 6, samples: 32 },
56423
+ { maxDiff: Infinity, samples: MAX_SAMPLES_PER_FRAME }
56424
+ ];
56425
+ SPATIAL_TWEEN_PROPERTIES = [
56426
+ "x",
56427
+ "y",
56428
+ "z",
56429
+ "xPercent",
56430
+ "yPercent",
56431
+ "rotation",
56432
+ "rotate",
56433
+ "rotationX",
56434
+ "rotationY",
56435
+ "rotationZ",
56436
+ "scale",
56437
+ "scaleX",
56438
+ "scaleY",
56439
+ "scaleZ",
56440
+ "skewX",
56441
+ "skewY",
56442
+ "perspective",
56443
+ "transformPerspective",
56444
+ "top",
56445
+ "left",
56446
+ "right",
56447
+ "bottom",
56448
+ "width",
56449
+ "height",
56450
+ "translate",
56451
+ "translateX",
56452
+ "translateY",
56453
+ "translateZ",
56454
+ "transform"
56455
+ ];
56377
56456
  SRGB_TO_LINEAR = (() => {
56378
56457
  const lut = new Float64Array(256);
56379
56458
  for (let i = 0; i < 256; i++) lut[i] = srgbByteToLinear(i);
@@ -59995,7 +60074,13 @@ function isStaticDedupFrameAnalysisSafe(totalFrames) {
59995
60074
  return Number.isFinite(totalFrames) && Number.isSafeInteger(totalFrames) && totalFrames > 0 && totalFrames <= MAX_STATIC_DEDUP_ANALYSIS_FRAMES;
59996
60075
  }
59997
60076
  async function computeStaticFrameSet(page, fps) {
59998
- const result = await page.evaluate(() => {
60077
+ const result = await page.evaluate((spatialProps) => {
60078
+ const SPATIAL_PROPS = new Set(spatialProps);
60079
+ function isSpatial(vars) {
60080
+ if (!vars) return false;
60081
+ for (const key2 of Object.keys(vars)) if (SPATIAL_PROPS.has(key2)) return true;
60082
+ return false;
60083
+ }
59999
60084
  const intervals2 = [];
60000
60085
  let tweenCount2 = 0;
60001
60086
  let hasTimelineCall2 = false;
@@ -60003,7 +60088,7 @@ async function computeStaticFrameSet(page, fps) {
60003
60088
  if (typeof tl.getChildren !== "function") return;
60004
60089
  if (typeof tl.vars?.onUpdate === "function") {
60005
60090
  const total = typeof tl.totalDuration === "function" ? tl.totalDuration() : 0;
60006
- if (total > 0) intervals2.push({ start: offset2, end: offset2 + total });
60091
+ if (total > 0) intervals2.push({ start: offset2, end: offset2 + total, spatial: true });
60007
60092
  }
60008
60093
  for (const child of tl.getChildren(false, true, true)) {
60009
60094
  const start = offset2 + (typeof child.startTime === "function" ? child.startTime() : 0);
@@ -60011,14 +60096,14 @@ async function computeStaticFrameSet(page, fps) {
60011
60096
  const total = typeof child.totalDuration === "function" ? child.totalDuration() : single;
60012
60097
  if (typeof child.getChildren === "function") {
60013
60098
  if (total > single + 1e-6) {
60014
- intervals2.push({ start, end: start + total });
60099
+ intervals2.push({ start, end: start + total, spatial: true });
60015
60100
  walk(child, start);
60016
60101
  } else {
60017
60102
  walk(child, start);
60018
60103
  }
60019
60104
  } else {
60020
60105
  tweenCount2++;
60021
- intervals2.push({ start, end: start + total });
60106
+ intervals2.push({ start, end: start + total, spatial: isSpatial(child.vars) });
60022
60107
  if (total <= 1e-6 && (typeof child.vars?.onComplete === "function" || typeof child.vars?.onReverseComplete === "function")) {
60023
60108
  hasTimelineCall2 = true;
60024
60109
  }
@@ -60059,7 +60144,7 @@ async function computeStaticFrameSet(page, fps) {
60059
60144
  hasUnresolvableClipStart: hasUnresolvableClipStart2,
60060
60145
  hasTimelineCall: hasTimelineCall2
60061
60146
  };
60062
- });
60147
+ }, SPATIAL_TWEEN_PROPERTIES);
60063
60148
  const {
60064
60149
  intervals,
60065
60150
  tweenCount,
@@ -60075,6 +60160,7 @@ async function computeStaticFrameSet(page, fps) {
60075
60160
  return {
60076
60161
  totalFrames,
60077
60162
  staticFrameSet: /* @__PURE__ */ new Set(),
60163
+ nonSpatialOnlyFrameSet: /* @__PURE__ */ new Set(),
60078
60164
  hasVideo,
60079
60165
  hasCanvas,
60080
60166
  hasNonGsapAnim,
@@ -60084,10 +60170,14 @@ async function computeStaticFrameSet(page, fps) {
60084
60170
  };
60085
60171
  }
60086
60172
  const animated = /* @__PURE__ */ new Set();
60087
- for (const { start, end } of intervals) {
60173
+ const spatialFrameSet = /* @__PURE__ */ new Set();
60174
+ for (const { start, end, spatial } of intervals) {
60088
60175
  const lo = Math.max(0, Math.floor(start * fps));
60089
60176
  const hi = Math.min(totalFrames - 1, Math.ceil(end * fps));
60090
- for (let f = lo; f <= hi; f++) animated.add(f);
60177
+ for (let f = lo; f <= hi; f++) {
60178
+ animated.add(f);
60179
+ if (spatial) spatialFrameSet.add(f);
60180
+ }
60091
60181
  }
60092
60182
  for (const f of await computeClipBoundaryFrames(page, fps)) animated.add(f);
60093
60183
  const reasons = [];
@@ -60105,9 +60195,14 @@ async function computeStaticFrameSet(page, fps) {
60105
60195
  if (!animated.has(f) && !animated.has(f - 1)) staticFrameSet.add(f);
60106
60196
  }
60107
60197
  }
60198
+ const nonSpatialOnlyFrameSet = /* @__PURE__ */ new Set();
60199
+ if (eligible) {
60200
+ for (const f of animated) if (!spatialFrameSet.has(f)) nonSpatialOnlyFrameSet.add(f);
60201
+ }
60108
60202
  return {
60109
60203
  totalFrames,
60110
60204
  staticFrameSet,
60205
+ nonSpatialOnlyFrameSet,
60111
60206
  hasVideo,
60112
60207
  hasCanvas,
60113
60208
  hasNonGsapAnim,
@@ -60317,6 +60412,11 @@ async function verifyStaticFramesSafe(session, page, staticFrames, fps, sampleCo
60317
60412
  }
60318
60413
  }
60319
60414
  async function armStaticDedup(session, page, logInitPhase) {
60415
+ let sharedStaticFrameStats;
60416
+ if (session.options.motionBlur && session.options.motionBlur.samplesPerFrame === void 0 && !session.motionBlurNonSpatialFrames) {
60417
+ sharedStaticFrameStats = await computeStaticFrameSet(page, fpsToNumber(session.options.fps));
60418
+ session.motionBlurNonSpatialFrames = sharedStaticFrameStats.nonSpatialOnlyFrameSet;
60419
+ }
60320
60420
  if (session.staticFrames || session.staticDedupSkipReason) return;
60321
60421
  session.staticDedupEnabled = session.config?.staticFrameDedup !== false;
60322
60422
  if (!session.staticDedupEnabled) return;
@@ -60341,7 +60441,7 @@ async function armStaticDedup(session, page, logInitPhase) {
60341
60441
  return;
60342
60442
  }
60343
60443
  const fps = fpsToNumber(session.options.fps);
60344
- const stats = await computeStaticFrameSet(page, fps);
60444
+ const stats = sharedStaticFrameStats ?? await computeStaticFrameSet(page, fps);
60345
60445
  if (!stats.eligible || stats.staticFrameSet.size === 0) {
60346
60446
  session.staticDedupSkipReason = "ineligible";
60347
60447
  logInitPhase(`static-frame dedup: disabled (${stats.reason})`);
@@ -60587,6 +60687,25 @@ function finalizeSessionInit(session) {
60587
60687
  session.motionBlur = resolveSessionMotionBlur(session);
60588
60688
  session.isInitialized = true;
60589
60689
  }
60690
+ async function resolveAdaptiveSampleCount(session, frameIndex, absFrameIndex, plan2, fps, sampleSeek) {
60691
+ if (session.motionBlurNonSpatialFrames?.has(absFrameIndex)) {
60692
+ return {
60693
+ samplesPerFrame: DEFAULT_SAMPLES_PER_FRAME,
60694
+ seekMs: 0,
60695
+ beforeCaptureMs: 0,
60696
+ screenshotMs: 0
60697
+ };
60698
+ }
60699
+ const { windowStart, windowEnd } = motionBlurProbeTimes(plan2, absFrameIndex, fps);
60700
+ const probeA = await captureFrameSurface(session, frameIndex, windowStart, sampleSeek);
60701
+ const probeB = await captureFrameSurface(session, frameIndex, windowEnd, sampleSeek);
60702
+ return {
60703
+ samplesPerFrame: adaptiveSampleCount(probeDiffMagnitude(probeA.buffer, probeB.buffer)),
60704
+ seekMs: probeA.seekMs + probeB.seekMs,
60705
+ beforeCaptureMs: probeA.beforeCaptureMs + probeB.beforeCaptureMs,
60706
+ screenshotMs: probeA.screenshotMs + probeB.screenshotMs
60707
+ };
60708
+ }
60590
60709
  async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan2) {
60591
60710
  const fps = fpsToNumber(session.options.fps);
60592
60711
  const frameTime = quantizeSeekTime(absFrameIndex / fps, fps);
@@ -60597,8 +60716,23 @@ async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan2
60597
60716
  suppressEvents: true,
60598
60717
  subFrameDivisions: plan2.subFrameDivisions
60599
60718
  };
60719
+ let samplesPerFrame = plan2.fixedSamplesPerFrame;
60720
+ if (samplesPerFrame === null) {
60721
+ const chosen = await resolveAdaptiveSampleCount(
60722
+ session,
60723
+ frameIndex,
60724
+ absFrameIndex,
60725
+ plan2,
60726
+ fps,
60727
+ sampleSeek
60728
+ );
60729
+ samplesPerFrame = chosen.samplesPerFrame;
60730
+ totals.seekMs += chosen.seekMs;
60731
+ totals.beforeCaptureMs += chosen.beforeCaptureMs;
60732
+ totals.screenshotMs += chosen.screenshotMs;
60733
+ }
60600
60734
  const accumulator = new MotionBlurAccumulator(plan2.blend);
60601
- for (const sampleTime of motionBlurSampleTimes(plan2, absFrameIndex, fps)) {
60735
+ for (const sampleTime of motionBlurSampleTimes(plan2, absFrameIndex, fps, samplesPerFrame)) {
60602
60736
  const sample = await captureFrameSurface(session, frameIndex, sampleTime, sampleSeek);
60603
60737
  totals.seekMs += sample.seekMs;
60604
60738
  totals.beforeCaptureMs += sample.beforeCaptureMs;
@@ -100520,8 +100654,8 @@ async function runFontLocalize(io, localize) {
100520
100654
  }
100521
100655
 
100522
100656
  // src/version.ts
100523
- var VERSION = true ? "0.8.45" : "0.0.0-dev";
100524
- var PRODUCER_VERSION = true ? "0.8.45" : "0.0.0-dev";
100657
+ var VERSION = true ? "0.8.46" : "0.0.0-dev";
100658
+ var PRODUCER_VERSION = true ? "0.8.46" : "0.0.0-dev";
100525
100659
 
100526
100660
  // src/fontLocalizeCli.ts
100527
100661
  async function readStdin() {
@@ -1,4 +1,4 @@
1
- "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.45/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
1
+ "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.46/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;