From 122866927bbd1a0293f8f9759587a23f148ec58a Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 28 Jun 2026 08:21:30 -0500 Subject: [PATCH] scope /recordings/unavailable query to the caller's allowed cameras --- frigate/api/record.py | 20 ++++----- frigate/test/http_api/test_http_media.py | 52 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/frigate/api/record.py b/frigate/api/record.py index 04b81cf38a..5fc3950e20 100644 --- a/frigate/api/record.py +++ b/frigate/api/record.py @@ -269,12 +269,12 @@ async def no_recordings( cameras = params.cameras if cameras != "all": requested = set(unquote(cameras).split(",")) - filtered = requested.intersection(allowed_cameras) - if not filtered: - return JSONResponse(content=[]) - cameras = ",".join(filtered) + camera_list = list(requested.intersection(allowed_cameras)) else: - cameras = allowed_cameras + camera_list = list(allowed_cameras) + + if not camera_list: + return JSONResponse(content=[]) before = params.before or datetime.datetime.now().timestamp() after = ( @@ -283,12 +283,10 @@ async def no_recordings( ) scale = params.scale - clauses = [(Recordings.end_time >= after) & (Recordings.start_time <= before)] - if cameras != "all": - camera_list = cameras.split(",") - clauses.append((Recordings.camera << camera_list)) - else: - camera_list = allowed_cameras + clauses = [ + (Recordings.end_time >= after) & (Recordings.start_time <= before), + (Recordings.camera << camera_list), + ] # Get recording start times data: list[Recordings] = ( diff --git a/frigate/test/http_api/test_http_media.py b/frigate/test/http_api/test_http_media.py index 58f9f92477..fa4e69473b 100644 --- a/frigate/test/http_api/test_http_media.py +++ b/frigate/test/http_api/test_http_media.py @@ -475,3 +475,55 @@ class TestHttpMedia(BaseTestHttp): assert response.status_code == 200 assert response.json() == [] + + def test_recordings_unavailable_cameras_all_scopes_to_allowed_cameras(self): + """cameras=all must not error and must only consider allowed cameras. + + allowed_cameras is mocked to ["front_door"]. A back_door recording that + would otherwise fill the gap must be ignored, and the request must not + 500 the way it did when cameras was reassigned to a list. + """ + with AuthTestClient(self.app) as client: + # front_door has a 20s gap (1010-1030). + Recordings.insert( + id="front_a", + path="/media/recordings/front_a.mp4", + camera="front_door", + start_time=1000, + end_time=1010, + duration=10, + motion=0, + ).execute() + Recordings.insert( + id="front_b", + path="/media/recordings/front_b.mp4", + camera="front_door", + start_time=1030, + end_time=1040, + duration=10, + motion=0, + ).execute() + # back_door is not in allowed_cameras; its full-window coverage must + # not mask the front_door gap. + Recordings.insert( + id="back_a", + path="/media/recordings/back_a.mp4", + camera="back_door", + start_time=1000, + end_time=1040, + duration=40, + motion=0, + ).execute() + + response = client.get( + "/recordings/unavailable", + params={ + "after": 1000, + "before": 1040, + "scale": 5, + "cameras": "all", + }, + ) + + assert response.status_code == 200 + assert response.json() == [{"start_time": 1010, "end_time": 1030}]