@camstack/addon-decoder-ffmpeg 1.2.13 → 1.2.14

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/index.js CHANGED
@@ -23714,13 +23714,24 @@ method(object({
23714
23714
  /** Playback-speed multiplier for the render (1 = realtime). */
23715
23715
  var ExportSpeedSchema = number().min(.25).max(32);
23716
23716
  /**
23717
- * One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
23717
+ * One dense interval, in WALL-CLOCK SECONDS FROM THE EXPORT'S OWN `fromMs`.
23718
23718
  *
23719
- * Relative and not absolute epoch on purpose: the renderer's frame-select
23720
- * expression sees ffmpeg's `t`, which starts at 0 for the export's source
23721
- * playlist. Handing it absolute epochs would make every call site responsible
23722
- * for the same subtraction, and the one that forgot would emit a filter that
23723
- * selects nothing silently, as a uniform timelapse.
23719
+ * **Wall clock, not ffmpeg's `t`** and the recorder translates. A caller
23720
+ * derives these bounds from things that happened at a TIME (a track's
23721
+ * `firstSeen`), while `t` runs over the source playlist: the concatenation of
23722
+ * every segment present for the range, with each recording GAP removed. The
23723
+ * two agree only on a window that recorded without one interruption, and only
23724
+ * the render side knows the segments, so the translation lives there
23725
+ * (`export-dense-map.ts`, addon-pipeline).
23726
+ *
23727
+ * It was not always so. These seconds were fed to `between(t,…)` verbatim, and
23728
+ * on a 10 h window holding 29,393 s of footage every range landed late by the
23729
+ * gap accumulated before it — up to 6,607 s, well past EOF. Nothing matched,
23730
+ * the video was a uniform timelapse, and the log line reported the five ranges
23731
+ * that had been ASKED for (2026-08-13, export `57d14363`, camera 615).
23732
+ *
23733
+ * Relative and not absolute epoch, because an absolute epoch would make every
23734
+ * call site responsible for the same subtraction.
23724
23735
  */
23725
23736
  var ExportDenseRangeSchema = object({
23726
23737
  fromSec: number().nonnegative(),
@@ -30850,25 +30861,32 @@ object({
30850
30861
  var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
30851
30862
  object({
30852
30863
  /**
30853
- * How long a retained native frame is served before it counts as a miss.
30864
+ * How many delivered frames the worker HOLDS at once, waiting for each one's
30865
+ * detection result.
30854
30866
  *
30855
- * Must cover the FULL late-crop horizon: detection inference + the
30856
- * cross-process inference-result hop to hub post-analysis + tracking + the
30857
- * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
30858
- * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
30859
- * RAM per busy camera grows linearly with no measured hit-rate gain.
30867
+ * This replaced a TTL on 2026-08-13, and the replacement is the whole point:
30868
+ * a time window was never related to the event the pixels were waiting for.
30869
+ * A held frame now lives from delivery until the runner has its `FrameResult`
30870
+ * at which moment the runner cuts the subject tiles it actually wanted and
30871
+ * releases the frame. The bound exists only so a runner that stops answering
30872
+ * cannot pin RAM: above it the OLDEST held frame is dropped and counted.
30873
+ *
30874
+ * Sizing: the steady state is `inferenceLatency × deliveredFps`, measured at
30875
+ * 40-160 ms × ≤25 fps = 1-4 frames. The default leaves headroom for a hiccup
30876
+ * without ever approaching the old resident set (43 frames × 24.9 MB at 4K).
30877
+ * Raising it does not buy hit rate — it buys tolerance for a slow runner, and
30878
+ * `holdOverflow` on the metrics line is what says you need it.
30860
30879
  */
30861
- ttlMs: number().int().min(250).max(1e4),
30880
+ holdFrames: number().int().min(1).max(64),
30862
30881
  /**
30863
30882
  * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
30864
30883
  *
30865
- * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
30866
- * which one is actually binding before reasoning from that. At the shipped
30867
- * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
30868
- * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
30869
- * `leaseFrames` on the metrics line say which. When the ceiling binds, a
30870
- * change that admits fewer frames buys retention WINDOW at constant RAM
30871
- * rather than giving RAM back — lower this knob if RAM is what you wanted.
30884
+ * Since 2026-08-13 this is a SAFETY ceiling and nothing else: `holdFrames`
30885
+ * is what decides how much is held, and the ceiling is the number above which
30886
+ * something is wrong. Before that it was the effective cap at 1024 MB with
30887
+ * a 2 800 ms TTL a 4K camera sat pinned at `leaseMb:1020, leaseFrames:43`
30888
+ * with the TTL expiring nothing, which is exactly the confusion the hold
30889
+ * removes. `leaseMb` / `leaseFrames` still say what is resident.
30872
30890
  * `0` DISABLES the lease entirely and falls the worker back to the tiny
30873
30891
  * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
30874
30892
  * to replace).
@@ -30894,22 +30912,45 @@ object({
30894
30912
  * there is the signal that some caller names frames outside the inference set
30895
30913
  * and that this must go back to `all`.
30896
30914
  */
30897
- admission: NativeLeaseAdmissionSchema
30915
+ admission: NativeLeaseAdmissionSchema,
30916
+ /**
30917
+ * RAM ceiling per decode worker, in MB, for the SUBJECT TILES — the
30918
+ * compressed native crops the worker cuts at the moment a frame's detection
30919
+ * result arrives, and keeps long after the frame itself is freed.
30920
+ *
30921
+ * This is the knob that replaced the old retention window, and it buys about
30922
+ * three orders of magnitude more of it: a tile is one subject at native
30923
+ * resolution, JPEG-encoded (~60-120 KB on a 4K person), against ~24.9 MB for
30924
+ * the frame it was cut from. A frame on which nothing was detected costs
30925
+ * nothing at all, which is the real change — the old lease paid per FRAME and
30926
+ * was interrogated per SUBJECT.
30927
+ *
30928
+ * `0` DISABLES tiles, leaving only the hold window and the ≤640 RAM
30929
+ * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
30930
+ * reproduce that.
30931
+ */
30932
+ tileBudgetMb: number().int().min(0).max(1024)
30898
30933
  });
30899
30934
  /**
30900
- * The values in force when the operator has set nothing — byte-for-byte the
30901
- * constants the decode worker shipped with as env-var defaults, so making these
30902
- * settings changed no behaviour on the day it landed.
30935
+ * The values in force when the operator has set nothing.
30936
+ *
30937
+ * `budgetMb` stays at 1024 on the day the hold landed, deliberately: it stopped
30938
+ * being the retention window and became the OOM ceiling, and lowering a ceiling
30939
+ * in the same change that redefines it would make a regression and a retune
30940
+ * indistinguishable. Cut it once `tileHits` / `holdOverflow` have been read on
30941
+ * live traffic.
30903
30942
  */
30904
30943
  var DEFAULT_NATIVE_LEASE_SETTINGS = {
30905
- ttlMs: 1200,
30944
+ holdFrames: 8,
30906
30945
  budgetMb: 1024,
30907
30946
  activityMs: 15e3,
30947
+ tileBudgetMb: 64,
30908
30948
  admission: "inferred"
30909
30949
  };
30910
- DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
30950
+ DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
30911
30951
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
30912
30952
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
30953
+ DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
30913
30954
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
30914
30955
  /**
30915
30956
  * Fixed-capacity ring buffer. When full, push() overwrites the oldest entry.
package/dist/index.mjs CHANGED
@@ -23710,13 +23710,24 @@ method(object({
23710
23710
  /** Playback-speed multiplier for the render (1 = realtime). */
23711
23711
  var ExportSpeedSchema = number().min(.25).max(32);
23712
23712
  /**
23713
- * One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
23713
+ * One dense interval, in WALL-CLOCK SECONDS FROM THE EXPORT'S OWN `fromMs`.
23714
23714
  *
23715
- * Relative and not absolute epoch on purpose: the renderer's frame-select
23716
- * expression sees ffmpeg's `t`, which starts at 0 for the export's source
23717
- * playlist. Handing it absolute epochs would make every call site responsible
23718
- * for the same subtraction, and the one that forgot would emit a filter that
23719
- * selects nothing silently, as a uniform timelapse.
23715
+ * **Wall clock, not ffmpeg's `t`** and the recorder translates. A caller
23716
+ * derives these bounds from things that happened at a TIME (a track's
23717
+ * `firstSeen`), while `t` runs over the source playlist: the concatenation of
23718
+ * every segment present for the range, with each recording GAP removed. The
23719
+ * two agree only on a window that recorded without one interruption, and only
23720
+ * the render side knows the segments, so the translation lives there
23721
+ * (`export-dense-map.ts`, addon-pipeline).
23722
+ *
23723
+ * It was not always so. These seconds were fed to `between(t,…)` verbatim, and
23724
+ * on a 10 h window holding 29,393 s of footage every range landed late by the
23725
+ * gap accumulated before it — up to 6,607 s, well past EOF. Nothing matched,
23726
+ * the video was a uniform timelapse, and the log line reported the five ranges
23727
+ * that had been ASKED for (2026-08-13, export `57d14363`, camera 615).
23728
+ *
23729
+ * Relative and not absolute epoch, because an absolute epoch would make every
23730
+ * call site responsible for the same subtraction.
23720
23731
  */
23721
23732
  var ExportDenseRangeSchema = object({
23722
23733
  fromSec: number().nonnegative(),
@@ -30846,25 +30857,32 @@ object({
30846
30857
  var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
30847
30858
  object({
30848
30859
  /**
30849
- * How long a retained native frame is served before it counts as a miss.
30860
+ * How many delivered frames the worker HOLDS at once, waiting for each one's
30861
+ * detection result.
30850
30862
  *
30851
- * Must cover the FULL late-crop horizon: detection inference + the
30852
- * cross-process inference-result hop to hub post-analysis + tracking + the
30853
- * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
30854
- * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
30855
- * RAM per busy camera grows linearly with no measured hit-rate gain.
30863
+ * This replaced a TTL on 2026-08-13, and the replacement is the whole point:
30864
+ * a time window was never related to the event the pixels were waiting for.
30865
+ * A held frame now lives from delivery until the runner has its `FrameResult`
30866
+ * at which moment the runner cuts the subject tiles it actually wanted and
30867
+ * releases the frame. The bound exists only so a runner that stops answering
30868
+ * cannot pin RAM: above it the OLDEST held frame is dropped and counted.
30869
+ *
30870
+ * Sizing: the steady state is `inferenceLatency × deliveredFps`, measured at
30871
+ * 40-160 ms × ≤25 fps = 1-4 frames. The default leaves headroom for a hiccup
30872
+ * without ever approaching the old resident set (43 frames × 24.9 MB at 4K).
30873
+ * Raising it does not buy hit rate — it buys tolerance for a slow runner, and
30874
+ * `holdOverflow` on the metrics line is what says you need it.
30856
30875
  */
30857
- ttlMs: number().int().min(250).max(1e4),
30876
+ holdFrames: number().int().min(1).max(64),
30858
30877
  /**
30859
30878
  * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
30860
30879
  *
30861
- * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
30862
- * which one is actually binding before reasoning from that. At the shipped
30863
- * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
30864
- * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
30865
- * `leaseFrames` on the metrics line say which. When the ceiling binds, a
30866
- * change that admits fewer frames buys retention WINDOW at constant RAM
30867
- * rather than giving RAM back — lower this knob if RAM is what you wanted.
30880
+ * Since 2026-08-13 this is a SAFETY ceiling and nothing else: `holdFrames`
30881
+ * is what decides how much is held, and the ceiling is the number above which
30882
+ * something is wrong. Before that it was the effective cap at 1024 MB with
30883
+ * a 2 800 ms TTL a 4K camera sat pinned at `leaseMb:1020, leaseFrames:43`
30884
+ * with the TTL expiring nothing, which is exactly the confusion the hold
30885
+ * removes. `leaseMb` / `leaseFrames` still say what is resident.
30868
30886
  * `0` DISABLES the lease entirely and falls the worker back to the tiny
30869
30887
  * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
30870
30888
  * to replace).
@@ -30890,22 +30908,45 @@ object({
30890
30908
  * there is the signal that some caller names frames outside the inference set
30891
30909
  * and that this must go back to `all`.
30892
30910
  */
30893
- admission: NativeLeaseAdmissionSchema
30911
+ admission: NativeLeaseAdmissionSchema,
30912
+ /**
30913
+ * RAM ceiling per decode worker, in MB, for the SUBJECT TILES — the
30914
+ * compressed native crops the worker cuts at the moment a frame's detection
30915
+ * result arrives, and keeps long after the frame itself is freed.
30916
+ *
30917
+ * This is the knob that replaced the old retention window, and it buys about
30918
+ * three orders of magnitude more of it: a tile is one subject at native
30919
+ * resolution, JPEG-encoded (~60-120 KB on a 4K person), against ~24.9 MB for
30920
+ * the frame it was cut from. A frame on which nothing was detected costs
30921
+ * nothing at all, which is the real change — the old lease paid per FRAME and
30922
+ * was interrogated per SUBJECT.
30923
+ *
30924
+ * `0` DISABLES tiles, leaving only the hold window and the ≤640 RAM
30925
+ * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
30926
+ * reproduce that.
30927
+ */
30928
+ tileBudgetMb: number().int().min(0).max(1024)
30894
30929
  });
30895
30930
  /**
30896
- * The values in force when the operator has set nothing — byte-for-byte the
30897
- * constants the decode worker shipped with as env-var defaults, so making these
30898
- * settings changed no behaviour on the day it landed.
30931
+ * The values in force when the operator has set nothing.
30932
+ *
30933
+ * `budgetMb` stays at 1024 on the day the hold landed, deliberately: it stopped
30934
+ * being the retention window and became the OOM ceiling, and lowering a ceiling
30935
+ * in the same change that redefines it would make a regression and a retune
30936
+ * indistinguishable. Cut it once `tileHits` / `holdOverflow` have been read on
30937
+ * live traffic.
30899
30938
  */
30900
30939
  var DEFAULT_NATIVE_LEASE_SETTINGS = {
30901
- ttlMs: 1200,
30940
+ holdFrames: 8,
30902
30941
  budgetMb: 1024,
30903
30942
  activityMs: 15e3,
30943
+ tileBudgetMb: 64,
30904
30944
  admission: "inferred"
30905
30945
  };
30906
- DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
30946
+ DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
30907
30947
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
30908
30948
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
30949
+ DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
30909
30950
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
30910
30951
  /**
30911
30952
  * Fixed-capacity ring buffer. When full, push() overwrites the oldest entry.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-ffmpeg",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
4
4
  "description": "Standalone ffmpeg-subprocess decoder fallback addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",