From 98bab95fcb5693560fd74c4c7e8e78c9926d1525 Mon Sep 17 00:00:00 2001 From: Billy Jacoby Date: Sun, 10 Sep 2023 22:31:49 -0400 Subject: [PATCH] feat: retrieve snapshotURLs for events --- src/hooks/api/types/config.ts | 278 ++++++++++++++++++ src/hooks/api/useCameraEvents.ts | 38 ++- src/hooks/api/useConfig.ts | 10 +- .../CameraScreen/components/CameraEvent.tsx | 60 +++- 4 files changed, 365 insertions(+), 21 deletions(-) create mode 100644 src/hooks/api/types/config.ts diff --git a/src/hooks/api/types/config.ts b/src/hooks/api/types/config.ts new file mode 100644 index 0000000..d4e71c6 --- /dev/null +++ b/src/hooks/api/types/config.ts @@ -0,0 +1,278 @@ +export interface FrigateConfig { + birdseye: FrigateConfigBirdseye; + //? Not sure how to type this... + cameras: { + [cameraName: string]: CameraConfig; + }; + database: Database; + detect: Detect; + detectors: any; + environment_vars: any; + ffmpeg: Ffmpeg; + go2rtc: Go2RTC; + live: Live; + logger: any; + model: Model; + motion: null; + mqtt: Mqtt; + objects: any; + plus: any; + record: Record; + rtmp: any; + snapshots: Snapshots; + telemetry: Telemetry; + timestamp_style: TimestampStyle; + ui: FrigateConfigUI; +} + +export interface FrigateConfigUI { + date_style: string; + live_mode: string; + strftime_fmt: null; + time_format: string; + time_style: string; + timezone: null; + use_experimental: boolean; +} + +export interface Telemetry { + version_check: boolean; +} + +export interface Snapshots { + bounding_box: boolean; + crop: boolean; + enabled: boolean; + height: number | null; + quality: number; + required_zones: any[]; + timestamp: boolean; + clean_copy?: boolean; + retain?: SnapshotsRetain; +} + +export interface SnapshotsRetain { + default: number; + mode: string; + objects: any; +} + +export interface Model { + height: number; + input_pixel_format: string; + input_tensor: string; + labelmap: any; + labelmap_path: null; + model_type: string; + path: null; + width: number; +} + +export interface Go2RTC { + streams: Streams; + webrtc: Webrtc; +} + +export interface Streams { + frigate_back_yard: string[]; + frigate_back_yard_sub: string[]; + frigate_front_door: string[]; + frigate_front_door_sub: string[]; + frigate_garage: string[]; + frigate_garage_sub: string[]; +} + +export interface Webrtc { + candidates: string[]; +} + +export interface Database { + path: string; +} + +export interface FrigateConfigBirdseye { + enabled: boolean; + height: number; + mode: string; + quality: number; + restream: boolean; + width: number; +} + +export interface CameraConfig { + best_image_timeout: number; + birdseye: Birdseye; + detect: Detect; + enabled: boolean; + ffmpeg: Ffmpeg; + ffmpeg_cmds: FfmpegCmd[]; + live: Live; + motion: Motion; + mqtt: Mqtt; + name: string; + objects: Objects; + record: Record; + rtmp: Rtmp; + snapshots: Mqtt; + timestamp_style: TimestampStyle; + ui: UI; + zones: Zones; +} + +export interface Birdseye { + enabled: boolean; + mode: string; +} + +export interface Detect { + enabled: boolean; + fps: number; + height: number; + max_disappeared: number; + stationary: Stationary; + width: number; +} + +export interface Stationary { + interval: number; + max_frames: MaxFrames; + threshold: number; +} + +export interface MaxFrames { + default: null; + objects: Zones; +} + +export interface Zones {} + +export interface Ffmpeg { + global_args: string[]; + hwaccel_args: any[]; + input_args: string; + inputs: Input[]; + output_args: OutputArgs; +} + +export interface Input { + global_args: any[]; + hwaccel_args: any[]; + input_args: string; + path: string; + roles: string[]; +} + +export interface OutputArgs { + detect: string[]; + record: string; + rtmp: string; +} + +export interface FfmpegCmd { + cmd: string; + roles: string[]; +} + +export interface Live { + height: number; + quality: number; + stream_name: string; +} + +export interface Motion { + contour_area: number; + delta_alpha: number; + frame_alpha: number; + frame_height: number; + improve_contrast: boolean; + mask: string[]; + mqtt_off_delay: number; + threshold: number; +} + +export interface Mqtt { + bounding_box: boolean; + crop: boolean; + enabled: boolean; + height: number | null; + quality: number; + required_zones: any[]; + timestamp: boolean; + clean_copy?: boolean; + retain?: MqttRetain; +} + +export interface MqttRetain { + default: number; + mode: string; + objects: Zones; +} + +export interface Objects { + filters: Filters; + mask: string; + track: string[]; +} + +export interface Filters { + bear: Bear; + car: Bear; + cat: Bear; + dog: Bear; + person: Bear; +} + +export interface Bear { + mask: null | string; + max_area: number; + max_ratio: number; + min_area: number; + min_ratio: number; + min_score: number; + threshold: number; +} + +export interface Record { + enabled: boolean; + enabled_in_config: boolean; + events: Events; + expire_interval: number; + retain: RecordRetain; + retain_days: null; +} + +export interface Events { + objects: null; + post_capture: number; + pre_capture: number; + required_zones: any[]; + retain: MqttRetain; +} + +export interface RecordRetain { + days: number; + mode: string; +} + +export interface Rtmp { + enabled: boolean; +} + +export interface TimestampStyle { + color: Color; + effect: null; + format: string; + position: string; + thickness: number; +} + +export interface Color { + blue: number; + green: number; + red: number; +} + +export interface UI { + dashboard: boolean; + order: number; +} diff --git a/src/hooks/api/useCameraEvents.ts b/src/hooks/api/useCameraEvents.ts index 37905e2..06e262a 100644 --- a/src/hooks/api/useCameraEvents.ts +++ b/src/hooks/api/useCameraEvents.ts @@ -23,6 +23,7 @@ interface FrigateEvent { sub_label: null | string; thumbnail: string; top_score: number; + snapshotURL?: string; } interface CameraEventParams extends Record { @@ -38,7 +39,31 @@ interface CameraEventParams extends Record { in_progress?: string; } -const fetchEvents = async (queryParams?: CameraEventParams) => { +interface SnapshotQueryParams extends Record { + h?: string; + bbox?: string; + timestamp?: string; + crop?: string; + quality?: string; +} + +const buildSnapshotURL = ( + eventId: string, + queryParams?: SnapshotQueryParams, +) => { + let params: URLSearchParams | undefined; + let url = URL + `/${eventId}/snapshot.jpg`; + if (queryParams) { + params = new URLSearchParams(queryParams as Record); + url = url + '?' + params; + } + return url; +}; + +const fetchEvents = async ( + queryParams?: CameraEventParams, + snapShotQueryParams?: SnapshotQueryParams, +) => { let params: URLSearchParams | undefined; if (queryParams) { params = new URLSearchParams(queryParams as Record); @@ -50,7 +75,16 @@ const fetchEvents = async (queryParams?: CameraEventParams) => { const data = await response.json(); if (response.ok) { if (data) { - return Promise.resolve(data as FrigateEvent[]); + const returnData: FrigateEvent[] = []; + for (const event of data) { + if (event.has_snapshot) { + const snapshotURL = buildSnapshotURL(event.id, snapShotQueryParams); + returnData.push({...event, snapshotURL}); + } else { + returnData.push(event); + } + } + return Promise.resolve(returnData); } } else { return Promise.reject(new Error('ResNotOK')); diff --git a/src/hooks/api/useConfig.ts b/src/hooks/api/useConfig.ts index d7c9af2..c8a9825 100644 --- a/src/hooks/api/useConfig.ts +++ b/src/hooks/api/useConfig.ts @@ -2,9 +2,7 @@ import {useQuery, UseQueryOptions} from 'react-query'; import {API_BASE} from '@env'; -interface Config { - cameras: object[]; -} +import {FrigateConfig} from './types/config'; const URL = `${API_BASE}api/config`; const fetchConfig = async () => { @@ -13,7 +11,7 @@ const fetchConfig = async () => { const data = await response.json(); if (response.ok) { if (data) { - return Promise.resolve(data as Config); + return Promise.resolve(data as FrigateConfig); } } else { return Promise.reject(new Error('ResNotOK')); @@ -23,9 +21,9 @@ const fetchConfig = async () => { } }; -export const useConfig = ( +export const useConfig = ( options?: UseQueryOptions< - Config | undefined, + FrigateConfig | undefined, unknown, TData | undefined, string[] diff --git a/src/screens/CameraScreen/components/CameraEvent.tsx b/src/screens/CameraScreen/components/CameraEvent.tsx index 582390e..3cbc3bb 100644 --- a/src/screens/CameraScreen/components/CameraEvent.tsx +++ b/src/screens/CameraScreen/components/CameraEvent.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Image, useWindowDimensions, View} from 'react-native'; +import {ImageBackground, useWindowDimensions, View} from 'react-native'; import {BaseText} from '@components'; import {useCameraEvents} from '@hooks'; @@ -7,27 +7,61 @@ import {useCameraEvents} from '@hooks'; export const CameraEvent = ({cameraName}: {cameraName: string}) => { const {data} = useCameraEvents({cameras: cameraName, limit: '10'}); const {width} = useWindowDimensions(); - const imageSize = width * 0.9; + const imageSize = width * 0.97; const lastEvent = data?.[0]; + const getDateString = (date: Date) => { + return ( + date.toLocaleString(undefined, { + dateStyle: 'short', + }) + + ' - ' + + date.toLocaleString(undefined, { + timeStyle: 'short', + }) + ); + }; + const lastEventEnded = - lastEvent && new Date(lastEvent?.end_time * 1000).toLocaleString(); + lastEvent && getDateString(new Date(lastEvent?.end_time * 1000)); const lastThumbnail = lastEvent && 'data:image/png;base64,' + lastEvent.thumbnail; + const lastEventImage = lastEvent && lastEvent.snapshotURL; return ( - - - {cameraName} - {!!lastEventEnded && {lastEventEnded}} + + + + {cameraName.replaceAll('_', ' ').toLocaleUpperCase()} + + {!!lastEventEnded && ( + + {lastEventEnded} + + )} - {lastThumbnail && ( - + {(lastEventImage || lastThumbnail) && ( + + + )} );