mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
Fixes (#18833)
* Don't allow editing of sub label until object lifecycle has ended * Update sub labels in ended review segments When manually editing a sub label for a tracked object from the UI, any review segments containing that tracked object did not have their sub_labels and objects values altered * simplify * Additional onvif debug logs in get_camera_status * Ensure that best object is only set when the snapshot is actually updated. * Don't hide downlaod button when there is no review item --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
parent
8a9ebe9292
commit
cc368dd20f
@ -291,11 +291,9 @@ class CameraState:
|
|||||||
new_obj.thumbnail_data = thumbnail_data
|
new_obj.thumbnail_data = thumbnail_data
|
||||||
tracked_objects[id].thumbnail_data = thumbnail_data
|
tracked_objects[id].thumbnail_data = thumbnail_data
|
||||||
object_type = new_obj.obj_data["label"]
|
object_type = new_obj.obj_data["label"]
|
||||||
self.best_objects[object_type] = new_obj
|
|
||||||
|
|
||||||
# call event handlers
|
# call event handlers
|
||||||
for c in self.callbacks["snapshot"]:
|
self.send_mqtt_snapshot(new_obj, object_type)
|
||||||
c(self.name, self.best_objects[object_type], frame_name)
|
|
||||||
|
|
||||||
for c in self.callbacks["start"]:
|
for c in self.callbacks["start"]:
|
||||||
c(self.name, new_obj, frame_name)
|
c(self.name, new_obj, frame_name)
|
||||||
@ -417,13 +415,9 @@ class CameraState:
|
|||||||
or (now - current_best.thumbnail_data["frame_time"])
|
or (now - current_best.thumbnail_data["frame_time"])
|
||||||
> self.camera_config.best_image_timeout
|
> self.camera_config.best_image_timeout
|
||||||
):
|
):
|
||||||
self.best_objects[object_type] = obj
|
self.send_mqtt_snapshot(obj, object_type)
|
||||||
for c in self.callbacks["snapshot"]:
|
|
||||||
c(self.name, self.best_objects[object_type], frame_name)
|
|
||||||
else:
|
else:
|
||||||
self.best_objects[object_type] = obj
|
self.send_mqtt_snapshot(obj, object_type)
|
||||||
for c in self.callbacks["snapshot"]:
|
|
||||||
c(self.name, self.best_objects[object_type], frame_name)
|
|
||||||
|
|
||||||
for c in self.callbacks["camera_activity"]:
|
for c in self.callbacks["camera_activity"]:
|
||||||
c(self.name, camera_activity)
|
c(self.name, camera_activity)
|
||||||
@ -472,6 +466,20 @@ class CameraState:
|
|||||||
|
|
||||||
self.previous_frame_id = frame_name
|
self.previous_frame_id = frame_name
|
||||||
|
|
||||||
|
def send_mqtt_snapshot(self, new_obj: TrackedObject, object_type: str) -> None:
|
||||||
|
for c in self.callbacks["snapshot"]:
|
||||||
|
updated = c(self.name, new_obj)
|
||||||
|
|
||||||
|
# if the snapshot was not updated, then this object is not a best object
|
||||||
|
# but all new objects should be considered the next best object
|
||||||
|
# so we remove the label from the best objects
|
||||||
|
if updated:
|
||||||
|
self.best_objects[object_type] = new_obj
|
||||||
|
else:
|
||||||
|
if object_type in self.best_objects:
|
||||||
|
self.best_objects.pop(object_type)
|
||||||
|
break
|
||||||
|
|
||||||
def save_manual_event_image(
|
def save_manual_event_image(
|
||||||
self,
|
self,
|
||||||
frame: np.ndarray | None,
|
frame: np.ndarray | None,
|
||||||
|
@ -792,6 +792,10 @@ class OnvifController:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"{camera_name}: Pan/tilt status: {pan_tilt_status}, Zoom status: {zoom_status}"
|
||||||
|
)
|
||||||
|
|
||||||
if pan_tilt_status == "IDLE" and (zoom_status is None or zoom_status == "IDLE"):
|
if pan_tilt_status == "IDLE" and (zoom_status is None or zoom_status == "IDLE"):
|
||||||
self.cams[camera_name]["active"] = False
|
self.cams[camera_name]["active"] = False
|
||||||
if not self.ptz_metrics[camera_name].motor_stopped.is_set():
|
if not self.ptz_metrics[camera_name].motor_stopped.is_set():
|
||||||
|
@ -11,7 +11,7 @@ from typing import Any
|
|||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from peewee import DoesNotExist
|
from peewee import SQL, DoesNotExist
|
||||||
|
|
||||||
from frigate.camera.state import CameraState
|
from frigate.camera.state import CameraState
|
||||||
from frigate.comms.config_updater import ConfigSubscriber
|
from frigate.comms.config_updater import ConfigSubscriber
|
||||||
@ -29,9 +29,13 @@ from frigate.config import (
|
|||||||
RecordConfig,
|
RecordConfig,
|
||||||
SnapshotsConfig,
|
SnapshotsConfig,
|
||||||
)
|
)
|
||||||
from frigate.const import FAST_QUEUE_TIMEOUT, UPDATE_CAMERA_ACTIVITY
|
from frigate.const import (
|
||||||
|
FAST_QUEUE_TIMEOUT,
|
||||||
|
UPDATE_CAMERA_ACTIVITY,
|
||||||
|
UPSERT_REVIEW_SEGMENT,
|
||||||
|
)
|
||||||
from frigate.events.types import EventStateEnum, EventTypeEnum
|
from frigate.events.types import EventStateEnum, EventTypeEnum
|
||||||
from frigate.models import Event, Timeline
|
from frigate.models import Event, ReviewSegment, Timeline
|
||||||
from frigate.track.tracked_object import TrackedObject
|
from frigate.track.tracked_object import TrackedObject
|
||||||
from frigate.util.image import SharedMemoryFrameManager
|
from frigate.util.image import SharedMemoryFrameManager
|
||||||
|
|
||||||
@ -152,7 +156,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def snapshot(camera, obj: TrackedObject, frame_name: str):
|
def snapshot(camera: str, obj: TrackedObject) -> bool:
|
||||||
mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
|
mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
|
||||||
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
|
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
|
||||||
jpg_bytes = obj.get_img_bytes(
|
jpg_bytes = obj.get_img_bytes(
|
||||||
@ -185,6 +189,10 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
retain=True,
|
retain=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def camera_activity(camera, activity):
|
def camera_activity(camera, activity):
|
||||||
last_activity = self.camera_activity.get(camera)
|
last_activity = self.camera_activity.get(camera)
|
||||||
|
|
||||||
@ -357,6 +365,60 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
data=Timeline.data.update({"sub_label": (sub_label, score)})
|
data=Timeline.data.update({"sub_label": (sub_label, score)})
|
||||||
).where(Timeline.source_id == event_id).execute()
|
).where(Timeline.source_id == event_id).execute()
|
||||||
|
|
||||||
|
# only update ended review segments
|
||||||
|
# manually updating a sub_label from the UI is only possible for ended tracked objects
|
||||||
|
try:
|
||||||
|
review_segment = ReviewSegment.get(
|
||||||
|
(
|
||||||
|
SQL(
|
||||||
|
"json_extract(data, '$.detections') LIKE ?",
|
||||||
|
[f'%"{event_id}"%'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
& (ReviewSegment.end_time.is_null(False))
|
||||||
|
)
|
||||||
|
|
||||||
|
segment_data = review_segment.data
|
||||||
|
detection_ids = segment_data.get("detections", [])
|
||||||
|
|
||||||
|
# Rebuild objects list and sync sub_labels
|
||||||
|
objects_list = []
|
||||||
|
sub_labels = set()
|
||||||
|
events = Event.select(Event.id, Event.label, Event.sub_label).where(
|
||||||
|
Event.id.in_(detection_ids)
|
||||||
|
)
|
||||||
|
for det_event in events:
|
||||||
|
if det_event.sub_label:
|
||||||
|
sub_labels.add(det_event.sub_label)
|
||||||
|
objects_list.append(
|
||||||
|
f"{det_event.label}-verified"
|
||||||
|
) # eg, "bird-verified"
|
||||||
|
else:
|
||||||
|
objects_list.append(det_event.label) # eg, "bird"
|
||||||
|
|
||||||
|
segment_data["sub_labels"] = list(sub_labels)
|
||||||
|
segment_data["objects"] = objects_list
|
||||||
|
|
||||||
|
updated_data = {
|
||||||
|
ReviewSegment.id.name: review_segment.id,
|
||||||
|
ReviewSegment.camera.name: review_segment.camera,
|
||||||
|
ReviewSegment.start_time.name: review_segment.start_time,
|
||||||
|
ReviewSegment.end_time.name: review_segment.end_time,
|
||||||
|
ReviewSegment.severity.name: review_segment.severity,
|
||||||
|
ReviewSegment.thumb_path.name: review_segment.thumb_path,
|
||||||
|
ReviewSegment.data.name: segment_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.requestor.send_data(UPSERT_REVIEW_SEGMENT, updated_data)
|
||||||
|
logger.debug(
|
||||||
|
f"Updated sub_label for event {event_id} in review segment {review_segment.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except ReviewSegment.DoesNotExist:
|
||||||
|
logger.debug(
|
||||||
|
f"No review segment found with event ID {event_id} when updating sub_label"
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_recognized_license_plate(
|
def set_recognized_license_plate(
|
||||||
|
@ -721,7 +721,7 @@ function ObjectDetailsTab({
|
|||||||
ns: "objects",
|
ns: "objects",
|
||||||
})}
|
})}
|
||||||
{search.sub_label && ` (${search.sub_label})`}
|
{search.sub_label && ` (${search.sub_label})`}
|
||||||
{isAdmin && (
|
{isAdmin && search.end_time && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<span>
|
<span>
|
||||||
@ -1242,13 +1242,13 @@ export function VideoTab({ search }: VideoTabProps) {
|
|||||||
<>
|
<>
|
||||||
<span tabIndex={0} className="sr-only" />
|
<span tabIndex={0} className="sr-only" />
|
||||||
<GenericVideoPlayer source={source}>
|
<GenericVideoPlayer source={source}>
|
||||||
{reviewItem && (
|
<div
|
||||||
<div
|
className={cn(
|
||||||
className={cn(
|
"absolute top-2 z-10 flex items-center gap-2",
|
||||||
"absolute top-2 z-10 flex items-center gap-2",
|
isIOS ? "right-8" : "right-2",
|
||||||
isIOS ? "right-8" : "right-2",
|
)}
|
||||||
)}
|
>
|
||||||
>
|
{reviewItem && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Chip
|
<Chip
|
||||||
@ -1271,25 +1271,25 @@ export function VideoTab({ search }: VideoTabProps) {
|
|||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</TooltipPortal>
|
</TooltipPortal>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
)}
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<a
|
<TooltipTrigger asChild>
|
||||||
download
|
<a
|
||||||
href={`${baseUrl}api/${search.camera}/${clipTimeRange}/clip.mp4`}
|
download
|
||||||
>
|
href={`${baseUrl}api/${search.camera}/${clipTimeRange}/clip.mp4`}
|
||||||
<Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500">
|
>
|
||||||
<FaDownload className="size-4 text-white" />
|
<Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500">
|
||||||
</Chip>
|
<FaDownload className="size-4 text-white" />
|
||||||
</a>
|
</Chip>
|
||||||
</TooltipTrigger>
|
</a>
|
||||||
<TooltipPortal>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipPortal>
|
||||||
{t("button.download", { ns: "common" })}
|
<TooltipContent>
|
||||||
</TooltipContent>
|
{t("button.download", { ns: "common" })}
|
||||||
</TooltipPortal>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</TooltipPortal>
|
||||||
</div>
|
</Tooltip>
|
||||||
)}
|
</div>
|
||||||
</GenericVideoPlayer>
|
</GenericVideoPlayer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user