@camstack/types 1.2.206 → 1.2.208

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.
@@ -234,6 +234,34 @@ export interface FfmpegAudioSidecar {
234
234
  readonly rtpUrl: string;
235
235
  readonly sdpFile: string;
236
236
  }
237
+ /**
238
+ * An explicit `-filter_complex` graph, for an invocation that combines MORE
239
+ * THAN ONE input into one output — a composite/grid camera being the case this
240
+ * exists for: N decodes, one composition, ONE encode, inside ONE child. Frames
241
+ * never cross a process boundary at frame-rate (D9/D18), so the composition
242
+ * has to happen where the decodes already are.
243
+ *
244
+ * The graph text is the caller's — the builder does not compose, parse or
245
+ * validate it. What the builder owns is the consequence: a graph names its
246
+ * OUTPUT PADS, and those pads are what `-map` selects. Declaring the labels
247
+ * here is what lets the `-map` be DERIVED instead of hardcoded.
248
+ *
249
+ * Input pads are addressed by the input's ORDINAL: `[0:v]` is
250
+ * {@link FfmpegInvocation.input}, `[1:v]` is `extraInputs[0]`, and so on, in
251
+ * the order the builder emits them.
252
+ */
253
+ export interface FfmpegFilterGraph {
254
+ /** The `-filter_complex` value, verbatim. */
255
+ readonly graph: string;
256
+ /** The graph's VIDEO output pad label, without brackets (e.g. `grid`). */
257
+ readonly videoOutLabel: string;
258
+ /**
259
+ * The graph's AUDIO output pad label, without brackets. Absent / `null` ⇒
260
+ * the graph produces no audio and the audio plane is mapped from input 0
261
+ * (`0:a:0?`) like any single-input invocation.
262
+ */
263
+ readonly audioOutLabel?: string | null;
264
+ }
237
265
  export interface FfmpegInvocation {
238
266
  readonly logLevel: 'error' | 'warning' | 'info';
239
267
  /**
@@ -244,6 +272,21 @@ export interface FfmpegInvocation {
244
272
  */
245
273
  readonly decodeHwAccel: string | null;
246
274
  readonly input: FfmpegInputPlan;
275
+ /**
276
+ * Inputs 1..N-1, emitted after {@link input} in this order, each with its own
277
+ * complete input block (`-hwaccel` included — it is per-input, and an option
278
+ * that lands after ITS `-i` is accepted, ignored, and silently software).
279
+ *
280
+ * Absent or empty ⇒ the single-input invocation every existing call site
281
+ * builds, argv unchanged.
282
+ */
283
+ readonly extraInputs?: readonly FfmpegInputPlan[];
284
+ /**
285
+ * The composition, when there is one. Present ⇒ `-filter_complex` is emitted
286
+ * after the last `-i` and the output `-map`s are taken from its declared
287
+ * pads. See {@link FfmpegFilterGraph}.
288
+ */
289
+ readonly filterGraph?: FfmpegFilterGraph | null;
247
290
  readonly video: FfmpegVideoPlan;
248
291
  readonly audio: FfmpegAudioPlan;
249
292
  /** `0` = auto (omit `-threads` and let ffmpeg decide). */
@@ -262,8 +305,35 @@ export declare function isSoftwareDecode(decodeHwAccel: string | null): boolean;
262
305
  * appended to this list by a caller — that is the whole point of the function.
263
306
  */
264
307
  export declare function buildInputArgs(input: FfmpegInputPlan, decodeHwAccel: string | null): string[];
308
+ /**
309
+ * Which stream each output plane is fed from.
310
+ *
311
+ * `null` means **emit no `-map` at all** and let ffmpeg select — which is what
312
+ * a single-input invocation has always done, and the only shape that keeps the
313
+ * shipped argv byte-identical. It is never a synonym for "input 0".
314
+ */
315
+ export interface FfmpegStreamMaps {
316
+ readonly video: string | null;
317
+ readonly audio: string | null;
318
+ }
319
+ /** The facts a map derives from: how many inputs there are, and the graph. */
320
+ export type FfmpegStreamSelection = Pick<FfmpegInvocation, 'extraInputs' | 'filterGraph'>;
321
+ /**
322
+ * Derive the output `-map` selectors. The three cases, in the order they are
323
+ * decided:
324
+ *
325
+ * 1. **A filter graph** — the picture is the graph's declared video pad, and
326
+ * the audio is its audio pad when it has one, otherwise input 0's mic. A
327
+ * graph's output is not addressable any other way.
328
+ * 2. **N inputs, no graph** — map input 0 EXPLICITLY. ffmpeg's own selection
329
+ * picks the "best" stream across ALL inputs, so the egress would silently
330
+ * become whichever camera happens to have the largest frame.
331
+ * 3. **One input, no graph** — no `-map`. Anything else would change the argv
332
+ * of every call site that exists today for no gain.
333
+ */
334
+ export declare function resolveStreamMaps(selection: FfmpegStreamSelection): FfmpegStreamMaps;
265
335
  /** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
266
- export declare function buildVideoArgs(video: FfmpegVideoPlan, outputArgs: readonly string[]): string[];
336
+ export declare function buildVideoArgs(video: FfmpegVideoPlan, outputArgs: readonly string[], filterGraph?: FfmpegFilterGraph | null): string[];
267
337
  /** The whole audio block, after `-i`. */
268
338
  export declare function buildAudioArgs(audio: FfmpegAudioPlan): string[];
269
339
  /**
@@ -274,10 +344,20 @@ export declare function buildAudioArgs(audio: FfmpegAudioPlan): string[];
274
344
  * [<input.extraArgs>] │
275
345
  * [-fflags <flag>…] │
276
346
  * [-rtsp_transport tcp] │
277
- * -i <url> ─┘
278
- * <video block> <threads> <audio block> ─┐ OUTPUT options.
347
+ * -i <url> ─┘ …repeated per extraInput
348
+ * [-filter_complex <graph>] ─ global, after the LAST -i
349
+ * [-map <video>] <video block> <threads> ─┐ OUTPUT options.
350
+ * [-map <audio>] <audio block> │
279
351
  * <consumer outputArgs verbatim> │
280
352
  * <sink> ─┘ terminal
353
+ *
354
+ * The `-map`s are DERIVED (see {@link resolveStreamMaps}), never constants:
355
+ * one input with no graph emits none at all, which is why the argv of every
356
+ * call site that predates the N-input extension is unchanged to the byte.
357
+ *
358
+ * {@link FfmpegInvocation.audioSidecar} is the one map that stays literal
359
+ * (`0:a:0?`): the sidecar is defined as the SOURCE's microphone on its own RTP
360
+ * output, not as whatever the main output happens to carry.
281
361
  */
282
362
  export declare function buildFfmpegArgs(inv: FfmpegInvocation): string[];
283
363
  /**
@@ -6666,6 +6666,20 @@ export type AppRouter = TrpcCoreRouter<{
6666
6666
  output: z.infer<typeof ptzCapability.methods.goHome.output>;
6667
6667
  meta: object;
6668
6668
  }>;
6669
+ getHomePreset: TRPCQueryProcedure<{
6670
+ input: {
6671
+ [x: string]: unknown;
6672
+ } & z.input<typeof ptzCapability.methods.getHomePreset.input>;
6673
+ output: z.infer<typeof ptzCapability.methods.getHomePreset.output>;
6674
+ meta: object;
6675
+ }>;
6676
+ setHomePreset: TRPCMutationProcedure<{
6677
+ input: {
6678
+ [x: string]: unknown;
6679
+ } & z.input<typeof ptzCapability.methods.setHomePreset.input>;
6680
+ output: z.infer<typeof ptzCapability.methods.setHomePreset.output>;
6681
+ meta: object;
6682
+ }>;
6669
6683
  getPosition: TRPCQueryProcedure<{
6670
6684
  input: {
6671
6685
  [x: string]: unknown;
@@ -6,7 +6,7 @@
6
6
  * scope+access check inside `protectedProcedure` (see
7
7
  * `server/backend/src/api/trpc/trpc.middleware.ts`).
8
8
  *
9
- * Coverage: 1048 method paths across 130 capabilities.
9
+ * Coverage: 1050 method paths across 130 capabilities.
10
10
  */
11
11
  import type { CapabilityMethodAccess } from '../capabilities/capability-definition.js';
12
12
  export interface MethodAccessRecord {
@@ -6,7 +6,7 @@
6
6
  * system-scope cap that takes a deviceId was previously never device-filtered
7
7
  * — see the generator header).
8
8
  *
9
- * Coverage: 380 methods carry a device reference, of which
9
+ * Coverage: 382 methods carry a device reference, of which
10
10
  * 141 are on SYSTEM-scope caps.
11
11
  *
12
12
  * Top-level fields, number arrays, and one-level arrays of objects carrying
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-DqtlfkSS.js");
2
+ const require_sleep = require("./sleep-BAAUFDCk.js");
3
3
  const require_event_category = require("./event-category-BVfsrBYA.js");
4
- const require_canonical_hash = require("./canonical-hash-9lydiEzx.js");
4
+ const require_canonical_hash = require("./canonical-hash-CZ2JqJc3.js");
5
5
  const require_enums = require("./enums.js");
6
6
  const require_err_msg = require("./err-msg-COpsHMw2.js");
7
7
  let zod = require("zod");
@@ -8893,7 +8893,7 @@ var streamBrokerCapability = {
8893
8893
  * - the signal is ENABLED for this camera (the operator's per-camera
8894
8894
  * choice — a camera may declare a signal the operator does not want
8895
8895
  * waking anything), and
8896
- * - a consumer is actually attached and waiting on the prestream. Waking
8896
+ * - a consumer is actually attached and waiting on the sentry. Waking
8897
8897
  * a camera nobody is watching is the one thing a battery camera must
8898
8898
  * never do, and a signal arriving with no consumer is exactly that.
8899
8899
  *
@@ -34875,14 +34875,81 @@ function summarisePrivacyAudio(profiles) {
34875
34875
  }
34876
34876
  //#endregion
34877
34877
  //#region src/capabilities/ptz.cap.ts
34878
+ /**
34879
+ * What a preset id DOES on this camera.
34880
+ *
34881
+ * On several PTZ firmwares the preset namespace is shared: low ids are stored
34882
+ * positions, a reserved band triggers camera FUNCTIONS. The dispensa camera
34883
+ * (Hikvision) returns its own reserved band in the preset list, named by the
34884
+ * firmware:
34885
+ *
34886
+ * 34 Back to origin · 39 Day mode · 40 Night mode · 46 Day/Night Auto Mode
34887
+ * 92 Set manual limits · 93 Save manual limits · 94 Remote reboot
34888
+ * 95 Call OSD menu
34889
+ *
34890
+ * We used to hand all of these to the UI as ordinary presets. Saving over one
34891
+ * is what produced `PUT /ISAPI/PTZCtrl/channels/1/presets/34 -> HTTP 500`: the
34892
+ * camera refusing to let "Back to origin" be overwritten, surfaced to the
34893
+ * operator as a transport failure.
34894
+ *
34895
+ * A function preset stays fully usable as a DESTINATION -- `goToPreset('34')`
34896
+ * is the single most useful thing that camera can do. Only `savePreset` and
34897
+ * `deletePreset` are gated.
34898
+ */
34899
+ var PtzPresetFunctionSchema = zod.z.enum([
34900
+ "home",
34901
+ "day-mode",
34902
+ "night-mode",
34903
+ "day-night-auto",
34904
+ "set-limits",
34905
+ "save-limits",
34906
+ "reboot",
34907
+ "osd-menu"
34908
+ ]);
34878
34909
  var PtzPresetSchema = zod.z.object({
34879
34910
  id: zod.z.string(),
34880
- name: zod.z.string()
34911
+ name: zod.z.string(),
34912
+ /**
34913
+ * The firmware function this id performs, when the provider KNOWS the id is
34914
+ * reserved. `null` = an ordinary stored position, OR a reserved id this
34915
+ * provider does not recognise -- the two are told apart by `writable`.
34916
+ */
34917
+ fn: PtzPresetFunctionSchema.nullable(),
34918
+ /**
34919
+ * Whether `savePreset` / `deletePreset` may target this id. A provider sets
34920
+ * it false only for a band it KNOWS is reserved; where the firmware is not
34921
+ * documented it stays true and a refusal comes from the camera, named.
34922
+ */
34923
+ writable: zod.z.boolean()
34881
34924
  });
34925
+ /**
34926
+ * Where the head is pointing -- per axis, and `null` when the driver cannot
34927
+ * read it.
34928
+ *
34929
+ * It used to be three REQUIRED numbers, which left a provider that cannot read
34930
+ * position no way to say so. All four said `0`:
34931
+ *
34932
+ * reolink `return { pan: 0, tilt: 0, zoom: 0 }` "until a position-query
34933
+ * path lands upstream"
34934
+ * hikvision stub, "firmware-dependent and frequently absent"
34935
+ * amcrest stub, "no generic absolute-position read for this model family"
34936
+ * onvif throws
34937
+ *
34938
+ * So every consumer asking where a camera points was told "perfectly centred"
34939
+ * by four cameras that had never looked. That is D393 -- a measurement that
34940
+ * FAILED is `null`, never `0`, and the type says so all the way to the
34941
+ * decision -- the same shape as the unreadable `statfs` folded into "0 bytes of
34942
+ * headroom", which evacuated a healthy disk.
34943
+ *
34944
+ * D393 also says to check for a narrower structural TWIN of the result type.
34945
+ * There is one: `PtzStatusSchema` extends this, so the lie had already
34946
+ * propagated into `getStatus`, which is the surface `getPosition`'s own comment
34947
+ * tells callers to migrate to.
34948
+ */
34882
34949
  var PtzPositionSchema = zod.z.object({
34883
- pan: zod.z.number(),
34884
- tilt: zod.z.number(),
34885
- zoom: zod.z.number()
34950
+ pan: zod.z.number().nullable(),
34951
+ tilt: zod.z.number().nullable(),
34952
+ zoom: zod.z.number().nullable()
34886
34953
  });
34887
34954
  var PtzMoveCommandSchema = zod.z.object({
34888
34955
  pan: zod.z.number().optional(),
@@ -34904,7 +34971,72 @@ var PtzOptionsSchema = zod.z.object({
34904
34971
  maxPresets: zod.z.number().optional(),
34905
34972
  /** Whether the camera exposes a controllable autofocus toggle
34906
34973
  * (boolean `hasX` per the getOptions availability convention). */
34907
- hasAutofocus: zod.z.boolean()
34974
+ hasAutofocus: zod.z.boolean(),
34975
+ /**
34976
+ * How many distinct speeds this camera's NATIVE scale offers.
34977
+ *
34978
+ * The cap's `speed` is normalized 0..1 and each provider maps it down --
34979
+ * Baichuan 1..63, Dahua 1..8, ISAPI's percentage -100..100. The normalized
34980
+ * value hides how coarse that really is: asking an amcrest for `0.37` is
34981
+ * meaningless, because it has eight steps and three of them round to the
34982
+ * same one. A loop that tunes its own gain has to know the granularity of
34983
+ * the knob it is turning.
34984
+ *
34985
+ * `null` = the driver does not know, or the scale is continuous (ONVIF takes
34986
+ * a float). Never a made-up number.
34987
+ */
34988
+ speedSteps: zod.z.number().int().positive().nullable(),
34989
+ /**
34990
+ * How long ONE `move` pulse runs on this driver, ms.
34991
+ *
34992
+ * `move` is a self-terminating burst everywhere, but every provider hardcoded
34993
+ * its own duration in private -- 200 reolink, 350 amcrest, 500 hikvision,
34994
+ * 1000 onvif -- so no caller could read it. It is the DENOMINATOR of "how far
34995
+ * did the head travel per pulse": without it a measured displacement has no
34996
+ * gain to be divided into.
34997
+ *
34998
+ * `null` = the driver cannot say.
34999
+ */
35000
+ moveImpulseMs: zod.z.number().int().positive().nullable()
35001
+ });
35002
+ /**
35003
+ * Which preset `goHome` goes to, and WHO decided.
35004
+ *
35005
+ * `goHome` used to be three vendor "conventions" written in three comments --
35006
+ * Reolink preset 0, Hikvision preset '1', Dahua preset 1 -- and on this fleet
35007
+ * not one of the three cameras honours its own:
35008
+ *
35009
+ * 592 Videocamera camera Daniel (reolink) preset 0 holds "stanza"
35010
+ * 1438 Videocamera dispensa (hikvision) preset 1 holds "Credenza"
35011
+ * 3836 Videocamera studio (amcrest) preset 1 holds "Preset1"
35012
+ *
35013
+ * So `goHome` meant "go wherever the operator happened to save in slot 0 or 1",
35014
+ * and two of the three swallowed the failure in a bare `catch`. For autotrack
35015
+ * this is the HOTTEST path -- it runs every time a subject is released -- so it
35016
+ * needs an answer that is true per camera and audible when it is missing.
35017
+ *
35018
+ * `source` is what makes it honest:
35019
+ * - `operator` -- picked in the UI, stored by the provider.
35020
+ * - `firmware` -- the camera itself named a preset `fn: 'home'` (Hikvision's
35021
+ * "Back to origin"), adopted as the default with nothing to configure.
35022
+ * - `native` -- the driver homes WITHOUT a preset at all. ONVIF does:
35023
+ * `goHome` is `ptzAbsoluteMove({x:0, y:0, zoom:0})`. `presetId` is null and
35024
+ * that is not a failure -- there is nothing to pick, and the UI must offer
35025
+ * no picker.
35026
+ * - `none` -- nothing is configured and the camera names nothing.
35027
+ * `goHome` REFUSES rather than moving the head somewhere arbitrary.
35028
+ *
35029
+ * `source === 'none'` is the refusal condition, NOT `presetId === null`: the
35030
+ * native case has no preset and homes perfectly well.
35031
+ */
35032
+ var PtzHomePresetSchema = zod.z.object({
35033
+ presetId: zod.z.string().nullable(),
35034
+ source: zod.z.enum([
35035
+ "operator",
35036
+ "firmware",
35037
+ "native",
35038
+ "none"
35039
+ ])
34908
35040
  });
34909
35041
  var ptzCapability = {
34910
35042
  name: "ptz",
@@ -34945,7 +35077,25 @@ var ptzCapability = {
34945
35077
  auth: "admin"
34946
35078
  }),
34947
35079
  getOptions: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), PtzOptionsSchema),
35080
+ /**
35081
+ * Move to the camera's configured home preset. THROWS when
35082
+ * `getHomePreset()` resolves to `none` -- a head that did not move must
35083
+ * never look like a head that went home.
35084
+ */
34948
35085
  goHome: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
35086
+ /** Which preset `goHome` targets, and who decided it. */
35087
+ getHomePreset: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), PtzHomePresetSchema),
35088
+ /**
35089
+ * Pin the home preset for this camera. `null` clears the operator's choice
35090
+ * and falls back to whatever the firmware names, or to `none`.
35091
+ */
35092
+ setHomePreset: require_sleep.method(zod.z.object({
35093
+ deviceId: zod.z.number(),
35094
+ presetId: zod.z.string().nullable()
35095
+ }), zod.z.void(), {
35096
+ kind: "mutation",
35097
+ auth: "admin"
35098
+ }),
34949
35099
  /**
34950
35100
  * Pull the current PTZ position. Redundant with the auto-injected
34951
35101
  * `getStatus` method (see `status` below); kept for callers that
@@ -48226,6 +48376,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
48226
48376
  addonId: null,
48227
48377
  access: "delete"
48228
48378
  },
48379
+ "ptz.getHomePreset": {
48380
+ capName: "ptz",
48381
+ capScope: "device",
48382
+ addonId: null,
48383
+ access: "view"
48384
+ },
48229
48385
  "ptz.getOptions": {
48230
48386
  capName: "ptz",
48231
48387
  capScope: "device",
@@ -48274,6 +48430,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
48274
48430
  addonId: null,
48275
48431
  access: "create"
48276
48432
  },
48433
+ "ptz.setHomePreset": {
48434
+ capName: "ptz",
48435
+ capScope: "device",
48436
+ addonId: null,
48437
+ access: "create"
48438
+ },
48277
48439
  "ptz.stop": {
48278
48440
  capName: "ptz",
48279
48441
  capScope: "device",
@@ -51718,6 +51880,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
51718
51880
  form: "single",
51719
51881
  optional: false
51720
51882
  }],
51883
+ "ptz.getHomePreset": [{
51884
+ name: "deviceId",
51885
+ form: "single",
51886
+ optional: false
51887
+ }],
51721
51888
  "ptz.getOptions": [{
51722
51889
  name: "deviceId",
51723
51890
  form: "single",
@@ -51758,6 +51925,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
51758
51925
  form: "single",
51759
51926
  optional: false
51760
51927
  }],
51928
+ "ptz.setHomePreset": [{
51929
+ name: "deviceId",
51930
+ form: "single",
51931
+ optional: false
51932
+ }],
51761
51933
  "ptz.stop": [{
51762
51934
  name: "deviceId",
51763
51935
  form: "single",
@@ -58384,9 +58556,11 @@ exports.PtzAutotrackRuntimeStateSchema = PtzAutotrackRuntimeStateSchema;
58384
58556
  exports.PtzAutotrackSettingsSchema = PtzAutotrackSettingsSchema;
58385
58557
  exports.PtzAutotrackStatusSchema = PtzAutotrackStatusSchema;
58386
58558
  exports.PtzAutotrackTargetOptionSchema = PtzAutotrackTargetOptionSchema;
58559
+ exports.PtzHomePresetSchema = PtzHomePresetSchema;
58387
58560
  exports.PtzMoveCommandSchema = PtzMoveCommandSchema;
58388
58561
  exports.PtzOptionsSchema = PtzOptionsSchema;
58389
58562
  exports.PtzPositionSchema = PtzPositionSchema;
58563
+ exports.PtzPresetFunctionSchema = PtzPresetFunctionSchema;
58390
58564
  exports.PtzPresetSchema = PtzPresetSchema;
58391
58565
  exports.PtzStatusSchema = PtzStatusSchema;
58392
58566
  exports.QueryFilterSchema = QueryFilterSchema;
@@ -59107,6 +59281,7 @@ exports.resolveMutate = resolveMutate;
59107
59281
  exports.resolvePoolMemoryPolicy = resolvePoolMemoryPolicy;
59108
59282
  exports.resolveRecordingProfiles = resolveRecordingProfiles;
59109
59283
  exports.resolveRunnerId = resolveRunnerId;
59284
+ exports.resolveStreamMaps = require_canonical_hash.resolveStreamMaps;
59110
59285
  exports.resolveVariantModelId = resolveVariantModelId;
59111
59286
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
59112
59287
  exports.roleSpec = roleSpec;