@camstack/types 1.2.40 → 1.2.42

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.
Files changed (35) hide show
  1. package/dist/addon.js +1 -1
  2. package/dist/addon.mjs +1 -1
  3. package/dist/canonical-hash-7nfBbEqR.mjs +35 -0
  4. package/dist/canonical-hash-BcZHRHIx.js +40 -0
  5. package/dist/capabilities/index.d.ts +2 -2
  6. package/dist/capabilities/notification-rules.cap.d.ts +41 -0
  7. package/dist/capabilities/pipeline-analytics.cap.d.ts +93 -6
  8. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +123 -0
  9. package/dist/capabilities/pipeline-runner.cap.d.ts +143 -7
  10. package/dist/capabilities/platform-probe.cap.d.ts +3 -3
  11. package/dist/capabilities/recording.cap.d.ts +3 -0
  12. package/dist/capabilities/stream-broker.cap.d.ts +300 -0
  13. package/dist/encode-profile.d.ts +2 -0
  14. package/dist/ffmpeg/encode-defaults.d.ts +89 -0
  15. package/dist/ffmpeg/hwaccel.d.ts +98 -0
  16. package/dist/ffmpeg/invocation.d.ts +250 -0
  17. package/dist/ffmpeg/process.d.ts +135 -0
  18. package/dist/ffmpeg/sharing-key.d.ts +39 -0
  19. package/dist/generated/addon-api.d.ts +56 -0
  20. package/dist/generated/device-proxy.d.ts +1 -1
  21. package/dist/generated/method-access-map.d.ts +1 -1
  22. package/dist/generated/system-proxy.d.ts +2 -2
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.js +1595 -28
  25. package/dist/index.mjs +1548 -29
  26. package/dist/interfaces/camera-switches.d.ts +217 -0
  27. package/dist/interfaces/ops-log.d.ts +4 -0
  28. package/dist/interfaces/pipeline-runner-capability.d.ts +9 -1
  29. package/dist/node.d.ts +2 -0
  30. package/dist/node.js +270 -36
  31. package/dist/node.mjs +269 -36
  32. package/dist/pipeline/detail-crop.d.ts +122 -0
  33. package/dist/{sleep-CXimb854.mjs → sleep-BmNKsY7v.mjs} +5 -0
  34. package/dist/{sleep-DTce7-ch.js → sleep-Cvi1JxZp.js} +5 -0
  35. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { type InferProvider } from './capability-definition.js';
3
2
  import type { ConfigField } from '../interfaces/config-ui.js';
3
+ import { type InferProvider } from './capability-definition.js';
4
4
  /**
5
5
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
6
6
  * decode worker resolves it against the RETAINED native frame's real pixel dims,
@@ -60,6 +60,27 @@ declare const DetailResultSchema: z.ZodObject<{
60
60
  }, z.core.$strip>;
61
61
  export type DetailParent = z.infer<typeof DetailParentSchema>;
62
62
  export type DetailResult = z.infer<typeof DetailResultSchema>;
63
+ /**
64
+ * Why an executing node REFUSED a stateless step run (`runStatelessStep`).
65
+ *
66
+ * A refusal is a first-class answer, not an error, because the caller's next
67
+ * move depends on WHICH one it is — and because "the pass produced nothing"
68
+ * must never be reachable without a named, counted cause. The two tiers:
69
+ *
70
+ * - **node-level** (`unknown-step`, `model-not-servable`) — this node can
71
+ * never serve this (step, model) pair. The caller drops it from its rotation
72
+ * and retries the same work elsewhere; nothing about the work changes.
73
+ * - **work-level** (`unreadable-frame`, `execution-failed`) — this node is
74
+ * fine, this one request is not. Retrying it on another node would only
75
+ * spread the same failure.
76
+ */
77
+ declare const StatelessStepRefusalSchema: z.ZodEnum<{
78
+ "unknown-step": "unknown-step";
79
+ "model-not-servable": "model-not-servable";
80
+ "unreadable-frame": "unreadable-frame";
81
+ "execution-failed": "execution-failed";
82
+ }>;
83
+ export type StatelessStepRefusal = z.infer<typeof StatelessStepRefusalSchema>;
63
84
  /**
64
85
  * Source enum for motion signals fed to the runner. Extensible — add
65
86
  * new variants here when new motion-trigger paths are wired in
@@ -533,12 +554,29 @@ export declare const pipelineRunnerCapability: {
533
554
  * for a single tracked detection. The per-frame plane (`runPipeline`
534
555
  * with `plane: 'frame'`) skips crop children entirely; a track-level
535
556
  * caller invokes this per-track, on its own cadence, instead of on
536
- * every frame. Takes either a `frameHandle` (shm lease/session
537
- * preferred, zero-copy) or a `cropJpeg` fallback when the lease/
538
- * session backing the frame is already gone. `steps` narrows which
539
- * configured children to run (default: all configured children for
540
- * `parent.className`). Returns `null` when neither frame source is
541
- * resolvable (handle evicted and no cropJpeg fallback supplied).
557
+ * every frame. `steps` narrows which configured children to run
558
+ * (default: all configured children for `parent.className`).
559
+ *
560
+ * ## Three pixel sources, and WHO decides the rectangle
561
+ *
562
+ * Tried in this order; the difference between them is not the pixels but
563
+ * which side derives the crop, and confusing the two is how the CLIP index
564
+ * ended up holding two incomparable feature spaces:
565
+ *
566
+ * 1. `frameHandle` — shm lease/session, preferred and zero-copy. **The
567
+ * runner cuts**, applying the cluster crop convention.
568
+ * 2. `frameJpeg` — a FULL FRAME supplied by the caller (the embedding
569
+ * rebuild's stored key frame). **The runner cuts**, applying the same
570
+ * convention to the same function, so a rebuilt vector lands in the same
571
+ * feature space as a live one.
572
+ * 3. `cropJpeg` — an ALREADY-CUT tile. **The caller decided the
573
+ * rectangle**; the runner applies NO padding and no squaring and feeds
574
+ * it to the model verbatim. Only for the lease-miss retry, where the
575
+ * caller holds pixels the runner can no longer reach.
576
+ *
577
+ * Returns `null` when no source resolves (handle evicted and no fallback
578
+ * supplied), when the camera is not attached, or when no enabled step
579
+ * matches — every one of those is logged on the runner with the deviceId.
542
580
  */
543
581
  readonly runDetailSubtree: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
544
582
  deviceId: z.ZodNumber;
@@ -560,6 +598,7 @@ export declare const pipelineRunnerCapability: {
560
598
  nodeId: z.ZodString;
561
599
  slotCount: z.ZodNumber;
562
600
  }, z.core.$strip>>;
601
+ frameJpeg: z.ZodOptional<z.ZodString>;
563
602
  cropJpeg: z.ZodOptional<z.ZodString>;
564
603
  parent: z.ZodObject<{
565
604
  bbox: z.ZodObject<{
@@ -588,6 +627,92 @@ export declare const pipelineRunnerCapability: {
588
627
  nativeFaceShortSidePx: z.ZodOptional<z.ZodNumber>;
589
628
  }, z.core.$strip>>;
590
629
  }, z.core.$strip>>, "mutation">;
630
+ /**
631
+ * Run ONE enrichment step against caller-supplied pixels, with NO camera
632
+ * session — no attach, no frame handle, no device affinity.
633
+ *
634
+ * ## Why this exists next to `runDetailSubtree` and not inside it
635
+ *
636
+ * `runDetailSubtree` resolves its step tree from the LIVE ATTACH STATE. That
637
+ * is correct for it: its reason to exist is a frame HANDLE, and a handle is
638
+ * only resolvable while a decode session is open. But it makes the method
639
+ * useless to anything walking history — cameras run `detectionMode:
640
+ * 'on-motion'` and detach ~30 s after motion stops, so a fleet-wide
641
+ * embedding rebuild found most cameras detached and rebuilt only whatever
642
+ * happened to be attached at that second (measured 2026-08-06: 1125 tracks
643
+ * scanned, 121 rebuilt, 601 refused as "camera is not attached").
644
+ *
645
+ * A caller that ships its own pixels needs none of that machinery. So this
646
+ * method takes the three things a step actually needs — an image, a box, a
647
+ * step id — resolves the step from the CATALOG rather than from an
648
+ * attachment, and runs it on whichever node the caller pinned.
649
+ *
650
+ * ## What it deliberately keeps
651
+ *
652
+ * The crop rectangle is derived by the SAME `deriveDetailCropRect` call the
653
+ * live detail plane uses, from the same cluster-wide convention
654
+ * ([D52](../../../docs/decisions/adr-0052.md)). `frameJpeg` is a FULL frame,
655
+ * never a pre-cut tile, for exactly the reason `runDetailSubtree` documents:
656
+ * a caller that cuts is a second feature space.
657
+ *
658
+ * ## What it deliberately drops
659
+ *
660
+ * No device jump and no `deviceKey`: the jump roster comes from the attach
661
+ * config, which does not exist here. The step runs on the node's default
662
+ * engine. This is a maintenance path, not a latency-sensitive one.
663
+ *
664
+ * ## Routing
665
+ *
666
+ * NOT device-bound. `sourceDeviceId` is DIAGNOSTIC — the camera whose track
667
+ * these pixels came from, so every log line on the executing node can carry
668
+ * `tags: { deviceId }`. It is deliberately not named `deviceId`: that field
669
+ * is a routing hint that would send the call to the camera's owning node,
670
+ * which is the placement constraint this method exists to remove. Callers
671
+ * choose the node with `nodePin(nodeId)`; unpinned, an unowned call is
672
+ * served hub-in-process like any other singleton.
673
+ */
674
+ readonly runStatelessStep: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
675
+ stepId: z.ZodString;
676
+ modelId: z.ZodString;
677
+ frameJpeg: z.ZodString;
678
+ bbox: z.ZodObject<{
679
+ x: z.ZodNumber;
680
+ y: z.ZodNumber;
681
+ w: z.ZodNumber;
682
+ h: z.ZodNumber;
683
+ }, z.core.$strip>;
684
+ className: z.ZodString;
685
+ sourceDeviceId: z.ZodNumber;
686
+ }, z.core.$strip>, z.ZodDiscriminatedUnion<[z.ZodObject<{
687
+ kind: z.ZodLiteral<"ran">;
688
+ nodeId: z.ZodString;
689
+ modelId: z.ZodString;
690
+ details: z.ZodArray<z.ZodObject<{
691
+ stepId: z.ZodString;
692
+ className: z.ZodString;
693
+ score: z.ZodNumber;
694
+ bbox: z.ZodOptional<z.ZodObject<{
695
+ x: z.ZodNumber;
696
+ y: z.ZodNumber;
697
+ w: z.ZodNumber;
698
+ h: z.ZodNumber;
699
+ }, z.core.$strip>>;
700
+ embedding: z.ZodOptional<z.ZodString>;
701
+ label: z.ZodOptional<z.ZodString>;
702
+ alignedCropJpeg: z.ZodOptional<z.ZodString>;
703
+ nativeFaceShortSidePx: z.ZodOptional<z.ZodNumber>;
704
+ }, z.core.$strip>>;
705
+ }, z.core.$strip>, z.ZodObject<{
706
+ kind: z.ZodLiteral<"refused">;
707
+ nodeId: z.ZodString;
708
+ reason: z.ZodEnum<{
709
+ "unknown-step": "unknown-step";
710
+ "model-not-servable": "model-not-servable";
711
+ "unreadable-frame": "unreadable-frame";
712
+ "execution-failed": "execution-failed";
713
+ }>;
714
+ detail: z.ZodString;
715
+ }, z.core.$strip>], "kind">, "mutation">;
591
716
  };
592
717
  };
593
718
  export type IPipelineRunnerProvider = InferProvider<typeof pipelineRunnerCapability>;
@@ -605,4 +730,15 @@ export type IPipelineRunnerProvider = InferProvider<typeof pipelineRunnerCapabil
605
730
  */
606
731
  export type RunDetailSubtreeInput = Parameters<IPipelineRunnerProvider['runDetailSubtree']>[0];
607
732
  export type RunDetailSubtreeResult = NonNullable<Awaited<ReturnType<IPipelineRunnerProvider['runDetailSubtree']>>>;
733
+ /**
734
+ * Named input/output types for `runStatelessStep`, derived the same way and for
735
+ * the same reason as the `runDetailSubtree` pair above — the method's input
736
+ * stays an inline `z.object({…})` literal so `scripts/lib/parse-cap.ts` keeps
737
+ * classifying it correctly. Here the classification wanted is the OPPOSITE one:
738
+ * there is no `deviceId: z.number(` in the body, so the method is NOT
739
+ * device-bound and never lands on `DeviceProxy` — placement is the caller's
740
+ * `nodePin`, not the camera's owner.
741
+ */
742
+ export type RunStatelessStepInput = Parameters<IPipelineRunnerProvider['runStatelessStep']>[0];
743
+ export type RunStatelessStepResult = Awaited<ReturnType<IPipelineRunnerProvider['runStatelessStep']>>;
608
744
  export { RunnerCameraConfigSchema, RunnerLocalLoadSchema, RunnerLocalMetricsSchema };
@@ -277,12 +277,12 @@ export declare const platformProbeCapability: {
277
277
  coreml: "coreml";
278
278
  openvino: "openvino";
279
279
  none: "none";
280
- tensorrt: "tensorrt";
281
- cuda: "cuda";
282
280
  videotoolbox: "videotoolbox";
283
- nvdec: "nvdec";
284
281
  vaapi: "vaapi";
285
282
  qsv: "qsv";
283
+ cuda: "cuda";
284
+ tensorrt: "tensorrt";
285
+ nvdec: "nvdec";
286
286
  d3d11va: "d3d11va";
287
287
  dxva2: "dxva2";
288
288
  amf: "amf";
@@ -355,6 +355,7 @@ export declare const recordingCapability: {
355
355
  operator: "operator";
356
356
  retention: "retention";
357
357
  quota: "quota";
358
+ maintenance: "maintenance";
358
359
  }>>;
359
360
  }, z.core.$strip>, z.ZodObject<{
360
361
  floorMs: z.ZodNullable<z.ZodNumber>;
@@ -393,12 +394,14 @@ export declare const recordingCapability: {
393
394
  rescan: "rescan";
394
395
  "retention-run": "retention-run";
395
396
  relocate: "relocate";
397
+ "orphan-audit": "orphan-audit";
396
398
  }>;
397
399
  reason: z.ZodEnum<{
398
400
  manual: "manual";
399
401
  operator: "operator";
400
402
  retention: "retention";
401
403
  quota: "quota";
404
+ maintenance: "maintenance";
402
405
  }>;
403
406
  deviceId: z.ZodNullable<z.ZodNumber>;
404
407
  nodeId: z.ZodString;
@@ -279,6 +279,179 @@ export declare const RtpSourceSchema: z.ZodObject<{
279
279
  pipelineKey: z.ZodString;
280
280
  }, z.core.$strip>;
281
281
  export type RtpSource = z.infer<typeof RtpSourceSchema>;
282
+ /**
283
+ * The encode request — **structured and serialisable, with NO raw-flag escape
284
+ * hatch.** This is deliberate and it is the one lesson taken from
285
+ * `getStreamWithCodec`: that method's `outputArgs: string[]` is simultaneously
286
+ * its extensibility mechanism AND part of `pipelineKeyFor`'s sharing key, so
287
+ * adding a flag silently forks the shared child, and two consumers that mean
288
+ * the same thing but spell it differently never share. Here every knob is a
289
+ * NAMED field: a new requirement becomes a schema field (and a codegen run),
290
+ * never an opaque array.
291
+ *
292
+ * `inputArgs` / `outputArgs` are omitted from the profile for the same reason.
293
+ * The operator-facing derived-stream transform editor still has them — that is
294
+ * a different surface (`publishCameraStream({ kind: 'derived' })`) with a
295
+ * different purpose (reshaping a badly-behaved SOURCE), and it is unchanged.
296
+ */
297
+ export declare const EgressEncodeSchema: z.ZodObject<{
298
+ audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
299
+ codec: z.ZodEnum<{
300
+ copy: "copy";
301
+ opus: "opus";
302
+ aac: "aac";
303
+ pcmu: "pcmu";
304
+ pcma: "pcma";
305
+ }>;
306
+ bitrateKbps: z.ZodOptional<z.ZodNumber>;
307
+ sampleRateHz: z.ZodOptional<z.ZodNumber>;
308
+ channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
309
+ }, z.core.$strip>]>;
310
+ video: z.ZodObject<{
311
+ codec: z.ZodEnum<{
312
+ h264: "h264";
313
+ h265: "h265";
314
+ copy: "copy";
315
+ }>;
316
+ profile: z.ZodOptional<z.ZodEnum<{
317
+ high: "high";
318
+ baseline: "baseline";
319
+ main: "main";
320
+ }>>;
321
+ level: z.ZodOptional<z.ZodString>;
322
+ width: z.ZodOptional<z.ZodNumber>;
323
+ height: z.ZodOptional<z.ZodNumber>;
324
+ fps: z.ZodOptional<z.ZodNumber>;
325
+ bitrateKbps: z.ZodOptional<z.ZodNumber>;
326
+ gopFrames: z.ZodOptional<z.ZodNumber>;
327
+ bf: z.ZodOptional<z.ZodNumber>;
328
+ preset: z.ZodOptional<z.ZodEnum<{
329
+ fast: "fast";
330
+ ultrafast: "ultrafast";
331
+ superfast: "superfast";
332
+ veryfast: "veryfast";
333
+ faster: "faster";
334
+ medium: "medium";
335
+ }>>;
336
+ tune: z.ZodOptional<z.ZodEnum<{
337
+ zerolatency: "zerolatency";
338
+ film: "film";
339
+ animation: "animation";
340
+ }>>;
341
+ }, z.core.$strip>;
342
+ }, z.core.$strip>;
343
+ export type EgressEncode = z.infer<typeof EgressEncodeSchema>;
344
+ /**
345
+ * How the encoder is bounded. `'tight'` is a one-second VBV window for a
346
+ * consumer whose budget is enforced per second (HomeKit); `'relaxed'` is two
347
+ * seconds, letting a keyframe spike borrow from the next second (a browser,
348
+ * an Echo). Named rather than numeric so the INTENT survives.
349
+ */
350
+ export declare const EgressRateControlSchema: z.ZodEnum<{
351
+ tight: "tight";
352
+ relaxed: "relaxed";
353
+ }>;
354
+ export type EgressRateControl = z.infer<typeof EgressRateControlSchema>;
355
+ export declare const EgressTranscodeRequestSchema: z.ZodObject<{
356
+ deviceId: z.ZodNumber;
357
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
358
+ kind: z.ZodLiteral<"profile">;
359
+ profile: z.ZodEnum<{
360
+ high: "high";
361
+ mid: "mid";
362
+ low: "low";
363
+ }>;
364
+ }, z.core.$strip>, z.ZodObject<{
365
+ kind: z.ZodLiteral<"cam-stream">;
366
+ camStreamId: z.ZodString;
367
+ }, z.core.$strip>], "kind">;
368
+ encode: z.ZodObject<{
369
+ audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
370
+ codec: z.ZodEnum<{
371
+ copy: "copy";
372
+ opus: "opus";
373
+ aac: "aac";
374
+ pcmu: "pcmu";
375
+ pcma: "pcma";
376
+ }>;
377
+ bitrateKbps: z.ZodOptional<z.ZodNumber>;
378
+ sampleRateHz: z.ZodOptional<z.ZodNumber>;
379
+ channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
380
+ }, z.core.$strip>]>;
381
+ video: z.ZodObject<{
382
+ codec: z.ZodEnum<{
383
+ h264: "h264";
384
+ h265: "h265";
385
+ copy: "copy";
386
+ }>;
387
+ profile: z.ZodOptional<z.ZodEnum<{
388
+ high: "high";
389
+ baseline: "baseline";
390
+ main: "main";
391
+ }>>;
392
+ level: z.ZodOptional<z.ZodString>;
393
+ width: z.ZodOptional<z.ZodNumber>;
394
+ height: z.ZodOptional<z.ZodNumber>;
395
+ fps: z.ZodOptional<z.ZodNumber>;
396
+ bitrateKbps: z.ZodOptional<z.ZodNumber>;
397
+ gopFrames: z.ZodOptional<z.ZodNumber>;
398
+ bf: z.ZodOptional<z.ZodNumber>;
399
+ preset: z.ZodOptional<z.ZodEnum<{
400
+ fast: "fast";
401
+ ultrafast: "ultrafast";
402
+ superfast: "superfast";
403
+ veryfast: "veryfast";
404
+ faster: "faster";
405
+ medium: "medium";
406
+ }>>;
407
+ tune: z.ZodOptional<z.ZodEnum<{
408
+ zerolatency: "zerolatency";
409
+ film: "film";
410
+ animation: "animation";
411
+ }>>;
412
+ }, z.core.$strip>;
413
+ }, z.core.$strip>;
414
+ rateControl: z.ZodOptional<z.ZodEnum<{
415
+ tight: "tight";
416
+ relaxed: "relaxed";
417
+ }>>;
418
+ bitstreamFilter: z.ZodOptional<z.ZodEnum<{
419
+ dump_extra: "dump_extra";
420
+ h264_mp4toannexb: "h264_mp4toannexb";
421
+ hevc_mp4toannexb: "hevc_mp4toannexb";
422
+ }>>;
423
+ pixelFormat: z.ZodOptional<z.ZodEnum<{
424
+ yuv420p: "yuv420p";
425
+ nv12: "nv12";
426
+ }>>;
427
+ decodeHwAccel: z.ZodOptional<z.ZodEnum<{
428
+ none: "none";
429
+ auto: "auto";
430
+ videotoolbox: "videotoolbox";
431
+ vaapi: "vaapi";
432
+ qsv: "qsv";
433
+ cuda: "cuda";
434
+ }>>;
435
+ hostname: z.ZodOptional<z.ZodString>;
436
+ tag: z.ZodOptional<z.ZodString>;
437
+ }, z.core.$strip>;
438
+ export type EgressTranscodeRequest = z.infer<typeof EgressTranscodeRequestSchema>;
439
+ export declare const EgressTranscodeSchema: z.ZodObject<{
440
+ url: z.ZodString;
441
+ pipelineKey: z.ZodString;
442
+ videoCodec: z.ZodEnum<{
443
+ H264: "H264";
444
+ H265: "H265";
445
+ }>;
446
+ resolution: z.ZodObject<{
447
+ width: z.ZodNumber;
448
+ height: z.ZodNumber;
449
+ }, z.core.$strip>;
450
+ transcoded: z.ZodBoolean;
451
+ encoder: z.ZodString;
452
+ decodeHwAccel: z.ZodNullable<z.ZodString>;
453
+ }, z.core.$strip>;
454
+ export type EgressTranscode = z.infer<typeof EgressTranscodeSchema>;
282
455
  export declare const streamBrokerCapability: {
283
456
  readonly name: "stream-broker";
284
457
  readonly scope: "system";
@@ -321,6 +494,7 @@ export declare const streamBrokerCapability: {
321
494
  baseline: "baseline";
322
495
  main: "main";
323
496
  }>>;
497
+ level: z.ZodOptional<z.ZodString>;
324
498
  width: z.ZodOptional<z.ZodNumber>;
325
499
  height: z.ZodOptional<z.ZodNumber>;
326
500
  fps: z.ZodOptional<z.ZodNumber>;
@@ -691,6 +865,132 @@ export declare const streamBrokerCapability: {
691
865
  released: z.ZodBoolean;
692
866
  refcount: z.ZodNumber;
693
867
  }, z.core.$strip>, "mutation">;
868
+ /**
869
+ * THE ffmpeg primitive. Acquire an encoded stream matching a structured
870
+ * encode plan; the broker builds the argv through the ONE builder
871
+ * (`@camstack/types` `ffmpeg/invocation.ts`), resolves decode hardware from
872
+ * the DECODER ADDON's per-node ranking, and returns a dialable RTSP url.
873
+ *
874
+ * **Refcounted and deduplicated on EXACT match.** Two requesters whose
875
+ * requests produce the same `egressTranscodeSharingKey` receive the SAME
876
+ * `pipelineKey` and the same child process. Nearly-identical requests are
877
+ * NOT merged.
878
+ *
879
+ * **The handle is immutable.** There is deliberately no `reconfigure`
880
+ * method and this one never accepts a `pipelineKey` alongside encode
881
+ * parameters: a consumer whose requirements change releases and
882
+ * re-acquires. A mutable shared handle is exactly what made Alexa's old
883
+ * `derived:alexa-<id>` stream a co-tenant hazard — one consumer's
884
+ * downgrade dragged every other consumer down with it.
885
+ *
886
+ * Placement: unpinned, `classifyCapRoute` serves this hub-in-process
887
+ * (Priority 1). A caller that wants the transcode elsewhere passes
888
+ * `nodePin(nodeId)` and a `hostname` it can dial.
889
+ */
890
+ readonly acquireEgressTranscode: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
891
+ deviceId: z.ZodNumber;
892
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
893
+ kind: z.ZodLiteral<"profile">;
894
+ profile: z.ZodEnum<{
895
+ high: "high";
896
+ mid: "mid";
897
+ low: "low";
898
+ }>;
899
+ }, z.core.$strip>, z.ZodObject<{
900
+ kind: z.ZodLiteral<"cam-stream">;
901
+ camStreamId: z.ZodString;
902
+ }, z.core.$strip>], "kind">;
903
+ encode: z.ZodObject<{
904
+ audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
905
+ codec: z.ZodEnum<{
906
+ copy: "copy";
907
+ opus: "opus";
908
+ aac: "aac";
909
+ pcmu: "pcmu";
910
+ pcma: "pcma";
911
+ }>;
912
+ bitrateKbps: z.ZodOptional<z.ZodNumber>;
913
+ sampleRateHz: z.ZodOptional<z.ZodNumber>;
914
+ channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
915
+ }, z.core.$strip>]>;
916
+ video: z.ZodObject<{
917
+ codec: z.ZodEnum<{
918
+ h264: "h264";
919
+ h265: "h265";
920
+ copy: "copy";
921
+ }>;
922
+ profile: z.ZodOptional<z.ZodEnum<{
923
+ high: "high";
924
+ baseline: "baseline";
925
+ main: "main";
926
+ }>>;
927
+ level: z.ZodOptional<z.ZodString>;
928
+ width: z.ZodOptional<z.ZodNumber>;
929
+ height: z.ZodOptional<z.ZodNumber>;
930
+ fps: z.ZodOptional<z.ZodNumber>;
931
+ bitrateKbps: z.ZodOptional<z.ZodNumber>;
932
+ gopFrames: z.ZodOptional<z.ZodNumber>;
933
+ bf: z.ZodOptional<z.ZodNumber>;
934
+ preset: z.ZodOptional<z.ZodEnum<{
935
+ fast: "fast";
936
+ ultrafast: "ultrafast";
937
+ superfast: "superfast";
938
+ veryfast: "veryfast";
939
+ faster: "faster";
940
+ medium: "medium";
941
+ }>>;
942
+ tune: z.ZodOptional<z.ZodEnum<{
943
+ zerolatency: "zerolatency";
944
+ film: "film";
945
+ animation: "animation";
946
+ }>>;
947
+ }, z.core.$strip>;
948
+ }, z.core.$strip>;
949
+ rateControl: z.ZodOptional<z.ZodEnum<{
950
+ tight: "tight";
951
+ relaxed: "relaxed";
952
+ }>>;
953
+ bitstreamFilter: z.ZodOptional<z.ZodEnum<{
954
+ dump_extra: "dump_extra";
955
+ h264_mp4toannexb: "h264_mp4toannexb";
956
+ hevc_mp4toannexb: "hevc_mp4toannexb";
957
+ }>>;
958
+ pixelFormat: z.ZodOptional<z.ZodEnum<{
959
+ yuv420p: "yuv420p";
960
+ nv12: "nv12";
961
+ }>>;
962
+ decodeHwAccel: z.ZodOptional<z.ZodEnum<{
963
+ none: "none";
964
+ auto: "auto";
965
+ videotoolbox: "videotoolbox";
966
+ vaapi: "vaapi";
967
+ qsv: "qsv";
968
+ cuda: "cuda";
969
+ }>>;
970
+ hostname: z.ZodOptional<z.ZodString>;
971
+ tag: z.ZodOptional<z.ZodString>;
972
+ }, z.core.$strip>, z.ZodObject<{
973
+ url: z.ZodString;
974
+ pipelineKey: z.ZodString;
975
+ videoCodec: z.ZodEnum<{
976
+ H264: "H264";
977
+ H265: "H265";
978
+ }>;
979
+ resolution: z.ZodObject<{
980
+ width: z.ZodNumber;
981
+ height: z.ZodNumber;
982
+ }, z.core.$strip>;
983
+ transcoded: z.ZodBoolean;
984
+ encoder: z.ZodString;
985
+ decodeHwAccel: z.ZodNullable<z.ZodString>;
986
+ }, z.core.$strip>, "mutation">;
987
+ /** Drop one reference. The child dies when the last holder releases. */
988
+ readonly releaseEgressTranscode: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
989
+ pipelineKey: z.ZodString;
990
+ }, z.core.$strip>, z.ZodObject<{
991
+ released: z.ZodBoolean;
992
+ refcount: z.ZodNumber;
993
+ }, z.core.$strip>, "mutation">;
694
994
  /**
695
995
  * ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
696
996
  *
@@ -22,6 +22,7 @@ export declare const VideoEncodeSchema: z.ZodObject<{
22
22
  baseline: "baseline";
23
23
  main: "main";
24
24
  }>>;
25
+ level: z.ZodOptional<z.ZodString>;
25
26
  width: z.ZodOptional<z.ZodNumber>;
26
27
  height: z.ZodOptional<z.ZodNumber>;
27
28
  fps: z.ZodOptional<z.ZodNumber>;
@@ -68,6 +69,7 @@ export declare const EncodeProfileSchema: z.ZodObject<{
68
69
  baseline: "baseline";
69
70
  main: "main";
70
71
  }>>;
72
+ level: z.ZodOptional<z.ZodString>;
71
73
  width: z.ZodOptional<z.ZodNumber>;
72
74
  height: z.ZodOptional<z.ZodNumber>;
73
75
  fps: z.ZodOptional<z.ZodNumber>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The ONE home for live-egress encode constants.
3
+ *
4
+ * Before this file there were five sets, in four packages, that nobody could
5
+ * diff: the WebRTC fallback, Alexa's "fallback", the transcode-down ceiling,
6
+ * the broker's audio presets, and HomeKit's inline block. Three of them
7
+ * disagreed about Opus channel count alone. They live here now because the
8
+ * consumers are separate ADDONS and addons may never import each other —
9
+ * `@camstack/types` is the only legal shared home.
10
+ *
11
+ * ## What changed value, and what kept a deliberate divergence
12
+ *
13
+ * KEPT, with the reason:
14
+ * - **VBV window.** HomeKit uses a ONE-second window; the browser and Alexa
15
+ * use TWO. HomeKit's link budget is enforced per second by the controller,
16
+ * so a keyframe allowed to borrow from the next second overshoots and the
17
+ * iOS client drops the stream. A browser wants the opposite: let a keyframe
18
+ * spike, keep the average. Encoded as {@link FfmpegRateControl}, not as two
19
+ * copies of a number.
20
+ * - **`preset`.** HomeKit and the browser use `ultrafast` (the encoder is on
21
+ * the interactive path); Alexa uses `veryfast` (Echo tolerates ~200 ms more
22
+ * latency and the extra quality is visible on a 10" panel).
23
+ * - **`-bsf:v dump_extra`** is HomeKit-only on BOTH the copy and encode
24
+ * branches: HAP negotiates its own SDP and cannot carry out-of-band
25
+ * extradata. Nothing else needs it, and emitting it elsewhere costs bytes.
26
+ *
27
+ * CHANGED — the divergences that were accidents:
28
+ * - **Opus channels.** Was 2 (broker preset), 1 (Alexa), 1 (HomeKit). Now 1
29
+ * everywhere: every consumer here is a camera microphone, which is mono at
30
+ * the source, and encoding a duplicated second channel spent bitrate on
31
+ * nothing. The broker's 2 was the outlier and it is the one that changed.
32
+ * - **Alexa gained `level: '3.1'`.** Its SDP advertises `profile-level-id=
33
+ * 42e01f` and its encoder was unconstrained. Now it matches.
34
+ * - **Alexa GOP.** Was 25 frames at 25 fps (1 s); the browser is 30 at 25 fps
35
+ * (1.2 s). Both are now expressed as `gopSeconds` × fps so the intent is
36
+ * legible; Alexa's numeric GOP is unchanged, the browser's rounds to 30.
37
+ */
38
+ import type { EncodeProfile } from '../encode-profile.js';
39
+ import type { FfmpegAudioEncodePlan, FfmpegRateControl } from './invocation.js';
40
+ /**
41
+ * The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
42
+ * Baseline because it is the one profile every consumer in this repo decodes
43
+ * (Echo, iOS, an old browser); 3.1 because that is what the SDPs advertise.
44
+ */
45
+ export declare const BASE_LIVE_EGRESS_PROFILE: EncodeProfile;
46
+ /**
47
+ * The browser WebRTC viewer's transcode profile. Used only when the broker
48
+ * MUST spawn ffmpeg (source codec ∉ the browser-accepted set — an H.265 source
49
+ * on an H.264-only browser). Audio is `passthrough` because werift carries the
50
+ * audio plane out-of-band; putting it in the video pipeline would encode a
51
+ * track nothing reads.
52
+ */
53
+ export declare const WEBRTC_EGRESS_PROFILE: EncodeProfile;
54
+ /**
55
+ * Alexa's egress profile. Alexa's audio ALSO rides the WebRTC session's own
56
+ * audio plane (out-of-band), so this is `passthrough` exactly like the browser
57
+ * — see the ADR for why the previous in-band Opus was encoded and discarded.
58
+ */
59
+ export declare const ALEXA_EGRESS_PROFILE: EncodeProfile;
60
+ /** VBV window for a consumer whose budget is enforced per second (HomeKit). */
61
+ export declare const RATE_CONTROL_TIGHT: FfmpegRateControl;
62
+ /** VBV window for a consumer that tolerates a keyframe spike (browser, Echo). */
63
+ export declare const RATE_CONTROL_RELAXED: FfmpegRateControl;
64
+ /**
65
+ * Transcode-DOWN ceiling: the shape a remote/unhealthy WebRTC session is
66
+ * reduced to. 360p / 400 kbps is the `low` rung of the adaptive ladder — a
67
+ * session steered here is already failing, so the ceiling is chosen to survive
68
+ * a bad link, not to look good.
69
+ */
70
+ export declare const TRANSCODE_DOWN_MAX_HEIGHT = 360;
71
+ export declare const TRANSCODE_DOWN_MAX_BITRATE_KBPS = 400;
72
+ /**
73
+ * Camera-microphone audio, per codec. Defined in `./invocation.js` (an import
74
+ * cycle otherwise) and re-exported here, which is where to read it.
75
+ */
76
+ export { AUDIO_PRESETS } from './invocation.js';
77
+ /**
78
+ * HomeKit's two-way audio leg. `lowdelay` + a short frame duration because the
79
+ * leg is interactive; `globalHeader` because HAP owns the SDP and ffmpeg must
80
+ * not put extradata in-band. `sampleRateHz` and `frameDurationMs` are
81
+ * NEGOTIATED per session — the controller picks them — so they are absent here
82
+ * and filled in by the caller.
83
+ */
84
+ export declare const HAP_AUDIO_BITRATE_KBPS = 24;
85
+ export declare const HAP_AUDIO_BASE: FfmpegAudioEncodePlan;
86
+ /** HomeKit's `-bufsize` for the audio plane — four seconds of its 24 kbps. */
87
+ export declare const HAP_AUDIO_VBV_KBITS: number;
88
+ /** Seconds between forced IDRs on a HomeKit transcode. */
89
+ export declare const HAP_KEYFRAME_INTERVAL_SEC = 4;