diff --git a/src/camera-manager/frigate/requests.ts b/src/camera-manager/frigate/requests.ts index 2dbd7482..40efe299 100644 --- a/src/camera-manager/frigate/requests.ts +++ b/src/camera-manager/frigate/requests.ts @@ -115,6 +115,7 @@ export interface NativeFrigateEventQuery { instance_id?: string; cameras?: string[]; labels?: string[]; + sub_labels?: string[]; zones?: string[]; after?: number; before?: number; diff --git a/src/camera-manager/frigate/types.ts b/src/camera-manager/frigate/types.ts index fda44992..bbf0d7a3 100644 --- a/src/camera-manager/frigate/types.ts +++ b/src/camera-manager/frigate/types.ts @@ -155,6 +155,11 @@ export type FrigateReviewSeverity = // Review data schema (only fields we need for display) const frigateReviewDataSchema = z.object({ objects: z.string().array().optional(), + + // Frigate adds sub_labels for non-attribute identifications (e.g. Frigate+ + // species name for a bird). Note: The matching `objects` entry carries a + // `-verified` suffix in that case. + sub_labels: z.string().array().optional(), zones: z.string().array().optional(), metadata: z .object({ diff --git a/src/camera-manager/frigate/util.ts b/src/camera-manager/frigate/util.ts index de58f61a..9ad80bce 100644 --- a/src/camera-manager/frigate/util.ts +++ b/src/camera-manager/frigate/util.ts @@ -105,12 +105,24 @@ export const getRecordingID = ( * @param review The Frigate review item. */ export const getReviewTitle = (review: FrigateReview): string => { + // Frigate flags Frigate+ verified detections by suffixing the object label + // with `-verified`. The user-meaningful identification (e.g. species) lives + // in `sub_labels` instead, so strip the suffix and surface sub_labels as a + // tag separated by ': ' (matching event title formatting). const objects = review.data.objects?.length - ? review.data.objects.map((o) => prettifyTitle(o)).join(', ') + ? review.data.objects + .map((o) => prettifyTitle(o.replace(/-verified$/, ''))) + .join(', ') + : ''; + const subLabels = review.data.sub_labels?.length + ? review.data.sub_labels.map((s) => prettifyTitle(s)).join(', ') : ''; - if (objects) { - return objects; + if (objects && subLabels) { + return `${objects}: ${subLabels}`; + } + if (objects || subLabels) { + return objects || subLabels; } const durationSeconds = Math.round( @@ -121,7 +133,7 @@ export const getReviewTitle = (review: FrigateReview): string => { return `${formatDateAndTime( new Date(review.start_time * 1000), - )} [${durationSeconds}s${objects}]`; + )} [${durationSeconds}s]`; }; /** diff --git a/tests/camera-manager/frigate/requests.test.ts b/tests/camera-manager/frigate/requests.test.ts index 740ae803..9c2a3538 100644 --- a/tests/camera-manager/frigate/requests.test.ts +++ b/tests/camera-manager/frigate/requests.test.ts @@ -146,6 +146,7 @@ describe('frigate requests', () => { instance_id: 'clientID', cameras: ['camera.office'], labels: ['person'], + sub_labels: ['John'], zones: ['zone'], after: 0, before: 1, @@ -163,6 +164,7 @@ describe('frigate requests', () => { instance_id: 'clientID', cameras: ['camera.office'], labels: ['person'], + sub_labels: ['John'], zones: ['zone'], after: 0, before: 1, diff --git a/tests/camera-manager/frigate/util.test.ts b/tests/camera-manager/frigate/util.test.ts index e4b24ecd..0fe8cec3 100644 --- a/tests/camera-manager/frigate/util.test.ts +++ b/tests/camera-manager/frigate/util.test.ts @@ -195,6 +195,43 @@ describe('getReviewTitle', () => { ).toBe('Person, Dog'); }); + it('should append sub_labels as tags after objects', () => { + expect( + getReviewTitle( + createFrigateReview({ + data: { + objects: ['bird-verified'], + sub_labels: ['Mourning Dove'], + }, + }), + ), + ).toBe('Bird: Mourning Dove'); + }); + + it('should strip -verified suffix from objects', () => { + expect( + getReviewTitle( + createFrigateReview({ + data: { + objects: ['bird-verified', 'person'], + }, + }), + ), + ).toBe('Bird, Person'); + }); + + it('should fall back to sub_labels when objects are absent', () => { + expect( + getReviewTitle( + createFrigateReview({ + data: { + sub_labels: ['John'], + }, + }), + ), + ).toBe('John'); + }); + it('should get in-progress review title', () => { vi.useFakeTimers(); vi.setSystemTime(add(start, { seconds: 60 }));