mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +03:00
Merge remote-tracking branch 'upstream/dev' into unit-test-for-review-controller
This commit is contained in:
commit
654997470f
@ -94,6 +94,18 @@ Message published for each changed tracked object. The first message is publishe
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `frigate/tracked_object_update`
|
||||||
|
|
||||||
|
Message published for updates to tracked object metadata, for example when GenAI runs and returns a tracked object description.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "description",
|
||||||
|
"id": "1607123955.475377-mxklsc",
|
||||||
|
"description": "The car is a red sedan moving away from the camera."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `frigate/reviews`
|
### `frigate/reviews`
|
||||||
|
|
||||||
Message published for each changed review item. The first message is published when the `detection` or `alert` is initiated. When additional objects are detected or when a zone change occurs, it will publish a, `update` message with the same id. When the review activity has ended a final `end` message is published.
|
Message published for each changed review item. The first message is published when the `detection` or `alert` is initiated. When additional objects are detected or when a zone change occurs, it will publish a, `update` message with the same id. When the review activity has ended a final `end` message is published.
|
||||||
|
|||||||
@ -68,7 +68,7 @@ from frigate.stats.util import stats_init
|
|||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
from frigate.timeline import TimelineProcessor
|
from frigate.timeline import TimelineProcessor
|
||||||
from frigate.util.builtin import empty_and_close_queue
|
from frigate.util.builtin import empty_and_close_queue
|
||||||
from frigate.util.image import UntrackedSharedMemory
|
from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory
|
||||||
from frigate.util.object import get_camera_regions_grid
|
from frigate.util.object import get_camera_regions_grid
|
||||||
from frigate.version import VERSION
|
from frigate.version import VERSION
|
||||||
from frigate.video import capture_camera, track_camera
|
from frigate.video import capture_camera, track_camera
|
||||||
@ -91,6 +91,7 @@ class FrigateApp:
|
|||||||
self.processes: dict[str, int] = {}
|
self.processes: dict[str, int] = {}
|
||||||
self.embeddings: Optional[EmbeddingsContext] = None
|
self.embeddings: Optional[EmbeddingsContext] = None
|
||||||
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
|
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
|
||||||
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def ensure_dirs(self) -> None:
|
def ensure_dirs(self) -> None:
|
||||||
@ -432,6 +433,11 @@ class FrigateApp:
|
|||||||
logger.info(f"Capture process not started for disabled camera {name}")
|
logger.info(f"Capture process not started for disabled camera {name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# pre-create shms
|
||||||
|
for i in range(shm_frame_count):
|
||||||
|
frame_size = config.frame_shape_yuv[0] * config.frame_shape_yuv[1]
|
||||||
|
self.frame_manager.create(f"{config.name}{i}", frame_size)
|
||||||
|
|
||||||
capture_process = util.Process(
|
capture_process = util.Process(
|
||||||
target=capture_camera,
|
target=capture_camera,
|
||||||
name=f"camera_capture:{name}",
|
name=f"camera_capture:{name}",
|
||||||
@ -711,6 +717,7 @@ class FrigateApp:
|
|||||||
self.event_metadata_updater.stop()
|
self.event_metadata_updater.stop()
|
||||||
self.inter_zmq_proxy.stop()
|
self.inter_zmq_proxy.stop()
|
||||||
|
|
||||||
|
self.frame_manager.cleanup()
|
||||||
while len(self.detection_shms) > 0:
|
while len(self.detection_shms) > 0:
|
||||||
shm = self.detection_shms.pop()
|
shm = self.detection_shms.pop()
|
||||||
shm.close()
|
shm.close()
|
||||||
|
|||||||
@ -22,7 +22,7 @@ from frigate.const import (
|
|||||||
)
|
)
|
||||||
from frigate.models import Event, Previews, Recordings, ReviewSegment
|
from frigate.models import Event, Previews, Recordings, ReviewSegment
|
||||||
from frigate.ptz.onvif import OnvifCommandEnum, OnvifController
|
from frigate.ptz.onvif import OnvifCommandEnum, OnvifController
|
||||||
from frigate.types import ModelStatusTypesEnum
|
from frigate.types import ModelStatusTypesEnum, TrackedObjectUpdateTypesEnum
|
||||||
from frigate.util.object import get_camera_regions_grid
|
from frigate.util.object import get_camera_regions_grid
|
||||||
from frigate.util.services import restart_frigate
|
from frigate.util.services import restart_frigate
|
||||||
|
|
||||||
@ -137,8 +137,14 @@ class Dispatcher:
|
|||||||
event.data["description"] = payload["description"]
|
event.data["description"] = payload["description"]
|
||||||
event.save()
|
event.save()
|
||||||
self.publish(
|
self.publish(
|
||||||
"event_update",
|
"tracked_object_update",
|
||||||
json.dumps({"id": event.id, "description": event.data["description"]}),
|
json.dumps(
|
||||||
|
{
|
||||||
|
"type": TrackedObjectUpdateTypesEnum.description,
|
||||||
|
"id": event.id,
|
||||||
|
"description": event.data["description"],
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_update_model_state():
|
def handle_update_model_state():
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class AuthConfig(FrigateBaseModel):
|
|||||||
default=False, title="Reset the admin password on startup"
|
default=False, title="Reset the admin password on startup"
|
||||||
)
|
)
|
||||||
cookie_name: str = Field(
|
cookie_name: str = Field(
|
||||||
default="frigate_token", title="Name for jwt token cookie", pattern=r"^[a-z]_*$"
|
default="frigate_token", title="Name for jwt token cookie", pattern=r"^[a-z_]+$"
|
||||||
)
|
)
|
||||||
cookie_secure: bool = Field(default=False, title="Set secure flag on cookie")
|
cookie_secure: bool = Field(default=False, title="Set secure flag on cookie")
|
||||||
session_length: int = Field(
|
session_length: int = Field(
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from frigate.const import CLIPS_DIR, UPDATE_EVENT_DESCRIPTION
|
|||||||
from frigate.events.types import EventTypeEnum
|
from frigate.events.types import EventTypeEnum
|
||||||
from frigate.genai import get_genai_client
|
from frigate.genai import get_genai_client
|
||||||
from frigate.models import Event
|
from frigate.models import Event
|
||||||
|
from frigate.types import TrackedObjectUpdateTypesEnum
|
||||||
from frigate.util.builtin import serialize
|
from frigate.util.builtin import serialize
|
||||||
from frigate.util.image import SharedMemoryFrameManager, calculate_region
|
from frigate.util.image import SharedMemoryFrameManager, calculate_region
|
||||||
|
|
||||||
@ -287,7 +288,11 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
# fire and forget description update
|
# fire and forget description update
|
||||||
self.requestor.send_data(
|
self.requestor.send_data(
|
||||||
UPDATE_EVENT_DESCRIPTION,
|
UPDATE_EVENT_DESCRIPTION,
|
||||||
{"id": event.id, "description": description},
|
{
|
||||||
|
"type": TrackedObjectUpdateTypesEnum.description,
|
||||||
|
"id": event.id,
|
||||||
|
"description": description,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Embed the description
|
# Embed the description
|
||||||
|
|||||||
@ -388,7 +388,7 @@ class BirdsEyeFrameManager:
|
|||||||
for cam, cam_data in self.cameras.items()
|
for cam, cam_data in self.cameras.items()
|
||||||
if self.config.cameras[cam].birdseye.enabled
|
if self.config.cameras[cam].birdseye.enabled
|
||||||
and cam_data["last_active_frame"] > 0
|
and cam_data["last_active_frame"] > 0
|
||||||
and cam_data["current_frame"] - cam_data["last_active_frame"]
|
and cam_data["current_frame_time"] - cam_data["last_active_frame"]
|
||||||
< self.inactivity_threshold
|
< self.inactivity_threshold
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -405,7 +405,7 @@ class BirdsEyeFrameManager:
|
|||||||
limited_active_cameras = sorted(
|
limited_active_cameras = sorted(
|
||||||
active_cameras,
|
active_cameras,
|
||||||
key=lambda active_camera: (
|
key=lambda active_camera: (
|
||||||
self.cameras[active_camera]["current_frame"]
|
self.cameras[active_camera]["current_frame_time"]
|
||||||
- self.cameras[active_camera]["last_active_frame"]
|
- self.cameras[active_camera]["last_active_frame"]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -517,7 +517,7 @@ class BirdsEyeFrameManager:
|
|||||||
self.copy_to_position(
|
self.copy_to_position(
|
||||||
position[1],
|
position[1],
|
||||||
position[0],
|
position[0],
|
||||||
frame,
|
self.cameras[position[0]]["current_frame"],
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -689,7 +689,8 @@ class BirdsEyeFrameManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# update the last active frame for the camera
|
# update the last active frame for the camera
|
||||||
self.cameras[camera]["current_frame"] = frame_time
|
self.cameras[camera]["current_frame"] = frame.copy()
|
||||||
|
self.cameras[camera]["current_frame_time"] = frame_time
|
||||||
if self.camera_active(camera_config.mode, object_count, motion_count):
|
if self.camera_active(camera_config.mode, object_count, motion_count):
|
||||||
self.cameras[camera]["last_active_frame"] = frame_time
|
self.cameras[camera]["last_active_frame"] = frame_time
|
||||||
|
|
||||||
@ -739,9 +740,10 @@ class Birdseye:
|
|||||||
)
|
)
|
||||||
self.birdseye_manager = BirdsEyeFrameManager(config, stop_event)
|
self.birdseye_manager = BirdsEyeFrameManager(config, stop_event)
|
||||||
self.config_subscriber = ConfigSubscriber("config/birdseye/")
|
self.config_subscriber = ConfigSubscriber("config/birdseye/")
|
||||||
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
|
|
||||||
if config.birdseye.restream:
|
if config.birdseye.restream:
|
||||||
self.birdseye_buffer = SharedMemoryFrameManager().create(
|
self.birdseye_buffer = self.frame_manager.create(
|
||||||
"birdseye",
|
"birdseye",
|
||||||
self.birdseye_manager.yuv_shape[0] * self.birdseye_manager.yuv_shape[1],
|
self.birdseye_manager.yuv_shape[0] * self.birdseye_manager.yuv_shape[1],
|
||||||
)
|
)
|
||||||
@ -755,7 +757,7 @@ class Birdseye:
|
|||||||
current_tracked_objects: list[dict[str, any]],
|
current_tracked_objects: list[dict[str, any]],
|
||||||
motion_boxes: list[list[int]],
|
motion_boxes: list[list[int]],
|
||||||
frame_time: float,
|
frame_time: float,
|
||||||
frame,
|
frame: np.ndarray,
|
||||||
) -> None:
|
) -> None:
|
||||||
# check if there is an updated config
|
# check if there is an updated config
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@ -19,3 +19,7 @@ class ModelStatusTypesEnum(str, Enum):
|
|||||||
downloading = "downloading"
|
downloading = "downloading"
|
||||||
downloaded = "downloaded"
|
downloaded = "downloaded"
|
||||||
error = "error"
|
error = "error"
|
||||||
|
|
||||||
|
|
||||||
|
class TrackedObjectUpdateTypesEnum(str, Enum):
|
||||||
|
description = "description"
|
||||||
|
|||||||
@ -790,11 +790,15 @@ class SharedMemoryFrameManager(FrameManager):
|
|||||||
self.shm_store: dict[str, UntrackedSharedMemory] = {}
|
self.shm_store: dict[str, UntrackedSharedMemory] = {}
|
||||||
|
|
||||||
def create(self, name: str, size) -> AnyStr:
|
def create(self, name: str, size) -> AnyStr:
|
||||||
shm = UntrackedSharedMemory(
|
try:
|
||||||
name=name,
|
shm = UntrackedSharedMemory(
|
||||||
create=True,
|
name=name,
|
||||||
size=size,
|
create=True,
|
||||||
)
|
size=size,
|
||||||
|
)
|
||||||
|
except FileExistsError:
|
||||||
|
shm = UntrackedSharedMemory(name=name)
|
||||||
|
|
||||||
self.shm_store[name] = shm
|
self.shm_store[name] = shm
|
||||||
return shm.buf
|
return shm.buf
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,8 @@ def capture_frames(
|
|||||||
ffmpeg_process,
|
ffmpeg_process,
|
||||||
config: CameraConfig,
|
config: CameraConfig,
|
||||||
shm_frame_count: int,
|
shm_frame_count: int,
|
||||||
frame_shape,
|
frame_index: int,
|
||||||
|
frame_shape: tuple[int, int],
|
||||||
frame_manager: FrameManager,
|
frame_manager: FrameManager,
|
||||||
frame_queue,
|
frame_queue,
|
||||||
fps: mp.Value,
|
fps: mp.Value,
|
||||||
@ -108,12 +109,6 @@ def capture_frames(
|
|||||||
skipped_eps = EventsPerSecond()
|
skipped_eps = EventsPerSecond()
|
||||||
skipped_eps.start()
|
skipped_eps.start()
|
||||||
|
|
||||||
# pre-create shms
|
|
||||||
for i in range(shm_frame_count):
|
|
||||||
frame_manager.create(f"{config.name}{i}", frame_size)
|
|
||||||
|
|
||||||
frame_index = 0
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
fps.value = frame_rate.eps()
|
fps.value = frame_rate.eps()
|
||||||
skipped_fps.value = skipped_eps.eps()
|
skipped_fps.value = skipped_eps.eps()
|
||||||
@ -150,8 +145,6 @@ def capture_frames(
|
|||||||
|
|
||||||
frame_index = 0 if frame_index == shm_frame_count - 1 else frame_index + 1
|
frame_index = 0 if frame_index == shm_frame_count - 1 else frame_index + 1
|
||||||
|
|
||||||
frame_manager.cleanup()
|
|
||||||
|
|
||||||
|
|
||||||
class CameraWatchdog(threading.Thread):
|
class CameraWatchdog(threading.Thread):
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -159,7 +152,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
camera_name,
|
camera_name,
|
||||||
config: CameraConfig,
|
config: CameraConfig,
|
||||||
shm_frame_count: int,
|
shm_frame_count: int,
|
||||||
frame_queue,
|
frame_queue: mp.Queue,
|
||||||
camera_fps,
|
camera_fps,
|
||||||
skipped_fps,
|
skipped_fps,
|
||||||
ffmpeg_pid,
|
ffmpeg_pid,
|
||||||
@ -181,6 +174,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
self.frame_shape = self.config.frame_shape_yuv
|
self.frame_shape = self.config.frame_shape_yuv
|
||||||
self.frame_size = self.frame_shape[0] * self.frame_shape[1]
|
self.frame_size = self.frame_shape[0] * self.frame_shape[1]
|
||||||
self.fps_overflow_count = 0
|
self.fps_overflow_count = 0
|
||||||
|
self.frame_index = 0
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
self.sleeptime = self.config.ffmpeg.retry_interval
|
self.sleeptime = self.config.ffmpeg.retry_interval
|
||||||
|
|
||||||
@ -302,6 +296,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
self.capture_thread = CameraCapture(
|
self.capture_thread = CameraCapture(
|
||||||
self.config,
|
self.config,
|
||||||
self.shm_frame_count,
|
self.shm_frame_count,
|
||||||
|
self.frame_index,
|
||||||
self.ffmpeg_detect_process,
|
self.ffmpeg_detect_process,
|
||||||
self.frame_shape,
|
self.frame_shape,
|
||||||
self.frame_queue,
|
self.frame_queue,
|
||||||
@ -342,9 +337,10 @@ class CameraCapture(threading.Thread):
|
|||||||
self,
|
self,
|
||||||
config: CameraConfig,
|
config: CameraConfig,
|
||||||
shm_frame_count: int,
|
shm_frame_count: int,
|
||||||
|
frame_index: int,
|
||||||
ffmpeg_process,
|
ffmpeg_process,
|
||||||
frame_shape,
|
frame_shape: tuple[int, int],
|
||||||
frame_queue,
|
frame_queue: mp.Queue,
|
||||||
fps,
|
fps,
|
||||||
skipped_fps,
|
skipped_fps,
|
||||||
stop_event,
|
stop_event,
|
||||||
@ -353,6 +349,7 @@ class CameraCapture(threading.Thread):
|
|||||||
self.name = f"capture:{config.name}"
|
self.name = f"capture:{config.name}"
|
||||||
self.config = config
|
self.config = config
|
||||||
self.shm_frame_count = shm_frame_count
|
self.shm_frame_count = shm_frame_count
|
||||||
|
self.frame_index = frame_index
|
||||||
self.frame_shape = frame_shape
|
self.frame_shape = frame_shape
|
||||||
self.frame_queue = frame_queue
|
self.frame_queue = frame_queue
|
||||||
self.fps = fps
|
self.fps = fps
|
||||||
@ -368,6 +365,7 @@ class CameraCapture(threading.Thread):
|
|||||||
self.ffmpeg_process,
|
self.ffmpeg_process,
|
||||||
self.config,
|
self.config,
|
||||||
self.shm_frame_count,
|
self.shm_frame_count,
|
||||||
|
self.frame_index,
|
||||||
self.frame_shape,
|
self.frame_shape,
|
||||||
self.frame_manager,
|
self.frame_manager,
|
||||||
self.frame_queue,
|
self.frame_queue,
|
||||||
|
|||||||
@ -407,9 +407,9 @@ export function useImproveContrast(camera: string): {
|
|||||||
return { payload: payload as ToggleableSetting, send };
|
return { payload: payload as ToggleableSetting, send };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEventUpdate(): { payload: string } {
|
export function useTrackedObjectUpdate(): { payload: string } {
|
||||||
const {
|
const {
|
||||||
value: { payload },
|
value: { payload },
|
||||||
} = useWs("event_update", "");
|
} = useWs("tracked_object_update", "");
|
||||||
return useDeepMemo(JSON.parse(payload as string));
|
return useDeepMemo(JSON.parse(payload as string));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -309,7 +309,7 @@ function ObjectDetailsTab({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.sub_label) {
|
if (search.sub_label && search.data?.sub_label_score) {
|
||||||
return Math.round((search.data?.sub_label_score ?? 0) * 100);
|
return Math.round((search.data?.sub_label_score ?? 0) * 100);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
useEmbeddingsReindexProgress,
|
useEmbeddingsReindexProgress,
|
||||||
useEventUpdate,
|
useTrackedObjectUpdate,
|
||||||
useModelState,
|
useModelState,
|
||||||
} from "@/api/ws";
|
} from "@/api/ws";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
@ -227,15 +227,15 @@ export default function Explore() {
|
|||||||
|
|
||||||
// mutation and revalidation
|
// mutation and revalidation
|
||||||
|
|
||||||
const eventUpdate = useEventUpdate();
|
const trackedObjectUpdate = useTrackedObjectUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (eventUpdate) {
|
if (trackedObjectUpdate) {
|
||||||
mutate();
|
mutate();
|
||||||
}
|
}
|
||||||
// mutate / revalidate when event description updates come in
|
// mutate / revalidate when event description updates come in
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [eventUpdate]);
|
}, [trackedObjectUpdate]);
|
||||||
|
|
||||||
// embeddings reindex progress
|
// embeddings reindex progress
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { SearchResult } from "@/types/search";
|
|||||||
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
||||||
import useImageLoaded from "@/hooks/use-image-loaded";
|
import useImageLoaded from "@/hooks/use-image-loaded";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import { useEventUpdate } from "@/api/ws";
|
import { useTrackedObjectUpdate } from "@/api/ws";
|
||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
import TimeAgo from "@/components/dynamic/TimeAgo";
|
import TimeAgo from "@/components/dynamic/TimeAgo";
|
||||||
import SearchResultActions from "@/components/menu/SearchResultActions";
|
import SearchResultActions from "@/components/menu/SearchResultActions";
|
||||||
@ -72,13 +72,13 @@ export default function ExploreView({
|
|||||||
}, {});
|
}, {});
|
||||||
}, [events]);
|
}, [events]);
|
||||||
|
|
||||||
const eventUpdate = useEventUpdate();
|
const trackedObjectUpdate = useTrackedObjectUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutate();
|
mutate();
|
||||||
// mutate / revalidate when event description updates come in
|
// mutate / revalidate when event description updates come in
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [eventUpdate]);
|
}, [trackedObjectUpdate]);
|
||||||
|
|
||||||
// update search detail when results change
|
// update search detail when results change
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user