diff --git a/frigate/config/config.py b/frigate/config/config.py index d1cc2101b..6873e6b88 100644 --- a/frigate/config/config.py +++ b/frigate/config/config.py @@ -26,7 +26,6 @@ from frigate.plus import PlusApi from frigate.util.builtin import ( deep_merge, get_ffmpeg_arg_list, - load_labels, ) from frigate.util.config import ( CURRENT_CONFIG_VERSION, @@ -638,17 +637,12 @@ class FrigateConfig(FrigateBaseModel): if self.ffmpeg.hwaccel_args == "auto": self.ffmpeg.hwaccel_args = auto_detect_hwaccel() - # Populate global audio filters for all audio labels - all_audio_labels = { - label - for label in load_labels("/audio-labelmap.txt", prefill=521).values() - if label - } - + # Populate global audio filters from listen. Existing user-defined + # entries for labels not in listen are preserved but unused at runtime. if self.audio.filters is None: self.audio.filters = {} - for key in sorted(all_audio_labels - self.audio.filters.keys()): + for key in sorted(set(self.audio.listen) - self.audio.filters.keys()): self.audio.filters[key] = AudioFilterConfig() self.audio.filters = dict(sorted(self.audio.filters.items())) @@ -840,7 +834,9 @@ class FrigateConfig(FrigateBaseModel): if camera_config.audio.filters is None: camera_config.audio.filters = {} - for key in sorted(all_audio_labels - camera_config.audio.filters.keys()): + for key in sorted( + set(camera_config.audio.listen) - camera_config.audio.filters.keys() + ): camera_config.audio.filters[key] = AudioFilterConfig() camera_config.audio.filters = dict( diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 3a8909b30..c63f27430 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -10,7 +10,7 @@ from ruamel.yaml.constructor import DuplicateKeyError from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.const import MODEL_CACHE_DIR from frigate.detectors import DetectorTypeEnum -from frigate.util.builtin import deep_merge, load_labels +from frigate.util.builtin import deep_merge class TestConfig(unittest.TestCase): @@ -309,16 +309,11 @@ class TestConfig(unittest.TestCase): } frigate_config = FrigateConfig(**config) - all_audio_labels = { - label - for label in load_labels("/audio-labelmap.txt", prefill=521).values() - if label + assert set(frigate_config.cameras["back"].audio.filters.keys()) == { + "speech", + "yell", } - assert all_audio_labels.issubset( - set(frigate_config.cameras["back"].audio.filters.keys()) - ) - def test_override_audio_filters(self): config = { "mqtt": {"host": "mqtt"}, @@ -345,7 +340,8 @@ class TestConfig(unittest.TestCase): frigate_config = FrigateConfig(**config) assert "speech" in frigate_config.cameras["back"].audio.filters assert frigate_config.cameras["back"].audio.filters["speech"].threshold == 0.9 - assert "babbling" in frigate_config.cameras["back"].audio.filters + assert "yell" in frigate_config.cameras["back"].audio.filters + assert "babbling" not in frigate_config.cameras["back"].audio.filters def test_inherit_object_filters(self): config = { diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 66cc2cc4a..6c32ffae6 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -22,7 +22,7 @@ import { modifySchemaForSection, getEffectiveDefaultsForSection, sanitizeOverridesForSection, - synthesizeMissingObjectFilters, + synthesizeMissingFilters, } from "./section-special-cases"; import { getSectionValidation } from "../section-validations"; import { useConfigOverride } from "@/hooks/use-config-override"; @@ -370,7 +370,7 @@ export function ConfigSection({ return {}; } - return synthesizeMissingObjectFilters( + return synthesizeMissingFilters( sectionPath, rawSectionValue, modifiedSchema ?? undefined, diff --git a/web/src/components/config-form/sections/section-special-cases.ts b/web/src/components/config-form/sections/section-special-cases.ts index d8121aea8..62a4bfa85 100644 --- a/web/src/components/config-form/sections/section-special-cases.ts +++ b/web/src/components/config-form/sections/section-special-cases.ts @@ -128,22 +128,31 @@ export function getEffectiveDefaultsForSection( return schemaDefaults; } +// Sections whose `filters` dict is keyed by a sibling list field. The backend +// auto-populates these filters at config init but doesn't re-run after profile +// merges, so we synthesize the missing entries on the frontend. +const FILTER_SECTIONS: Record = { + objects: { listField: "track" }, + audio: { listField: "listen" }, +}; + /** - * Add default filter entries for any label in `objects.track` that isn't - * already in `objects.filters`, so each tracked label gets a collapsible. - * The backend only auto-populates filters at config init, not after profile - * merges. + * Add default filter entries for any label in the section's list field + * (e.g. `objects.track`, `audio.listen`) that isn't already in `filters`, so + * each label gets a collapsible. The backend only auto-populates filters at + * config init, not after profile merges. */ -export function synthesizeMissingObjectFilters( +export function synthesizeMissingFilters( sectionPath: string, data: unknown, sectionSchema: RJSFSchema | undefined, ): unknown { - if (sectionPath !== "objects") return data; + const sectionConfig = FILTER_SECTIONS[sectionPath]; + if (!sectionConfig) return data; if (!isJsonObject(data)) return data; - const trackValue = (data as JsonObject).track; - if (!Array.isArray(trackValue) || trackValue.length === 0) return data; + const listValue = (data as JsonObject)[sectionConfig.listField]; + if (!Array.isArray(listValue) || listValue.length === 0) return data; const properties = (sectionSchema as { properties?: Record }) ?.properties; @@ -160,7 +169,7 @@ export function synthesizeMissingObjectFilters( const newFilters: JsonObject = { ...existingFilters }; let added = false; - for (const label of trackValue) { + for (const label of listValue) { if (typeof label !== "string") continue; if (Object.prototype.hasOwnProperty.call(newFilters, label)) continue; newFilters[label] = (