@camstack/types 1.2.207 → 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.
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
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
4
  const require_err_msg = require("./err-msg-COpsHMw2.js");
5
5
  //#region src/generated/cap-input-defaults.ts
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as DeviceType, Et as normalizeAddonInitResult, G as ReadinessRegistry, H as nodePin, I as expandCapMethods, K as ReadinessTimeoutError, S as DEVICE_CHILDREN_BATCH_MAX, St as DATAPLANE_SECRET_HEADER, T as describeCustomActions, Tt as BaseAddon, U as readNodePin, Y as scopeKey, a as asJsonObject, b as deviceOpsCapability, d as BOOT_RECOVERY_BACKOFF_MS, f as DEVICE_SCOPED_CAPS, h as createEventBusSliceSource, j as adminUiCapability, kt as emitReadiness, m as createDeviceProxy, p as isDeviceScopedCap, s as asString, t as sleep, u as parseJsonUnknown, wt as DisposerChain, x as viewerUiCapability } from "./sleep-BDR76Ykr.mjs";
1
+ import { A as DeviceType, Et as normalizeAddonInitResult, G as ReadinessRegistry, H as nodePin, I as expandCapMethods, K as ReadinessTimeoutError, S as DEVICE_CHILDREN_BATCH_MAX, St as DATAPLANE_SECRET_HEADER, T as describeCustomActions, Tt as BaseAddon, U as readNodePin, Y as scopeKey, a as asJsonObject, b as deviceOpsCapability, d as BOOT_RECOVERY_BACKOFF_MS, f as DEVICE_SCOPED_CAPS, h as createEventBusSliceSource, j as adminUiCapability, kt as emitReadiness, m as createDeviceProxy, p as isDeviceScopedCap, s as asString, t as sleep, u as parseJsonUnknown, wt as DisposerChain, x as viewerUiCapability } from "./sleep-B1y0Fo1U.mjs";
2
2
  import { t as EventCategory } from "./event-category-BVDXG4tB.mjs";
3
3
  import { t as errMsg } from "./err-msg-IQTHeDzc.mjs";
4
4
  //#region src/generated/cap-input-defaults.ts
@@ -64,8 +64,41 @@ function buildInputArgs(input, decodeHwAccel) {
64
64
  args.push("-i", input.url);
65
65
  return args;
66
66
  }
67
+ /**
68
+ * Derive the output `-map` selectors. The three cases, in the order they are
69
+ * decided:
70
+ *
71
+ * 1. **A filter graph** — the picture is the graph's declared video pad, and
72
+ * the audio is its audio pad when it has one, otherwise input 0's mic. A
73
+ * graph's output is not addressable any other way.
74
+ * 2. **N inputs, no graph** — map input 0 EXPLICITLY. ffmpeg's own selection
75
+ * picks the "best" stream across ALL inputs, so the egress would silently
76
+ * become whichever camera happens to have the largest frame.
77
+ * 3. **One input, no graph** — no `-map`. Anything else would change the argv
78
+ * of every call site that exists today for no gain.
79
+ */
80
+ function resolveStreamMaps(selection) {
81
+ const graph = selection.filterGraph;
82
+ if (graph) return {
83
+ video: `[${graph.videoOutLabel}]`,
84
+ audio: graph.audioOutLabel ? `[${graph.audioOutLabel}]` : "0:a:0?"
85
+ };
86
+ if (selection.extraInputs?.length) return {
87
+ video: "0:v:0",
88
+ audio: "0:a:0?"
89
+ };
90
+ return {
91
+ video: null,
92
+ audio: null
93
+ };
94
+ }
67
95
  /** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
68
- function buildVideoFilterArgs(scale, outputArgs) {
96
+ function buildVideoFilterArgs(scale, outputArgs, filterGraph) {
97
+ if (filterGraph) {
98
+ if (scale) throw new Error("FfmpegInvocation: video.scale cannot be combined with a filter graph — scale inside the graph instead (the -vf would feed an output the graph already feeds)");
99
+ if (outputArgs.some((a) => a === "-vf")) throw new Error("FfmpegInvocation: outputArgs carries a -vf while a filter graph feeds the same output — fold the filter into the graph");
100
+ return [];
101
+ }
69
102
  if (!scale) return [];
70
103
  if (outputArgs.some((a) => a === "-vf")) return [];
71
104
  if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}`];
@@ -89,14 +122,17 @@ function buildRateControlArgs(video) {
89
122
  ];
90
123
  }
91
124
  /** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
92
- function buildVideoArgs(video, outputArgs) {
93
- if (video.kind === "copy") return [
94
- "-c:v",
95
- "copy",
96
- ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
97
- ];
125
+ function buildVideoArgs(video, outputArgs, filterGraph) {
126
+ if (video.kind === "copy") {
127
+ if (filterGraph) throw new Error("FfmpegInvocation: a filter graph produces a NEW picture, so video.kind must be `encode`");
128
+ return [
129
+ "-c:v",
130
+ "copy",
131
+ ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
132
+ ];
133
+ }
98
134
  const args = [
99
- ...buildVideoFilterArgs(video.scale, outputArgs),
135
+ ...buildVideoFilterArgs(video.scale, outputArgs, filterGraph),
100
136
  "-c:v",
101
137
  video.encoder
102
138
  ];
@@ -251,20 +287,39 @@ function buildAudioSidecarArgs(sidecar) {
251
287
  * [<input.extraArgs>] │
252
288
  * [-fflags <flag>…] │
253
289
  * [-rtsp_transport tcp] │
254
- * -i <url> ─┘
255
- * <video block> <threads> <audio block> ─┐ OUTPUT options.
290
+ * -i <url> ─┘ …repeated per extraInput
291
+ * [-filter_complex <graph>] ─ global, after the LAST -i
292
+ * [-map <video>] <video block> <threads> ─┐ OUTPUT options.
293
+ * [-map <audio>] <audio block> │
256
294
  * <consumer outputArgs verbatim> │
257
295
  * <sink> ─┘ terminal
296
+ *
297
+ * The `-map`s are DERIVED (see {@link resolveStreamMaps}), never constants:
298
+ * one input with no graph emits none at all, which is why the argv of every
299
+ * call site that predates the N-input extension is unchanged to the byte.
300
+ *
301
+ * {@link FfmpegInvocation.audioSidecar} is the one map that stays literal
302
+ * (`0:a:0?`): the sidecar is defined as the SOURCE's microphone on its own RTP
303
+ * output, not as whatever the main output happens to carry.
258
304
  */
259
305
  function buildFfmpegArgs(inv) {
260
- const head = [...logBannerArgs(inv.logLevel), ...buildInputArgs(inv.input, inv.decodeHwAccel)];
306
+ const graph = inv.filterGraph ?? null;
307
+ const head = [
308
+ ...logBannerArgs(inv.logLevel),
309
+ ...buildInputArgs(inv.input, inv.decodeHwAccel),
310
+ ...(inv.extraInputs ?? []).flatMap((extra) => buildInputArgs(extra, inv.decodeHwAccel)),
311
+ ...graph ? ["-filter_complex", graph.graph] : []
312
+ ];
261
313
  const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
314
+ const maps = resolveStreamMaps(inv);
315
+ const videoMapArgs = maps.video ? ["-map", maps.video] : [];
316
+ const audioMapArgs = maps.audio ? ["-map", maps.audio] : [];
262
317
  if (inv.sink.kind === "rtp-outputs") {
263
318
  const videoLeg = inv.sink.video ? [
264
319
  "-an",
265
320
  "-map",
266
- "0:v:0",
267
- ...buildVideoArgs(inv.video, inv.outputArgs),
321
+ maps.video ?? "0:v:0",
322
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph),
268
323
  ...threadArgs,
269
324
  ...inv.outputArgs,
270
325
  ...buildRtpOutputArgs(inv.sink.video)
@@ -272,7 +327,7 @@ function buildFfmpegArgs(inv) {
272
327
  const audioLeg = inv.sink.audio ? [
273
328
  "-vn",
274
329
  "-map",
275
- "0:a:0?",
330
+ maps.audio ?? "0:a:0?",
276
331
  ...buildAudioArgs(inv.audio),
277
332
  ...buildRtpOutputArgs(inv.sink.audio)
278
333
  ] : [];
@@ -282,15 +337,17 @@ function buildFfmpegArgs(inv) {
282
337
  ...audioLeg
283
338
  ];
284
339
  }
340
+ const mutedAudio = isElementaryVideoSink(inv.sink) || inv.audio.kind === "none";
285
341
  const audioArgs = isElementaryVideoSink(inv.sink) ? ["-an"] : buildAudioArgs(inv.audio);
286
- const sinkArgs = buildStdoutOrRtspSinkArgs(inv.sink);
287
342
  return [
288
343
  ...head,
289
- ...buildVideoArgs(inv.video, inv.outputArgs),
344
+ ...videoMapArgs,
345
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph),
290
346
  ...threadArgs,
347
+ ...mutedAudio ? [] : audioMapArgs,
291
348
  ...audioArgs,
292
349
  ...inv.outputArgs,
293
- ...sinkArgs,
350
+ ...buildStdoutOrRtspSinkArgs(inv.sink),
294
351
  ...inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []
295
352
  ];
296
353
  }
@@ -686,3 +743,9 @@ Object.defineProperty(exports, "pickVideoEncoder", {
686
743
  return pickVideoEncoder;
687
744
  }
688
745
  });
746
+ Object.defineProperty(exports, "resolveStreamMaps", {
747
+ enumerable: true,
748
+ get: function() {
749
+ return resolveStreamMaps;
750
+ }
751
+ });
@@ -64,8 +64,41 @@ function buildInputArgs(input, decodeHwAccel) {
64
64
  args.push("-i", input.url);
65
65
  return args;
66
66
  }
67
+ /**
68
+ * Derive the output `-map` selectors. The three cases, in the order they are
69
+ * decided:
70
+ *
71
+ * 1. **A filter graph** — the picture is the graph's declared video pad, and
72
+ * the audio is its audio pad when it has one, otherwise input 0's mic. A
73
+ * graph's output is not addressable any other way.
74
+ * 2. **N inputs, no graph** — map input 0 EXPLICITLY. ffmpeg's own selection
75
+ * picks the "best" stream across ALL inputs, so the egress would silently
76
+ * become whichever camera happens to have the largest frame.
77
+ * 3. **One input, no graph** — no `-map`. Anything else would change the argv
78
+ * of every call site that exists today for no gain.
79
+ */
80
+ function resolveStreamMaps(selection) {
81
+ const graph = selection.filterGraph;
82
+ if (graph) return {
83
+ video: `[${graph.videoOutLabel}]`,
84
+ audio: graph.audioOutLabel ? `[${graph.audioOutLabel}]` : "0:a:0?"
85
+ };
86
+ if (selection.extraInputs?.length) return {
87
+ video: "0:v:0",
88
+ audio: "0:a:0?"
89
+ };
90
+ return {
91
+ video: null,
92
+ audio: null
93
+ };
94
+ }
67
95
  /** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
68
- function buildVideoFilterArgs(scale, outputArgs) {
96
+ function buildVideoFilterArgs(scale, outputArgs, filterGraph) {
97
+ if (filterGraph) {
98
+ if (scale) throw new Error("FfmpegInvocation: video.scale cannot be combined with a filter graph — scale inside the graph instead (the -vf would feed an output the graph already feeds)");
99
+ if (outputArgs.some((a) => a === "-vf")) throw new Error("FfmpegInvocation: outputArgs carries a -vf while a filter graph feeds the same output — fold the filter into the graph");
100
+ return [];
101
+ }
69
102
  if (!scale) return [];
70
103
  if (outputArgs.some((a) => a === "-vf")) return [];
71
104
  if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}`];
@@ -89,14 +122,17 @@ function buildRateControlArgs(video) {
89
122
  ];
90
123
  }
91
124
  /** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
92
- function buildVideoArgs(video, outputArgs) {
93
- if (video.kind === "copy") return [
94
- "-c:v",
95
- "copy",
96
- ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
97
- ];
125
+ function buildVideoArgs(video, outputArgs, filterGraph) {
126
+ if (video.kind === "copy") {
127
+ if (filterGraph) throw new Error("FfmpegInvocation: a filter graph produces a NEW picture, so video.kind must be `encode`");
128
+ return [
129
+ "-c:v",
130
+ "copy",
131
+ ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
132
+ ];
133
+ }
98
134
  const args = [
99
- ...buildVideoFilterArgs(video.scale, outputArgs),
135
+ ...buildVideoFilterArgs(video.scale, outputArgs, filterGraph),
100
136
  "-c:v",
101
137
  video.encoder
102
138
  ];
@@ -251,20 +287,39 @@ function buildAudioSidecarArgs(sidecar) {
251
287
  * [<input.extraArgs>] │
252
288
  * [-fflags <flag>…] │
253
289
  * [-rtsp_transport tcp] │
254
- * -i <url> ─┘
255
- * <video block> <threads> <audio block> ─┐ OUTPUT options.
290
+ * -i <url> ─┘ …repeated per extraInput
291
+ * [-filter_complex <graph>] ─ global, after the LAST -i
292
+ * [-map <video>] <video block> <threads> ─┐ OUTPUT options.
293
+ * [-map <audio>] <audio block> │
256
294
  * <consumer outputArgs verbatim> │
257
295
  * <sink> ─┘ terminal
296
+ *
297
+ * The `-map`s are DERIVED (see {@link resolveStreamMaps}), never constants:
298
+ * one input with no graph emits none at all, which is why the argv of every
299
+ * call site that predates the N-input extension is unchanged to the byte.
300
+ *
301
+ * {@link FfmpegInvocation.audioSidecar} is the one map that stays literal
302
+ * (`0:a:0?`): the sidecar is defined as the SOURCE's microphone on its own RTP
303
+ * output, not as whatever the main output happens to carry.
258
304
  */
259
305
  function buildFfmpegArgs(inv) {
260
- const head = [...logBannerArgs(inv.logLevel), ...buildInputArgs(inv.input, inv.decodeHwAccel)];
306
+ const graph = inv.filterGraph ?? null;
307
+ const head = [
308
+ ...logBannerArgs(inv.logLevel),
309
+ ...buildInputArgs(inv.input, inv.decodeHwAccel),
310
+ ...(inv.extraInputs ?? []).flatMap((extra) => buildInputArgs(extra, inv.decodeHwAccel)),
311
+ ...graph ? ["-filter_complex", graph.graph] : []
312
+ ];
261
313
  const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
314
+ const maps = resolveStreamMaps(inv);
315
+ const videoMapArgs = maps.video ? ["-map", maps.video] : [];
316
+ const audioMapArgs = maps.audio ? ["-map", maps.audio] : [];
262
317
  if (inv.sink.kind === "rtp-outputs") {
263
318
  const videoLeg = inv.sink.video ? [
264
319
  "-an",
265
320
  "-map",
266
- "0:v:0",
267
- ...buildVideoArgs(inv.video, inv.outputArgs),
321
+ maps.video ?? "0:v:0",
322
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph),
268
323
  ...threadArgs,
269
324
  ...inv.outputArgs,
270
325
  ...buildRtpOutputArgs(inv.sink.video)
@@ -272,7 +327,7 @@ function buildFfmpegArgs(inv) {
272
327
  const audioLeg = inv.sink.audio ? [
273
328
  "-vn",
274
329
  "-map",
275
- "0:a:0?",
330
+ maps.audio ?? "0:a:0?",
276
331
  ...buildAudioArgs(inv.audio),
277
332
  ...buildRtpOutputArgs(inv.sink.audio)
278
333
  ] : [];
@@ -282,15 +337,17 @@ function buildFfmpegArgs(inv) {
282
337
  ...audioLeg
283
338
  ];
284
339
  }
340
+ const mutedAudio = isElementaryVideoSink(inv.sink) || inv.audio.kind === "none";
285
341
  const audioArgs = isElementaryVideoSink(inv.sink) ? ["-an"] : buildAudioArgs(inv.audio);
286
- const sinkArgs = buildStdoutOrRtspSinkArgs(inv.sink);
287
342
  return [
288
343
  ...head,
289
- ...buildVideoArgs(inv.video, inv.outputArgs),
344
+ ...videoMapArgs,
345
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph),
290
346
  ...threadArgs,
347
+ ...mutedAudio ? [] : audioMapArgs,
291
348
  ...audioArgs,
292
349
  ...inv.outputArgs,
293
- ...sinkArgs,
350
+ ...buildStdoutOrRtspSinkArgs(inv.sink),
294
351
  ...inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []
295
352
  ];
296
353
  }
@@ -614,4 +671,4 @@ function replaceWithSortedKeys(_key, value) {
614
671
  return value;
615
672
  }
616
673
  //#endregion
617
- export { buildAudioArgs as a, buildVideoArgs as c, logBannerArgs as d, pickVideoEncoder as f, audioPlanFromEncodeProfile as i, invocationFromEncodeProfile as l, Fmp4BoxSplitter as n, buildFfmpegArgs as o, AUDIO_PRESETS as r, buildInputArgs as s, canonicalHash as t, isSoftwareDecode as u };
674
+ export { buildAudioArgs as a, buildVideoArgs as c, logBannerArgs as d, pickVideoEncoder as f, audioPlanFromEncodeProfile as i, invocationFromEncodeProfile as l, Fmp4BoxSplitter as n, buildFfmpegArgs as o, resolveStreamMaps as p, AUDIO_PRESETS as r, buildInputArgs as s, canonicalHash as t, isSoftwareDecode as u };
@@ -186,7 +186,7 @@ export { type IPowerMeterProvider, type PowerMeterStatus, PowerMeterStatusSchema
186
186
  export { type GpsLocation, type IPresenceProvider, type PresenceStatus, PresenceStatusSchema, presenceCapability, } from './presence.cap.js';
187
187
  export { type IPressureSensorProvider, type PressureSensorStatus, PressureSensorStatusSchema, pressureSensorCapability, } from './pressure-sensor.cap.js';
188
188
  export { type IPrivacyMaskProvider, type PrivacyMaskOptions, PrivacyMaskOptionsSchema, type PrivacyMaskPatch, PrivacyMaskPatchSchema, type PrivacyMaskRegion, PrivacyMaskRegionSchema, type PrivacyMaskShape, PrivacyMaskShapeSchema, type PrivacyMaskStatus, PrivacyMaskStatusSchema, privacyMaskCapability, summarisePrivacyAudio, } from './privacy-mask.cap.js';
189
- export { type IPtzProvider, PtzMoveCommandSchema, type PtzOptions, PtzOptionsSchema, PtzPositionSchema, PtzPresetSchema, type PtzStatus, PtzStatusSchema, ptzCapability, } from './ptz.cap.js';
189
+ export { type IPtzProvider, type PtzHomePreset, PtzHomePresetSchema, PtzMoveCommandSchema, type PtzOptions, PtzOptionsSchema, PtzPositionSchema, type PtzPreset, type PtzPresetFunction, PtzPresetFunctionSchema, PtzPresetSchema, type PtzStatus, PtzStatusSchema, ptzCapability, } from './ptz.cap.js';
190
190
  export { type IPtzAutotrackProvider, type PtzAutotrackRuntimeState, PtzAutotrackRuntimeStateSchema, type PtzAutotrackSettings, PtzAutotrackSettingsSchema, type PtzAutotrackStatus, PtzAutotrackStatusSchema, type PtzAutotrackTargetOption, PtzAutotrackTargetOptionSchema, ptzAutotrackCapability, } from './ptz-autotrack.cap.js';
191
191
  export { type IRebootProvider, rebootCapability } from './reboot.cap.js';
192
192
  export { type IRecordingProvider, type LocateSegmentResult, LocateSegmentResultSchema, RECORDING_TIMELINE_BATCH_MAX, type ReadGopBytesResult, ReadGopBytesResultSchema, type ReadSegmentBytesResult, ReadSegmentBytesResultSchema, type ReadWindowBytesResult, ReadWindowBytesResultSchema, type RecordingAvailability, type RecordingAvailabilityForDevice, RecordingAvailabilityForDeviceSchema, RecordingAvailabilitySchema, type RecordingDays, type RecordingDaysForDevice, RecordingDaysForDeviceSchema, RecordingDaysSchema, type RecordingDeviceUsage, RecordingDeviceUsageSchema, type RecordingLocationUsage, RecordingLocationUsageSchema, type RecordingManifest, RecordingManifestSchema, RecordingRangeSchema, type RecordingRebalanceInput, RecordingRebalanceInputSchema, type RecordingRebalanceMove, RecordingRebalanceMoveSchema, type RecordingRebalancePlan, RecordingRebalancePlanSchema, type RecordingRebalanceSkip, type RecordingRebalanceSkipReason, RecordingRebalanceSkipReasonSchema, RecordingRebalanceSkipSchema, type RecordingStatus, RecordingStatusSchema, type RecordingStorageUsage, RecordingStorageUsageSchema, recordingCapability, } from './recording.cap.js';
@@ -1,14 +1,81 @@
1
1
  import { z } from 'zod';
2
2
  import { type InferProvider } from './capability-definition.js';
3
3
  import { DeviceType } from '../device/device-type.js';
4
+ /**
5
+ * What a preset id DOES on this camera.
6
+ *
7
+ * On several PTZ firmwares the preset namespace is shared: low ids are stored
8
+ * positions, a reserved band triggers camera FUNCTIONS. The dispensa camera
9
+ * (Hikvision) returns its own reserved band in the preset list, named by the
10
+ * firmware:
11
+ *
12
+ * 34 Back to origin · 39 Day mode · 40 Night mode · 46 Day/Night Auto Mode
13
+ * 92 Set manual limits · 93 Save manual limits · 94 Remote reboot
14
+ * 95 Call OSD menu
15
+ *
16
+ * We used to hand all of these to the UI as ordinary presets. Saving over one
17
+ * is what produced `PUT /ISAPI/PTZCtrl/channels/1/presets/34 -> HTTP 500`: the
18
+ * camera refusing to let "Back to origin" be overwritten, surfaced to the
19
+ * operator as a transport failure.
20
+ *
21
+ * A function preset stays fully usable as a DESTINATION -- `goToPreset('34')`
22
+ * is the single most useful thing that camera can do. Only `savePreset` and
23
+ * `deletePreset` are gated.
24
+ */
25
+ export declare const PtzPresetFunctionSchema: z.ZodEnum<{
26
+ home: "home";
27
+ reboot: "reboot";
28
+ "day-mode": "day-mode";
29
+ "night-mode": "night-mode";
30
+ "day-night-auto": "day-night-auto";
31
+ "set-limits": "set-limits";
32
+ "save-limits": "save-limits";
33
+ "osd-menu": "osd-menu";
34
+ }>;
35
+ export type PtzPresetFunction = z.infer<typeof PtzPresetFunctionSchema>;
4
36
  declare const PtzPresetSchema: z.ZodObject<{
5
37
  id: z.ZodString;
6
38
  name: z.ZodString;
39
+ fn: z.ZodNullable<z.ZodEnum<{
40
+ home: "home";
41
+ reboot: "reboot";
42
+ "day-mode": "day-mode";
43
+ "night-mode": "night-mode";
44
+ "day-night-auto": "day-night-auto";
45
+ "set-limits": "set-limits";
46
+ "save-limits": "save-limits";
47
+ "osd-menu": "osd-menu";
48
+ }>>;
49
+ writable: z.ZodBoolean;
7
50
  }, z.core.$strip>;
51
+ /**
52
+ * Where the head is pointing -- per axis, and `null` when the driver cannot
53
+ * read it.
54
+ *
55
+ * It used to be three REQUIRED numbers, which left a provider that cannot read
56
+ * position no way to say so. All four said `0`:
57
+ *
58
+ * reolink `return { pan: 0, tilt: 0, zoom: 0 }` "until a position-query
59
+ * path lands upstream"
60
+ * hikvision stub, "firmware-dependent and frequently absent"
61
+ * amcrest stub, "no generic absolute-position read for this model family"
62
+ * onvif throws
63
+ *
64
+ * So every consumer asking where a camera points was told "perfectly centred"
65
+ * by four cameras that had never looked. That is D393 -- a measurement that
66
+ * FAILED is `null`, never `0`, and the type says so all the way to the
67
+ * decision -- the same shape as the unreadable `statfs` folded into "0 bytes of
68
+ * headroom", which evacuated a healthy disk.
69
+ *
70
+ * D393 also says to check for a narrower structural TWIN of the result type.
71
+ * There is one: `PtzStatusSchema` extends this, so the lie had already
72
+ * propagated into `getStatus`, which is the surface `getPosition`'s own comment
73
+ * tells callers to migrate to.
74
+ */
8
75
  declare const PtzPositionSchema: z.ZodObject<{
9
- pan: z.ZodNumber;
10
- tilt: z.ZodNumber;
11
- zoom: z.ZodNumber;
76
+ pan: z.ZodNullable<z.ZodNumber>;
77
+ tilt: z.ZodNullable<z.ZodNumber>;
78
+ zoom: z.ZodNullable<z.ZodNumber>;
12
79
  }, z.core.$strip>;
13
80
  declare const PtzMoveCommandSchema: z.ZodObject<{
14
81
  pan: z.ZodOptional<z.ZodNumber>;
@@ -20,9 +87,9 @@ declare const PtzMoveCommandSchema: z.ZodObject<{
20
87
  * `getPosition` / `move` still operate on the bare position; the
21
88
  * auto-injected `getStatus` returns this richer block. */
22
89
  declare const PtzStatusSchema: z.ZodObject<{
23
- pan: z.ZodNumber;
24
- tilt: z.ZodNumber;
25
- zoom: z.ZodNumber;
90
+ pan: z.ZodNullable<z.ZodNumber>;
91
+ tilt: z.ZodNullable<z.ZodNumber>;
92
+ zoom: z.ZodNullable<z.ZodNumber>;
26
93
  autofocus: z.ZodBoolean;
27
94
  }, z.core.$strip>;
28
95
  /** Per-camera PTZ availability descriptor — drives the UI. */
@@ -33,7 +100,49 @@ declare const PtzOptionsSchema: z.ZodObject<{
33
100
  supportsPresets: z.ZodBoolean;
34
101
  maxPresets: z.ZodOptional<z.ZodNumber>;
35
102
  hasAutofocus: z.ZodBoolean;
103
+ speedSteps: z.ZodNullable<z.ZodNumber>;
104
+ moveImpulseMs: z.ZodNullable<z.ZodNumber>;
36
105
  }, z.core.$strip>;
106
+ /**
107
+ * Which preset `goHome` goes to, and WHO decided.
108
+ *
109
+ * `goHome` used to be three vendor "conventions" written in three comments --
110
+ * Reolink preset 0, Hikvision preset '1', Dahua preset 1 -- and on this fleet
111
+ * not one of the three cameras honours its own:
112
+ *
113
+ * 592 Videocamera camera Daniel (reolink) preset 0 holds "stanza"
114
+ * 1438 Videocamera dispensa (hikvision) preset 1 holds "Credenza"
115
+ * 3836 Videocamera studio (amcrest) preset 1 holds "Preset1"
116
+ *
117
+ * So `goHome` meant "go wherever the operator happened to save in slot 0 or 1",
118
+ * and two of the three swallowed the failure in a bare `catch`. For autotrack
119
+ * this is the HOTTEST path -- it runs every time a subject is released -- so it
120
+ * needs an answer that is true per camera and audible when it is missing.
121
+ *
122
+ * `source` is what makes it honest:
123
+ * - `operator` -- picked in the UI, stored by the provider.
124
+ * - `firmware` -- the camera itself named a preset `fn: 'home'` (Hikvision's
125
+ * "Back to origin"), adopted as the default with nothing to configure.
126
+ * - `native` -- the driver homes WITHOUT a preset at all. ONVIF does:
127
+ * `goHome` is `ptzAbsoluteMove({x:0, y:0, zoom:0})`. `presetId` is null and
128
+ * that is not a failure -- there is nothing to pick, and the UI must offer
129
+ * no picker.
130
+ * - `none` -- nothing is configured and the camera names nothing.
131
+ * `goHome` REFUSES rather than moving the head somewhere arbitrary.
132
+ *
133
+ * `source === 'none'` is the refusal condition, NOT `presetId === null`: the
134
+ * native case has no preset and homes perfectly well.
135
+ */
136
+ export declare const PtzHomePresetSchema: z.ZodObject<{
137
+ presetId: z.ZodNullable<z.ZodString>;
138
+ source: z.ZodEnum<{
139
+ operator: "operator";
140
+ none: "none";
141
+ native: "native";
142
+ firmware: "firmware";
143
+ }>;
144
+ }, z.core.$strip>;
145
+ export type PtzHomePreset = z.infer<typeof PtzHomePresetSchema>;
37
146
  export declare const ptzCapability: {
38
147
  readonly name: "ptz";
39
148
  readonly scope: "device";
@@ -73,6 +182,17 @@ export declare const ptzCapability: {
73
182
  }, z.core.$strip>, z.ZodArray<z.ZodObject<{
74
183
  id: z.ZodString;
75
184
  name: z.ZodString;
185
+ fn: z.ZodNullable<z.ZodEnum<{
186
+ home: "home";
187
+ reboot: "reboot";
188
+ "day-mode": "day-mode";
189
+ "night-mode": "night-mode";
190
+ "day-night-auto": "day-night-auto";
191
+ "set-limits": "set-limits";
192
+ "save-limits": "save-limits";
193
+ "osd-menu": "osd-menu";
194
+ }>>;
195
+ writable: z.ZodBoolean;
76
196
  }, z.core.$strip>>, import("./capability-definition.js").CapabilityMethodKind>;
77
197
  readonly goToPreset: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
78
198
  deviceId: z.ZodNumber;
@@ -96,10 +216,37 @@ export declare const ptzCapability: {
96
216
  supportsPresets: z.ZodBoolean;
97
217
  maxPresets: z.ZodOptional<z.ZodNumber>;
98
218
  hasAutofocus: z.ZodBoolean;
219
+ speedSteps: z.ZodNullable<z.ZodNumber>;
220
+ moveImpulseMs: z.ZodNullable<z.ZodNumber>;
99
221
  }, z.core.$strip>, import("./capability-definition.js").CapabilityMethodKind>;
222
+ /**
223
+ * Move to the camera's configured home preset. THROWS when
224
+ * `getHomePreset()` resolves to `none` -- a head that did not move must
225
+ * never look like a head that went home.
226
+ */
100
227
  readonly goHome: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
101
228
  deviceId: z.ZodNumber;
102
229
  }, z.core.$strip>, z.ZodVoid, "mutation">;
230
+ /** Which preset `goHome` targets, and who decided it. */
231
+ readonly getHomePreset: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
232
+ deviceId: z.ZodNumber;
233
+ }, z.core.$strip>, z.ZodObject<{
234
+ presetId: z.ZodNullable<z.ZodString>;
235
+ source: z.ZodEnum<{
236
+ operator: "operator";
237
+ none: "none";
238
+ native: "native";
239
+ firmware: "firmware";
240
+ }>;
241
+ }, z.core.$strip>, import("./capability-definition.js").CapabilityMethodKind>;
242
+ /**
243
+ * Pin the home preset for this camera. `null` clears the operator's choice
244
+ * and falls back to whatever the firmware names, or to `none`.
245
+ */
246
+ readonly setHomePreset: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
247
+ deviceId: z.ZodNumber;
248
+ presetId: z.ZodNullable<z.ZodString>;
249
+ }, z.core.$strip>, z.ZodVoid, "mutation">;
103
250
  /**
104
251
  * Pull the current PTZ position. Redundant with the auto-injected
105
252
  * `getStatus` method (see `status` below); kept for callers that
@@ -109,9 +256,9 @@ export declare const ptzCapability: {
109
256
  readonly getPosition: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
110
257
  deviceId: z.ZodNumber;
111
258
  }, z.core.$strip>, z.ZodObject<{
112
- pan: z.ZodNumber;
113
- tilt: z.ZodNumber;
114
- zoom: z.ZodNumber;
259
+ pan: z.ZodNullable<z.ZodNumber>;
260
+ tilt: z.ZodNullable<z.ZodNumber>;
261
+ zoom: z.ZodNullable<z.ZodNumber>;
115
262
  }, z.core.$strip>, import("./capability-definition.js").CapabilityMethodKind>;
116
263
  /** Toggle the camera's autofocus. Only meaningful when
117
264
  * `getOptions().hasAutofocus` is true. */
@@ -122,15 +269,16 @@ export declare const ptzCapability: {
122
269
  };
123
270
  readonly status: {
124
271
  readonly schema: z.ZodObject<{
125
- pan: z.ZodNumber;
126
- tilt: z.ZodNumber;
127
- zoom: z.ZodNumber;
272
+ pan: z.ZodNullable<z.ZodNumber>;
273
+ tilt: z.ZodNullable<z.ZodNumber>;
274
+ zoom: z.ZodNullable<z.ZodNumber>;
128
275
  autofocus: z.ZodBoolean;
129
276
  }, z.core.$strip>;
130
277
  readonly kind: "command-driven";
131
278
  };
132
279
  };
133
280
  export type IPtzProvider = InferProvider<typeof ptzCapability>;
281
+ export type PtzPreset = z.infer<typeof PtzPresetSchema>;
134
282
  export type PtzOptions = z.infer<typeof PtzOptionsSchema>;
135
283
  export type PtzStatus = z.infer<typeof PtzStatusSchema>;
136
284
  export { PtzPresetSchema, PtzPositionSchema, PtzStatusSchema, PtzMoveCommandSchema, PtzOptionsSchema, };