diff --git a/frigate/review/maintainer.py b/frigate/review/maintainer.py index 9728d19dd..6633c2015 100644 --- a/frigate/review/maintainer.py +++ b/frigate/review/maintainer.py @@ -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), [], diff --git a/web/src/components/card/AnimatedEventCard.tsx b/web/src/components/card/AnimatedEventCard.tsx index 2944e6c33..03199e01a 100644 --- a/web/src/components/card/AnimatedEventCard.tsx +++ b/web/src/components/card/AnimatedEventCard.tsx @@ -83,7 +83,7 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) { - {`${[...event.data.objects, ...event.data.audio, ...(event.data.sub_labels || [])].join(", ")} detected`} + {`${[...event.data.objects, ...event.data.audio].join(", ").replaceAll("-verified", "")} detected`} ); diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 4c547e4ea..353bb6583 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -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"); - })}
{formattedDate}
{ const cameraConfig = config.cameras[camera]; cameraConfig.objects.track.forEach((label) => { - if (!ATTRIBUTES.includes(label)) { - labels.add(label); - } + labels.add(label); }); if (cameraConfig.audio.enabled_in_config) { diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 3f2ed212a..5afea161b 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -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"); - })} )} @@ -237,13 +234,10 @@ export default function PreviewThumbnailPlayer({ - {[ - ...(review.data.objects || []), - ...(review.data.audio || []), - ...(review.data.sub_labels || []), - ] + {[...(review.data.objects || []), ...(review.data.audio || [])] .filter((item) => item !== undefined) - .join(", ")} + .join(", ") + .replaceAll("-verified", "")} diff --git a/web/src/types/review.ts b/web/src/types/review.ts index ffd33b620..e18d3b785 100644 --- a/web/src/types/review.ts +++ b/web/src/types/review.ts @@ -15,7 +15,6 @@ export type ReviewData = { audio: string[]; detections: string[]; objects: string[]; - sub_labels?: string[]; significant_motion_areas: number[]; zones: string[]; }; diff --git a/web/src/utils/iconUtil.tsx b/web/src/utils/iconUtil.tsx index d688048b4..0c326f4d5 100644 --- a/web/src/utils/iconUtil.tsx +++ b/web/src/utils/iconUtil.tsx @@ -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 ; @@ -48,24 +53,32 @@ export function getIconForLabel(label: string, className?: string) { return ; case "person": return ; + // audio case "crying": case "laughter": case "scream": case "speech": case "yell": return ; - default: - return ; - } -} - -export function getIconForSubLabel(label: string, className?: string) { - switch (label) { + // sub labels case "amazon": return ; case "fedex": return ; case "ups": return ; + default: + return ; } } + +function getVerifiedIcon(label: string, className?: string) { + const simpleLabel = label.substring(0, label.lastIndexOf("-")); + + return ( +
+ {getIconForLabel(simpleLabel, className)} + +
+ ); +}