mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Improve review book keeping (#10735)
* Improve review book keeping * Cleanup * Cleanup for new labels * Final cleanup * Fix sub label checking
This commit is contained in:
		
							parent
							
								
									89f843cf95
								
							
						
					
					
						commit
						4d522be7fb
					
				@ -17,7 +17,7 @@ from frigate.comms.config_updater import ConfigSubscriber
 | 
			
		||||
from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum
 | 
			
		||||
from frigate.comms.inter_process import InterProcessRequestor
 | 
			
		||||
from frigate.config import CameraConfig, FrigateConfig
 | 
			
		||||
from frigate.const import CLIPS_DIR, UPSERT_REVIEW_SEGMENT
 | 
			
		||||
from frigate.const import ALL_ATTRIBUTE_LABELS, CLIPS_DIR, UPSERT_REVIEW_SEGMENT
 | 
			
		||||
from frigate.models import ReviewSegment
 | 
			
		||||
from frigate.object_processing import TrackedObject
 | 
			
		||||
from frigate.util.image import SharedMemoryFrameManager, calculate_16_9_crop
 | 
			
		||||
@ -45,9 +45,7 @@ class PendingReviewSegment:
 | 
			
		||||
        camera: str,
 | 
			
		||||
        frame_time: float,
 | 
			
		||||
        severity: SeverityEnum,
 | 
			
		||||
        detections: set[str] = set(),
 | 
			
		||||
        objects: set[str] = set(),
 | 
			
		||||
        sub_labels: set[str] = set(),
 | 
			
		||||
        detections: dict[str, str],
 | 
			
		||||
        zones: set[str] = set(),
 | 
			
		||||
        audio: set[str] = set(),
 | 
			
		||||
        motion: list[int] = [],
 | 
			
		||||
@ -58,8 +56,6 @@ class PendingReviewSegment:
 | 
			
		||||
        self.start_time = frame_time
 | 
			
		||||
        self.severity = severity
 | 
			
		||||
        self.detections = detections
 | 
			
		||||
        self.objects = objects
 | 
			
		||||
        self.sub_labels = sub_labels
 | 
			
		||||
        self.zones = zones
 | 
			
		||||
        self.audio = audio
 | 
			
		||||
        self.sig_motion_areas = motion
 | 
			
		||||
@ -114,9 +110,8 @@ class PendingReviewSegment:
 | 
			
		||||
            ReviewSegment.severity: self.severity.value,
 | 
			
		||||
            ReviewSegment.thumb_path: path,
 | 
			
		||||
            ReviewSegment.data: {
 | 
			
		||||
                "detections": list(self.detections),
 | 
			
		||||
                "objects": list(self.objects),
 | 
			
		||||
                "sub_labels": list(self.sub_labels),
 | 
			
		||||
                "detections": list(set(self.detections.keys())),
 | 
			
		||||
                "objects": list(set(self.detections.values())),
 | 
			
		||||
                "zones": list(self.zones),
 | 
			
		||||
                "audio": list(self.audio),
 | 
			
		||||
                "significant_motion_areas": self.sig_motion_areas,
 | 
			
		||||
@ -180,11 +175,12 @@ class ReviewSegmentMaintainer(threading.Thread):
 | 
			
		||||
                self.frame_manager.close(frame_id)
 | 
			
		||||
 | 
			
		||||
            for object in active_objects:
 | 
			
		||||
                segment.detections.add(object["id"])
 | 
			
		||||
                segment.objects.add(object["label"])
 | 
			
		||||
 | 
			
		||||
                if object["sub_label"]:
 | 
			
		||||
                    segment.sub_labels.add(object["sub_label"][0])
 | 
			
		||||
                if not object["sub_label"]:
 | 
			
		||||
                    segment.detections[object["id"]] = object["label"]
 | 
			
		||||
                elif object["sub_label"][0] in ALL_ATTRIBUTE_LABELS:
 | 
			
		||||
                    segment.detections[object["id"]] = object["sub_label"][0]
 | 
			
		||||
                else:
 | 
			
		||||
                    segment.detections[object["id"]] = f'{object["label"]}-verified'
 | 
			
		||||
 | 
			
		||||
                # if object is alert label and has qualified for recording
 | 
			
		||||
                # mark this review as alert
 | 
			
		||||
@ -224,9 +220,7 @@ class ReviewSegmentMaintainer(threading.Thread):
 | 
			
		||||
 | 
			
		||||
        if len(active_objects) > 0:
 | 
			
		||||
            has_sig_object = False
 | 
			
		||||
            detections: set = set()
 | 
			
		||||
            objects: set = set()
 | 
			
		||||
            sub_labels: set = set()
 | 
			
		||||
            detections: dict[str, str] = {}
 | 
			
		||||
            zones: set = set()
 | 
			
		||||
 | 
			
		||||
            for object in active_objects:
 | 
			
		||||
@ -237,11 +231,12 @@ class ReviewSegmentMaintainer(threading.Thread):
 | 
			
		||||
                ):
 | 
			
		||||
                    has_sig_object = True
 | 
			
		||||
 | 
			
		||||
                detections.add(object["id"])
 | 
			
		||||
                objects.add(object["label"])
 | 
			
		||||
 | 
			
		||||
                if object["sub_label"]:
 | 
			
		||||
                    sub_labels.add(object["sub_label"][0])
 | 
			
		||||
                if not object["sub_label"]:
 | 
			
		||||
                    detections[object["id"]] = object["label"]
 | 
			
		||||
                elif object["sub_label"][0] in ALL_ATTRIBUTE_LABELS:
 | 
			
		||||
                    detections[object["id"]] = object["sub_label"][0]
 | 
			
		||||
                else:
 | 
			
		||||
                    detections[object["id"]] = f'{object["label"]}-verified'
 | 
			
		||||
 | 
			
		||||
                zones.update(object["current_zones"])
 | 
			
		||||
 | 
			
		||||
@ -250,8 +245,6 @@ class ReviewSegmentMaintainer(threading.Thread):
 | 
			
		||||
                frame_time,
 | 
			
		||||
                SeverityEnum.alert if has_sig_object else SeverityEnum.detection,
 | 
			
		||||
                detections,
 | 
			
		||||
                objects=objects,
 | 
			
		||||
                sub_labels=sub_labels,
 | 
			
		||||
                audio=set(),
 | 
			
		||||
                zones=zones,
 | 
			
		||||
                motion=[],
 | 
			
		||||
@ -268,9 +261,8 @@ class ReviewSegmentMaintainer(threading.Thread):
 | 
			
		||||
                camera,
 | 
			
		||||
                frame_time,
 | 
			
		||||
                SeverityEnum.signification_motion,
 | 
			
		||||
                detections=set(),
 | 
			
		||||
                objects=set(),
 | 
			
		||||
                sub_labels=set(),
 | 
			
		||||
                detections={},
 | 
			
		||||
                audio=set(),
 | 
			
		||||
                motion=motion,
 | 
			
		||||
                zones=set(),
 | 
			
		||||
            )
 | 
			
		||||
@ -340,9 +332,7 @@ class ReviewSegmentMaintainer(threading.Thread):
 | 
			
		||||
                        camera,
 | 
			
		||||
                        frame_time,
 | 
			
		||||
                        SeverityEnum.detection,
 | 
			
		||||
                        set(),
 | 
			
		||||
                        set(),
 | 
			
		||||
                        set(),
 | 
			
		||||
                        {},
 | 
			
		||||
                        set(),
 | 
			
		||||
                        set(audio_detections),
 | 
			
		||||
                        [],
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
 | 
			
		||||
        </div>
 | 
			
		||||
      </TooltipTrigger>
 | 
			
		||||
      <TooltipContent>
 | 
			
		||||
        {`${[...event.data.objects, ...event.data.audio, ...(event.data.sub_labels || [])].join(", ")} detected`}
 | 
			
		||||
        {`${[...event.data.objects, ...event.data.audio].join(", ").replaceAll("-verified", "")} detected`}
 | 
			
		||||
      </TooltipContent>
 | 
			
		||||
    </Tooltip>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { baseUrl } from "@/api/baseUrl";
 | 
			
		||||
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
 | 
			
		||||
import { FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
import { ReviewSegment } from "@/types/review";
 | 
			
		||||
import { getIconForLabel, getIconForSubLabel } from "@/utils/iconUtil";
 | 
			
		||||
import { getIconForLabel } from "@/utils/iconUtil";
 | 
			
		||||
import { isSafari } from "react-device-detect";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import TimeAgo from "../dynamic/TimeAgo";
 | 
			
		||||
@ -57,9 +57,6 @@ export default function ReviewCard({
 | 
			
		||||
          {event.data.audio.map((audio) => {
 | 
			
		||||
            return getIconForLabel(audio, "size-3 text-white");
 | 
			
		||||
          })}
 | 
			
		||||
          {event.data.sub_labels?.map((sub) => {
 | 
			
		||||
            return getIconForSubLabel(sub, "size-3 text-white");
 | 
			
		||||
          })}
 | 
			
		||||
          <div className="font-extra-light text-xs">{formattedDate}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <TimeAgo
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,6 @@ import MobileReviewSettingsDrawer, {
 | 
			
		||||
  DrawerFeatures,
 | 
			
		||||
} from "../overlay/MobileReviewSettingsDrawer";
 | 
			
		||||
 | 
			
		||||
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
 | 
			
		||||
const REVIEW_FILTERS = [
 | 
			
		||||
  "cameras",
 | 
			
		||||
  "reviewed",
 | 
			
		||||
@ -77,9 +76,7 @@ export default function ReviewFilterGroup({
 | 
			
		||||
    cameras.forEach((camera) => {
 | 
			
		||||
      const cameraConfig = config.cameras[camera];
 | 
			
		||||
      cameraConfig.objects.track.forEach((label) => {
 | 
			
		||||
        if (!ATTRIBUTES.includes(label)) {
 | 
			
		||||
        labels.add(label);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (cameraConfig.audio.enabled_in_config) {
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import { useApiHost } from "@/api";
 | 
			
		||||
import { isCurrentHour } from "@/utils/dateUtil";
 | 
			
		||||
import { ReviewSegment } from "@/types/review";
 | 
			
		||||
import { Slider } from "../ui/slider-no-thumb";
 | 
			
		||||
import { getIconForLabel, getIconForSubLabel } from "@/utils/iconUtil";
 | 
			
		||||
import { getIconForLabel } from "@/utils/iconUtil";
 | 
			
		||||
import TimeAgo from "../dynamic/TimeAgo";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import { FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
@ -227,9 +227,6 @@ export default function PreviewThumbnailPlayer({
 | 
			
		||||
                        {review.data.audio.map((audio) => {
 | 
			
		||||
                          return getIconForLabel(audio, "size-3 text-white");
 | 
			
		||||
                        })}
 | 
			
		||||
                        {review.data.sub_labels?.map((sub) => {
 | 
			
		||||
                          return getIconForSubLabel(sub, "size-3 text-white");
 | 
			
		||||
                        })}
 | 
			
		||||
                      </Chip>
 | 
			
		||||
                    </>
 | 
			
		||||
                  )}
 | 
			
		||||
@ -237,13 +234,10 @@ export default function PreviewThumbnailPlayer({
 | 
			
		||||
              </TooltipTrigger>
 | 
			
		||||
            </div>
 | 
			
		||||
            <TooltipContent className="capitalize">
 | 
			
		||||
              {[
 | 
			
		||||
                ...(review.data.objects || []),
 | 
			
		||||
                ...(review.data.audio || []),
 | 
			
		||||
                ...(review.data.sub_labels || []),
 | 
			
		||||
              ]
 | 
			
		||||
              {[...(review.data.objects || []), ...(review.data.audio || [])]
 | 
			
		||||
                .filter((item) => item !== undefined)
 | 
			
		||||
                .join(", ")}
 | 
			
		||||
                .join(", ")
 | 
			
		||||
                .replaceAll("-verified", "")}
 | 
			
		||||
            </TooltipContent>
 | 
			
		||||
          </Tooltip>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,6 @@ export type ReviewData = {
 | 
			
		||||
  audio: string[];
 | 
			
		||||
  detections: string[];
 | 
			
		||||
  objects: string[];
 | 
			
		||||
  sub_labels?: string[];
 | 
			
		||||
  significant_motion_areas: number[];
 | 
			
		||||
  zones: string[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import {
 | 
			
		||||
  FaAmazon,
 | 
			
		||||
  FaCarSide,
 | 
			
		||||
  FaCat,
 | 
			
		||||
  FaCheckCircle,
 | 
			
		||||
  FaCircle,
 | 
			
		||||
  FaDog,
 | 
			
		||||
  FaFedex,
 | 
			
		||||
@ -34,6 +35,10 @@ export function getIconForGroup(icon: string, className: string = "size-4") {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getIconForLabel(label: string, className?: string) {
 | 
			
		||||
  if (label.endsWith("-verified")) {
 | 
			
		||||
    return getVerifiedIcon(label, className);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (label) {
 | 
			
		||||
    case "car":
 | 
			
		||||
      return <FaCarSide key={label} className={className} />;
 | 
			
		||||
@ -48,24 +53,32 @@ export function getIconForLabel(label: string, className?: string) {
 | 
			
		||||
      return <LuBox key={label} className={className} />;
 | 
			
		||||
    case "person":
 | 
			
		||||
      return <BsPersonWalking key={label} className={className} />;
 | 
			
		||||
    // audio
 | 
			
		||||
    case "crying":
 | 
			
		||||
    case "laughter":
 | 
			
		||||
    case "scream":
 | 
			
		||||
    case "speech":
 | 
			
		||||
    case "yell":
 | 
			
		||||
      return <MdRecordVoiceOver key={label} className={className} />;
 | 
			
		||||
    default:
 | 
			
		||||
      return <LuLassoSelect key={label} className={className} />;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getIconForSubLabel(label: string, className?: string) {
 | 
			
		||||
  switch (label) {
 | 
			
		||||
    // sub labels
 | 
			
		||||
    case "amazon":
 | 
			
		||||
      return <FaAmazon key={label} className={className} />;
 | 
			
		||||
    case "fedex":
 | 
			
		||||
      return <FaFedex key={label} className={className} />;
 | 
			
		||||
    case "ups":
 | 
			
		||||
      return <FaUps key={label} className={className} />;
 | 
			
		||||
    default:
 | 
			
		||||
      return <LuLassoSelect key={label} className={className} />;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getVerifiedIcon(label: string, className?: string) {
 | 
			
		||||
  const simpleLabel = label.substring(0, label.lastIndexOf("-"));
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex items-center">
 | 
			
		||||
      {getIconForLabel(simpleLabel, className)}
 | 
			
		||||
      <FaCheckCircle className="absolute size-2 translate-x-[80%] translate-y-3/4" />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user