From fa16539429a5165843112816aaffefed629d90cc Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 15 Dec 2025 08:32:11 -0700 Subject: [PATCH] Miscellaneous Fixes (#21289) * Exclude yolov9 license plate from migraphx runner * clarify auth endpoint return in openapi schema * Clarify ROCm enrichments * fix object mask creation * Consider audio activity when deciding if recording segments should be kept due to motion * ensure python defs match openapi spec for auth endpoints * Fix check for audio activity to keep a segemnt * fix calendar popover modal bug on export dialog --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- .../hardware_acceleration_enrichments.md | 2 +- docs/static/frigate-api.yaml | 25 ++++++----- frigate/api/auth.py | 43 +++++++++++++++---- frigate/detectors/detection_runners.py | 1 + frigate/record/cleanup.py | 3 ++ frigate/record/maintainer.py | 2 +- web/src/components/overlay/ExportDialog.tsx | 14 ++++-- .../settings/ObjectMaskEditPane.tsx | 13 ++++++ 8 files changed, 79 insertions(+), 24 deletions(-) diff --git a/docs/docs/configuration/hardware_acceleration_enrichments.md b/docs/docs/configuration/hardware_acceleration_enrichments.md index 45c7cd4d1..fac2ffa61 100644 --- a/docs/docs/configuration/hardware_acceleration_enrichments.md +++ b/docs/docs/configuration/hardware_acceleration_enrichments.md @@ -13,7 +13,7 @@ Object detection and enrichments (like Semantic Search, Face Recognition, and Li - **AMD** - - ROCm will automatically be detected and used for enrichments in the `-rocm` Frigate image. + - ROCm support in the `-rocm` Frigate image is automatically detected for enrichments, but only some enrichment models are available due to ROCm's focus on LLMs and limited stability with certain neural network models. Frigate disables models that perform poorly or are unstable to ensure reliable operation, so only compatible enrichments may be active. - **Intel** diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml index 1bd5e95b8..624688965 100644 --- a/docs/static/frigate-api.yaml +++ b/docs/static/frigate-api.yaml @@ -17,20 +17,25 @@ paths: summary: Authenticate request description: |- Authenticates the current request based on proxy headers or JWT token. - Returns user role and permissions for camera access. This endpoint verifies authentication credentials and manages JWT token refresh. + On success, no JSON body is returned; authentication state is communicated via response headers and cookies. operationId: auth_auth_get responses: - "200": - description: Successful Response - content: - application/json: - schema: {} "202": - description: Authentication Accepted - content: - application/json: - schema: {} + description: Authentication Accepted (no response body, different headers depending on auth method) + headers: + remote-user: + description: Authenticated username or "anonymous" in proxy-only mode + schema: + type: string + remote-role: + description: Resolved role (e.g., admin, viewer, or custom) + schema: + type: string + Set-Cookie: + description: May include refreshed JWT cookie ("frigate-token") when applicable + schema: + type: string "401": description: Authentication Failed /profile: diff --git a/frigate/api/auth.py b/frigate/api/auth.py index cae80dc5b..95ee4f9dc 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -553,7 +553,32 @@ def resolve_role( "/auth", dependencies=[Depends(allow_public())], summary="Authenticate request", - description="Authenticates the current request based on proxy headers or JWT token. Returns user role and permissions for camera access.", + description=( + "Authenticates the current request based on proxy headers or JWT token. " + "This endpoint verifies authentication credentials and manages JWT token refresh. " + "On success, no JSON body is returned; authentication state is communicated via response headers and cookies." + ), + status_code=202, + responses={ + 202: { + "description": "Authentication Accepted (no response body)", + "headers": { + "remote-user": { + "description": 'Authenticated username or "anonymous" in proxy-only mode', + "schema": {"type": "string"}, + }, + "remote-role": { + "description": "Resolved role (e.g., admin, viewer, or custom)", + "schema": {"type": "string"}, + }, + "Set-Cookie": { + "description": "May include refreshed JWT cookie when applicable", + "schema": {"type": "string"}, + }, + }, + }, + 401: {"description": "Authentication Failed"}, + }, ) def auth(request: Request): auth_config: AuthConfig = request.app.frigate_config.auth @@ -698,7 +723,7 @@ def auth(request: Request): "/profile", dependencies=[Depends(allow_any_authenticated())], summary="Get user profile", - description="Returns the current authenticated user's profile including username, role, and allowed cameras.", + description="Returns the current authenticated user's profile including username, role, and allowed cameras. This endpoint requires authentication and returns information about the user's permissions.", ) def profile(request: Request): username = request.headers.get("remote-user", "anonymous") @@ -717,7 +742,7 @@ def profile(request: Request): "/logout", dependencies=[Depends(allow_public())], summary="Logout user", - description="Logs out the current user by clearing the session cookie.", + description="Logs out the current user by clearing the session cookie. After logout, subsequent requests will require re-authentication.", ) def logout(request: Request): auth_config: AuthConfig = request.app.frigate_config.auth @@ -733,7 +758,7 @@ limiter = Limiter(key_func=get_remote_addr) "/login", dependencies=[Depends(allow_public())], summary="Login with credentials", - description="Authenticates a user with username and password. Returns a JWT token as a secure HTTP-only cookie that can be used for subsequent API requests. The token can also be retrieved and used as a Bearer token in the Authorization header.", + description='Authenticates a user with username and password. Returns a JWT token as a secure HTTP-only cookie that can be used for subsequent API requests. The JWT token can also be retrieved from the response and used as a Bearer token in the Authorization header.\n\nExample using Bearer token:\n```\ncurl -H "Authorization: Bearer " https://frigate_ip:8971/api/profile\n```', ) @limiter.limit(limit_value=rateLimiter.get_limit) def login(request: Request, body: AppPostLoginBody): @@ -776,7 +801,7 @@ def login(request: Request, body: AppPostLoginBody): "/users", dependencies=[Depends(require_role(["admin"]))], summary="Get all users", - description="Returns a list of all users with their usernames and roles. Requires admin role.", + description="Returns a list of all users with their usernames and roles. Requires admin role. Each user object contains the username and assigned role.", ) def get_users(): exports = ( @@ -789,7 +814,7 @@ def get_users(): "/users", dependencies=[Depends(require_role(["admin"]))], summary="Create new user", - description="Creates a new user with the specified username, password, and role. Requires admin role. Password must meet strength requirements.", + description='Creates a new user with the specified username, password, and role. Requires admin role. Password must meet strength requirements: minimum 8 characters, at least one uppercase letter, at least one digit, and at least one special character (!@#$%^&*(),.?":{} |<>).', ) def create_user( request: Request, @@ -823,7 +848,7 @@ def create_user( "/users/{username}", dependencies=[Depends(require_role(["admin"]))], summary="Delete user", - description="Deletes a user by username. The built-in admin user cannot be deleted. Requires admin role.", + description="Deletes a user by username. The built-in admin user cannot be deleted. Requires admin role. Returns success message or error if user not found.", ) def delete_user(request: Request, username: str): # Prevent deletion of the built-in admin user @@ -840,7 +865,7 @@ def delete_user(request: Request, username: str): "/users/{username}/password", dependencies=[Depends(allow_any_authenticated())], summary="Update user password", - description="Updates a user's password. Users can only change their own password unless they have admin role. Requires the current password to verify identity. Password must meet strength requirements (minimum 8 characters, uppercase letter, digit, and special character).", + description="Updates a user's password. Users can only change their own password unless they have admin role. Requires the current password to verify identity for non-admin users. Password must meet strength requirements: minimum 8 characters, at least one uppercase letter, at least one digit, and at least one special character (!@#$%^&*(),.?\":{} |<>). If user changes their own password, a new JWT cookie is automatically issued.", ) async def update_password( request: Request, @@ -926,7 +951,7 @@ async def update_password( "/users/{username}/role", dependencies=[Depends(require_role(["admin"]))], summary="Update user role", - description="Updates a user's role. The built-in admin user's role cannot be modified. Requires admin role.", + description="Updates a user's role. The built-in admin user's role cannot be modified. Requires admin role. Valid roles are defined in the configuration.", ) async def update_role( request: Request, diff --git a/frigate/detectors/detection_runners.py b/frigate/detectors/detection_runners.py index eb3a0ecb9..89ebb35eb 100644 --- a/frigate/detectors/detection_runners.py +++ b/frigate/detectors/detection_runners.py @@ -131,6 +131,7 @@ class ONNXModelRunner(BaseModelRunner): return model_type in [ EnrichmentModelTypeEnum.paddleocr.value, + EnrichmentModelTypeEnum.yolov9_license_plate.value, EnrichmentModelTypeEnum.jina_v1.value, EnrichmentModelTypeEnum.jina_v2.value, EnrichmentModelTypeEnum.facenet.value, diff --git a/frigate/record/cleanup.py b/frigate/record/cleanup.py index e15690e58..94dd43eba 100644 --- a/frigate/record/cleanup.py +++ b/frigate/record/cleanup.py @@ -119,6 +119,7 @@ class RecordingCleanup(threading.Thread): Recordings.path, Recordings.objects, Recordings.motion, + Recordings.dBFS, ) .where( (Recordings.camera == config.name) @@ -126,6 +127,7 @@ class RecordingCleanup(threading.Thread): ( (Recordings.end_time < continuous_expire_date) & (Recordings.motion == 0) + & (Recordings.dBFS == 0) ) | (Recordings.end_time < motion_expire_date) ) @@ -185,6 +187,7 @@ class RecordingCleanup(threading.Thread): mode == RetainModeEnum.motion and recording.motion == 0 and recording.objects == 0 + and recording.dBFS == 0 ) or (mode == RetainModeEnum.active_objects and recording.objects == 0) ): diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 8bfa726de..d60d7ccce 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -67,7 +67,7 @@ class SegmentInfo: if ( not keep and retain_mode == RetainModeEnum.motion - and (self.motion_count > 0 or self.average_dBFS > 0) + and (self.motion_count > 0 or self.average_dBFS != 0) ): keep = True diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index 976b20042..b8b5b9911 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -440,6 +440,7 @@ function CustomTimeSelector({
{ if (!open) { @@ -461,7 +462,10 @@ function CustomTimeSelector({ {formattedStart} - + { if (!open) { @@ -527,7 +532,10 @@ function CustomTimeSelector({ {formattedEnd} - + !globalObjectMasksArray.includes(mask), + ); + } + queryString = filteredMask .map((pointsArray) => { const coordinates = flattenPoints(parseCoordinates(pointsArray)).join(