mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01: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
 | 
			
		||||
            tracked_objects[id].thumbnail_data = thumbnail_data
 | 
			
		||||
            object_type = new_obj.obj_data["label"]
 | 
			
		||||
            self.best_objects[object_type] = new_obj
 | 
			
		||||
 | 
			
		||||
            # call event handlers
 | 
			
		||||
            for c in self.callbacks["snapshot"]:
 | 
			
		||||
                c(self.name, self.best_objects[object_type], frame_name)
 | 
			
		||||
            self.send_mqtt_snapshot(new_obj, object_type)
 | 
			
		||||
 | 
			
		||||
            for c in self.callbacks["start"]:
 | 
			
		||||
                c(self.name, new_obj, frame_name)
 | 
			
		||||
@ -417,13 +415,9 @@ class CameraState:
 | 
			
		||||
                    or (now - current_best.thumbnail_data["frame_time"])
 | 
			
		||||
                    > self.camera_config.best_image_timeout
 | 
			
		||||
                ):
 | 
			
		||||
                    self.best_objects[object_type] = obj
 | 
			
		||||
                    for c in self.callbacks["snapshot"]:
 | 
			
		||||
                        c(self.name, self.best_objects[object_type], frame_name)
 | 
			
		||||
                    self.send_mqtt_snapshot(obj, object_type)
 | 
			
		||||
            else:
 | 
			
		||||
                self.best_objects[object_type] = obj
 | 
			
		||||
                for c in self.callbacks["snapshot"]:
 | 
			
		||||
                    c(self.name, self.best_objects[object_type], frame_name)
 | 
			
		||||
                self.send_mqtt_snapshot(obj, object_type)
 | 
			
		||||
 | 
			
		||||
        for c in self.callbacks["camera_activity"]:
 | 
			
		||||
            c(self.name, camera_activity)
 | 
			
		||||
@ -472,6 +466,20 @@ class CameraState:
 | 
			
		||||
 | 
			
		||||
            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(
 | 
			
		||||
        self,
 | 
			
		||||
        frame: np.ndarray | None,
 | 
			
		||||
 | 
			
		||||
@ -792,6 +792,10 @@ class OnvifController:
 | 
			
		||||
            )
 | 
			
		||||
            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"):
 | 
			
		||||
            self.cams[camera_name]["active"] = False
 | 
			
		||||
            if not self.ptz_metrics[camera_name].motor_stopped.is_set():
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ from typing import Any
 | 
			
		||||
 | 
			
		||||
import cv2
 | 
			
		||||
import numpy as np
 | 
			
		||||
from peewee import DoesNotExist
 | 
			
		||||
from peewee import SQL, DoesNotExist
 | 
			
		||||
 | 
			
		||||
from frigate.camera.state import CameraState
 | 
			
		||||
from frigate.comms.config_updater import ConfigSubscriber
 | 
			
		||||
@ -29,9 +29,13 @@ from frigate.config import (
 | 
			
		||||
    RecordConfig,
 | 
			
		||||
    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.models import Event, Timeline
 | 
			
		||||
from frigate.models import Event, ReviewSegment, Timeline
 | 
			
		||||
from frigate.track.tracked_object import TrackedObject
 | 
			
		||||
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
 | 
			
		||||
            if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
 | 
			
		||||
                jpg_bytes = obj.get_img_bytes(
 | 
			
		||||
@ -185,6 +189,10 @@ class TrackedObjectProcessor(threading.Thread):
 | 
			
		||||
                                retain=True,
 | 
			
		||||
                            )
 | 
			
		||||
 | 
			
		||||
                    return True
 | 
			
		||||
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        def camera_activity(camera, activity):
 | 
			
		||||
            last_activity = self.camera_activity.get(camera)
 | 
			
		||||
 | 
			
		||||
@ -357,6 +365,60 @@ class TrackedObjectProcessor(threading.Thread):
 | 
			
		||||
                data=Timeline.data.update({"sub_label": (sub_label, score)})
 | 
			
		||||
            ).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
 | 
			
		||||
 | 
			
		||||
    def set_recognized_license_plate(
 | 
			
		||||
 | 
			
		||||
@ -721,7 +721,7 @@ function ObjectDetailsTab({
 | 
			
		||||
                ns: "objects",
 | 
			
		||||
              })}
 | 
			
		||||
              {search.sub_label && ` (${search.sub_label})`}
 | 
			
		||||
              {isAdmin && (
 | 
			
		||||
              {isAdmin && search.end_time && (
 | 
			
		||||
                <Tooltip>
 | 
			
		||||
                  <TooltipTrigger asChild>
 | 
			
		||||
                    <span>
 | 
			
		||||
@ -1242,13 +1242,13 @@ export function VideoTab({ search }: VideoTabProps) {
 | 
			
		||||
    <>
 | 
			
		||||
      <span tabIndex={0} className="sr-only" />
 | 
			
		||||
      <GenericVideoPlayer source={source}>
 | 
			
		||||
        {reviewItem && (
 | 
			
		||||
          <div
 | 
			
		||||
            className={cn(
 | 
			
		||||
              "absolute top-2 z-10 flex items-center gap-2",
 | 
			
		||||
              isIOS ? "right-8" : "right-2",
 | 
			
		||||
            )}
 | 
			
		||||
          >
 | 
			
		||||
        <div
 | 
			
		||||
          className={cn(
 | 
			
		||||
            "absolute top-2 z-10 flex items-center gap-2",
 | 
			
		||||
            isIOS ? "right-8" : "right-2",
 | 
			
		||||
          )}
 | 
			
		||||
        >
 | 
			
		||||
          {reviewItem && (
 | 
			
		||||
            <Tooltip>
 | 
			
		||||
              <TooltipTrigger>
 | 
			
		||||
                <Chip
 | 
			
		||||
@ -1271,25 +1271,25 @@ export function VideoTab({ search }: VideoTabProps) {
 | 
			
		||||
                </TooltipContent>
 | 
			
		||||
              </TooltipPortal>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
            <Tooltip>
 | 
			
		||||
              <TooltipTrigger asChild>
 | 
			
		||||
                <a
 | 
			
		||||
                  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>
 | 
			
		||||
                </a>
 | 
			
		||||
              </TooltipTrigger>
 | 
			
		||||
              <TooltipPortal>
 | 
			
		||||
                <TooltipContent>
 | 
			
		||||
                  {t("button.download", { ns: "common" })}
 | 
			
		||||
                </TooltipContent>
 | 
			
		||||
              </TooltipPortal>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
          )}
 | 
			
		||||
          <Tooltip>
 | 
			
		||||
            <TooltipTrigger asChild>
 | 
			
		||||
              <a
 | 
			
		||||
                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>
 | 
			
		||||
              </a>
 | 
			
		||||
            </TooltipTrigger>
 | 
			
		||||
            <TooltipPortal>
 | 
			
		||||
              <TooltipContent>
 | 
			
		||||
                {t("button.download", { ns: "common" })}
 | 
			
		||||
              </TooltipContent>
 | 
			
		||||
            </TooltipPortal>
 | 
			
		||||
          </Tooltip>
 | 
			
		||||
        </div>
 | 
			
		||||
      </GenericVideoPlayer>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user