Compare commits

...

4 Commits

Author SHA1 Message Date
Josh Hawkins
bfe46d9f4a use export id for key instead of name 2026-01-17 16:18:43 -06:00
Josh Hawkins
4dd2567848 add test for percentage based zone filters 2026-01-17 15:33:07 -06:00
Josh Hawkins
208a83cf79 ensure all zone filters are converted to pixels
zone-level filters were never converted from percentage area to pixels. RuntimeFilterConfig was only applied to filters at the camera level, not zone.filters.

Fixes https://github.com/blakeblackshear/frigate/discussions/21694
2026-01-17 15:32:49 -06:00
Josh Hawkins
af942fb64e ensure users only see recognized plates from accessible cameras in explore 2026-01-17 14:56:18 -06:00
4 changed files with 65 additions and 4 deletions

View File

@ -23,7 +23,12 @@ from markupsafe import escape
from peewee import SQL, fn, operator from peewee import SQL, fn, operator
from pydantic import ValidationError from pydantic import ValidationError
from frigate.api.auth import allow_any_authenticated, allow_public, require_role from frigate.api.auth import (
allow_any_authenticated,
allow_public,
get_allowed_cameras_for_filter,
require_role,
)
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
from frigate.api.defs.request.app_body import AppConfigSetBody from frigate.api.defs.request.app_body import AppConfigSetBody
from frigate.api.defs.tags import Tags from frigate.api.defs.tags import Tags
@ -687,13 +692,19 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
@router.get( @router.get(
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())] "/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
) )
def get_recognized_license_plates(split_joined: Optional[int] = None): def get_recognized_license_plates(
split_joined: Optional[int] = None,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
):
try: try:
query = ( query = (
Event.select( Event.select(
SQL("json_extract(data, '$.recognized_license_plate') AS plate") SQL("json_extract(data, '$.recognized_license_plate') AS plate")
) )
.where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL")) .where(
(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
& (Event.camera << allowed_cameras)
)
.distinct() .distinct()
) )
recognized_license_plates = [row[0] for row in query.tuples()] recognized_license_plates = [row[0] for row in query.tuples()]

View File

@ -662,6 +662,13 @@ class FrigateConfig(FrigateBaseModel):
# generate zone contours # generate zone contours
if len(camera_config.zones) > 0: if len(camera_config.zones) > 0:
for zone in camera_config.zones.values(): for zone in camera_config.zones.values():
if zone.filters:
for object_name, filter_config in zone.filters.items():
zone.filters[object_name] = RuntimeFilterConfig(
frame_shape=camera_config.frame_shape,
**filter_config.model_dump(exclude_unset=True),
)
zone.generate_contour(camera_config.frame_shape) zone.generate_contour(camera_config.frame_shape)
# Set live view stream if none is set # Set live view stream if none is set

View File

@ -632,6 +632,49 @@ class TestConfig(unittest.TestCase):
) )
assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0) assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0)
def test_zone_filter_area_percent_converts_to_pixels(self):
config = {
"mqtt": {"host": "mqtt"},
"record": {
"alerts": {
"retain": {
"days": 20,
}
}
},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]}
]
},
"detect": {
"height": 1080,
"width": 1920,
"fps": 5,
},
"zones": {
"notification": {
"coordinates": "0.03,1,0.025,0,0.626,0,0.643,1",
"objects": ["person"],
"filters": {"person": {"min_area": 0.1}},
}
},
}
},
}
frigate_config = FrigateConfig(**config)
expected_min_area = int(1080 * 1920 * 0.1)
assert (
frigate_config.cameras["back"]
.zones["notification"]
.filters["person"]
.min_area
== expected_min_area
)
def test_zone_relative_matches_explicit(self): def test_zone_relative_matches_explicit(self):
config = { config = {
"mqtt": {"host": "mqtt"}, "mqtt": {"host": "mqtt"},

View File

@ -206,7 +206,7 @@ function Exports() {
> >
{Object.values(exports).map((item) => ( {Object.values(exports).map((item) => (
<ExportCard <ExportCard
key={item.name} key={item.id}
className={ className={
search == "" || filteredExports.includes(item) ? "" : "hidden" search == "" || filteredExports.includes(item) ? "" : "hidden"
} }