mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-11 13:21:10 +03:00
fix birdseye reordering widget in settings
cameras are only sorted while the layout is built, so a config change that reorders them needs a fresh layout even when the active set is unchanged, which is always the case in continuous mode
This commit is contained in:
@@ -335,6 +335,7 @@ class BirdsEyeFrameManager:
|
||||
|
||||
self.camera_layout: list[Any] = []
|
||||
self.active_cameras: set[str] = set()
|
||||
self.layout_camera_order: list[str] = []
|
||||
self.last_output_time = 0.0
|
||||
|
||||
def add_camera(self, cam: str) -> None:
|
||||
@@ -372,6 +373,13 @@ class BirdsEyeFrameManager:
|
||||
if cam in self.cameras:
|
||||
del self.cameras[cam]
|
||||
|
||||
def sort_cameras(self, cameras: set[str]) -> list[str]:
|
||||
"""Sort cameras by birdseye order, falling back to name when tied."""
|
||||
return sorted(
|
||||
cameras,
|
||||
key=lambda camera: (self.config.cameras[camera].birdseye.order, camera),
|
||||
)
|
||||
|
||||
def clear_frame(self) -> None:
|
||||
logger.debug("Clearing the birdseye frame")
|
||||
self.frame[:] = self.blank_frame
|
||||
@@ -482,6 +490,7 @@ class BirdsEyeFrameManager:
|
||||
# if the layout needs to be cleared
|
||||
self.camera_layout = []
|
||||
self.active_cameras = set()
|
||||
self.layout_camera_order = []
|
||||
self.clear_frame()
|
||||
frame_changed = True
|
||||
layout_changed = True
|
||||
@@ -500,21 +509,21 @@ class BirdsEyeFrameManager:
|
||||
else:
|
||||
reset_layout = True
|
||||
|
||||
sorted_active_cameras = self.sort_cameras(active_cameras)
|
||||
|
||||
if not reset_layout and sorted_active_cameras != self.layout_camera_order:
|
||||
logger.debug("Birdseye camera order changed")
|
||||
reset_layout = True
|
||||
|
||||
if reset_layout:
|
||||
logger.debug("Resetting Birdseye layout...")
|
||||
self.clear_frame()
|
||||
self.active_cameras = active_cameras
|
||||
self.layout_camera_order = sorted_active_cameras
|
||||
layout_changed = True # Layout is changing due to reset
|
||||
# this also converts added_cameras from a set to a list since we need
|
||||
# to pop elements in order
|
||||
active_cameras_to_add = sorted(
|
||||
active_cameras,
|
||||
# sort cameras by order and by name if the order is the same
|
||||
key=lambda active_camera: (
|
||||
self.config.cameras[active_camera].birdseye.order,
|
||||
active_camera,
|
||||
),
|
||||
)
|
||||
active_cameras_to_add = sorted_active_cameras
|
||||
if len(active_cameras) == 1:
|
||||
# show single camera as fullscreen
|
||||
camera = active_cameras_to_add[0]
|
||||
@@ -780,6 +789,7 @@ class BirdsEyeFrameManager:
|
||||
frame_changed, layout_changed = False, False
|
||||
self.active_cameras = set()
|
||||
self.camera_layout = []
|
||||
self.layout_camera_order = []
|
||||
print(traceback.format_exc())
|
||||
|
||||
# if the frame was updated or the fps is too low, send frame
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Test camera user and password cleanup."""
|
||||
|
||||
import multiprocessing as mp
|
||||
import unittest
|
||||
|
||||
from frigate.output.birdseye import get_canvas_shape
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.output.birdseye import BirdsEyeFrameManager, get_canvas_shape
|
||||
|
||||
|
||||
class TestBirdseye(unittest.TestCase):
|
||||
@@ -45,3 +47,70 @@ class TestBirdseye(unittest.TestCase):
|
||||
canvas_width, canvas_height = get_canvas_shape(width, height)
|
||||
assert canvas_width == width # width will be the same
|
||||
assert canvas_height != height
|
||||
|
||||
|
||||
class TestBirdseyeCameraOrder(unittest.TestCase):
|
||||
"""Test that birdseye reacts to camera order changes without a restart."""
|
||||
|
||||
def setUp(self):
|
||||
config = {
|
||||
"mqtt": {"enabled": False},
|
||||
"birdseye": {"enabled": True, "mode": "continuous"},
|
||||
"cameras": {
|
||||
camera: {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]}
|
||||
]
|
||||
},
|
||||
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||
}
|
||||
for camera in ("back", "front", "side")
|
||||
},
|
||||
}
|
||||
self.config = FrigateConfig(**config)
|
||||
self.manager = BirdsEyeFrameManager(self.config, mp.Event())
|
||||
|
||||
# mark every camera as continuously active with no frame to draw, which
|
||||
# exercises the layout without needing real yuv frames
|
||||
for camera_data in self.manager.cameras.values():
|
||||
camera_data["current_frame"] = None
|
||||
camera_data["current_frame_time"] = 1.0
|
||||
camera_data["last_active_frame"] = 1.0
|
||||
|
||||
def layout_order(self) -> list[str]:
|
||||
"""Return the cameras in the order the current layout renders them."""
|
||||
return [position[0] for row in self.manager.camera_layout for position in row]
|
||||
|
||||
def test_layout_uses_configured_order(self):
|
||||
"""Test the layout is sorted by order, then by name when tied."""
|
||||
self.config.cameras["side"].birdseye.order = 0
|
||||
self.config.cameras["back"].birdseye.order = 10
|
||||
self.config.cameras["front"].birdseye.order = 20
|
||||
|
||||
self.manager.update_frame()
|
||||
|
||||
assert self.layout_order() == ["side", "back", "front"]
|
||||
|
||||
def test_order_change_rebuilds_layout(self):
|
||||
"""Test a reorder relayouts even though the active cameras are unchanged."""
|
||||
self.manager.update_frame()
|
||||
assert self.layout_order() == ["back", "front", "side"]
|
||||
|
||||
# a stable active set means only an order change can reset the layout,
|
||||
# which is what a settings reorder publishes to this process
|
||||
self.config.cameras["side"].birdseye.order = -10
|
||||
|
||||
_, layout_changed = self.manager.update_frame()
|
||||
|
||||
assert layout_changed
|
||||
assert self.layout_order() == ["side", "back", "front"]
|
||||
|
||||
def test_unchanged_order_keeps_layout(self):
|
||||
"""Test a repeat update with no order change doesn't reset the layout."""
|
||||
self.manager.update_frame()
|
||||
|
||||
_, layout_changed = self.manager.update_frame()
|
||||
|
||||
assert not layout_changed
|
||||
assert self.layout_order() == ["back", "front", "side"]
|
||||
|
||||
@@ -91,6 +91,7 @@ export default function BirdseyeCameraReorder({
|
||||
try {
|
||||
await axios.put("config/set", {
|
||||
requires_restart: 0,
|
||||
update_topic: "config/cameras/*/birdseye",
|
||||
config_data: { cameras: cameraUpdates },
|
||||
});
|
||||
await updateConfig();
|
||||
|
||||
Reference in New Issue
Block a user