mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-15 08:21:16 +03:00
don't report auto-populated object and audio filters as camera overrides
This commit is contained in:
parent
6c801743bc
commit
91b3ed1202
@ -102,6 +102,91 @@ function stripAutoDerivedMissingFromGlobal(
|
|||||||
return cloned;
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sections carrying a `filters` map that the backend materializes per-camera:
|
||||||
|
* every label in the camera's label list (`objects.track`, `audio.listen`)
|
||||||
|
* without an explicit filter gets a default entry. The global map gets no
|
||||||
|
* equivalent treatment for those labels, so a label the camera picks up exists
|
||||||
|
* on the camera side alone.
|
||||||
|
*/
|
||||||
|
const AUTO_POPULATED_FILTER_SECTIONS = ["objects", "audio"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the schema defaults for a single `<section>.filters` entry, in the
|
||||||
|
* normalized/collapsed shape config values are compared in. Returns undefined
|
||||||
|
* when the schema hasn't loaded or doesn't describe the filters map.
|
||||||
|
*/
|
||||||
|
function getDefaultFilter(
|
||||||
|
sectionPath: string,
|
||||||
|
schema?: RJSFSchema,
|
||||||
|
): JsonValue | undefined {
|
||||||
|
if (!schema) return undefined;
|
||||||
|
const sectionSchema = extractSectionSchema(schema, sectionPath, "camera");
|
||||||
|
const filtersSchema = sectionSchema?.properties?.filters as
|
||||||
|
| RJSFSchema
|
||||||
|
| undefined;
|
||||||
|
if (!filtersSchema) return undefined;
|
||||||
|
|
||||||
|
// An optional map (`dict[str, X] | None`, as `audio.filters` is declared)
|
||||||
|
// becomes an anyOf, so the entry schema can sit in a branch rather than on
|
||||||
|
// the map schema itself.
|
||||||
|
const candidates = [
|
||||||
|
filtersSchema,
|
||||||
|
...((filtersSchema.anyOf ?? filtersSchema.oneOf ?? []) as RJSFSchema[]),
|
||||||
|
];
|
||||||
|
const filterSchema = candidates.find((candidate) =>
|
||||||
|
isJsonObject(candidate?.additionalProperties as JsonValue),
|
||||||
|
)?.additionalProperties;
|
||||||
|
if (!filterSchema || typeof filterSchema !== "object") return undefined;
|
||||||
|
return collapseEmpty(
|
||||||
|
normalizeConfigValue(applySchemaDefaults(filterSchema as RJSFSchema, {})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete a global baseline with the filter entries the backend only
|
||||||
|
* materializes per-camera.
|
||||||
|
*
|
||||||
|
* The effective global value for a label with no explicit global filter is the
|
||||||
|
* filter's schema defaults, not "absent": that is exactly what a camera
|
||||||
|
* inherits for it. Filling those in keeps an untouched label from reading as a
|
||||||
|
* camera override, and lets a label the camera does filter report the specific
|
||||||
|
* changed field rather than the whole filter.
|
||||||
|
*/
|
||||||
|
function withDefaultFilters(
|
||||||
|
sectionPath: string,
|
||||||
|
globalValue: JsonValue,
|
||||||
|
cameraValue: JsonValue,
|
||||||
|
schema?: RJSFSchema,
|
||||||
|
): JsonValue {
|
||||||
|
if (
|
||||||
|
!AUTO_POPULATED_FILTER_SECTIONS.includes(sectionPath) ||
|
||||||
|
!isJsonObject(globalValue) ||
|
||||||
|
!isJsonObject(cameraValue)
|
||||||
|
) {
|
||||||
|
return globalValue;
|
||||||
|
}
|
||||||
|
const cameraFilters = cameraValue.filters;
|
||||||
|
if (!isJsonObject(cameraFilters)) return globalValue;
|
||||||
|
|
||||||
|
const globalFilters = isJsonObject(globalValue.filters)
|
||||||
|
? globalValue.filters
|
||||||
|
: undefined;
|
||||||
|
const missing = Object.keys(cameraFilters).filter(
|
||||||
|
(label) => globalFilters?.[label] === undefined,
|
||||||
|
);
|
||||||
|
if (missing.length === 0) return globalValue;
|
||||||
|
|
||||||
|
const defaultFilter = getDefaultFilter(sectionPath, schema);
|
||||||
|
if (defaultFilter === undefined) return globalValue;
|
||||||
|
|
||||||
|
const filters: JsonObject = { ...globalFilters };
|
||||||
|
for (const label of missing) {
|
||||||
|
filters[label] = defaultFilter;
|
||||||
|
}
|
||||||
|
return { ...globalValue, filters };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the given field is auto-derived for `sectionPath` and the global
|
* Whether the given field is auto-derived for `sectionPath` and the global
|
||||||
* value at that path is missing — in which case a per-camera value should
|
* value at that path is missing — in which case a per-camera value should
|
||||||
@ -290,7 +375,7 @@ export function useConfigOverride({
|
|||||||
"camera",
|
"camera",
|
||||||
buildHiddenFieldContext(config, "camera", cameraName),
|
buildHiddenFieldContext(config, "camera", cameraName),
|
||||||
);
|
);
|
||||||
const collapsedGlobal = stripHiddenPaths(
|
const collapsedGlobalRaw = stripHiddenPaths(
|
||||||
collapseEmpty(normalizedGlobalValue),
|
collapseEmpty(normalizedGlobalValue),
|
||||||
hiddenFields,
|
hiddenFields,
|
||||||
);
|
);
|
||||||
@ -300,9 +385,15 @@ export function useConfigOverride({
|
|||||||
);
|
);
|
||||||
const collapsedCamera = stripAutoDerivedMissingFromGlobal(
|
const collapsedCamera = stripAutoDerivedMissingFromGlobal(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
collapsedGlobal,
|
collapsedGlobalRaw,
|
||||||
collapsedCameraRaw,
|
collapsedCameraRaw,
|
||||||
);
|
);
|
||||||
|
const collapsedGlobal = withDefaultFilters(
|
||||||
|
sectionPath,
|
||||||
|
collapsedGlobalRaw,
|
||||||
|
collapsedCamera,
|
||||||
|
schema,
|
||||||
|
);
|
||||||
|
|
||||||
const comparisonGlobal = compareFields
|
const comparisonGlobal = compareFields
|
||||||
? pickFields(collapsedGlobal, compareFields)
|
? pickFields(collapsedGlobal, compareFields)
|
||||||
@ -446,7 +537,7 @@ export function useAllCameraOverrides(
|
|||||||
"camera",
|
"camera",
|
||||||
buildHiddenFieldContext(config, "camera", cameraName),
|
buildHiddenFieldContext(config, "camera", cameraName),
|
||||||
);
|
);
|
||||||
const collapsedGlobal = stripHiddenPaths(
|
const collapsedGlobalRaw = stripHiddenPaths(
|
||||||
collapseEmpty(globalValue),
|
collapseEmpty(globalValue),
|
||||||
hiddenFields,
|
hiddenFields,
|
||||||
);
|
);
|
||||||
@ -456,9 +547,15 @@ export function useAllCameraOverrides(
|
|||||||
);
|
);
|
||||||
const collapsedCamera = stripAutoDerivedMissingFromGlobal(
|
const collapsedCamera = stripAutoDerivedMissingFromGlobal(
|
||||||
key,
|
key,
|
||||||
collapsedGlobal,
|
collapsedGlobalRaw,
|
||||||
collapsedCameraRaw,
|
collapsedCameraRaw,
|
||||||
);
|
);
|
||||||
|
const collapsedGlobal = withDefaultFilters(
|
||||||
|
key,
|
||||||
|
collapsedGlobalRaw,
|
||||||
|
collapsedCamera,
|
||||||
|
schema,
|
||||||
|
);
|
||||||
const comparisonGlobal = compareFields
|
const comparisonGlobal = compareFields
|
||||||
? pickFields(collapsedGlobal, compareFields)
|
? pickFields(collapsedGlobal, compareFields)
|
||||||
: collapsedGlobal;
|
: collapsedGlobal;
|
||||||
@ -714,9 +811,15 @@ export function useCamerasOverridingSection(
|
|||||||
globalValue,
|
globalValue,
|
||||||
collapseEmpty(cameraSectionValues[idx]),
|
collapseEmpty(cameraSectionValues[idx]),
|
||||||
);
|
);
|
||||||
for (const delta of collectFieldDeltas(
|
const effectiveGlobalValue = withDefaultFilters(
|
||||||
|
sectionPath,
|
||||||
globalValue,
|
globalValue,
|
||||||
cameraValue,
|
cameraValue,
|
||||||
|
schema,
|
||||||
|
);
|
||||||
|
for (const delta of collectFieldDeltas(
|
||||||
|
effectiveGlobalValue,
|
||||||
|
cameraValue,
|
||||||
compareFields,
|
compareFields,
|
||||||
)) {
|
)) {
|
||||||
if (isCrossCameraIgnoredPath(delta.fieldPath)) continue;
|
if (isCrossCameraIgnoredPath(delta.fieldPath)) continue;
|
||||||
@ -738,7 +841,7 @@ export function useCamerasOverridingSection(
|
|||||||
if (deltasByPath.has(path)) continue;
|
if (deltasByPath.has(path)) continue;
|
||||||
if (isCrossCameraIgnoredPath(path)) continue;
|
if (isCrossCameraIgnoredPath(path)) continue;
|
||||||
if (!isPathAllowed(path, compareFields)) continue;
|
if (!isPathAllowed(path, compareFields)) continue;
|
||||||
const g = get(globalValue, path);
|
const g = get(effectiveGlobalValue, path);
|
||||||
const p = get(normalizedProfile, path);
|
const p = get(normalizedProfile, path);
|
||||||
if (!isEqual(g, p)) {
|
if (!isEqual(g, p)) {
|
||||||
deltasByPath.set(path, {
|
deltasByPath.set(path, {
|
||||||
@ -791,18 +894,24 @@ export function useCameraSectionDeltas(
|
|||||||
const sectionMeta = OVERRIDABLE_SECTIONS.find((s) => s.key === sectionPath);
|
const sectionMeta = OVERRIDABLE_SECTIONS.find((s) => s.key === sectionPath);
|
||||||
const compareFields = sectionMeta?.compareFields;
|
const compareFields = sectionMeta?.compareFields;
|
||||||
|
|
||||||
const globalValue = collapseEmpty(
|
const rawGlobalValue = collapseEmpty(
|
||||||
getEffectiveGlobalBaseline(config, sectionPath, compareFields, schema),
|
getEffectiveGlobalBaseline(config, sectionPath, compareFields, schema),
|
||||||
);
|
);
|
||||||
const cameraValue = stripAutoDerivedMissingFromGlobal(
|
const cameraValue = stripAutoDerivedMissingFromGlobal(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
globalValue,
|
rawGlobalValue,
|
||||||
collapseEmpty(
|
collapseEmpty(
|
||||||
normalizeConfigValue(
|
normalizeConfigValue(
|
||||||
getBaseCameraSectionValue(config, cameraName, sectionPath),
|
getBaseCameraSectionValue(config, cameraName, sectionPath),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const globalValue = withDefaultFilters(
|
||||||
|
sectionPath,
|
||||||
|
rawGlobalValue,
|
||||||
|
cameraValue,
|
||||||
|
schema,
|
||||||
|
);
|
||||||
|
|
||||||
const hiddenFields = getEffectiveHiddenFields(
|
const hiddenFields = getEffectiveHiddenFields(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user