mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-14 17:43:01 +03:00
Miscellaneous fixes (#24970)
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / AMD64 Smoke Test (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / AMD64 Smoke Test (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* analytics notifications flag and device grant warnings - report notifications as on when any camera enables them, not only the global setting - don't warn about device nodes the runtime users can already use, such as world-rw nodes in an unprivileged LXC * clarify which stream the All Cameras dashboard uses * add exception catchall for autotracking calibration * only default review api after to 24 hours when no bounds are given The review endpoint replaced a missing or zero `after` with 24 hours ago, so a `before`-only query older than 24 hours returned nothing and `after=0` couldn't mean no lower bound. The default now applies only when neither `before` nor `after` is sent, a `before`-only query has no lower bound, and both bounds use `is not None` checks. The Live dashboard alerts bar sends no bounds and keeps the 24 hour window.
This commit is contained in:
@@ -57,6 +57,17 @@ done
|
||||
granted=0
|
||||
failed=0
|
||||
|
||||
reachable() {
|
||||
local node="$1" user
|
||||
local access=(-r "$node" -a -w "$node")
|
||||
if [[ -d "$node" ]]; then
|
||||
access=(-r "$node" -a -x "$node")
|
||||
fi
|
||||
for user in frigate go2rtc; do
|
||||
s6-setuidgid "$user" test "${access[@]}" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
grant() {
|
||||
local node="$1"
|
||||
# nullglob only drops patterns that hold a metacharacter, so a literal
|
||||
@@ -72,6 +83,11 @@ grant() {
|
||||
fi
|
||||
if setfacl -m "$spec" "$node" 2>/dev/null; then
|
||||
granted=$((granted + 1))
|
||||
|
||||
# an unprivileged LXC can't set ACLs on its idmapped nodes, which are often
|
||||
# already world-rw
|
||||
elif reachable "$node"; then
|
||||
return 0
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
echo "[WARN] could not grant device access on ${node}; see EXTRA_GROUPS in the non-root docs for the fallback"
|
||||
|
||||
@@ -98,7 +98,7 @@ Additionally, when creating and editing camera groups in the UI, you can choose
|
||||
|
||||
:::note
|
||||
|
||||
Frigate's default dashboard ("All Cameras") will always use the first entry you've defined in streams when playing live streams from your cameras.
|
||||
Frigate's default dashboard ("All Cameras") always uses the first entry in `streams`. That should be your highest quality stream, because Auto starts on the first stream and only steps down from there. To show a lower quality stream on a dashboard, create a camera group and choose the stream in each camera's [streaming settings](#streaming-options-on-camera-group-dashboards) for that group.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -139,7 +139,8 @@ def collect(ctx: ReportContext) -> FeaturesSection:
|
||||
restream=config.birdseye.restream,
|
||||
),
|
||||
mqtt=config.mqtt.enabled,
|
||||
notifications=config.notifications.enabled,
|
||||
notifications=config.notifications.enabled
|
||||
or any(camera.notifications.enabled for camera in cameras),
|
||||
auth=config.auth.enabled,
|
||||
proxy_auth=config.proxy.header_map.user is not None,
|
||||
tls=config.tls.enabled,
|
||||
|
||||
+11
-4
@@ -82,12 +82,19 @@ async def review(
|
||||
reviewed = params.reviewed
|
||||
limit = params.limit
|
||||
severity = params.severity
|
||||
before = params.before or datetime.datetime.now().timestamp()
|
||||
after = (
|
||||
params.after
|
||||
or (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
|
||||
before = (
|
||||
params.before
|
||||
if params.before is not None
|
||||
else datetime.datetime.now().timestamp()
|
||||
)
|
||||
|
||||
if params.after is not None:
|
||||
after = params.after
|
||||
elif params.before is not None:
|
||||
after = 0
|
||||
else:
|
||||
after = (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
|
||||
|
||||
clauses = [
|
||||
(ReviewSegment.start_time < before)
|
||||
& ((ReviewSegment.end_time.is_null(True)) | (ReviewSegment.end_time > after))
|
||||
|
||||
@@ -318,7 +318,13 @@ class PtzAutoTracker(threading.Thread):
|
||||
) = map(float, camera_config.onvif.autotracking.movement_weights)
|
||||
|
||||
if startup and camera_config.onvif.autotracking.calibrate_on_startup:
|
||||
await self._calibrate_camera(camera)
|
||||
try:
|
||||
await self._calibrate_camera(camera)
|
||||
except Exception:
|
||||
logger.exception(f"{camera}: Autotracker calibration failed")
|
||||
self.calibrating[camera] = False
|
||||
self._disable(camera, "calibration failed")
|
||||
return
|
||||
|
||||
self.dispatcher.publish(f"{camera}/ptz_autotracker/active", "OFF", retain=False)
|
||||
self.autotracker_init[camera] = True
|
||||
|
||||
@@ -89,6 +89,37 @@ class TestHttpReview(BaseTestHttp):
|
||||
assert response_json[0]["id"] == id
|
||||
assert response_json[0]["has_been_reviewed"] == False
|
||||
|
||||
def test_get_review_before_only_has_no_lower_bound(self):
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
id = "123456.random"
|
||||
super().insert_mock_review_segment(id, now - 72 * 3600, now - 71 * 3600)
|
||||
response = client.get("/review", params={"before": now - 48 * 3600})
|
||||
assert response.status_code == 200
|
||||
assert [r["id"] for r in response.json()] == [id]
|
||||
|
||||
def test_get_review_after_zero_is_not_defaulted(self):
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
id = "123456.random"
|
||||
super().insert_mock_review_segment(id, now - 72 * 3600, now - 71 * 3600)
|
||||
response = client.get("/review", params={"after": 0})
|
||||
assert response.status_code == 200
|
||||
assert [r["id"] for r in response.json()] == [id]
|
||||
|
||||
def test_get_review_no_bounds_defaults_to_last_24_hours(self):
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
super().insert_mock_review_segment(
|
||||
"123456.random", now - 72 * 3600, now - 71 * 3600
|
||||
)
|
||||
response = client.get("/review", params={"limit": 10})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
def test_get_review_with_time_filter_no_matches(self):
|
||||
"""Test that review items outside the range are not returned."""
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
@@ -100,6 +100,17 @@ class TestFeaturesCollector(unittest.TestCase):
|
||||
self.assertEqual(section.users, {"admin": 1, "viewer": 1, "custom": 1})
|
||||
self.assertEqual((section.camera_groups, section.profiles), (1, 0))
|
||||
self.assertFalse(section.plus_api_key)
|
||||
self.assertFalse(section.notifications)
|
||||
|
||||
def test_camera_notifications_count_as_on(self):
|
||||
config = {
|
||||
**CONFIG,
|
||||
"cameras": {"front": {**FRONT_CAMERA, "notifications": {"enabled": True}}},
|
||||
}
|
||||
|
||||
section = features.collect(make_context(make_config(config), {}))
|
||||
|
||||
self.assertTrue(section.notifications)
|
||||
|
||||
|
||||
class TestFeatureMappings(unittest.TestCase):
|
||||
|
||||
@@ -193,6 +193,35 @@ class TestAutotrackerPrediction(unittest.TestCase):
|
||||
self.assertEqual(self._target_box([-1, -2, 1, 2], 0), 0.2)
|
||||
|
||||
|
||||
class TestAutotrackerCalibrationFailure(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_calibration_failure_disables_autotracking(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
camera_config = tracker.config.cameras[CAMERA]
|
||||
camera_config.onvif.autotracking.calibrate_on_startup = True
|
||||
tracker.tracked_object_history = {}
|
||||
tracker.tracked_object_metrics = {}
|
||||
tracker.move_metrics = {}
|
||||
tracker.intercept = {}
|
||||
tracker.move_coefficients = {}
|
||||
tracker.zoom_time = {}
|
||||
tracker.move_queues = {}
|
||||
tracker.move_queue_locks = {}
|
||||
tracker.onvif.loop = asyncio.get_running_loop()
|
||||
tracker.onvif.cams = {CAMERA: {"init": True, "features": ["pt-r-fov"]}}
|
||||
tracker.onvif.get_service_capabilities = AsyncMock(return_value=True)
|
||||
tracker.onvif.get_camera_status = AsyncMock()
|
||||
tracker._process_move_queue = AsyncMock()
|
||||
tracker._calibrate_camera = AsyncMock(side_effect=RuntimeError("disconnected"))
|
||||
tracker.autotracker_init[CAMERA] = False
|
||||
|
||||
with self.assertLogs("frigate.ptz.autotrack", level="ERROR"):
|
||||
await tracker._autotracker_setup(camera_config, CAMERA, startup=True)
|
||||
|
||||
self.assertFalse(camera_config.onvif.autotracking.enabled)
|
||||
self.assertFalse(tracker.autotracker_init[CAMERA])
|
||||
self.assertFalse(tracker.calibrating[CAMERA])
|
||||
|
||||
|
||||
class TestAutotrackerMoveQueue(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_absolute_zoom_does_not_overwrite_the_pan_tilt_sample(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
|
||||
Reference in New Issue
Block a user