From fc145016ea1482f94ebbd1bf4923f6036f4bcdfe Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 15 Sep 2024 09:01:15 -0600 Subject: [PATCH 01/36] Use smarter logic for default ffmpeg handling (#13748) --- docker/main/rootfs/usr/local/go2rtc/create_config.py | 3 ++- frigate/config.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/main/rootfs/usr/local/go2rtc/create_config.py b/docker/main/rootfs/usr/local/go2rtc/create_config.py index 6229586e5..f1b0cfe53 100644 --- a/docker/main/rootfs/usr/local/go2rtc/create_config.py +++ b/docker/main/rootfs/usr/local/go2rtc/create_config.py @@ -2,6 +2,7 @@ import json import os +import shutil import sys from pathlib import Path @@ -108,7 +109,7 @@ else: # ensure ffmpeg path is set correctly path = config.get("ffmpeg", {}).get("path", "default") if path == "default": - if int(os.getenv("", "59") or "59") >= 59: + if shutil.which("ffmpeg") is None: ffmpeg_path = "/usr/lib/ffmpeg/7.0/bin/ffmpeg" else: ffmpeg_path = "ffmpeg" diff --git a/frigate/config.py b/frigate/config.py index 7e557f0a3..b63bbb44e 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -3,6 +3,7 @@ from __future__ import annotations import json import logging import os +import shutil from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union @@ -888,7 +889,7 @@ class FfmpegConfig(FrigateBaseModel): @property def ffmpeg_path(self) -> str: if self.path == "default": - if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59: + if shutil.which("ffmpeg") is None: return "/usr/lib/ffmpeg/7.0/bin/ffmpeg" else: return "ffmpeg" From 0c86c77d4208ed122d68ca222868fb89737e86ae Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 15 Sep 2024 11:42:52 -0600 Subject: [PATCH 02/36] Ffmpeg 6 (#13754) * Move back to ffmpeg 6 * Use ffmpeg 6 --- docker/main/install_deps.sh | 16 ++++++++-------- .../rootfs/usr/local/go2rtc/create_config.py | 6 +++--- frigate/config.py | 12 ++++++------ frigate/ffmpeg_presets.py | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docker/main/install_deps.sh b/docker/main/install_deps.sh index 34f2e093e..5fa661143 100755 --- a/docker/main/install_deps.sh +++ b/docker/main/install_deps.sh @@ -40,25 +40,25 @@ apt-get -qq install --no-install-recommends --no-install-suggests -y \ # btbn-ffmpeg -> amd64 if [[ "${TARGETARCH}" == "amd64" ]]; then mkdir -p /usr/lib/ffmpeg/5.0 - mkdir -p /usr/lib/ffmpeg/7.0 + mkdir -p /usr/lib/ffmpeg/6.0 wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux64-gpl-5.1.tar.xz" tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay - wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-13-12-57/ffmpeg-n7.0.2-17-gf705bc5b73-linux64-gpl-7.0.tar.xz" - tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 - rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay + wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-08-31-12-50/ffmpeg-n6.1.2-2-gb534cc666e-linux64-gpl-6.1.tar.xz" + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/6.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/6.0/doc /usr/lib/ffmpeg/6.0/bin/ffplay fi # ffmpeg -> arm64 if [[ "${TARGETARCH}" == "arm64" ]]; then mkdir -p /usr/lib/ffmpeg/5.0 - mkdir -p /usr/lib/ffmpeg/7.0 + mkdir -p /usr/lib/ffmpeg/6.0 wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linuxarm64-gpl-5.1.tar.xz" tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay - wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-13-12-57/ffmpeg-n7.0.2-17-gf705bc5b73-linuxarm64-gpl-7.0.tar.xz" - tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 - rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay + wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-08-31-12-50/ffmpeg-n6.1.2-2-gb534cc666e-linuxarm64-gpl-6.1.tar.xz" + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/6.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/6.0/doc /usr/lib/ffmpeg/6.0/bin/ffplay fi # arch specific packages diff --git a/docker/main/rootfs/usr/local/go2rtc/create_config.py b/docker/main/rootfs/usr/local/go2rtc/create_config.py index f1b0cfe53..d33e4da9e 100644 --- a/docker/main/rootfs/usr/local/go2rtc/create_config.py +++ b/docker/main/rootfs/usr/local/go2rtc/create_config.py @@ -110,11 +110,11 @@ else: path = config.get("ffmpeg", {}).get("path", "default") if path == "default": if shutil.which("ffmpeg") is None: - ffmpeg_path = "/usr/lib/ffmpeg/7.0/bin/ffmpeg" + ffmpeg_path = "/usr/lib/ffmpeg/6.0/bin/ffmpeg" else: ffmpeg_path = "ffmpeg" -elif path == "7.0": - ffmpeg_path = "/usr/lib/ffmpeg/7.0/bin/ffmpeg" +elif path == "6.0": + ffmpeg_path = "/usr/lib/ffmpeg/6.0/bin/ffmpeg" elif path == "5.0": ffmpeg_path = "/usr/lib/ffmpeg/5.0/bin/ffmpeg" else: diff --git a/frigate/config.py b/frigate/config.py index b63bbb44e..8c66931c3 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -890,11 +890,11 @@ class FfmpegConfig(FrigateBaseModel): def ffmpeg_path(self) -> str: if self.path == "default": if shutil.which("ffmpeg") is None: - return "/usr/lib/ffmpeg/7.0/bin/ffmpeg" + return "/usr/lib/ffmpeg/6.0/bin/ffmpeg" else: return "ffmpeg" - elif self.path == "7.0": - return "/usr/lib/ffmpeg/7.0/bin/ffmpeg" + elif self.path == "6.0": + return "/usr/lib/ffmpeg/6.0/bin/ffmpeg" elif self.path == "5.0": return "/usr/lib/ffmpeg/5.0/bin/ffmpeg" else: @@ -904,11 +904,11 @@ class FfmpegConfig(FrigateBaseModel): def ffprobe_path(self) -> str: if self.path == "default": if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59: - return "/usr/lib/ffmpeg/7.0/bin/ffprobe" + return "/usr/lib/ffmpeg/6.0/bin/ffprobe" else: return "ffprobe" - elif self.path == "7.0": - return "/usr/lib/ffmpeg/7.0/bin/ffprobe" + elif self.path == "6.0": + return "/usr/lib/ffmpeg/6.0/bin/ffprobe" elif self.path == "5.0": return "/usr/lib/ffmpeg/5.0/bin/ffprobe" else: diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 19103cdf8..25dd809cb 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -91,7 +91,7 @@ PRESETS_HW_ACCEL_DECODE["preset-nvidia-mjpeg"] = PRESETS_HW_ACCEL_DECODE[ PRESETS_HW_ACCEL_SCALE = { "preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}", "preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}", - FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,eq=gamma=1.05", + FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", From 329bece28d4a3cb56ee3daba557675eb54893657 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 15 Sep 2024 12:43:03 -0500 Subject: [PATCH 03/36] stop web linter from complaining (#13755) --- web/src/types/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/types/search.ts b/web/src/types/search.ts index 762da2c82..9aaf35849 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -54,4 +54,4 @@ export type SearchQueryParams = { page?: number; }; -export type SearchQuery = [string, SearchQueryParams] | null; \ No newline at end of file +export type SearchQuery = [string, SearchQueryParams] | null; From e4ea35e626a2354747a1dfee67cb4fba4f95e056 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 15 Sep 2024 19:30:30 -0600 Subject: [PATCH 04/36] Add onnxruntime nvidia providers (#13756) --- docker/tensorrt/requirements-amd64.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/tensorrt/requirements-amd64.txt b/docker/tensorrt/requirements-amd64.txt index 214202e43..6cbfacd40 100644 --- a/docker/tensorrt/requirements-amd64.txt +++ b/docker/tensorrt/requirements-amd64.txt @@ -9,4 +9,5 @@ nvidia-cuda-runtime-cu11 == 11.8.*; platform_machine == 'x86_64' nvidia-cublas-cu11 == 11.11.3.6; platform_machine == 'x86_64' nvidia-cudnn-cu11 == 8.6.0.*; platform_machine == 'x86_64' onnx==1.14.0; platform_machine == 'x86_64' -protobuf==3.20.3; platform_machine == 'x86_64' \ No newline at end of file +onnxruntime-gpu==1.18.0; platform_machine == 'x86_64' +protobuf==3.20.3; platform_machine == 'x86_64' From 06ccf7e9e948f1c94f925fe5b665e483041551eb Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 16 Sep 2024 07:56:20 -0600 Subject: [PATCH 05/36] Always close connection to shm frame after detection (#13766) --- frigate/object_detection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frigate/object_detection.py b/frigate/object_detection.py index 22ff84f89..de383dffa 100644 --- a/frigate/object_detection.py +++ b/frigate/object_detection.py @@ -125,6 +125,7 @@ def run_detector( start.value = datetime.datetime.now().timestamp() detections = object_detector.detect_raw(input_frame) duration = datetime.datetime.now().timestamp() - start.value + frame_manager.close(connection_id) outputs[connection_id]["np"][:] = detections[:] out_events[connection_id].set() start.value = 0.0 From e3edcf057c9fb0abb8414be3a379c06c0f44c612 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:46:11 -0500 Subject: [PATCH 06/36] write prompts for genai at the camera level (#13767) --- docs/docs/configuration/genai.md | 16 ++++++++++++++-- docs/docs/configuration/reference.md | 14 +++++++++++++- frigate/config.py | 10 ++++++++-- frigate/embeddings/maintainer.py | 5 ++++- frigate/genai/__init__.py | 11 +++++++---- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/docs/docs/configuration/genai.md b/docs/docs/configuration/genai.md index 623cf588c..26954effe 100644 --- a/docs/docs/configuration/genai.md +++ b/docs/docs/configuration/genai.md @@ -124,13 +124,25 @@ genai: model: llava prompt: "Describe the {label} in these images from the {camera} security camera." object_prompts: - person: "Describe the main person in these images (gender, age, clothing, activity, etc). Do not include where the activity is occurring (sidewalk, concrete, driveway, etc). If delivering a package, include the company the package is from." + person: "Describe the main person in these images (gender, age, clothing, activity, etc). Do not include where the activity is occurring (sidewalk, concrete, driveway, etc)." car: "Label the primary vehicle in these images with just the name of the company if it is a delivery vehicle, or the color make and model." ``` +Prompts can also be overriden at the camera level to provide a more detailed prompt to the model about your specific camera, if you desire. + +```yaml +cameras: + front_door: + genai: + prompt: "Describe the {label} in these images from the {camera} security camera at the front door of a house, aimed outward toward the street." + object_prompts: + person: "Describe the main person in these images (gender, age, clothing, activity, etc). Do not include where the activity is occurring (sidewalk, concrete, driveway, etc). If delivering a package, include the company the package is from." + cat: "Describe the cat in these images (color, size, tail). Indicate whether or not the cat is by the flower pots. If the cat is chasing a mouse, make up a name for the mouse." +``` + ### Experiment with prompts -Providers also has a public facing chat interface for their models. Download a couple different thumbnails or snapshots from Frigate and try new things in the playground to get descriptions to your liking before updating the prompt in Frigate. +Many providers also have a public facing chat interface for their models. Download a couple of different thumbnails or snapshots from Frigate and try new things in the playground to get descriptions to your liking before updating the prompt in Frigate. - OpenAI - [ChatGPT](https://chatgpt.com) - Gemini - [Google AI Studio](https://aistudio.google.com) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 1ae0739e7..020e59979 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -504,7 +504,7 @@ semantic_search: # to Google or OpenAI's LLMs to generate descriptions. It can be overridden at # the camera level (enabled: False) to enhance privacy for indoor cameras. genai: - # Optional: Enable Google Gemini description generation (default: shown below) + # Optional: Enable AI description generation (default: shown below) enabled: False # Required if enabled: Provider must be one of ollama, gemini, or openai provider: ollama @@ -712,6 +712,18 @@ cameras: # By default the cameras are sorted alphabetically. order: 0 + # Optional: Configuration for AI generated tracked object descriptions + genai: + # Optional: Enable AI description generation (default: shown below) + enabled: False + # Optional: The default prompt for generating descriptions. Can use replacement + # variables like "label", "sub_label", "camera" to make more dynamic. (default: shown below) + prompt: "Describe the {label} in the sequence of images with as much detail as possible. Do not describe the background." + # Optional: Object specific prompts to customize description results + # Format: {label}: {prompt} + object_prompts: + person: "My special person prompt." + # Optional ui: # Optional: Set a timezone to use in the UI (default: use browser local time) diff --git a/frigate/config.py b/frigate/config.py index 8c66931c3..79d6c2343 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -763,8 +763,14 @@ class GenAIConfig(FrigateBaseModel): object_prompts: Dict[str, str] = Field(default={}, title="Object specific prompts.") -class GenAICameraConfig(FrigateBaseModel): +# uses BaseModel because some global attributes are not available at the camera level +class GenAICameraConfig(BaseModel): enabled: bool = Field(default=False, title="Enable GenAI for camera.") + prompt: str = Field( + default="Describe the {label} in the sequence of images with as much detail as possible. Do not describe the background.", + title="Default caption prompt.", + ) + object_prompts: Dict[str, str] = Field(default={}, title="Object specific prompts.") class AudioConfig(FrigateBaseModel): @@ -1520,7 +1526,7 @@ class FrigateConfig(FrigateBaseModel): "live": ..., "objects": ..., "review": ..., - "genai": {"enabled"}, + "genai": ..., "motion": ..., "detect": ..., "ffmpeg": ..., diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index a60663e7d..8e4309d5e 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -171,8 +171,11 @@ class EmbeddingMaintainer(threading.Thread): self, event: Event, thumbnails: list[bytes], metadata: dict ) -> None: """Embed the description for an event.""" + camera_config = self.config.cameras[event.camera] - description = self.genai_client.generate_description(thumbnails, metadata) + description = self.genai_client.generate_description( + camera_config, thumbnails, metadata + ) if description is None: logger.debug("Failed to generate description for %s", event.id) diff --git a/frigate/genai/__init__.py b/frigate/genai/__init__.py index 3761fa62f..afc783021 100644 --- a/frigate/genai/__init__.py +++ b/frigate/genai/__init__.py @@ -4,7 +4,7 @@ import importlib import os from typing import Optional -from frigate.config import GenAIConfig, GenAIProviderEnum +from frigate.config import CameraConfig, GenAIConfig, GenAIProviderEnum PROVIDERS = {} @@ -28,11 +28,14 @@ class GenAIClient: self.provider = self._init_provider() def generate_description( - self, thumbnails: list[bytes], metadata: dict[str, any] + self, + camera_config: CameraConfig, + thumbnails: list[bytes], + metadata: dict[str, any], ) -> Optional[str]: """Generate a description for the frame.""" - prompt = self.genai_config.object_prompts.get( - metadata["label"], self.genai_config.prompt + prompt = camera_config.genai.object_prompts.get( + metadata["label"], camera_config.genai.prompt ).format(**metadata) return self._send(prompt, thumbnails) From 9bcb928715ce40429da197e850a41449a4f4141f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:46:35 -0500 Subject: [PATCH 07/36] check for onvif movement support before attempting movement (#13771) --- frigate/ptz/onvif.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index 9c20ebf62..d13b7bbc3 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -335,6 +335,10 @@ class OnvifController: ) self._stop(camera_name) + if "pt" not in self.cams[camera_name]["features"]: + logger.error(f"{camera_name} does not support ONVIF pan/tilt movement.") + return + self.cams[camera_name]["active"] = True onvif: ONVIFCamera = self.cams[camera_name]["onvif"] move_request = self.cams[camera_name]["move_request"] @@ -476,6 +480,10 @@ class OnvifController: ) self._stop(camera_name) + if "zoom" not in self.cams[camera_name]["features"]: + logger.error(f"{camera_name} does not support ONVIF zooming.") + return + self.cams[camera_name]["active"] = True onvif: ONVIFCamera = self.cams[camera_name]["onvif"] move_request = self.cams[camera_name]["move_request"] From 2f69f5afe642d3f90c5a7bcee65878cbd73f2401 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 16 Sep 2024 15:17:31 -0600 Subject: [PATCH 08/36] Add support for yolonas via ONNX and allow TensorRT execution provider to work correctly (#13776) * Add support for yolonas in onnx * Add correct deps * Set ld library path * Refactor cudnn to only be used in amd64 * Add onnx to docs and add explainer at the top * Undo change * Update comment * Remove uneccesary * Remove line change --- docker/tensorrt/Dockerfile.amd64 | 16 ++++++ docker/tensorrt/requirements-amd64.txt | 5 +- docs/docs/configuration/object_detectors.md | 56 +++++++++++++++++++ frigate/detectors/plugins/onnx.py | 62 +++++++++++++++++---- 4 files changed, 127 insertions(+), 12 deletions(-) diff --git a/docker/tensorrt/Dockerfile.amd64 b/docker/tensorrt/Dockerfile.amd64 index 075726eda..b1ea1ced0 100644 --- a/docker/tensorrt/Dockerfile.amd64 +++ b/docker/tensorrt/Dockerfile.amd64 @@ -12,12 +12,27 @@ ARG TARGETARCH COPY docker/tensorrt/requirements-amd64.txt /requirements-tensorrt.txt RUN mkdir -p /trt-wheels && pip3 wheel --wheel-dir=/trt-wheels -r /requirements-tensorrt.txt +# Build CuDNN +FROM tensorrt-base AS cudnn-deps + +ARG COMPUTE_LEVEL + +RUN apt-get update \ + && apt-get install -y git build-essential + +RUN wget https://developer.download.nvidia.com/compute/cuda/repos/debian11/x86_64/cuda-keyring_1.1-1_all.deb \ +&& dpkg -i cuda-keyring_1.1-1_all.deb \ +&& apt-get update \ +&& apt-get -y install cuda-toolkit \ +&& rm -rf /var/lib/apt/lists/* + FROM tensorrt-base AS frigate-tensorrt ENV TRT_VER=8.5.3 RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \ pip3 install -U /deps/trt-wheels/*.whl && \ ldconfig +ENV LD_LIBRARY_PATH=/usr/local/lib/python3.9/dist-packages/tensorrt:/usr/local/cuda/lib64:/usr/local/lib/python3.9/dist-packages/nvidia/cufft/lib WORKDIR /opt/frigate/ COPY --from=rootfs / / @@ -26,6 +41,7 @@ FROM devcontainer AS devcontainer-trt COPY --from=trt-deps /usr/local/lib/libyolo_layer.so /usr/local/lib/libyolo_layer.so COPY --from=trt-deps /usr/local/src/tensorrt_demos /usr/local/src/tensorrt_demos +COPY --from=cudnn-deps /usr/local/cuda-12.6 /usr/local/cuda COPY docker/tensorrt/detector/rootfs/ / COPY --from=trt-deps /usr/local/lib/libyolo_layer.so /usr/local/lib/libyolo_layer.so RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \ diff --git a/docker/tensorrt/requirements-amd64.txt b/docker/tensorrt/requirements-amd64.txt index 6cbfacd40..dd99190d0 100644 --- a/docker/tensorrt/requirements-amd64.txt +++ b/docker/tensorrt/requirements-amd64.txt @@ -7,7 +7,8 @@ cython == 0.29.*; platform_machine == 'x86_64' nvidia-cuda-runtime-cu12 == 12.1.*; platform_machine == 'x86_64' nvidia-cuda-runtime-cu11 == 11.8.*; platform_machine == 'x86_64' nvidia-cublas-cu11 == 11.11.3.6; platform_machine == 'x86_64' -nvidia-cudnn-cu11 == 8.6.0.*; platform_machine == 'x86_64' +nvidia-cudnn-cu11 == 8.5.0.*; platform_machine == 'x86_64' +nvidia-cufft-cu11==10.*; platform_machine == 'x86_64' onnx==1.14.0; platform_machine == 'x86_64' -onnxruntime-gpu==1.18.0; platform_machine == 'x86_64' +onnxruntime-gpu==1.17.*; platform_machine == 'x86_64' protobuf==3.20.3; platform_machine == 'x86_64' diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 154c9907f..0e58fd7d2 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -3,6 +3,24 @@ id: object_detectors title: Object Detectors --- +# Supported Hardware + +Frigate supports multiple different detectors that work on different types of hardware: + +**Most Hardware** +- [Coral EdgeTPU](#edge-tpu-detector): The Google Coral EdgeTPU is available in USB and m.2 format allowing for a wide range of compatibility with devices. + +**Intel** +- [OpenVino](#openvino-detector): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel CPUs to provide efficient object detection. +- [ONNX](#onnx): OpenVINO will automatically be detected and used as a detector in the default Frigate image when a supported ONNX model is configured. + +**Nvidia** +- [TensortRT](#nvidia-tensorrt-detector): TensorRT can run on Nvidia GPUs, using one of many default models. +- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` Frigate image when a supported ONNX is configured. + +**Rockchip** +- [RKNN](#rockchip-platform): RKNN models can run on Rockchip devices with included NPUs. + # Officially Supported Detectors Frigate provides the following builtin detector types: `cpu`, `edgetpu`, `openvino`, `tensorrt`, `rknn`, and `hailo8l`. By default, Frigate will use a single CPU detector. Other detectors may require additional configuration as described below. When using multiple detectors they will run in dedicated processes, but pull from a common queue of detection requests from across all cameras. @@ -278,6 +296,44 @@ model: height: 320 ``` +## ONNX + +ONNX is an open format for building machine learning models, these models can run on a wide variety of hardware. Frigate supports running ONNX models on CPU, OpenVINO, and TensorRT. + +### Supported Models + +There is no default model provided, the following formats are supported: + +#### YOLO-NAS + +[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb). + +:::warning + +The pre-trained YOLO-NAS weights from DeciAI are subject to their license and can't be used commercially. For more information, see: https://docs.deci.ai/super-gradients/latest/LICENSE.YOLONAS.html + +::: + +The input image size in this notebook is set to 320x320. This results in lower CPU usage and faster inference times without impacting performance in most cases due to the way Frigate crops video frames to areas of interest before running detection. The notebook and config can be updated to 640x640 if desired. + +After placing the downloaded onnx model in your config folder, you can use the following configuration: + +```yaml +detectors: + onnx: + type: onnx + +model: + model_type: yolonas + width: 320 # <--- should match whatever was set in notebook + height: 320 # <--- should match whatever was set in notebook + input_pixel_format: bgr + path: /config/yolo_nas_s.onnx + labelmap_path: /labelmap/coco-80.txt +``` + +Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects. + ## Deepstack / CodeProject.AI Server Detector The Deepstack / CodeProject.AI Server detector for Frigate allows you to integrate Deepstack and CodeProject.AI object detection capabilities into Frigate. CodeProject.AI and DeepStack are open-source AI platforms that can be run on various devices such as the Raspberry Pi, Nvidia Jetson, and other compatible hardware. It is important to note that the integration is performed over the network, so the inference times may not be as fast as native Frigate detectors, but it still provides an efficient and reliable solution for object detection and tracking. diff --git a/frigate/detectors/plugins/onnx.py b/frigate/detectors/plugins/onnx.py index 6b124f6c9..1939b7323 100644 --- a/frigate/detectors/plugins/onnx.py +++ b/frigate/detectors/plugins/onnx.py @@ -1,11 +1,15 @@ import logging +import cv2 import numpy as np from typing_extensions import Literal from frigate.detectors.detection_api import DetectionApi -from frigate.detectors.detector_config import BaseDetectorConfig -from frigate.detectors.util import preprocess +from frigate.detectors.detector_config import ( + BaseDetectorConfig, + ModelTypeEnum, + PixelFormatEnum, +) logger = logging.getLogger(__name__) @@ -21,7 +25,7 @@ class ONNXDetector(DetectionApi): def __init__(self, detector_config: ONNXDetectorConfig): try: - import onnxruntime + import onnxruntime as ort logger.info("ONNX: loaded onnxruntime module") except ModuleNotFoundError: @@ -32,16 +36,54 @@ class ONNXDetector(DetectionApi): path = detector_config.model.path logger.info(f"ONNX: loading {detector_config.model.path}") - self.model = onnxruntime.InferenceSession(path) + self.model = ort.InferenceSession(path, providers=ort.get_available_providers()) + + self.h = detector_config.model.height + self.w = detector_config.model.width + self.onnx_model_type = detector_config.model.model_type + self.onnx_model_px = detector_config.model.input_pixel_format + path = detector_config.model.path + logger.info(f"ONNX: {path} loaded") def detect_raw(self, tensor_input): model_input_name = self.model.get_inputs()[0].name model_input_shape = self.model.get_inputs()[0].shape - tensor_input = preprocess(tensor_input, model_input_shape, np.float32) - # ruff: noqa: F841 - tensor_output = self.model.run(None, {model_input_name: tensor_input})[0] - raise Exception( - "No models are currently supported via onnx. See the docs for more info." - ) + # adjust input shape + if self.onnx_model_type == ModelTypeEnum.yolonas: + tensor_input = cv2.dnn.blobFromImage( + tensor_input[0], + 1.0, + (model_input_shape[3], model_input_shape[2]), + None, + swapRB=self.onnx_model_px == PixelFormatEnum.bgr, + ).astype(np.uint8) + + tensor_output = self.model.run(None, {model_input_name: tensor_input}) + + if self.onnx_model_type == ModelTypeEnum.yolonas: + predictions = tensor_output[0] + + detections = np.zeros((20, 6), np.float32) + + for i, prediction in enumerate(predictions): + if i == 20: + break + (_, x_min, y_min, x_max, y_max, confidence, class_id) = prediction + # when running in GPU mode, empty predictions in the output have class_id of -1 + if class_id < 0: + break + detections[i] = [ + class_id, + confidence, + y_min / self.h, + x_min / self.w, + y_max / self.h, + x_max / self.w, + ] + return detections + else: + raise Exception( + f"{self.onnx_model_type} is currently not supported for rocm. See the docs for more info on supported models." + ) From 4fc8d33d316c3ed1781e6026b2e1ff1e0ebcc7b2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 16 Sep 2024 17:23:10 -0600 Subject: [PATCH 09/36] Fix detections logic (#13781) --- frigate/object_processing.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frigate/object_processing.py b/frigate/object_processing.py index 50d962917..e73186f46 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -1138,12 +1138,14 @@ class TrackedObjectProcessor(threading.Thread): ) ) or ( - not review_config.detections.labels - or obj.obj_data["label"] in review_config.detections.labels - ) - and ( - not review_config.detections.required_zones - or set(obj.entered_zones) & set(review_config.alerts.required_zones) + ( + not review_config.detections.labels + or obj.obj_data["label"] in review_config.detections.labels + ) + and ( + not review_config.detections.required_zones + or set(obj.entered_zones) & set(review_config.alerts.required_zones) + ) ) ): logger.debug( From 36d7eb7caaec6d920e130265a9e29d291ea2a3ad Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 16 Sep 2024 18:18:11 -0600 Subject: [PATCH 10/36] Support ONNX model caching (#13780) * Support model caching * Cleanup --- docker/tensorrt/Dockerfile.amd64 | 5 ++++- docker/tensorrt/requirements-amd64.txt | 2 +- frigate/detectors/plugins/onnx.py | 23 ++++++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docker/tensorrt/Dockerfile.amd64 b/docker/tensorrt/Dockerfile.amd64 index b1ea1ced0..61d3264c9 100644 --- a/docker/tensorrt/Dockerfile.amd64 +++ b/docker/tensorrt/Dockerfile.amd64 @@ -3,6 +3,8 @@ # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND=noninteractive +ARG TRT_BASE=nvcr.io/nvidia/tensorrt:23.03-py3 + # Make this a separate target so it can be built/cached optionally FROM wheels as trt-wheels ARG DEBIAN_FRONTEND @@ -13,7 +15,7 @@ COPY docker/tensorrt/requirements-amd64.txt /requirements-tensorrt.txt RUN mkdir -p /trt-wheels && pip3 wheel --wheel-dir=/trt-wheels -r /requirements-tensorrt.txt # Build CuDNN -FROM tensorrt-base AS cudnn-deps +FROM ${TRT_BASE} AS cudnn-deps ARG COMPUTE_LEVEL @@ -31,6 +33,7 @@ ENV TRT_VER=8.5.3 RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \ pip3 install -U /deps/trt-wheels/*.whl && \ ldconfig +COPY --from=cudnn-deps /usr/local/cuda-12.6 /usr/local/cuda ENV LD_LIBRARY_PATH=/usr/local/lib/python3.9/dist-packages/tensorrt:/usr/local/cuda/lib64:/usr/local/lib/python3.9/dist-packages/nvidia/cufft/lib WORKDIR /opt/frigate/ diff --git a/docker/tensorrt/requirements-amd64.txt b/docker/tensorrt/requirements-amd64.txt index dd99190d0..b5ad4fcbd 100644 --- a/docker/tensorrt/requirements-amd64.txt +++ b/docker/tensorrt/requirements-amd64.txt @@ -7,7 +7,7 @@ cython == 0.29.*; platform_machine == 'x86_64' nvidia-cuda-runtime-cu12 == 12.1.*; platform_machine == 'x86_64' nvidia-cuda-runtime-cu11 == 11.8.*; platform_machine == 'x86_64' nvidia-cublas-cu11 == 11.11.3.6; platform_machine == 'x86_64' -nvidia-cudnn-cu11 == 8.5.0.*; platform_machine == 'x86_64' +nvidia-cudnn-cu11 == 8.6.0.*; platform_machine == 'x86_64' nvidia-cufft-cu11==10.*; platform_machine == 'x86_64' onnx==1.14.0; platform_machine == 'x86_64' onnxruntime-gpu==1.17.*; platform_machine == 'x86_64' diff --git a/frigate/detectors/plugins/onnx.py b/frigate/detectors/plugins/onnx.py index 1939b7323..ccd0ffc68 100644 --- a/frigate/detectors/plugins/onnx.py +++ b/frigate/detectors/plugins/onnx.py @@ -36,7 +36,28 @@ class ONNXDetector(DetectionApi): path = detector_config.model.path logger.info(f"ONNX: loading {detector_config.model.path}") - self.model = ort.InferenceSession(path, providers=ort.get_available_providers()) + + providers = ort.get_available_providers() + options = [] + + for provider in providers: + if provider == "TensorrtExecutionProvider": + options.append( + { + "trt_timing_cache_enable": True, + "trt_timing_cache_path": "/config/model_cache/tensorrt/ort", + "trt_engine_cache_enable": True, + "trt_engine_cache_path": "/config/model_cache/tensorrt/ort/trt-engines", + } + ) + elif provider == "OpenVINOExecutionProvider": + options.append({"cache_dir": "/config/model_cache/openvino/ort"}) + else: + options.append({}) + + self.model = ort.InferenceSession( + path, providers=providers, provider_options=options + ) self.h = detector_config.model.height self.w = detector_config.model.width From 6bf2708c0e226b38efe3a49c02af4a69d5adbfc7 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 16 Sep 2024 18:18:32 -0600 Subject: [PATCH 11/36] Standardize bar graph y axis (#13772) * Standardize bar graph y axis * Fix lint --- web/src/components/graph/SystemGraph.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/web/src/components/graph/SystemGraph.tsx b/web/src/components/graph/SystemGraph.tsx index 3f5bfead3..098a1957e 100644 --- a/web/src/components/graph/SystemGraph.tsx +++ b/web/src/components/graph/SystemGraph.tsx @@ -32,6 +32,16 @@ export function ThresholdBarGraph({ [data], ); + const yMax = useMemo(() => { + if (unit != "%") { + return undefined; + } + + // @ts-expect-error y is valid + const yValues: number[] = data[0].data.map((point) => point?.y); + return Math.max(threshold.warning, ...yValues); + }, [data, threshold, unit]); + const { theme, systemTheme } = useTheme(); const formatTime = useCallback( @@ -130,9 +140,10 @@ export function ThresholdBarGraph({ formatter: (val: number) => Math.ceil(val).toString(), }, min: 0, + max: yMax, }, } as ApexCharts.ApexOptions; - }, [graphId, threshold, unit, systemTheme, theme, formatTime]); + }, [graphId, threshold, unit, yMax, systemTheme, theme, formatTime]); useEffect(() => { ApexCharts.exec(graphId, "updateOptions", options, true, true); From 8573016bef4a61e3a324697dbd6155440aff0f21 Mon Sep 17 00:00:00 2001 From: gtsiam Date: Tue, 17 Sep 2024 15:39:44 +0300 Subject: [PATCH 12/36] Formatting improvements (#13765) * Format makefiles * Handle all errors in rocm makefile * Remove CURRENT_UID and GID from makefile as they are unused * Removed unused vite.svg asset * Sort frigate-dictionary --- .cspell/frigate-dictionary.txt | 323 ++++++++++++++++----------------- Makefile | 29 ++- docker/hailo8l/h8l.mk | 11 +- docker/rockchip/rk.mk | 11 +- docker/rocm/rocm.mk | 48 ++++- docker/rpi/rpi.mk | 11 +- docker/tensorrt/trt.mk | 33 +++- web/public/vite.svg | 1 - 8 files changed, 271 insertions(+), 196 deletions(-) delete mode 100644 web/public/vite.svg diff --git a/.cspell/frigate-dictionary.txt b/.cspell/frigate-dictionary.txt index 397584bd9..3f10ab6ff 100644 --- a/.cspell/frigate-dictionary.txt +++ b/.cspell/frigate-dictionary.txt @@ -1,168 +1,167 @@ -rtmp -edgetpu -labelmap -rockchip -jetson -rocm -vaapi -CUDA -hwaccel -RTSP -Hikvision -Dahua -Amcrest -Reolink -Loryta -Beelink -Celeron -vaapi -blakeblackshear -workdir -onvif -autotracking -openvino -tflite -deepstack -codeproject -udev -tailscale -restream -restreaming -webrtc -ssdlite -mobilenet -mosquitto -datasheet -Jellyfin -Radeon -libva -Ubiquiti -Unifi -Tapo -Annke -autotracker -autotracked -variations -ONVIF -traefik -devcontainer -rootfs -ffprobe -autotrack -logpipe -imread -imwrite -imencode -imutils -thresholded -timelapse -ultrafast -sleeptime -radeontop -vainfo -tmpfs -homography -websockets -LIBAVFORMAT -NTSC -onnxruntime -fourcc -radeonsi -paho -imagestream -jsonify -cgroups -sysconf -memlimit -gpuload -nvml -setproctitle -psutil -Kalman -frontdoor -namedtuples -zeep -fflags -probesize -wallclock -rknn -socs -pydantic -shms -imdecode -colormap -webui -mse -jsmpeg -unreviewed -Chromecast -Swipeable -flac -scroller -cmdline -toggleable -bottombar -opencv -apexcharts -buildx -mqtt -rawvideo -defragment -Norfair -subclassing -yolo -tensorrt -blackshear -stylelint -HACS -homeassistant -hass -castable -mobiledet -framebuffer -mjpeg -substream -codeowner -noninteractive -restreamed -mountpoint -fstype -OWASP -iotop -letsencrypt -fullchain -lsusb -iostat -usermod -balena -passwordless -debconf -dpkg -poweroff -surveillance -qnap -homekit -colorspace -quantisation -skylake -Cuvid -foscam -onnx -numpy -protobuf aarch +Amcrest amdgpu -chipset -referer -mpegts -webp +Annke +apexcharts authelia authentik -unichip -rebranded -udevadm automations -unraid -hideable +autotrack +autotracked +autotracker +autotracking +balena +Beelink +blackshear +blakeblackshear +bottombar +buildx +castable +Celeron +cgroups +chipset +Chromecast +cmdline +codeowner +codeproject +colormap +colorspace +CUDA +Cuvid +Dahua +datasheet +debconf +deepstack +defragment +devcontainer +dpkg +edgetpu +fflags +ffprobe +flac +foscam +fourcc +framebuffer +frontdoor +fstype +fullchain +gpuload +HACS +hass healthcheck -keepalive \ No newline at end of file +hideable +Hikvision +homeassistant +homekit +homography +hwaccel +imagestream +imdecode +imencode +imread +imutils +imwrite +iostat +iotop +Jellyfin +jetson +jsmpeg +jsonify +Kalman +keepalive +labelmap +letsencrypt +LIBAVFORMAT +libva +logpipe +Loryta +lsusb +memlimit +mjpeg +mobiledet +mobilenet +mosquitto +mountpoint +mpegts +mqtt +mse +namedtuples +noninteractive +Norfair +NTSC +numpy +nvml +onnx +onnxruntime +onvif +ONVIF +opencv +openvino +OWASP +paho +passwordless +poweroff +probesize +protobuf +psutil +pydantic +qnap +quantisation +Radeon +radeonsi +radeontop +rawvideo +rebranded +referer +Reolink +restream +restreamed +restreaming +rknn +rockchip +rocm +rootfs +rtmp +RTSP +scroller +setproctitle +shms +skylake +sleeptime +socs +ssdlite +stylelint +subclassing +substream +surveillance +Swipeable +sysconf +tailscale +Tapo +tensorrt +tflite +thresholded +timelapse +tmpfs +toggleable +traefik +Ubiquiti +udev +udevadm +ultrafast +unichip +Unifi +unraid +unreviewed +usermod +vaapi +vainfo +variations +wallclock +webp +webrtc +websockets +webui +workdir +yolo +zeep diff --git a/Makefile b/Makefile index 8ff6231b7..b7c6ab821 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,6 @@ COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1) VERSION = 0.15.0 IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) -CURRENT_UID := $(shell id -u) -CURRENT_GID := $(shell id -g) BOARDS= #Initialized empty include docker/*/*.mk @@ -18,25 +16,38 @@ version: echo 'VERSION = "$(VERSION)-$(COMMIT_HASH)"' > frigate/version.py local: version - docker buildx build --target=frigate --tag frigate:latest --load --file docker/main/Dockerfile . + docker buildx build --target=frigate --file docker/main/Dockerfile . \ + --tag frigate:latest \ + --load amd64: - docker buildx build --platform linux/amd64 --target=frigate --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) --file docker/main/Dockerfile . + docker buildx build --target=frigate --file docker/main/Dockerfile . \ + --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) \ + --platform linux/amd64 arm64: - docker buildx build --platform linux/arm64 --target=frigate --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) --file docker/main/Dockerfile . + docker buildx build --target=frigate --file docker/main/Dockerfile . \ + --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) \ + --platform linux/arm64 build: version amd64 arm64 - docker buildx build --platform linux/arm64/v8,linux/amd64 --target=frigate --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) --file docker/main/Dockerfile . + docker buildx build --target=frigate --file docker/main/Dockerfile . \ + --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) \ + --platform linux/arm64/v8,linux/amd64 push: push-boards - docker buildx build --push --platform linux/arm64/v8,linux/amd64 --target=frigate --tag $(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH) --file docker/main/Dockerfile . + docker buildx build --target=frigate --file docker/main/Dockerfile . \ + --tag $(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH) \ + --platform linux/arm64/v8,linux/amd64 \ + --push run: local docker run --rm --publish=5000:5000 --volume=${PWD}/config:/config frigate:latest run_tests: local - docker run --rm --workdir=/opt/frigate --entrypoint= frigate:latest python3 -u -m unittest - docker run --rm --workdir=/opt/frigate --entrypoint= frigate:latest python3 -u -m mypy --config-file frigate/mypy.ini frigate + docker run --rm --workdir=/opt/frigate --entrypoint= frigate:latest \ + python3 -u -m unittest + docker run --rm --workdir=/opt/frigate --entrypoint= frigate:latest \ + python3 -u -m mypy --config-file frigate/mypy.ini frigate .PHONY: run_tests diff --git a/docker/hailo8l/h8l.mk b/docker/hailo8l/h8l.mk index 8d8a5d00f..318771802 100644 --- a/docker/hailo8l/h8l.mk +++ b/docker/hailo8l/h8l.mk @@ -1,10 +1,15 @@ BOARDS += h8l local-h8l: version - docker buildx bake --load --file=docker/hailo8l/h8l.hcl --set h8l.tags=frigate:latest-h8l h8l + docker buildx bake --file=docker/hailo8l/h8l.hcl h8l \ + --set h8l.tags=frigate:latest-h8l \ + --load build-h8l: version - docker buildx bake --file=docker/hailo8l/h8l.hcl --set h8l.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-h8l h8l + docker buildx bake --file=docker/hailo8l/h8l.hcl h8l \ + --set h8l.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-h8l push-h8l: build-h8l - docker buildx bake --push --file=docker/hailo8l/h8l.hcl --set h8l.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-h8l h8l \ No newline at end of file + docker buildx bake --file=docker/hailo8l/h8l.hcl h8l \ + --set h8l.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-h8l \ + --push \ No newline at end of file diff --git a/docker/rockchip/rk.mk b/docker/rockchip/rk.mk index 0d9bde16a..c8278f68b 100644 --- a/docker/rockchip/rk.mk +++ b/docker/rockchip/rk.mk @@ -1,10 +1,15 @@ BOARDS += rk local-rk: version - docker buildx bake --load --file=docker/rockchip/rk.hcl --set rk.tags=frigate:latest-rk rk + docker buildx bake --file=docker/rockchip/rk.hcl rk \ + --set rk.tags=frigate:latest-rk \ + --load build-rk: version - docker buildx bake --file=docker/rockchip/rk.hcl --set rk.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rk rk + docker buildx bake --file=docker/rockchip/rk.hcl rk \ + --set rk.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rk push-rk: build-rk - docker buildx bake --push --file=docker/rockchip/rk.hcl --set rk.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rk rk \ No newline at end of file + docker buildx bake --file=docker/rockchip/rk.hcl rk \ + --set rk.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rk \ + --push \ No newline at end of file diff --git a/docker/rocm/rocm.mk b/docker/rocm/rocm.mk index 2d3d034ee..c92a458f5 100644 --- a/docker/rocm/rocm.mk +++ b/docker/rocm/rocm.mk @@ -4,14 +4,50 @@ BOARDS += rocm ROCM_CHIPSETS:=gfx900:9.0.0 gfx1030:10.3.0 gfx1100:11.0.0 local-rocm: version - $(foreach chipset,$(ROCM_CHIPSETS),AMDGPU=$(word 1,$(subst :, ,$(chipset))) HSA_OVERRIDE_GFX_VERSION=$(word 2,$(subst :, ,$(chipset))) HSA_OVERRIDE=1 docker buildx bake --load --file=docker/rocm/rocm.hcl --set rocm.tags=frigate:latest-rocm-$(word 1,$(subst :, ,$(chipset))) rocm;) - unset HSA_OVERRIDE_GFX_VERSION && HSA_OVERRIDE=0 AMDGPU=gfx docker buildx bake --load --file=docker/rocm/rocm.hcl --set rocm.tags=frigate:latest-rocm rocm + $(foreach chipset,$(ROCM_CHIPSETS), \ + AMDGPU=$(word 1,$(subst :, ,$(chipset))) \ + HSA_OVERRIDE_GFX_VERSION=$(word 2,$(subst :, ,$(chipset))) \ + HSA_OVERRIDE=1 \ + docker buildx bake --file=docker/rocm/rocm.hcl rocm \ + --set rocm.tags=frigate:latest-rocm-$(word 1,$(subst :, ,$(chipset))) \ + --load \ + &&) true + + unset HSA_OVERRIDE_GFX_VERSION && \ + HSA_OVERRIDE=0 \ + AMDGPU=gfx \ + docker buildx bake --file=docker/rocm/rocm.hcl rocm \ + --set rocm.tags=frigate:latest-rocm \ + --load build-rocm: version - $(foreach chipset,$(ROCM_CHIPSETS),AMDGPU=$(word 1,$(subst :, ,$(chipset))) HSA_OVERRIDE_GFX_VERSION=$(word 2,$(subst :, ,$(chipset))) HSA_OVERRIDE=1 docker buildx bake --file=docker/rocm/rocm.hcl --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm-$(chipset) rocm;) - unset HSA_OVERRIDE_GFX_VERSION && HSA_OVERRIDE=0 AMDGPU=gfx docker buildx bake --file=docker/rocm/rocm.hcl --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm rocm + $(foreach chipset,$(ROCM_CHIPSETS), \ + AMDGPU=$(word 1,$(subst :, ,$(chipset))) \ + HSA_OVERRIDE_GFX_VERSION=$(word 2,$(subst :, ,$(chipset))) \ + HSA_OVERRIDE=1 \ + docker buildx bake --file=docker/rocm/rocm.hcl rocm \ + --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm-$(chipset) \ + &&) true + + unset HSA_OVERRIDE_GFX_VERSION && \ + HSA_OVERRIDE=0 \ + AMDGPU=gfx \ + docker buildx bake --file=docker/rocm/rocm.hcl rocm \ + --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm push-rocm: build-rocm - $(foreach chipset,$(ROCM_CHIPSETS),AMDGPU=$(word 1,$(subst :, ,$(chipset))) HSA_OVERRIDE_GFX_VERSION=$(word 2,$(subst :, ,$(chipset))) HSA_OVERRIDE=1 docker buildx bake --push --file=docker/rocm/rocm.hcl --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm-$(chipset) rocm;) - unset HSA_OVERRIDE_GFX_VERSION && HSA_OVERRIDE=0 AMDGPU=gfx docker buildx bake --push --file=docker/rocm/rocm.hcl --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm rocm + $(foreach chipset,$(ROCM_CHIPSETS), \ + AMDGPU=$(word 1,$(subst :, ,$(chipset))) \ + HSA_OVERRIDE_GFX_VERSION=$(word 2,$(subst :, ,$(chipset))) \ + HSA_OVERRIDE=1 \ + docker buildx bake --file=docker/rocm/rocm.hcl rocm \ + --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm-$(chipset) \ + --push \ + &&) true + unset HSA_OVERRIDE_GFX_VERSION && \ + HSA_OVERRIDE=0 \ + AMDGPU=gfx \ + docker buildx bake --file=docker/rocm/rocm.hcl rocm \ + --set rocm.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rocm \ + --push diff --git a/docker/rpi/rpi.mk b/docker/rpi/rpi.mk index c1282b011..290b30c31 100644 --- a/docker/rpi/rpi.mk +++ b/docker/rpi/rpi.mk @@ -1,10 +1,15 @@ BOARDS += rpi local-rpi: version - docker buildx bake --load --file=docker/rpi/rpi.hcl --set rpi.tags=frigate:latest-rpi rpi + docker buildx bake --file=docker/rpi/rpi.hcl rpi \ + --set rpi.tags=frigate:latest-rpi \ + --load build-rpi: version - docker buildx bake --file=docker/rpi/rpi.hcl --set rpi.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rpi rpi + docker buildx bake --file=docker/rpi/rpi.hcl rpi \ + --set rpi.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rpi push-rpi: build-rpi - docker buildx bake --push --file=docker/rpi/rpi.hcl --set rpi.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rpi rpi \ No newline at end of file + docker buildx bake --file=docker/rpi/rpi.hcl rpi \ + --set rpi.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-rpi \ + --push diff --git a/docker/tensorrt/trt.mk b/docker/tensorrt/trt.mk index ad2425c81..455e1ee11 100644 --- a/docker/tensorrt/trt.mk +++ b/docker/tensorrt/trt.mk @@ -7,20 +7,35 @@ JETPACK4_ARGS := ARCH=arm64 BASE_IMAGE=$(JETPACK4_BASE) SLIM_BASE=$(JETPACK4_BAS JETPACK5_ARGS := ARCH=arm64 BASE_IMAGE=$(JETPACK5_BASE) SLIM_BASE=$(JETPACK5_BASE) TRT_BASE=$(JETPACK5_BASE) local-trt: version - $(X86_DGPU_ARGS) docker buildx bake --load --file=docker/tensorrt/trt.hcl --set tensorrt.tags=frigate:latest-tensorrt tensorrt + $(X86_DGPU_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=frigate:latest-tensorrt \ + --load local-trt-jp4: version - $(JETPACK4_ARGS) docker buildx bake --load --file=docker/tensorrt/trt.hcl --set tensorrt.tags=frigate:latest-tensorrt-jp4 tensorrt + $(JETPACK4_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=frigate:latest-tensorrt-jp4 \ + --load local-trt-jp5: version - $(JETPACK5_ARGS) docker buildx bake --load --file=docker/tensorrt/trt.hcl --set tensorrt.tags=frigate:latest-tensorrt-jp5 tensorrt + $(JETPACK5_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=frigate:latest-tensorrt-jp5 \ + --load build-trt: - $(X86_DGPU_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt tensorrt - $(JETPACK4_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp4 tensorrt - $(JETPACK5_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp5 tensorrt + $(X86_DGPU_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt + $(JETPACK4_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp4 + $(JETPACK5_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp5 push-trt: build-trt - $(X86_DGPU_ARGS) docker buildx bake --push --file=docker/tensorrt/trt.hcl --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt tensorrt - $(JETPACK4_ARGS) docker buildx bake --push --file=docker/tensorrt/trt.hcl --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp4 tensorrt - $(JETPACK5_ARGS) docker buildx bake --push --file=docker/tensorrt/trt.hcl --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp5 tensorrt + $(X86_DGPU_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt \ + --push + $(JETPACK4_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp4 \ + --push + $(JETPACK5_ARGS) docker buildx bake --file=docker/tensorrt/trt.hcl tensorrt \ + --set tensorrt.tags=$(IMAGE_REPO):${GITHUB_REF_NAME}-$(COMMIT_HASH)-tensorrt-jp5 \ + --push diff --git a/web/public/vite.svg b/web/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/web/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From f7eaace7ae00d55376d083c46f0c9a7137298d20 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 17 Sep 2024 07:04:38 -0600 Subject: [PATCH 13/36] Change path for rockchip ffmpeg (#13792) --- docker/rockchip/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/rockchip/Dockerfile b/docker/rockchip/Dockerfile index 84a3cc442..9087efcd2 100644 --- a/docker/rockchip/Dockerfile +++ b/docker/rockchip/Dockerfile @@ -22,5 +22,5 @@ ADD https://github.com/MarcA711/rknn-toolkit2/releases/download/v2.0.0/librknnrt RUN rm -rf /usr/lib/btbn-ffmpeg/bin/ffmpeg RUN rm -rf /usr/lib/btbn-ffmpeg/bin/ffprobe -ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffmpeg /usr/lib/btbn-ffmpeg/bin/ -ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffprobe /usr/lib/btbn-ffmpeg/bin/ +ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffmpeg /usr/lib/ffmpeg/6.0/bin/ +ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffprobe /usr/lib/ffmpeg/6.0/bin/ From 1c24f0054a83148c7dcde5a18dd4841ea710bba4 Mon Sep 17 00:00:00 2001 From: gtsiam Date: Tue, 17 Sep 2024 16:26:25 +0300 Subject: [PATCH 14/36] Make logging code self-contained (#13785) * Make logging code self-contained. Rewrite logging code to use python's builting QueueListener, effectively moving the logging process into a thread of the Frigate app. Also, wrap this behaviour in a easy-to-use context manager to encourage some consistency. * Fixed typing errors * Remove todo note from log filter Co-authored-by: Nicolas Mowen * Do not access log record's msg directly * Clear all root handlers before starting app --------- Co-authored-by: Nicolas Mowen --- frigate/__main__.py | 23 ++++++++--- frigate/app.py | 19 +-------- frigate/log.py | 94 ++++++++++++++++++++++----------------------- process_clip.py | 3 +- 4 files changed, 67 insertions(+), 72 deletions(-) diff --git a/frigate/__main__.py b/frigate/__main__.py index 844206908..208af9e23 100644 --- a/frigate/__main__.py +++ b/frigate/__main__.py @@ -1,17 +1,28 @@ import faulthandler +import logging import threading from flask import cli from frigate.app import FrigateApp -faulthandler.enable() -threading.current_thread().name = "frigate" +def main() -> None: + faulthandler.enable() + + # Clear all existing handlers. + logging.basicConfig( + level=logging.INFO, + handlers=[], + force=True, + ) + + threading.current_thread().name = "frigate" + cli.show_server_banner = lambda *x: None + + # Run the main application. + FrigateApp().start() -cli.show_server_banner = lambda *x: None if __name__ == "__main__": - frigate_app = FrigateApp() - - frigate_app.start() + main() diff --git a/frigate/app.py b/frigate/app.py index 3401ff6dc..0059cbfe2 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -43,7 +43,7 @@ from frigate.events.audio import listen_to_audio from frigate.events.cleanup import EventCleanup from frigate.events.external import ExternalEventProcessor from frigate.events.maintainer import EventProcessor -from frigate.log import log_process, root_configurer +from frigate.log import log_thread from frigate.models import ( Event, Export, @@ -113,15 +113,6 @@ class FrigateApp: else: logger.debug(f"Skipping directory: {d}") - def init_logger(self) -> None: - self.log_process = mp.Process( - target=log_process, args=(self.log_queue,), name="log_process" - ) - self.log_process.daemon = True - self.log_process.start() - self.processes["logger"] = self.log_process.pid or 0 - root_configurer(self.log_queue) - def init_config(self) -> None: config_file = os.environ.get("CONFIG_FILE", "/config/config.yml") @@ -667,6 +658,7 @@ class FrigateApp: logger.info("********************************************************") logger.info("********************************************************") + @log_thread() def start(self) -> None: parser = argparse.ArgumentParser( prog="Frigate", @@ -675,7 +667,6 @@ class FrigateApp: parser.add_argument("--validate-config", action="store_true") args = parser.parse_args() - self.init_logger() logger.info(f"Starting Frigate ({VERSION})") try: @@ -702,13 +693,11 @@ class FrigateApp: print("*************************************************************") print("*** End Config Validation Errors ***") print("*************************************************************") - self.log_process.terminate() sys.exit(1) if args.validate_config: print("*************************************************************") print("*** Your config file is valid. ***") print("*************************************************************") - self.log_process.terminate() sys.exit(0) self.set_environment_vars() self.set_log_levels() @@ -725,7 +714,6 @@ class FrigateApp: self.init_dispatcher() except Exception as e: print(e) - self.log_process.terminate() sys.exit(1) self.start_detectors() self.start_video_output_processor() @@ -848,7 +836,4 @@ class FrigateApp: shm.close() shm.unlink() - self.log_process.terminate() - self.log_process.join() - os._exit(os.EX_OK) diff --git a/frigate/log.py b/frigate/log.py index 2947a3e38..475be50d4 100644 --- a/frigate/log.py +++ b/frigate/log.py @@ -1,71 +1,71 @@ -# adapted from https://medium.com/@jonathonbao/python3-logging-with-multiprocessing-f51f460b8778 +import atexit import logging import multiprocessing as mp import os -import queue -import signal import threading from collections import deque -from logging import handlers -from multiprocessing import Queue -from types import FrameType +from contextlib import AbstractContextManager, ContextDecorator +from logging.handlers import QueueHandler, QueueListener +from types import TracebackType from typing import Deque, Optional -from setproctitle import setproctitle +from typing_extensions import Self from frigate.util.builtin import clean_camera_user_pass - -def listener_configurer() -> None: - root = logging.getLogger() - - if root.hasHandlers(): - root.handlers.clear() - - console_handler = logging.StreamHandler() - formatter = logging.Formatter( - "[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s", "%Y-%m-%d %H:%M:%S" +LOG_HANDLER = logging.StreamHandler() +LOG_HANDLER.setFormatter( + logging.Formatter( + "[%(asctime)s] %(name)-30s %(levelname)-8s: %(message)s", + "%Y-%m-%d %H:%M:%S", ) - console_handler.setFormatter(formatter) - root.addHandler(console_handler) - root.setLevel(logging.INFO) +) + +LOG_HANDLER.addFilter( + lambda record: not record.getMessage().startswith( + "You are using a scalar distance function" + ) +) -def root_configurer(queue: Queue) -> None: - h = handlers.QueueHandler(queue) - root = logging.getLogger() +class log_thread(AbstractContextManager, ContextDecorator): + def __init__(self, *, handler: logging.Handler = LOG_HANDLER): + super().__init__() - if root.hasHandlers(): - root.handlers.clear() + self._handler = handler - root.addHandler(h) - root.setLevel(logging.INFO) + log_queue: mp.Queue = mp.Queue() + self._queue_handler = QueueHandler(log_queue) + self._log_listener = QueueListener( + log_queue, self._handler, respect_handler_level=True + ) -def log_process(log_queue: Queue) -> None: - threading.current_thread().name = "logger" - setproctitle("frigate.logger") - listener_configurer() + @property + def handler(self) -> logging.Handler: + return self._handler - stop_event = mp.Event() + def _stop_thread(self) -> None: + self._log_listener.stop() - def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None: - stop_event.set() + def __enter__(self) -> Self: + logging.getLogger().addHandler(self._queue_handler) - signal.signal(signal.SIGTERM, receiveSignal) - signal.signal(signal.SIGINT, receiveSignal) + atexit.register(self._stop_thread) + self._log_listener.start() - while True: - try: - record = log_queue.get(block=True, timeout=1.0) - except queue.Empty: - if stop_event.is_set(): - break - continue - if record.msg.startswith("You are using a scalar distance function"): - continue - logger = logging.getLogger(record.name) - logger.handle(record) + return self + + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_info: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + logging.getLogger().removeHandler(self._queue_handler) + + atexit.unregister(self._stop_thread) + self._stop_thread() # based on https://codereview.stackexchange.com/a/17959 diff --git a/process_clip.py b/process_clip.py index 73e2e7c41..77cfdba2c 100644 --- a/process_clip.py +++ b/process_clip.py @@ -28,8 +28,7 @@ from frigate.video import ( # noqa: E402 start_or_restart_ffmpeg, ) -logging.basicConfig() -logging.root.setLevel(logging.DEBUG) +logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) From 350abda21ac7f169ff25fe609c475571e06b4303 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 17 Sep 2024 07:27:47 -0600 Subject: [PATCH 15/36] Update docs dependencies (#13793) --- docs/package-lock.json | 928 ++++++++++++++++++++++++----------------- docs/package.json | 14 +- 2 files changed, 554 insertions(+), 388 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 5ded270a9..e8eb75686 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,20 +8,20 @@ "name": "docs", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@docusaurus/theme-mermaid": "^3.4.0", + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", + "@docusaurus/theme-mermaid": "^3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", - "prism-react-renderer": "^2.1.0", + "prism-react-renderer": "^2.4.0", "raw-loader": "^4.0.2", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.4.0", "@docusaurus/types": "^3.4.0", - "@types/react": "^18.2.79" + "@types/react": "^18.3.7" }, "engines": { "node": ">=18.0" @@ -73,82 +73,139 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", - "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/cache-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", - "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", "license": "MIT" }, "node_modules/@algolia/cache-in-memory": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", - "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/client-account": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", - "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", - "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", - "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.4.3.tgz", + "integrity": "sha512-6BoqQ1/Xjwol7kL5Z7TwSphff0mN4pwpydTi6VOkKs7X3piBj6cuJ3FLjHnaVCwMvcaO9hW3gbx+M0u1sYekig==", "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "peer": true, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", - "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-search": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", - "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.4.3.tgz", + "integrity": "sha512-SJ2ofwdyknkwfSXQi7xvrOR93lNxjsgS1+vOdOkOF1t6HgWxnPXHZoP2hUSsrKExSQWmeE7UUbpvhHZkFxGLeA==", "license": "MIT", + "peer": true, "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.4.3", + "@algolia/requester-browser-xhr": "5.4.3", + "@algolia/requester-fetch": "5.4.3", + "@algolia/requester-node-http": "5.4.3" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/events": { @@ -158,72 +215,132 @@ "license": "MIT" }, "node_modules/@algolia/logger-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", - "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", "license": "MIT" }, "node_modules/@algolia/logger-console": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", - "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", "license": "MIT", "dependencies": { - "@algolia/logger-common": "4.23.3" + "@algolia/logger-common": "4.24.0" } }, "node_modules/@algolia/recommend": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", - "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", "license": "MIT", "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", - "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.4.3.tgz", + "integrity": "sha512-XgxyUzUQei5MDNkjss5ioID00sRkazgYAojZpz8B1gNvWaSx/FQd/7MlVoi4HBtSJNi1pkgpsVGGlMp6nTZdyA==", "license": "MIT", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.4.3" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", - "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", "license": "MIT" }, - "node_modules/@algolia/requester-node-http": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", - "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", + "node_modules/@algolia/requester-fetch": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.4.3.tgz", + "integrity": "sha512-Z6VuKQrBd6+TzyL1jsLI1kkoeXTY/g3SR01Z674vTZpdZlimiI9HMMHkgHthtK1speMjfPGDcTggi4TcOxXpMQ==", "license": "MIT", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.4.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.4.3.tgz", + "integrity": "sha512-gNIaj31fFz3pbIM7LiS1iu4/1ZX+lJdWd+UiM9ECaGtZYpkdxxqbfyMHieISVLNsVezOpYgS2BYeKe8d5+se/Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.4.3" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", - "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/requester-common": "4.23.3" + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" } }, "node_modules/@ampproject/remapping": { @@ -570,9 +687,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz", - "integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1648,12 +1765,12 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.6.tgz", - "integrity": "sha512-vQfyXRtG/kNIcTYRd/49uJnwvMig9X3R4XsTVXRml2RFupZFY+2RDuK+/ymb+MfX2WuIHAgUZc2xEvQrnI7QCg==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.1.tgz", + "integrity": "sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2201,20 +2318,20 @@ } }, "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", + "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==", "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", + "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", + "@docsearch/css": "3.6.1", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -2239,9 +2356,9 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz", - "integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", + "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", "license": "MIT", "dependencies": { "@babel/core": "^7.23.3", @@ -2254,12 +2371,12 @@ "@babel/runtime": "^7.22.6", "@babel/runtime-corejs3": "^7.22.6", "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/cssnano-preset": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "autoprefixer": "^10.4.14", "babel-loader": "^9.1.3", "babel-plugin-dynamic-import-node": "^2.3.3", @@ -2320,14 +2437,15 @@ "node": ">=18.0" }, "peerDependencies": { + "@mdx-js/react": "^3.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz", - "integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", + "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", @@ -2340,9 +2458,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz", - "integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", + "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -2353,14 +2471,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz", - "integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", + "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -2392,12 +2510,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", - "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", + "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.4.0", + "@docusaurus/types": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2411,19 +2529,20 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", - "integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", + "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", - "cheerio": "^1.0.0-rc.12", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", + "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", @@ -2438,24 +2557,26 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz", - "integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", + "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2474,16 +2595,16 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", - "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", + "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2497,14 +2618,14 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz", - "integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", + "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2518,14 +2639,14 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz", - "integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", + "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2537,14 +2658,14 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz", - "integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", + "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2557,14 +2678,14 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz", - "integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", + "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2576,17 +2697,17 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz", - "integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", + "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2600,24 +2721,24 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz", - "integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", + "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/plugin-debug": "3.4.0", - "@docusaurus/plugin-google-analytics": "3.4.0", - "@docusaurus/plugin-google-gtag": "3.4.0", - "@docusaurus/plugin-google-tag-manager": "3.4.0", - "@docusaurus/plugin-sitemap": "3.4.0", - "@docusaurus/theme-classic": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-search-algolia": "3.4.0", - "@docusaurus/types": "3.4.0" + "@docusaurus/core": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/plugin-debug": "3.5.2", + "@docusaurus/plugin-google-analytics": "3.5.2", + "@docusaurus/plugin-google-gtag": "3.5.2", + "@docusaurus/plugin-google-tag-manager": "3.5.2", + "@docusaurus/plugin-sitemap": "3.5.2", + "@docusaurus/theme-classic": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-search-algolia": "3.5.2", + "@docusaurus/types": "3.5.2" }, "engines": { "node": ">=18.0" @@ -2628,27 +2749,27 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", - "integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", + "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.43", + "infima": "0.2.0-alpha.44", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2668,18 +2789,15 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", - "integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", + "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", "license": "MIT", "dependencies": { - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2693,21 +2811,22 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/theme-mermaid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz", - "integrity": "sha512-3w5QW0HEZ2O6x2w6lU3ZvOe1gNXP2HIoKDMJBil1VmLBc9PmpAG17VmfhI/p3L2etNmOiVs5GgniUqvn8AFEGQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.5.2.tgz", + "integrity": "sha512-7vWCnIe/KoyTN1Dc55FIyqO5hJ3YaV08Mr63Zej0L0mX1iGzt+qKSmeVUAJ9/aOalUhF0typV0RmNUSy5FAmCg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "mermaid": "^10.4.0", "tslib": "^2.6.0" }, @@ -2720,19 +2839,19 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", - "integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", + "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", "license": "MIT", "dependencies": { "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "algoliasearch": "^4.18.0", "algoliasearch-helper": "^3.13.3", "clsx": "^2.0.0", @@ -2751,9 +2870,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz", - "integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", + "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", @@ -2764,9 +2883,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", - "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", + "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", @@ -2785,13 +2904,13 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz", - "integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", + "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@svgr/webpack": "^8.1.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", @@ -2824,9 +2943,9 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz", - "integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", + "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", "license": "MIT", "dependencies": { "tslib": "^2.6.0" @@ -2844,14 +2963,14 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz", - "integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", + "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -3656,9 +3775,10 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prismjs": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", - "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + "version": "1.26.4", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz", + "integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==", + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.10", @@ -3676,9 +3796,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.3.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz", - "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz", + "integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4069,32 +4189,32 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", - "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", "license": "MIT", "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-account": "4.23.3", - "@algolia/client-analytics": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-personalization": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/recommend": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.21.0.tgz", - "integrity": "sha512-hjVOrL15I3Y3K8xG0icwG1/tWE+MocqBrhW6uVBWpU+/kVEMK0BnM2xdssj6mZM61eJ4iRxHR0djEI3ENOpR8w==", + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz", + "integrity": "sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -4103,6 +4223,45 @@ "algoliasearch": ">= 3.1 < 6" } }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -4216,9 +4375,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "funding": [ { "type": "opencollective", @@ -4235,11 +4394,11 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4488,9 +4647,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -4505,11 +4664,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -4621,9 +4781,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001611", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001611.tgz", - "integrity": "sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q==", + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", "funding": [ { "type": "opencollective", @@ -4637,7 +4797,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/ccount": { "version": "2.0.1", @@ -6431,9 +6592,10 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.692", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", - "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==" + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.24.tgz", + "integrity": "sha512-0x0wLCmpdKFCi9ulhvYZebgcPmHTkFVUfU2wzDykadkslKwT4oAmDTHEKLnlrDsMGZe4B+ksn8quZfZjYsBetA==", + "license": "ISC" }, "node_modules/elkjs": { "version": "0.8.2", @@ -6460,9 +6622,9 @@ } }, "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", "license": "MIT", "funding": { "type": "github", @@ -6514,9 +6676,10 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6655,13 +6818,12 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.1.tgz", - "integrity": "sha512-5mvUrF2suuv5f5cGDnDphIy4/gW86z82kl5qG6mM9z04SEQI4FB5Apmaw/TGEf3l55nLtMs5s51dmhUzvAHQCA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", + "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "is-plain-obj": "^4.0.0" + "@types/estree": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/remcohaszing" @@ -7676,9 +7838,9 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.3.tgz", - "integrity": "sha512-ICWvVOF2fq4+7CMmtCPD5CM4QKjPbHpPotE6+8tDooV0ZuyJVUzHsrNX+O5NaRbieTf0F7FfeBOMAwi6Td0+yQ==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -8225,9 +8387,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.43", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", - "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "version": "0.2.0-alpha.44", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", + "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", "license": "MIT", "engines": { "node": ">=12" @@ -9064,9 +9226,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -9971,9 +10133,9 @@ "license": "MIT" }, "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.1.tgz", + "integrity": "sha512-VGV2uxUzhEZmaP7NSFo2vtq7M2nUD+WfmYQD+d8i/1nHbzE+rMy9uzTvUybBbNiVbrhOZibg3gbyoARGqgDWyg==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -10118,9 +10280,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", @@ -10170,9 +10332,9 @@ "license": "MIT" }, "node_modules/micromark-extension-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -10246,9 +10408,9 @@ "license": "MIT" }, "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -10280,9 +10442,9 @@ "license": "MIT" }, "node_modules/micromark-extension-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", - "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -10366,9 +10528,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", - "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -11803,9 +11965,10 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" }, "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", @@ -12228,9 +12391,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -12944,9 +13108,10 @@ } }, "node_modules/prism-react-renderer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", - "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz", + "integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==", + "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" @@ -13397,9 +13562,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", - "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz", + "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", "license": "MIT", "engines": { "node": ">=14" @@ -13980,9 +14145,9 @@ "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "license": "MIT", "dependencies": { "escalade": "^3.1.1", @@ -14093,9 +14258,9 @@ } }, "node_modules/search-insights": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", - "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.2.tgz", + "integrity": "sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g==", "license": "MIT", "peer": true }, @@ -15316,9 +15481,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", @@ -15333,9 +15498,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -15609,9 +15775,9 @@ } }, "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", diff --git a/docs/package.json b/docs/package.json index d31b58671..e41023c66 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,15 +14,15 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@docusaurus/theme-mermaid": "^3.4.0", + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", + "@docusaurus/theme-mermaid": "^3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", - "prism-react-renderer": "^2.1.0", + "prism-react-renderer": "^2.4.0", "raw-loader": "^4.0.2", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "browserslist": { "production": [ @@ -39,7 +39,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.4.0", "@docusaurus/types": "^3.4.0", - "@types/react": "^18.2.79" + "@types/react": "^18.3.7" }, "engines": { "node": ">=18.0" From edababa88eefd8624202cdc5749c954b9027e224 Mon Sep 17 00:00:00 2001 From: gtsiam Date: Tue, 17 Sep 2024 18:41:46 +0300 Subject: [PATCH 16/36] Work through most of the cspell warnings in python (#13794) --- .cspell/frigate-dictionary.txt | 136 ++++++++++++++++++++++++++ cspell.json | 3 +- frigate/api/export.py | 6 +- frigate/comms/webpush.py | 6 +- frigate/config.py | 2 +- frigate/const.py | 8 +- frigate/detectors/plugins/hailo8l.py | 8 +- frigate/detectors/plugins/openvino.py | 14 +-- frigate/detectors/plugins/rknn.py | 10 +- frigate/detectors/plugins/tensorrt.py | 12 +-- frigate/detectors/util.py | 2 +- frigate/embeddings/embeddings.py | 2 +- frigate/motion/frigate_motion.py | 18 ++-- frigate/motion/improved_motion.py | 17 ++-- frigate/ptz/autotrack.py | 8 +- frigate/record/maintainer.py | 6 +- frigate/stats/util.py | 4 +- frigate/test/const.py | 2 +- frigate/util/object.py | 4 +- frigate/util/services.py | 6 +- 20 files changed, 208 insertions(+), 66 deletions(-) diff --git a/.cspell/frigate-dictionary.txt b/.cspell/frigate-dictionary.txt index 3f10ab6ff..6c0e8022f 100644 --- a/.cspell/frigate-dictionary.txt +++ b/.cspell/frigate-dictionary.txt @@ -1,10 +1,20 @@ aarch +absdiff +airockchip +Alloc Amcrest amdgpu +analyzeduration Annke apexcharts +arange +argmax +argmin +argpartition +ascontiguousarray authelia authentik +autodetected automations autotrack autotracked @@ -12,128 +22,229 @@ autotracker autotracking balena Beelink +BGRA +BHWC blackshear blakeblackshear bottombar buildx castable +cdist Celeron cgroups chipset +chromadb Chromecast cmdline codeowner +CODEOWNERS codeproject colormap colorspace +comms +ctypeslib CUDA Cuvid Dahua datasheet debconf +deci deepstack defragment devcontainer +DEVICEMAP +discardcorrupt dpkg +dsize +dtype +ECONNRESET edgetpu +faststart fflags ffprobe +fillna flac foscam fourcc framebuffer +fregate +frégate +fromarray +frombuffer frontdoor fstype fullchain +fullscreen +genai +generativeai +genpts +getpid gpuload HACS +Hailo hass +hconcat healthcheck hideable Hikvision homeassistant homekit homography +hsize +hstack +httpx hwaccel +hwdownload +hwmap +hwupload +iloc imagestream imdecode imencode imread imutils imwrite +interp iostat iotop +itemsize Jellyfin jetson +jetsons +joserfc jsmpeg jsonify Kalman keepalive +keepdims labelmap letsencrypt +levelname LIBAVFORMAT +libedgetpu +libnvinfer libva +libwebp +libx +libyolo +linalg +localzone logpipe Loryta +lstsq lsusb +markupsafe +maxsplit +MEMHOSTALLOC memlimit +meshgrid +metadatas +migraphx +minilm mjpeg +mkfifo mobiledet mobilenet +modelpath mosquitto mountpoint +movflags mpegts mqtt mse +msenc namedtuples +nbytes +nchw +ndarray +ndimage +nethogs +newaxis +nhwc +NOBLOCK +nobuffer +nokey +NONBLOCK noninteractive +noprint Norfair +nptype NTSC numpy +nvenc +nvhost nvml +nvmpi +ollama onnx onnxruntime onvif ONVIF +openai opencv openvino OWASP paho passwordless +popleft +posthog +postprocess poweroff +preexec probesize protobuf psutil +pubkey +putenv +pycache pydantic +pyobj +pysqlite +pytz +pywebpush qnap quantisation Radeon radeonsi radeontop rawvideo +rcond +RDONLY rebranded referer Reolink restream restreamed restreaming +rkmpp rknn +rkrga rockchip rocm +rocminfo rootfs rtmp RTSP +ruamel scroller setproctitle +setpts shms +SIGUSR skylake sleeptime +SNDMORE socs +sqliteq ssdlite +statm +stimeout stylelint subclassing substream +superfast surveillance +svscan Swipeable sysconf tailscale @@ -143,25 +254,50 @@ tflite thresholded timelapse tmpfs +tobytes toggleable traefik +tzlocal Ubiquiti udev udevadm ultrafast unichip +unidecode Unifi +unixepoch unraid unreviewed +userdata usermod vaapi vainfo variations +vconcat +vitb +vstream +vsync wallclock webp +webpush webrtc websockets webui +werkzeug workdir +WRONLY +wsgirefserver +wsgiutils +wsize +xaddr +xmaxs +xmins +XPUB +XSUB +ymaxs +ymins yolo +yolonas +yolox zeep +zerolatency diff --git a/cspell.json b/cspell.json index e325bbb69..132e51532 100644 --- a/cspell.json +++ b/cspell.json @@ -7,7 +7,8 @@ "*.db", "node_modules", "__pycache__", - "dist" + "dist", + "/audio-labelmap.txt" ], "language": "en", "dictionaryDefinitions": [ diff --git a/frigate/api/export.py b/frigate/api/export.py index 0993a3a87..e17416425 100644 --- a/frigate/api/export.py +++ b/frigate/api/export.py @@ -149,9 +149,9 @@ def export_delete(id: str): try: if process.name() != "ffmpeg": continue - flist = process.open_files() - if flist: - for nt in flist: + file_list = process.open_files() + if file_list: + for nt in file_list: if nt.path.startswith(EXPORT_DIR): files_in_use.append(nt.path.split("/")[-1]) except psutil.Error: diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 0cb75f332..602f8d11e 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -106,10 +106,10 @@ class WebPushClient(Communicator): # type: ignore[misc] def publish(self, topic: str, payload: Any, retain: bool = False) -> None: """Wrapper for publishing when client is in valid state.""" # check for updated notification config - _, updated_notif_config = self.config_subscriber.check_for_update() + _, updated_notification_config = self.config_subscriber.check_for_update() - if updated_notif_config: - self.config.notifications = updated_notif_config + if updated_notification_config: + self.config.notifications = updated_notification_config if not self.config.notifications.enabled: return diff --git a/frigate/config.py b/frigate/config.py index 79d6c2343..ad96cebef 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -1194,7 +1194,7 @@ class CameraConfig(FrigateBaseModel): + ffmpeg_output_args ) - # if there arent any outputs enabled for this input + # if there aren't any outputs enabled for this input if len(ffmpeg_output_args) == 0: return None diff --git a/frigate/const.py b/frigate/const.py index a6e945897..c5cc41b3e 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -12,7 +12,7 @@ FRIGATE_LOCALHOST = "http://127.0.0.1:5000" PLUS_ENV_VAR = "PLUS_API_KEY" PLUS_API_HOST = "https://api.frigate.video" -# Attribute & Object Consts +# Attribute & Object constants ATTRIBUTE_LABEL_MAP = { "person": ["face", "amazon"], @@ -31,7 +31,7 @@ LABEL_NMS_MAP = { } LABEL_NMS_DEFAULT = 0.4 -# Audio Consts +# Audio constants AUDIO_DURATION = 0.975 AUDIO_FORMAT = "s16le" @@ -39,7 +39,7 @@ AUDIO_MAX_BIT_RANGE = 32768.0 AUDIO_SAMPLE_RATE = 16000 AUDIO_MIN_CONFIDENCE = 0.5 -# DB Consts +# DB constants MAX_WAL_SIZE = 10 # MB @@ -49,7 +49,7 @@ FFMPEG_HWACCEL_NVIDIA = "preset-nvidia" FFMPEG_HWACCEL_VAAPI = "preset-vaapi" FFMPEG_HWACCEL_VULKAN = "preset-vulkan" -# Regex Consts +# Regex constants REGEX_CAMERA_NAME = r"^[a-zA-Z0-9_-]+$" REGEX_RTSP_CAMERA_USER_PASS = r":\/\/[a-zA-Z0-9_-]+:[\S]+@" diff --git a/frigate/detectors/plugins/hailo8l.py b/frigate/detectors/plugins/hailo8l.py index a157e240d..c2df04ffa 100644 --- a/frigate/detectors/plugins/hailo8l.py +++ b/frigate/detectors/plugins/hailo8l.py @@ -83,11 +83,11 @@ class HailoDetector(DetectionApi): self.network_group_params = self.network_group.create_params() # Create input and output virtual stream parameters - self.input_vstreams_params = InputVStreamParams.make( + self.input_vstream_params = InputVStreamParams.make( self.network_group, format_type=self.hef.get_input_vstream_infos()[0].format.type, ) - self.output_vstreams_params = OutputVStreamParams.make( + self.output_vstream_params = OutputVStreamParams.make( self.network_group, format_type=getattr(FormatType, output_type) ) @@ -162,8 +162,8 @@ class HailoDetector(DetectionApi): try: with InferVStreams( self.network_group, - self.input_vstreams_params, - self.output_vstreams_params, + self.input_vstream_params, + self.output_vstream_params, ) as infer_pipeline: input_dict = {} if isinstance(input_data, dict): diff --git a/frigate/detectors/plugins/openvino.py b/frigate/detectors/plugins/openvino.py index a6b0a51cd..897b84430 100644 --- a/frigate/detectors/plugins/openvino.py +++ b/frigate/detectors/plugins/openvino.py @@ -129,10 +129,10 @@ class OvDetector(DetectionApi): strides = [8, 16, 32] - hsizes = [self.h // stride for stride in strides] - wsizes = [self.w // stride for stride in strides] + hsize_list = [self.h // stride for stride in strides] + wsize_list = [self.w // stride for stride in strides] - for hsize, wsize, stride in zip(hsizes, wsizes, strides): + for hsize, wsize, stride in zip(hsize_list, wsize_list, strides): xv, yv = np.meshgrid(np.arange(wsize), np.arange(hsize)) grid = np.stack((xv, yv), 2).reshape(1, -1, 2) grids.append(grid) @@ -216,10 +216,12 @@ class OvDetector(DetectionApi): conf_mask = (image_pred[:, 4] * class_conf.squeeze() >= 0.3).squeeze() # Detections ordered as (x1, y1, x2, y2, obj_conf, class_conf, class_pred) - dets = np.concatenate((image_pred[:, :5], class_conf, class_pred), axis=1) - dets = dets[conf_mask] + detections = np.concatenate( + (image_pred[:, :5], class_conf, class_pred), axis=1 + ) + detections = detections[conf_mask] - ordered = dets[dets[:, 5].argsort()[::-1]][:20] + ordered = detections[detections[:, 5].argsort()[::-1]][:20] for i, object_detected in enumerate(ordered): detections[i] = self.process_yolo( diff --git a/frigate/detectors/plugins/rknn.py b/frigate/detectors/plugins/rknn.py index 7606313b5..dae5cc057 100644 --- a/frigate/detectors/plugins/rknn.py +++ b/frigate/detectors/plugins/rknn.py @@ -17,7 +17,7 @@ supported_socs = ["rk3562", "rk3566", "rk3568", "rk3576", "rk3588"] supported_models = {ModelTypeEnum.yolonas: "^deci-fp16-yolonas_[sml]$"} -model_chache_dir = "/config/model_cache/rknn_cache/" +model_cache_dir = "/config/model_cache/rknn_cache/" class RknnDetectorConfig(BaseDetectorConfig): @@ -110,7 +110,7 @@ class Rknn(DetectionApi): if model_matched: model_props["filename"] = model_path + f"-{soc}-v2.0.0-1.rknn" - model_props["path"] = model_chache_dir + model_props["filename"] + model_props["path"] = model_cache_dir + model_props["filename"] if not os.path.isfile(model_props["path"]): self.download_model(model_props["filename"]) @@ -125,12 +125,12 @@ class Rknn(DetectionApi): return model_props def download_model(self, filename): - if not os.path.isdir(model_chache_dir): - os.mkdir(model_chache_dir) + if not os.path.isdir(model_cache_dir): + os.mkdir(model_cache_dir) urllib.request.urlretrieve( f"https://github.com/MarcA711/rknn-models/releases/download/v2.0.0/{filename}", - model_chache_dir + filename, + model_cache_dir + filename, ) def check_config(self, config): diff --git a/frigate/detectors/plugins/tensorrt.py b/frigate/detectors/plugins/tensorrt.py index 64b0849c7..901d18ee7 100644 --- a/frigate/detectors/plugins/tensorrt.py +++ b/frigate/detectors/plugins/tensorrt.py @@ -285,14 +285,14 @@ class TensorRtDetector(DetectionApi): boxes, scores, classes """ # filter low-conf detections and concatenate results of all yolo layers - detections = [] + detection_list = [] for o in trt_outputs: - dets = o.reshape((-1, 7)) - dets = dets[dets[:, 4] * dets[:, 6] >= conf_th] - detections.append(dets) - detections = np.concatenate(detections, axis=0) + detections = o.reshape((-1, 7)) + detections = detections[detections[:, 4] * detections[:, 6] >= conf_th] + detection_list.append(detections) + detection_list = np.concatenate(detection_list, axis=0) - return detections + return detection_list def detect_raw(self, tensor_input): # Input tensor has the shape of the [height, width, 3] diff --git a/frigate/detectors/util.py b/frigate/detectors/util.py index 74fb82a47..08e54e7cb 100644 --- a/frigate/detectors/util.py +++ b/frigate/detectors/util.py @@ -26,7 +26,7 @@ def preprocess(tensor_input, model_input_shape, model_input_element_type): logger.warn( f"preprocess: tensor_input.shape {tensor_input.shape} and model_input_shape {model_input_shape} do not match!" ) - # cv2.dnn.blobFromImage is faster than numpying it + # cv2.dnn.blobFromImage is faster than running it through numpy return cv2.dnn.blobFromImage( tensor_input[0], 1.0 / 255, diff --git a/frigate/embeddings/embeddings.py b/frigate/embeddings/embeddings.py index c0dc51ea9..998738d50 100644 --- a/frigate/embeddings/embeddings.py +++ b/frigate/embeddings/embeddings.py @@ -15,7 +15,7 @@ from frigate.models import Event # Squelch posthog logging logging.getLogger("chromadb.telemetry.product.posthog").setLevel(logging.CRITICAL) -# Hotsawp the sqlite3 module for Chroma compatibility +# Hot-swap the sqlite3 module for Chroma compatibility try: from chromadb import Collection from chromadb import HttpClient as ChromaClient diff --git a/frigate/motion/frigate_motion.py b/frigate/motion/frigate_motion.py index 426166193..72097667b 100644 --- a/frigate/motion/frigate_motion.py +++ b/frigate/motion/frigate_motion.py @@ -55,13 +55,13 @@ class FrigateMotionDetector(MotionDetector): # Improve contrast if self.improve_contrast.value: - minval = np.percentile(resized_frame, 4) - maxval = np.percentile(resized_frame, 96) + min_value = np.percentile(resized_frame, 4) + max_value = np.percentile(resized_frame, 96) # don't adjust if the image is a single color - if minval < maxval: - resized_frame = np.clip(resized_frame, minval, maxval) + if min_value < max_value: + resized_frame = np.clip(resized_frame, min_value, max_value) resized_frame = ( - ((resized_frame - minval) / (maxval - minval)) * 255 + ((resized_frame - min_value) / (max_value - min_value)) * 255 ).astype(np.uint8) # mask frame @@ -100,13 +100,13 @@ class FrigateMotionDetector(MotionDetector): # dilate the thresholded image to fill in holes, then find contours # on thresholded image thresh_dilated = cv2.dilate(thresh, None, iterations=2) - cnts = cv2.findContours( + contours = cv2.findContours( thresh_dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) - cnts = imutils.grab_contours(cnts) + contours = imutils.grab_contours(contours) # loop over the contours - for c in cnts: + for c in contours: # if the contour is big enough, count it as motion contour_area = cv2.contourArea(c) if contour_area > self.contour_area.value: @@ -124,7 +124,7 @@ class FrigateMotionDetector(MotionDetector): thresh_dilated = cv2.cvtColor(thresh_dilated, cv2.COLOR_GRAY2BGR) # print("--------") # print(self.frame_counter) - for c in cnts: + for c in contours: contour_area = cv2.contourArea(c) if contour_area > self.contour_area.value: x, y, w, h = cv2.boundingRect(c) diff --git a/frigate/motion/improved_motion.py b/frigate/motion/improved_motion.py index 6fb17ec29..297337560 100644 --- a/frigate/motion/improved_motion.py +++ b/frigate/motion/improved_motion.py @@ -79,12 +79,15 @@ class ImprovedMotionDetector(MotionDetector): # Improve contrast if self.config.improve_contrast: # TODO tracking moving average of min/max to avoid sudden contrast changes - minval = np.percentile(resized_frame, 4).astype(np.uint8) - maxval = np.percentile(resized_frame, 96).astype(np.uint8) + min_value = np.percentile(resized_frame, 4).astype(np.uint8) + max_value = np.percentile(resized_frame, 96).astype(np.uint8) # skip contrast calcs if the image is a single color - if minval < maxval: + if min_value < max_value: # keep track of the last 50 contrast values - self.contrast_values[self.contrast_values_index] = [minval, maxval] + self.contrast_values[self.contrast_values_index] = [ + min_value, + max_value, + ] self.contrast_values_index += 1 if self.contrast_values_index == len(self.contrast_values): self.contrast_values_index = 0 @@ -122,14 +125,14 @@ class ImprovedMotionDetector(MotionDetector): # dilate the thresholded image to fill in holes, then find contours # on thresholded image thresh_dilated = cv2.dilate(thresh, None, iterations=1) - cnts = cv2.findContours( + contours = cv2.findContours( thresh_dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) - cnts = imutils.grab_contours(cnts) + contours = imutils.grab_contours(contours) # loop over the contours total_contour_area = 0 - for c in cnts: + for c in contours: # if the contour is big enough, count it as motion contour_area = cv2.contourArea(c) total_contour_area += contour_area diff --git a/frigate/ptz/autotrack.py b/frigate/ptz/autotrack.py index 92c4141a7..80195f5d8 100644 --- a/frigate/ptz/autotrack.py +++ b/frigate/ptz/autotrack.py @@ -268,9 +268,9 @@ class PtzAutoTracker: self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False return - movestatus_supported = self.onvif.get_service_capabilities(camera) + move_status_supported = self.onvif.get_service_capabilities(camera) - if movestatus_supported is None or movestatus_supported.lower() != "true": + if move_status_supported is None or move_status_supported.lower() != "true": logger.warning( f"Disabling autotracking for {camera}: ONVIF MoveStatus not supported" ) @@ -807,8 +807,8 @@ class PtzAutoTracker: invalid_delta = np.any(delta > delta_thresh) # Check variance - stdevs = np.std(velocities, axis=0) - high_variances = np.any(stdevs > var_thresh) + stdev_list = np.std(velocities, axis=0) + high_variances = np.any(stdev_list > var_thresh) # Check direction difference velocities = np.round(velocities) diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 5bcec62e6..66036f807 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -90,9 +90,9 @@ class RecordingMaintainer(threading.Thread): try: if process.name() != "ffmpeg": continue - flist = process.open_files() - if flist: - for nt in flist: + file_list = process.open_files() + if file_list: + for nt in file_list: if nt.path.startswith(CACHE_DIR): files_in_use.append(nt.path.split("/")[-1]) except psutil.Error: diff --git a/frigate/stats/util.py b/frigate/stats/util.py index ac28e3d89..e9a78742f 100644 --- a/frigate/stats/util.py +++ b/frigate/stats/util.py @@ -250,7 +250,7 @@ def stats_snapshot( ffmpeg_pid = ( camera_stats["ffmpeg_pid"].value if camera_stats["ffmpeg_pid"] else None ) - cpid = ( + capture_pid = ( camera_stats["capture_process"].pid if camera_stats["capture_process"] else None @@ -262,7 +262,7 @@ def stats_snapshot( "detection_fps": round(camera_stats["detection_fps"].value, 2), "detection_enabled": config.cameras[name].detect.enabled, "pid": pid, - "capture_pid": cpid, + "capture_pid": capture_pid, "ffmpeg_pid": ffmpeg_pid, "audio_rms": round(camera_stats["audio_rms"].value, 4), "audio_dBFS": round(camera_stats["audio_dBFS"].value, 4), diff --git a/frigate/test/const.py b/frigate/test/const.py index bef02e235..3cbd37799 100644 --- a/frigate/test/const.py +++ b/frigate/test/const.py @@ -1,4 +1,4 @@ -"""Consts for testing.""" +"""Constants for testing.""" TEST_DB = "test.db" TEST_DB_CLEANUPS = ["test.db", "test.db-shm", "test.db-wal"] diff --git a/frigate/util/object.py b/frigate/util/object.py index 66810d5e7..da0a54279 100644 --- a/frigate/util/object.py +++ b/frigate/util/object.py @@ -511,12 +511,12 @@ def reduce_detections( # due to min score requirement of NMSBoxes confidences = [0.6 if clipped(o, frame_shape) else o[1] for o in group] - idxs = cv2.dnn.NMSBoxes( + indices = cv2.dnn.NMSBoxes( boxes, confidences, 0.5, LABEL_NMS_MAP.get(label, LABEL_NMS_DEFAULT) ) # add objects - for index in idxs: + for index in indices: index = index if isinstance(index, np.int32) else index[0] obj = group[index] selected_objects.append(obj) diff --git a/frigate/util/services.py b/frigate/util/services.py index 03787d248..d83db3095 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -122,7 +122,7 @@ def get_cpu_stats() -> dict[str, dict]: stats = f.readline().split() utime = int(stats[13]) stime = int(stats[14]) - starttime = int(stats[21]) + start_time = int(stats[21]) with open("/proc/uptime") as f: system_uptime_sec = int(float(f.read().split()[0])) @@ -131,9 +131,9 @@ def get_cpu_stats() -> dict[str, dict]: process_utime_sec = utime // clk_tck process_stime_sec = stime // clk_tck - process_starttime_sec = starttime // clk_tck + process_start_time_sec = start_time // clk_tck - process_elapsed_sec = system_uptime_sec - process_starttime_sec + process_elapsed_sec = system_uptime_sec - process_start_time_sec process_usage_sec = process_utime_sec + process_stime_sec cpu_average_usage = process_usage_sec * 100 // process_elapsed_sec From bcae0cf441c81a6715425cfac98304b6ad00379f Mon Sep 17 00:00:00 2001 From: gtsiam Date: Tue, 17 Sep 2024 18:42:10 +0300 Subject: [PATCH 17/36] Fix vscode launch configuration (#13795) --- .vscode/launch.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fe709c92d..5c858267d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,10 +3,9 @@ "configurations": [ { "name": "Python: Launch Frigate", - "type": "python", + "type": "debugpy", "request": "launch", - "module": "frigate", - "justMyCode": true + "module": "frigate" } ] } From 90d7fc6bc53b4d6aafef2674ccd0d2c63ded36f2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 17 Sep 2024 11:04:51 -0600 Subject: [PATCH 18/36] Install no deps separately for wheel (#13799) * Install no deps separately for wheel * Fix order * fix arg --- docker/main/Dockerfile | 3 +++ docker/main/requirements-wheels-nodeps.txt | 1 + docker/main/requirements-wheels.txt | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docker/main/requirements-wheels-nodeps.txt diff --git a/docker/main/Dockerfile b/docker/main/Dockerfile index 92e28a381..4d238f29b 100644 --- a/docker/main/Dockerfile +++ b/docker/main/Dockerfile @@ -170,6 +170,9 @@ RUN /build_pysqlite3.sh COPY docker/main/requirements-wheels.txt /requirements-wheels.txt RUN pip3 wheel --wheel-dir=/wheels -r /requirements-wheels.txt +COPY docker/main/requirements-wheels-nodeps.txt /requirements-wheels-nodeps.txt +RUN pip3 wheel --no-deps --wheel-dir=/wheels -r /requirements-wheels-nodeps.txt + # Collect deps in a single layer FROM scratch AS deps-rootfs diff --git a/docker/main/requirements-wheels-nodeps.txt b/docker/main/requirements-wheels-nodeps.txt new file mode 100644 index 000000000..84eac63c2 --- /dev/null +++ b/docker/main/requirements-wheels-nodeps.txt @@ -0,0 +1 @@ +onnx_clip == 4.0.* diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index a81f6ec93..83265f0b7 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -33,7 +33,6 @@ openvino == 2024.1.* onnxruntime-openvino == 1.18.* ; platform_machine == 'x86_64' onnxruntime == 1.18.* ; platform_machine == 'aarch64' # Embeddings -onnx_clip == 4.0.* chromadb == 0.5.0 # Generative AI google-generativeai == 0.6.* From 2362d0e838cbc23cebed398fa444ff56c5545d1f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 17 Sep 2024 13:24:35 -0600 Subject: [PATCH 19/36] Override onnx clip loading (#13800) * Set caching options for hardware providers * Always use CPU for searching * Use new install strategy to remove onnxruntime and then install post wheels --- docker/main/Dockerfile | 12 +++- docker/main/requirements-wheels-nodeps.txt | 1 - docker/main/requirements-wheels-post.txt | 3 + docker/main/requirements-wheels.txt | 3 +- frigate/embeddings/embeddings.py | 5 +- frigate/embeddings/functions/clip.py | 65 +++++++++++++++++++++- 6 files changed, 82 insertions(+), 7 deletions(-) delete mode 100644 docker/main/requirements-wheels-nodeps.txt create mode 100644 docker/main/requirements-wheels-post.txt diff --git a/docker/main/Dockerfile b/docker/main/Dockerfile index 4d238f29b..3cd73cf95 100644 --- a/docker/main/Dockerfile +++ b/docker/main/Dockerfile @@ -170,8 +170,8 @@ RUN /build_pysqlite3.sh COPY docker/main/requirements-wheels.txt /requirements-wheels.txt RUN pip3 wheel --wheel-dir=/wheels -r /requirements-wheels.txt -COPY docker/main/requirements-wheels-nodeps.txt /requirements-wheels-nodeps.txt -RUN pip3 wheel --no-deps --wheel-dir=/wheels -r /requirements-wheels-nodeps.txt +COPY docker/main/requirements-wheels-post.txt /requirements-wheels-post.txt +RUN pip3 wheel --no-deps --wheel-dir=/wheels-post -r /requirements-wheels-post.txt # Collect deps in a single layer @@ -215,6 +215,14 @@ RUN --mount=type=bind,from=wheels,source=/wheels,target=/deps/wheels \ python3 -m pip install --upgrade pip && \ pip3 install -U /deps/wheels/*.whl +# We have to uninstall this dependency specifically +# as it will break onnxruntime-openvino +RUN pip3 uninstall -y onnxruntime + +RUN --mount=type=bind,from=wheels,source=/wheels-post,target=/deps/wheels \ + python3 -m pip install --upgrade pip && \ + pip3 install -U /deps/wheels/*.whl + COPY --from=deps-rootfs / / RUN ldconfig diff --git a/docker/main/requirements-wheels-nodeps.txt b/docker/main/requirements-wheels-nodeps.txt deleted file mode 100644 index 84eac63c2..000000000 --- a/docker/main/requirements-wheels-nodeps.txt +++ /dev/null @@ -1 +0,0 @@ -onnx_clip == 4.0.* diff --git a/docker/main/requirements-wheels-post.txt b/docker/main/requirements-wheels-post.txt new file mode 100644 index 000000000..c4ed33844 --- /dev/null +++ b/docker/main/requirements-wheels-post.txt @@ -0,0 +1,3 @@ +# ONNX +onnxruntime-openvino == 1.18.* ; platform_machine == 'x86_64' +onnxruntime == 1.18.* ; platform_machine == 'aarch64' \ No newline at end of file diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 83265f0b7..639d4b3c8 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -30,10 +30,9 @@ ws4py == 0.5.* unidecode == 1.3.* # OpenVino & ONNX openvino == 2024.1.* -onnxruntime-openvino == 1.18.* ; platform_machine == 'x86_64' -onnxruntime == 1.18.* ; platform_machine == 'aarch64' # Embeddings chromadb == 0.5.0 +onnx_clip == 4.0.* # Generative AI google-generativeai == 0.6.* ollama == 0.2.* diff --git a/frigate/embeddings/embeddings.py b/frigate/embeddings/embeddings.py index 998738d50..8179de15e 100644 --- a/frigate/embeddings/embeddings.py +++ b/frigate/embeddings/embeddings.py @@ -85,7 +85,10 @@ class Embeddings: @property def description(self) -> Collection: return self.client.get_or_create_collection( - name="event_description", embedding_function=MiniLMEmbedding() + name="event_description", + embedding_function=MiniLMEmbedding( + preferred_providers=["CPUExecutionProvider"] + ), ) def reindex(self) -> None: diff --git a/frigate/embeddings/functions/clip.py b/frigate/embeddings/functions/clip.py index 867938aff..6e44033df 100644 --- a/frigate/embeddings/functions/clip.py +++ b/frigate/embeddings/functions/clip.py @@ -1,9 +1,13 @@ """CLIP Embeddings for Frigate.""" +import errno +import logging import os +from pathlib import Path from typing import Tuple, Union import onnxruntime as ort +import requests from chromadb import EmbeddingFunction, Embeddings from chromadb.api.types import ( Documents, @@ -39,10 +43,69 @@ class Clip(OnnxClip): models = [] for model_file in [IMAGE_MODEL_FILE, TEXT_MODEL_FILE]: path = os.path.join(MODEL_CACHE_DIR, "clip", model_file) - models.append(OnnxClip._load_model(path, silent)) + models.append(Clip._load_model(path, silent)) return models[0], models[1] + @staticmethod + def _load_model(path: str, silent: bool): + providers = ort.get_available_providers() + options = [] + + for provider in providers: + if provider == "TensorrtExecutionProvider": + options.append( + { + "trt_timing_cache_enable": True, + "trt_timing_cache_path": "/config/model_cache/tensorrt/ort", + "trt_engine_cache_enable": True, + "trt_engine_cache_path": "/config/model_cache/tensorrt/ort/trt-engines", + } + ) + elif provider == "OpenVINOExecutionProvider": + options.append({"cache_dir": "/config/model_cache/openvino/ort"}) + else: + options.append({}) + + try: + if os.path.exists(path): + return ort.InferenceSession( + path, providers=providers, provider_options=options + ) + else: + raise FileNotFoundError( + errno.ENOENT, + os.strerror(errno.ENOENT), + path, + ) + except Exception: + s3_url = f"https://lakera-clip.s3.eu-west-1.amazonaws.com/{os.path.basename(path)}" + if not silent: + logging.info( + f"The model file ({path}) doesn't exist " + f"or it is invalid. Downloading it from the public S3 " + f"bucket: {s3_url}." # noqa: E501 + ) + + # Download from S3 + # Saving to a temporary file first to avoid corrupting the file + temporary_filename = Path(path).with_name(os.path.basename(path) + ".part") + + # Create any missing directories in the path + temporary_filename.parent.mkdir(parents=True, exist_ok=True) + + with requests.get(s3_url, stream=True) as r: + r.raise_for_status() + with open(temporary_filename, "wb") as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + f.flush() + # Finally move the temporary file to the correct location + temporary_filename.rename(path) + return ort.InferenceSession( + path, providers=provider, provider_options=options + ) + class ClipEmbedding(EmbeddingFunction): """Embedding function for CLIP model used in Chroma.""" From 38ff46e45cdaa686bb1ef94e4563856b9b898ebe Mon Sep 17 00:00:00 2001 From: gtsiam Date: Tue, 17 Sep 2024 23:52:55 +0300 Subject: [PATCH 20/36] Rewrite yaml loader (#13803) * Ignore entire __pycache__ folder instead of individual *.pyc files * Rewrite the yaml loader to match PyYAML The old implementation would fail in weird ways with configs that were incorrect in just the right way. The new implementation just does what PyYAML would do, only diverging in case of duplicate keys. * Clarify duplicate yaml key ValueError message Co-authored-by: Nicolas Mowen --------- Co-authored-by: Nicolas Mowen --- .gitignore | 2 +- frigate/config.py | 7 +++--- frigate/test/test_config.py | 5 +++-- frigate/util/builtin.py | 43 ++++++++++++++++++------------------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 195708e2d..11a147a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -*.pyc +__pycache__ *.swp debug .vscode/* diff --git a/frigate/config.py b/frigate/config.py index ad96cebef..af3ed0753 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union import numpy as np +import yaml from pydantic import ( BaseModel, ConfigDict, @@ -41,11 +42,11 @@ from frigate.ffmpeg_presets import ( ) from frigate.plus import PlusApi from frigate.util.builtin import ( + NoDuplicateKeysLoader, deep_merge, escape_special_characters, generate_color_palette, get_ffmpeg_arg_list, - load_config_with_no_duplicates, ) from frigate.util.config import StreamInfoRetriever, get_relative_coordinates from frigate.util.image import create_mask @@ -1764,7 +1765,7 @@ class FrigateConfig(FrigateBaseModel): raw_config = f.read() if config_file.endswith(YAML_EXT): - config = load_config_with_no_duplicates(raw_config) + config = yaml.load(raw_config, NoDuplicateKeysLoader) elif config_file.endswith(".json"): config = json.loads(raw_config) @@ -1772,5 +1773,5 @@ class FrigateConfig(FrigateBaseModel): @classmethod def parse_raw(cls, raw_config): - config = load_config_with_no_duplicates(raw_config) + config = yaml.load(raw_config, NoDuplicateKeysLoader) return cls.model_validate(config) diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index c703de893..1f0ff086d 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -4,13 +4,14 @@ import unittest from unittest.mock import patch import numpy as np +import yaml from pydantic import ValidationError from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.const import MODEL_CACHE_DIR from frigate.detectors import DetectorTypeEnum from frigate.plus import PlusApi -from frigate.util.builtin import deep_merge, load_config_with_no_duplicates +from frigate.util.builtin import NoDuplicateKeysLoader, deep_merge class TestConfig(unittest.TestCase): @@ -1537,7 +1538,7 @@ class TestConfig(unittest.TestCase): """ self.assertRaises( - ValueError, lambda: load_config_with_no_duplicates(raw_config) + ValueError, lambda: yaml.load(raw_config, NoDuplicateKeysLoader) ) def test_object_filter_ratios_work(self): diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 2c3051fc0..413c8d8ce 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -89,32 +89,31 @@ def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dic return merged -def load_config_with_no_duplicates(raw_config) -> dict: - """Get config ensuring duplicate keys are not allowed.""" +class NoDuplicateKeysLoader(yaml.loader.SafeLoader): + """A yaml SafeLoader that disallows duplicate keys""" - # https://stackoverflow.com/a/71751051 - # important to use SafeLoader here to avoid RCE - class PreserveDuplicatesLoader(yaml.loader.SafeLoader): - pass + def construct_mapping(self, node, deep=False): + mapping = super().construct_mapping(node, deep=deep) - def map_constructor(loader, node, deep=False): - keys = [loader.construct_object(node, deep=deep) for node, _ in node.value] - vals = [loader.construct_object(node, deep=deep) for _, node in node.value] - key_count = Counter(keys) - data = {} - for key, val in zip(keys, vals): - if key_count[key] > 1: - raise ValueError( - f"Config input {key} is defined multiple times for the same field, this is not allowed." + if len(node.value) != len(mapping): + # There's a duplicate key somewhere. Find it. + duplicate_keys = [ + key + for key, count in Counter( + self.construct_object(key, deep=deep) for key, _ in node.value ) - else: - data[key] = val - return data + if count > 1 + ] - PreserveDuplicatesLoader.add_constructor( - yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor - ) - return yaml.load(raw_config, PreserveDuplicatesLoader) + # This might be possible if PyYAML's construct_mapping() changes the node + # afterwards for some reason? I don't see why, but better safe than sorry. + assert len(duplicate_keys) > 0 + + raise ValueError( + f"Config field duplicates are not allowed, the following fields are duplicated in the config: {', '.join(duplicate_keys)}" + ) + + return mapping def clean_camera_user_pass(line: str) -> str: From 1ed864201021d0a7907b3e93a654ff666c3d0a0b Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 17 Sep 2024 14:54:44 -0600 Subject: [PATCH 21/36] Refactor onnx providers (#13804) * Ensure dirs exist for model caches * Formatting * Don't use tensorrt for embeddings --- frigate/detectors/plugins/onnx.py | 12 +++++++++++- frigate/embeddings/functions/clip.py | 18 ++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/frigate/detectors/plugins/onnx.py b/frigate/detectors/plugins/onnx.py index ccd0ffc68..117cdae02 100644 --- a/frigate/detectors/plugins/onnx.py +++ b/frigate/detectors/plugins/onnx.py @@ -1,4 +1,5 @@ import logging +import os import cv2 import numpy as np @@ -42,6 +43,9 @@ class ONNXDetector(DetectionApi): for provider in providers: if provider == "TensorrtExecutionProvider": + os.makedirs( + "/config/model_cache/tensorrt/ort/trt-engines", exist_ok=True + ) options.append( { "trt_timing_cache_enable": True, @@ -51,7 +55,13 @@ class ONNXDetector(DetectionApi): } ) elif provider == "OpenVINOExecutionProvider": - options.append({"cache_dir": "/config/model_cache/openvino/ort"}) + os.makedirs("/config/model_cache/openvino/ort", exist_ok=True) + options.append( + { + "cache_dir": "/config/model_cache/openvino/ort", + "device_type": "GPU", + } + ) else: options.append({}) diff --git a/frigate/embeddings/functions/clip.py b/frigate/embeddings/functions/clip.py index 6e44033df..8a5a05775 100644 --- a/frigate/embeddings/functions/clip.py +++ b/frigate/embeddings/functions/clip.py @@ -49,22 +49,24 @@ class Clip(OnnxClip): @staticmethod def _load_model(path: str, silent: bool): - providers = ort.get_available_providers() + providers = [] options = [] - for provider in providers: + for provider in ort.get_available_providers(): if provider == "TensorrtExecutionProvider": + continue + elif provider == "OpenVINOExecutionProvider": + # TODO need to verify openvino works correctly + os.makedirs("/config/model_cache/openvino/ort", exist_ok=True) + providers.append(provider) options.append( { - "trt_timing_cache_enable": True, - "trt_timing_cache_path": "/config/model_cache/tensorrt/ort", - "trt_engine_cache_enable": True, - "trt_engine_cache_path": "/config/model_cache/tensorrt/ort/trt-engines", + "cache_dir": "/config/model_cache/openvino/ort", + "device_type": "GPU", } ) - elif provider == "OpenVINOExecutionProvider": - options.append({"cache_dir": "/config/model_cache/openvino/ort"}) else: + providers.append(provider) options.append({}) try: From ff9e1da1deffb0b5718e49893e9cb9392080f509 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:03:22 -0500 Subject: [PATCH 22/36] Revert "Rewrite yaml loader (#13803)" (#13805) This reverts commit 38ff46e45cdaa686bb1ef94e4563856b9b898ebe. --- .gitignore | 2 +- frigate/config.py | 7 +++--- frigate/test/test_config.py | 5 ++--- frigate/util/builtin.py | 43 +++++++++++++++++++------------------ 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 11a147a6e..195708e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -__pycache__ +*.pyc *.swp debug .vscode/* diff --git a/frigate/config.py b/frigate/config.py index af3ed0753..ad96cebef 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -9,7 +9,6 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union import numpy as np -import yaml from pydantic import ( BaseModel, ConfigDict, @@ -42,11 +41,11 @@ from frigate.ffmpeg_presets import ( ) from frigate.plus import PlusApi from frigate.util.builtin import ( - NoDuplicateKeysLoader, deep_merge, escape_special_characters, generate_color_palette, get_ffmpeg_arg_list, + load_config_with_no_duplicates, ) from frigate.util.config import StreamInfoRetriever, get_relative_coordinates from frigate.util.image import create_mask @@ -1765,7 +1764,7 @@ class FrigateConfig(FrigateBaseModel): raw_config = f.read() if config_file.endswith(YAML_EXT): - config = yaml.load(raw_config, NoDuplicateKeysLoader) + config = load_config_with_no_duplicates(raw_config) elif config_file.endswith(".json"): config = json.loads(raw_config) @@ -1773,5 +1772,5 @@ class FrigateConfig(FrigateBaseModel): @classmethod def parse_raw(cls, raw_config): - config = yaml.load(raw_config, NoDuplicateKeysLoader) + config = load_config_with_no_duplicates(raw_config) return cls.model_validate(config) diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 1f0ff086d..c703de893 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -4,14 +4,13 @@ import unittest from unittest.mock import patch import numpy as np -import yaml from pydantic import ValidationError from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.const import MODEL_CACHE_DIR from frigate.detectors import DetectorTypeEnum from frigate.plus import PlusApi -from frigate.util.builtin import NoDuplicateKeysLoader, deep_merge +from frigate.util.builtin import deep_merge, load_config_with_no_duplicates class TestConfig(unittest.TestCase): @@ -1538,7 +1537,7 @@ class TestConfig(unittest.TestCase): """ self.assertRaises( - ValueError, lambda: yaml.load(raw_config, NoDuplicateKeysLoader) + ValueError, lambda: load_config_with_no_duplicates(raw_config) ) def test_object_filter_ratios_work(self): diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 413c8d8ce..2c3051fc0 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -89,31 +89,32 @@ def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dic return merged -class NoDuplicateKeysLoader(yaml.loader.SafeLoader): - """A yaml SafeLoader that disallows duplicate keys""" +def load_config_with_no_duplicates(raw_config) -> dict: + """Get config ensuring duplicate keys are not allowed.""" - def construct_mapping(self, node, deep=False): - mapping = super().construct_mapping(node, deep=deep) + # https://stackoverflow.com/a/71751051 + # important to use SafeLoader here to avoid RCE + class PreserveDuplicatesLoader(yaml.loader.SafeLoader): + pass - if len(node.value) != len(mapping): - # There's a duplicate key somewhere. Find it. - duplicate_keys = [ - key - for key, count in Counter( - self.construct_object(key, deep=deep) for key, _ in node.value + def map_constructor(loader, node, deep=False): + keys = [loader.construct_object(node, deep=deep) for node, _ in node.value] + vals = [loader.construct_object(node, deep=deep) for _, node in node.value] + key_count = Counter(keys) + data = {} + for key, val in zip(keys, vals): + if key_count[key] > 1: + raise ValueError( + f"Config input {key} is defined multiple times for the same field, this is not allowed." ) - if count > 1 - ] + else: + data[key] = val + return data - # This might be possible if PyYAML's construct_mapping() changes the node - # afterwards for some reason? I don't see why, but better safe than sorry. - assert len(duplicate_keys) > 0 - - raise ValueError( - f"Config field duplicates are not allowed, the following fields are duplicated in the config: {', '.join(duplicate_keys)}" - ) - - return mapping + PreserveDuplicatesLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor + ) + return yaml.load(raw_config, PreserveDuplicatesLoader) def clean_camera_user_pass(line: str) -> str: From e44a9e8921874756228fb6522e4356d2889f2fb2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 17 Sep 2024 16:20:18 -0600 Subject: [PATCH 23/36] Use cpu provider for embeddings models (#13806) --- frigate/embeddings/functions/clip.py | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/frigate/embeddings/functions/clip.py b/frigate/embeddings/functions/clip.py index 8a5a05775..a4e75a510 100644 --- a/frigate/embeddings/functions/clip.py +++ b/frigate/embeddings/functions/clip.py @@ -49,31 +49,11 @@ class Clip(OnnxClip): @staticmethod def _load_model(path: str, silent: bool): - providers = [] - options = [] - - for provider in ort.get_available_providers(): - if provider == "TensorrtExecutionProvider": - continue - elif provider == "OpenVINOExecutionProvider": - # TODO need to verify openvino works correctly - os.makedirs("/config/model_cache/openvino/ort", exist_ok=True) - providers.append(provider) - options.append( - { - "cache_dir": "/config/model_cache/openvino/ort", - "device_type": "GPU", - } - ) - else: - providers.append(provider) - options.append({}) + providers = ["CPUExecutionProvider"] try: if os.path.exists(path): - return ort.InferenceSession( - path, providers=providers, provider_options=options - ) + return ort.InferenceSession(path, providers=providers) else: raise FileNotFoundError( errno.ENOENT, @@ -104,9 +84,7 @@ class Clip(OnnxClip): f.flush() # Finally move the temporary file to the correct location temporary_filename.rename(path) - return ort.InferenceSession( - path, providers=provider, provider_options=options - ) + return ort.InferenceSession(path, providers=providers) class ClipEmbedding(EmbeddingFunction): From 5e0d8fe4c743e8dadeb90b7f7b8e2d9575c7ac5d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 18 Sep 2024 12:05:27 -0600 Subject: [PATCH 24/36] Make note of multi-model on GPU support (#13813) * Make note of multi-gpu support * fix typo --- docs/docs/configuration/object_detectors.md | 32 ++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 0e58fd7d2..556068cea 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -140,6 +140,22 @@ The OpenVINO device to be used is specified using the `"device"` attribute accor OpenVINO is supported on 6th Gen Intel platforms (Skylake) and newer. It will also run on AMD CPUs despite having no official support for it. A supported Intel platform is required to use the `GPU` device with OpenVINO. For detailed system requirements, see [OpenVINO System Requirements](https://docs.openvino.ai/2024/about-openvino/release-notes-openvino/system-requirements.html) +:::tip + +When using many cameras one detector may not be enough to keep up. Multiple detectors can be defined assuming GPU resources are available. An example configuration would be: + +```yaml +detectors: + ov_0: + type: openvino + device: GPU + ov_1: + type: openvino + device: GPU +``` + +::: + ### Supported Models #### SSDLite MobileNet v2 @@ -298,7 +314,21 @@ model: ## ONNX -ONNX is an open format for building machine learning models, these models can run on a wide variety of hardware. Frigate supports running ONNX models on CPU, OpenVINO, and TensorRT. +ONNX is an open format for building machine learning models, Frigate supports running ONNX models on CPU, OpenVINO, and TensorRT. On startup Frigate will automatically try to use a GPU if one is available. + +:::tip + +When using many cameras one detector may not be enough to keep up. Multiple detectors can be defined assuming GPU resources are available. An example configuration would be: + +```yaml +detectors: + onnx_0: + type: onnx + onnx_1: + type: onnx +``` + +::: ### Supported Models From efd11943074bd35ad285e6ecbcf6f9babc64e105 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:18:16 -0500 Subject: [PATCH 25/36] Improved search input (#13815) * create input with tags component * tweaks * only show filters pane when there are actual filters * special case for similarity searches * similarity search tweaks * populate suggestions values * scrollbar on outer div * clean up * separate custom hook * use command component * tooltips * regex tweaks * saved searches with confirmation dialogs * better date handling * fix filters * filter capitalization * filter instructions * replace underscore in filter type * alert dialog button color * toaster on success --- web/package-lock.json | 379 ++++++++++ web/package.json | 1 + .../components/filter/SearchFilterGroup.tsx | 7 +- .../components/input/DeleteSearchDialog.tsx | 46 ++ web/src/components/input/InputWithTags.tsx | 704 ++++++++++++++++++ web/src/components/input/SaveSearchDialog.tsx | 74 ++ web/src/components/ui/command.tsx | 153 ++++ web/src/hooks/use-suggestions.ts | 63 ++ web/src/pages/Explore.tsx | 17 +- web/src/types/search.ts | 7 + web/src/utils/dateUtil.ts | 72 ++ web/src/views/search/SearchView.tsx | 97 ++- 12 files changed, 1580 insertions(+), 40 deletions(-) create mode 100644 web/src/components/input/DeleteSearchDialog.tsx create mode 100644 web/src/components/input/InputWithTags.tsx create mode 100644 web/src/components/input/SaveSearchDialog.tsx create mode 100644 web/src/components/ui/command.tsx create mode 100644 web/src/hooks/use-suggestions.ts diff --git a/web/package-lock.json b/web/package-lock.json index de42a1ac7..77f0bfc0f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -34,6 +34,7 @@ "axios": "^1.7.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "^1.0.0", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", "embla-carousel-react": "^8.2.0", @@ -3722,6 +3723,384 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz", + "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/web/package.json b/web/package.json index bb15ea4b8..f8f4bc306 100644 --- a/web/package.json +++ b/web/package.json @@ -40,6 +40,7 @@ "axios": "^1.7.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "^1.0.0", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", "embla-carousel-react": "^8.2.0", diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 0b0b3278e..81090958e 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -109,11 +109,8 @@ export default function SearchFilterGroup({ return; } const cameraConfig = config.cameras[camera]; - cameraConfig.review.alerts.required_zones.forEach((zone) => { - zones.add(zone); - }); - cameraConfig.review.detections.required_zones.forEach((zone) => { - zones.add(zone); + Object.entries(cameraConfig.zones).map(([name, _]) => { + zones.add(name); }); }); diff --git a/web/src/components/input/DeleteSearchDialog.tsx b/web/src/components/input/DeleteSearchDialog.tsx new file mode 100644 index 000000000..0aaabdde5 --- /dev/null +++ b/web/src/components/input/DeleteSearchDialog.tsx @@ -0,0 +1,46 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +type DeleteSearchDialogProps = { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + searchName: string; +}; + +export function DeleteSearchDialog({ + isOpen, + onClose, + onConfirm, + searchName, +}: DeleteSearchDialogProps) { + return ( + + + + Are you sure? + + This will permanently delete the saved search "{searchName}". + + + + Cancel + + Delete + + + + + ); +} diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx new file mode 100644 index 000000000..e4b02fa73 --- /dev/null +++ b/web/src/components/input/InputWithTags.tsx @@ -0,0 +1,704 @@ +import { useState, useRef, useEffect, useCallback } from "react"; +import { + LuX, + LuFilter, + LuImage, + LuChevronDown, + LuChevronUp, + LuTrash2, + LuStar, +} from "react-icons/lu"; +import { + FilterType, + SavedSearchQuery, + SearchFilter, + SearchSource, +} from "@/types/search"; +import useSuggestions from "@/hooks/use-suggestions"; +import { + Command, + CommandInput, + CommandList, + CommandGroup, + CommandItem, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; +import { TooltipPortal } from "@radix-ui/react-tooltip"; +import { usePersistence } from "@/hooks/use-persistence"; +import { SaveSearchDialog } from "./SaveSearchDialog"; +import { DeleteSearchDialog } from "./DeleteSearchDialog"; +import { + convertLocalDateToTimestamp, + getIntlDateFormat, +} from "@/utils/dateUtil"; +import { toast } from "sonner"; + +type InputWithTagsProps = { + filters: SearchFilter; + setFilters: (filter: SearchFilter) => void; + search: string; + setSearch: (search: string) => void; + allSuggestions: { + [K in keyof SearchFilter]: string[]; + }; +}; + +export default function InputWithTags({ + filters, + setFilters, + search, + setSearch, + allSuggestions, +}: InputWithTagsProps) { + const [inputValue, setInputValue] = useState(search || ""); + const [currentFilterType, setCurrentFilterType] = useState( + null, + ); + const [inputFocused, setInputFocused] = useState(false); + const [isSimilaritySearch, setIsSimilaritySearch] = useState(false); + const inputRef = useRef(null); + const commandRef = useRef(null); + + // TODO: search history from browser storage + + const [searchHistory, setSearchHistory, searchHistoryLoaded] = usePersistence< + SavedSearchQuery[] + >("frigate-search-history"); + + const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [searchToDelete, setSearchToDelete] = useState(null); + + const handleSetSearchHistory = useCallback(() => { + setIsSaveDialogOpen(true); + }, []); + + const handleSaveSearch = useCallback( + (name: string) => { + if (searchHistoryLoaded) { + setSearchHistory([ + ...(searchHistory ?? []), + { + name: name, + search: search, + filter: filters, + }, + ]); + } + }, + [search, filters, searchHistory, setSearchHistory, searchHistoryLoaded], + ); + + const handleLoadSavedSearch = useCallback( + (name: string) => { + if (searchHistoryLoaded) { + const savedSearchEntry = searchHistory?.find( + (entry) => entry.name === name, + ); + if (savedSearchEntry) { + setFilters(savedSearchEntry.filter!); + setSearch(savedSearchEntry.search); + } + } + }, + [searchHistory, searchHistoryLoaded, setFilters, setSearch], + ); + + const handleDeleteSearch = useCallback((name: string) => { + setSearchToDelete(name); + setIsDeleteDialogOpen(true); + }, []); + + const confirmDeleteSearch = useCallback(() => { + if (searchToDelete && searchHistory) { + setSearchHistory( + searchHistory.filter((item) => item.name !== searchToDelete) ?? [], + ); + setSearchToDelete(null); + setIsDeleteDialogOpen(false); + } + }, [searchToDelete, searchHistory, setSearchHistory]); + + // suggestions + + const { suggestions, updateSuggestions } = useSuggestions( + filters, + allSuggestions, + searchHistory, + ); + + const resetSuggestions = useCallback( + (value: string) => { + setCurrentFilterType(null); + updateSuggestions(value, null); + }, + [updateSuggestions], + ); + + const filterSuggestions = useCallback( + (current_suggestions: string[]) => { + if (!inputValue || currentFilterType) return suggestions; + const words = inputValue.split(/[\s,]+/); + const lastNonEmptyWordIndex = words + .map((word) => word.trim()) + .lastIndexOf(words.filter((word) => word.trim() !== "").pop() || ""); + const currentWord = words[lastNonEmptyWordIndex]; + return current_suggestions.filter((suggestion) => + suggestion.toLowerCase().includes(currentWord.toLowerCase()), + ); + }, + [inputValue, suggestions, currentFilterType], + ); + + const removeFilter = useCallback( + (filterType: FilterType, filterValue: string | number) => { + const newFilters = { ...filters }; + if (Array.isArray(newFilters[filterType])) { + (newFilters[filterType] as string[]) = ( + newFilters[filterType] as string[] + ).filter((v) => v !== filterValue); + if ((newFilters[filterType] as string[]).length === 0) { + delete newFilters[filterType]; + } + } else if (filterType === "before" || filterType === "after") { + if (newFilters[filterType] === filterValue) { + delete newFilters[filterType]; + } + } else { + delete newFilters[filterType]; + } + setFilters(newFilters as SearchFilter); + }, + [filters, setFilters], + ); + + const createFilter = useCallback( + (type: FilterType, value: string) => { + if ( + allSuggestions[type as keyof SearchFilter]?.includes(value) || + type === "before" || + type === "after" + ) { + const newFilters = { ...filters }; + let timestamp = 0; + + switch (type) { + case "before": + case "after": + timestamp = convertLocalDateToTimestamp(value); + if (timestamp > 0) { + // Check for conflicts with existing before/after filters + if ( + type === "before" && + filters.after && + timestamp <= filters.after * 1000 + ) { + toast.error( + "The 'before' date must be later than the 'after' date.", + { + position: "top-center", + }, + ); + return; + } + if ( + type === "after" && + filters.before && + timestamp >= filters.before * 1000 + ) { + toast.error( + "The 'after' date must be earlier than the 'before' date.", + { + position: "top-center", + }, + ); + return; + } + if (type === "before") { + timestamp -= 1; + } + newFilters[type] = timestamp / 1000; + } + break; + case "search_type": + if (!newFilters.search_type) newFilters.search_type = []; + if ( + !(newFilters.search_type as SearchSource[]).includes( + value as SearchSource, + ) + ) { + (newFilters.search_type as SearchSource[]).push( + value as SearchSource, + ); + } + break; + case "event_id": + newFilters.event_id = value; + break; + default: + // Handle array types (cameras, labels, subLabels, zones) + if (!newFilters[type]) newFilters[type] = []; + if (Array.isArray(newFilters[type])) { + if (!(newFilters[type] as string[]).includes(value)) { + (newFilters[type] as string[]).push(value); + } + } + break; + } + + setFilters(newFilters); + setInputValue((prev) => prev.replace(`${type}:${value}`, "").trim()); + setCurrentFilterType(null); + } + }, + [filters, setFilters, allSuggestions], + ); + + // handlers + + const handleFilterCreation = useCallback( + (filterType: FilterType, filterValue: string) => { + const trimmedValue = filterValue.trim(); + if ( + allSuggestions[filterType as keyof SearchFilter]?.includes( + trimmedValue, + ) || + ((filterType === "before" || filterType === "after") && + trimmedValue.match(/^\d{8}$/)) + ) { + createFilter(filterType, trimmedValue); + setInputValue((prev) => { + const regex = new RegExp( + `${filterType}:${filterValue.trim()}[,\\s]*`, + ); + const newValue = prev.replace(regex, "").trim(); + return newValue.endsWith(",") + ? newValue.slice(0, -1).trim() + : newValue; + }); + setCurrentFilterType(null); + } + }, + [allSuggestions, createFilter], + ); + + const handleInputChange = useCallback( + (value: string) => { + setInputValue(value); + + const words = value.split(/[\s,]+/); + const lastNonEmptyWordIndex = words + .map((word) => word.trim()) + .lastIndexOf(words.filter((word) => word.trim() !== "").pop() || ""); + const currentWord = words[lastNonEmptyWordIndex]; + const isLastCharSpaceOrComma = value.endsWith(" ") || value.endsWith(","); + + // Check if the current word is a filter type + const filterTypeMatch = currentWord.match(/^(\w+):(.*)$/); + if (filterTypeMatch) { + const [_, filterType, filterValue] = filterTypeMatch as [ + string, + FilterType, + string, + ]; + + // Check if filter type is valid + if ( + filterType in allSuggestions || + filterType === "before" || + filterType === "after" + ) { + setCurrentFilterType(filterType); + + if (filterType === "before" || filterType === "after") { + // For before and after, we don't need to update suggestions + if (filterValue.match(/^\d{8}$/)) { + handleFilterCreation(filterType, filterValue); + } + } else { + updateSuggestions(filterValue, filterType); + + // Check if the last character is a space or comma + if (isLastCharSpaceOrComma) { + handleFilterCreation(filterType, filterValue); + } + } + } else { + resetSuggestions(value); + } + } else { + resetSuggestions(value); + } + }, + [updateSuggestions, resetSuggestions, allSuggestions, handleFilterCreation], + ); + + const handleInputFocus = useCallback(() => { + setInputFocused(true); + }, []); + + const handleClearInput = useCallback(() => { + setInputFocused(false); + setInputValue(""); + resetSuggestions(""); + setSearch(""); + inputRef?.current?.blur(); + setFilters({}); + setCurrentFilterType(null); + setIsSimilaritySearch(false); + }, [setFilters, resetSuggestions, setSearch]); + + const handleInputBlur = useCallback((e: React.FocusEvent) => { + if ( + commandRef.current && + !commandRef.current.contains(e.relatedTarget as Node) + ) { + setInputFocused(false); + } + }, []); + + const handleSuggestionClick = useCallback( + (suggestion: string) => { + if (currentFilterType) { + // Apply the selected suggestion to the current filter type + createFilter(currentFilterType, suggestion); + setInputValue((prev) => { + const regex = new RegExp(`${currentFilterType}:[^\\s,]*`, "g"); + return prev.replace(regex, "").trim(); + }); + } else if (suggestion in allSuggestions) { + // Set the suggestion as a new filter type + setCurrentFilterType(suggestion as FilterType); + setInputValue((prev) => { + // Remove any partial match of the filter type, including incomplete matches + const words = prev.split(/\s+/); + const lastWord = words[words.length - 1]; + if (lastWord && suggestion.startsWith(lastWord.toLowerCase())) { + words[words.length - 1] = suggestion + ":"; + } else { + words.push(suggestion + ":"); + } + return words.join(" ").trim(); + }); + } else { + // Add the suggestion as a standalone word + setInputValue((prev) => `${prev}${suggestion} `); + } + + inputRef.current?.focus(); + }, + [createFilter, currentFilterType, allSuggestions], + ); + + const handleSearch = useCallback( + (value: string) => { + setSearch(value); + setInputFocused(false); + inputRef?.current?.blur(); + }, + [setSearch], + ); + + const handleInputKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if ( + e.key === "Enter" && + inputValue.trim() !== "" && + filterSuggestions(suggestions).length == 0 + ) { + e.preventDefault(); + + handleSearch(inputValue); + } + }, + [inputValue, handleSearch, filterSuggestions, suggestions], + ); + + // effects + + useEffect(() => { + updateSuggestions(inputValue, currentFilterType); + }, [currentFilterType, inputValue, updateSuggestions]); + + useEffect(() => { + if (search?.startsWith("similarity:")) { + setIsSimilaritySearch(true); + setInputValue(""); + } else { + setIsSimilaritySearch(false); + setInputValue(search || ""); + } + }, [search]); + + return ( + <> + +
+ +
+ {(search || Object.keys(filters).length > 0) && ( + + + + + + Clear search + + + )} + + {(search || Object.keys(filters).length > 0) && ( + + + + + + Save search + + + )} + + {isSimilaritySearch && ( + + + + + + Similarity search active + + + )} + + + + + + +
+

How to use text filters

+

+ Filters help you narrow down your search results. Here's how + to use them: +

+
    +
  • + Type a filter name followed by a colon (e.g., "cameras:"). +
  • +
  • + Select a value from the suggestions or type your own. +
  • +
  • + Use multiple filters by adding them one after another. +
  • +
  • + Date filters (before: and after:) use{" "} + {getIntlDateFormat()} format. +
  • +
  • Remove filters by clicking the 'x' next to them.
  • +
+

+ Example:{" "} + + cameras:front_door label:person before:01012024 + +

+
+
+
+ + {inputFocused ? ( + setInputFocused(false)} + className="size-4 cursor-pointer text-secondary-foreground" + /> + ) : ( + setInputFocused(true)} + className="size-4 cursor-pointer text-secondary-foreground" + /> + )} +
+
+ + + {(Object.keys(filters).length > 0 || isSimilaritySearch) && ( + +
+ {isSimilaritySearch && ( + + Similarity Search + + + )} + {Object.entries(filters).map(([filterType, filterValues]) => + Array.isArray(filterValues) ? ( + filterValues.map((value, index) => ( + + {filterType.replaceAll("_", " ")}:{" "} + {value.replaceAll("_", " ")} + + + )) + ) : ( + + {filterType}: + {filterType === "before" || filterType === "after" + ? new Date( + (filterType === "before" + ? (filterValues as number) + 1 + : (filterValues as number)) * 1000, + ).toLocaleDateString( + window.navigator?.language || "en-US", + ) + : filterValues} + + + ), + )} +
+
+ )} + + {!currentFilterType && + !inputValue && + searchHistoryLoaded && + (searchHistory?.length ?? 0) > 0 && ( + + {searchHistory?.map((suggestion, index) => ( + handleLoadSavedSearch(suggestion.name)} + > + {suggestion.name} + + + + + + Delete saved search + + + + ))} + + )} + + {filterSuggestions(suggestions) + .filter( + (item) => + !searchHistory?.some((history) => history.name === item), + ) + .map((suggestion, index) => ( + handleSuggestionClick(suggestion)} + > + {suggestion} + + ))} + +
+
+ setIsSaveDialogOpen(false)} + onSave={handleSaveSearch} + /> + setIsDeleteDialogOpen(false)} + onConfirm={confirmDeleteSearch} + searchName={searchToDelete || ""} + /> + + ); +} diff --git a/web/src/components/input/SaveSearchDialog.tsx b/web/src/components/input/SaveSearchDialog.tsx new file mode 100644 index 000000000..c5bf29001 --- /dev/null +++ b/web/src/components/input/SaveSearchDialog.tsx @@ -0,0 +1,74 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogDescription, +} from "@/components/ui/dialog"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useState } from "react"; +import { isMobile } from "react-device-detect"; +import { toast } from "sonner"; + +type SaveSearchDialogProps = { + isOpen: boolean; + onClose: () => void; + onSave: (name: string) => void; +}; + +export function SaveSearchDialog({ + isOpen, + onClose, + onSave, +}: SaveSearchDialogProps) { + const [searchName, setSearchName] = useState(""); + + const handleSave = () => { + if (searchName.trim()) { + onSave(searchName.trim()); + setSearchName(""); + toast.success(`Search (${searchName.trim()}) has been saved.`, { + position: "top-center", + }); + onClose(); + } + }; + + return ( + + { + if (isMobile) { + e.preventDefault(); + } + }} + > + + Save Search + + Provide a name for this saved search. + + + setSearchName(e.target.value)} + placeholder="Enter a name for your search" + /> + + + + + + + ); +} diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx new file mode 100644 index 000000000..64be5e01a --- /dev/null +++ b/web/src/components/ui/command.tsx @@ -0,0 +1,153 @@ +import * as React from "react"; +import { type DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/web/src/hooks/use-suggestions.ts b/web/src/hooks/use-suggestions.ts new file mode 100644 index 000000000..9222c866c --- /dev/null +++ b/web/src/hooks/use-suggestions.ts @@ -0,0 +1,63 @@ +import { FilterType, SavedSearchQuery, SearchFilter } from "@/types/search"; +import { useCallback, useState } from "react"; + +// Custom hook for managing suggestions +export type UseSuggestionsType = ( + filters: SearchFilter, + allSuggestions: { [K in keyof SearchFilter]: string[] }, + searchHistory: SavedSearchQuery[], +) => ReturnType; + +// Define and export the useSuggestions hook +export default function useSuggestions( + filters: SearchFilter, + allSuggestions: { [K in keyof SearchFilter]: string[] }, + searchHistory?: SavedSearchQuery[], +) { + const [suggestions, setSuggestions] = useState([]); + + const updateSuggestions = useCallback( + (value: string, currentFilterType: FilterType | null) => { + if (currentFilterType && currentFilterType in allSuggestions) { + const filterValue = value.split(":").pop() || ""; + const currentFilterValues = filters[currentFilterType] || []; + setSuggestions( + allSuggestions[currentFilterType]?.filter( + (item) => + item.toLowerCase().startsWith(filterValue.toLowerCase()) && + !(currentFilterValues as (string | number)[]).includes(item), + ) ?? [], + ); + } else { + const availableFilters = Object.keys(allSuggestions).filter( + (filter) => { + const filterKey = filter as FilterType; + const filterValues = filters[filterKey]; + const suggestionValues = allSuggestions[filterKey]; + + if (!filterValues) return true; + if ( + Array.isArray(filterValues) && + Array.isArray(suggestionValues) + ) { + return filterValues.length < suggestionValues.length; + } + return false; + }, + ); + setSuggestions([ + ...(searchHistory?.map((search) => search.name) ?? []), + ...availableFilters, + "before", + "after", + ]); + } + }, + [filters, allSuggestions, searchHistory], + ); + + return { + suggestions, + updateSuggestions, + }; +} diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 21ab01f71..32c2e1579 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -20,7 +20,6 @@ export default function Explore() { // search field handler - const [searchTimeout, setSearchTimeout] = useState(); const [search, setSearch] = useState(""); const [searchTerm, setSearchTerm] = useState(""); @@ -50,18 +49,7 @@ export default function Explore() { }); useEffect(() => { - if (searchTimeout) { - clearTimeout(searchTimeout); - } - - setSearchTimeout( - setTimeout(() => { - setSearchTimeout(undefined); - setSearchTerm(search); - }, 750), - ); - // we only want to update the searchTerm when search changes - // eslint-disable-next-line react-hooks/exhaustive-deps + setSearchTerm(search); }, [search]); const searchQuery: SearchQuery = useMemo(() => { @@ -148,7 +136,7 @@ export default function Explore() { const { data, size, setSize, isValidating } = useSWRInfinite( getKey, { - revalidateFirstPage: false, + revalidateFirstPage: true, revalidateAll: false, }, ); @@ -277,6 +265,7 @@ export default function Explore() { isLoading={(isLoadingInitialData || isLoadingMore) ?? true} setSearch={setSearch} setSimilaritySearch={(search) => setSearch(`similarity:${search.id}`)} + setSearchFilter={setSearchFilter} onUpdateFilter={setSearchFilter} onOpenSearch={onOpenSearch} loadMore={loadMore} diff --git a/web/src/types/search.ts b/web/src/types/search.ts index 9aaf35849..141d3a72e 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -55,3 +55,10 @@ export type SearchQueryParams = { }; export type SearchQuery = [string, SearchQueryParams] | null; +export type FilterType = keyof SearchFilter; + +export type SavedSearchQuery = { + name: string; + search: string; + filter: SearchFilter | undefined; +}; diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts index 3699d63f7..fee8bd195 100644 --- a/web/src/utils/dateUtil.ts +++ b/web/src/utils/dateUtil.ts @@ -296,3 +296,75 @@ export function isCurrentHour(timestamp: number) { return timestamp > now.getTime() / 1000; } + +export const convertLocalDateToTimestamp = (dateString: string): number => { + // Ensure the date string is in the correct format (8 digits) + if (!/^\d{8}$/.test(dateString)) { + return 0; + } + + // Determine the local date format + const format = new Intl.DateTimeFormat() + .formatToParts(new Date()) + .reduce((acc, part) => { + if (part.type === "day") acc.push("D"); + if (part.type === "month") acc.push("M"); + if (part.type === "year") acc.push("Y"); + return acc; + }, [] as string[]) + .join(""); + + let day: string, month: string, year: string; + + // Parse the date string according to the detected format + switch (format) { + case "DMY": + [day, month, year] = [ + dateString.slice(0, 2), + dateString.slice(2, 4), + dateString.slice(4), + ]; + break; + case "MDY": + [month, day, year] = [ + dateString.slice(0, 2), + dateString.slice(2, 4), + dateString.slice(4), + ]; + break; + case "YMD": + [year, month, day] = [ + dateString.slice(0, 2), + dateString.slice(2, 4), + dateString.slice(4), + ]; + break; + default: + return 0; + } + + // Create a Date object based on the local timezone + const localDate = new Date(`${year}-${month}-${day}T00:00:00`); + + // Check if the date is valid + if (isNaN(localDate.getTime())) { + return 0; + } + + // Convert local date to UTC timestamp + const timestamp = localDate.getTime(); + + return timestamp; +}; + +export function getIntlDateFormat() { + return new Intl.DateTimeFormat() + .formatToParts(new Date()) + .reduce((acc, part) => { + if (part.type === "day") acc.push("DD"); + if (part.type === "month") acc.push("MM"); + if (part.type === "year") acc.push("YYYY"); + return acc; + }, [] as string[]) + .join(""); +} diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index e031d211c..b0ce92bec 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -3,7 +3,6 @@ import SearchFilterGroup from "@/components/filter/SearchFilterGroup"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import Chip from "@/components/indicators/Chip"; import SearchDetailDialog from "@/components/overlay/detail/SearchDetailDialog"; -import { Input } from "@/components/ui/input"; import { Toaster } from "@/components/ui/sonner"; import { Tooltip, @@ -12,16 +11,17 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { FrigateConfig } from "@/types/frigateConfig"; -import { SearchFilter, SearchResult } from "@/types/search"; +import { SearchFilter, SearchResult, SearchSource } from "@/types/search"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { isMobileOnly } from "react-device-detect"; -import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu"; +import { LuImage, LuSearchX, LuText } from "react-icons/lu"; import useSWR from "swr"; import ExploreView from "../explore/ExploreView"; import useKeyboardListener, { KeyModifiers, } from "@/hooks/use-keyboard-listener"; import scrollIntoView from "scroll-into-view-if-needed"; +import InputWithTags from "@/components/input/InputWithTags"; type SearchViewProps = { search: string; @@ -31,6 +31,7 @@ type SearchViewProps = { isLoading: boolean; setSearch: (search: string) => void; setSimilaritySearch: (search: SearchResult) => void; + setSearchFilter: (filter: SearchFilter) => void; onUpdateFilter: (filter: SearchFilter) => void; onOpenSearch: (item: SearchResult) => void; loadMore: () => void; @@ -44,6 +45,7 @@ export default function SearchView({ isLoading, setSearch, setSimilaritySearch, + setSearchFilter, onUpdateFilter, loadMore, hasMore, @@ -52,6 +54,69 @@ export default function SearchView({ revalidateOnFocus: false, }); + // suggestions values + + const allLabels = useMemo(() => { + if (!config) { + return []; + } + + const labels = new Set(); + const cameras = searchFilter?.cameras || Object.keys(config.cameras); + + cameras.forEach((camera) => { + if (camera == "birdseye") { + return; + } + const cameraConfig = config.cameras[camera]; + cameraConfig.objects.track.forEach((label) => { + labels.add(label); + }); + + if (cameraConfig.audio.enabled_in_config) { + cameraConfig.audio.listen.forEach((label) => { + labels.add(label); + }); + } + }); + + return [...labels].sort(); + }, [config, searchFilter]); + + const { data: allSubLabels } = useSWR("sub_labels"); + + const allZones = useMemo(() => { + if (!config) { + return []; + } + + const zones = new Set(); + const cameras = searchFilter?.cameras || Object.keys(config.cameras); + + cameras.forEach((camera) => { + if (camera == "birdseye") { + return; + } + const cameraConfig = config.cameras[camera]; + Object.entries(cameraConfig.zones).map(([name, _]) => { + zones.add(name); + }); + }); + + return [...zones].sort(); + }, [config, searchFilter]); + + const suggestionsValues = useMemo( + () => ({ + cameras: Object.keys(config?.cameras || {}), + labels: Object.values(allLabels || {}), + zones: Object.values(allZones || {}), + sub_labels: allSubLabels, + search_type: ["thumbnail", "description"] as SearchSource[], + }), + [config, allLabels, allZones, allSubLabels], + ); + // remove duplicate event ids const uniqueResults = useMemo(() => { @@ -192,7 +257,7 @@ export default function SearchView({
{config?.semantic_search?.enabled && ( -
- setSearch(e.target.value)} +
+ - {search && ( - setSearch("")} - /> - )}
)} From 4515eb46374819c11b2dfad7299a2fbe479109cc Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 18 Sep 2024 18:34:07 -0600 Subject: [PATCH 26/36] Rocm yolonas (#13816) * Implement ROCm detectors * Cleanup tensor input * Fixup image creation * Add support for yolonas in onnx * Get build working with onnx * Update docs and simplify config * Remove unused imports --- .github/workflows/ci.yml | 63 ++------- docker/rocm/Dockerfile | 13 +- docker/rocm/requirements-wheels-rocm.txt | 1 + .../dependencies.d/download-models | 0 .../s6-rc.d/compile-rocm-models/run | 20 --- .../s6-rc.d/compile-rocm-models/type | 1 - .../s6-overlay/s6-rc.d/compile-rocm-models/up | 1 - .../dependencies.d/compile-rocm-models | 0 docs/docs/configuration/object_detectors.md | 124 +++++++++++++++++- docs/docs/frigate/hardware.md | 4 + frigate/detectors/plugins/hailo8l.py | 13 +- frigate/detectors/plugins/onnx.py | 15 +-- frigate/detectors/plugins/rocm.py | 57 ++++++-- frigate/detectors/util.py | 36 ----- 14 files changed, 194 insertions(+), 154 deletions(-) create mode 100644 docker/rocm/requirements-wheels-rocm.txt delete mode 100644 docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/dependencies.d/download-models delete mode 100755 docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/run delete mode 100644 docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/type delete mode 100644 docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/up delete mode 100644 docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/frigate/dependencies.d/compile-rocm-models delete mode 100644 frigate/detectors/util.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e14516b04..619d71d48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,57 +179,18 @@ jobs: h8l.tags=${{ steps.setup.outputs.image-name }}-h8l *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-h8l *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-h8l,mode=max - #- name: AMD/ROCm general build - # env: - # AMDGPU: gfx - # HSA_OVERRIDE: 0 - # uses: docker/bake-action@v3 - # with: - # push: true - # targets: rocm - # files: docker/rocm/rocm.hcl - # set: | - # rocm.tags=${{ steps.setup.outputs.image-name }}-rocm - # *.cache-from=type=gha - #- name: AMD/ROCm gfx900 - # env: - # AMDGPU: gfx900 - # HSA_OVERRIDE: 1 - # HSA_OVERRIDE_GFX_VERSION: 9.0.0 - # uses: docker/bake-action@v3 - # with: - # push: true - # targets: rocm - # files: docker/rocm/rocm.hcl - # set: | - # rocm.tags=${{ steps.setup.outputs.image-name }}-rocm-gfx900 - # *.cache-from=type=gha - #- name: AMD/ROCm gfx1030 - # env: - # AMDGPU: gfx1030 - # HSA_OVERRIDE: 1 - # HSA_OVERRIDE_GFX_VERSION: 10.3.0 - # uses: docker/bake-action@v3 - # with: - # push: true - # targets: rocm - # files: docker/rocm/rocm.hcl - # set: | - # rocm.tags=${{ steps.setup.outputs.image-name }}-rocm-gfx1030 - # *.cache-from=type=gha - #- name: AMD/ROCm gfx1100 - # env: - # AMDGPU: gfx1100 - # HSA_OVERRIDE: 1 - # HSA_OVERRIDE_GFX_VERSION: 11.0.0 - # uses: docker/bake-action@v3 - # with: - # push: true - # targets: rocm - # files: docker/rocm/rocm.hcl - # set: | - # rocm.tags=${{ steps.setup.outputs.image-name }}-rocm-gfx1100 - # *.cache-from=type=gha + - name: AMD/ROCm general build + env: + AMDGPU: gfx + HSA_OVERRIDE: 0 + uses: docker/bake-action@v3 + with: + push: true + targets: rocm + files: docker/rocm/rocm.hcl + set: | + rocm.tags=${{ steps.setup.outputs.image-name }}-rocm + *.cache-from=type=gha # The majority of users running arm64 are rpi users, so the rpi # build should be the primary arm64 image assemble_default_build: diff --git a/docker/rocm/Dockerfile b/docker/rocm/Dockerfile index d13bcaead..a1d6ce832 100644 --- a/docker/rocm/Dockerfile +++ b/docker/rocm/Dockerfile @@ -23,11 +23,11 @@ COPY docker/rocm/rocm-pin-600 /etc/apt/preferences.d/ RUN apt-get update -RUN apt-get -y install --no-install-recommends migraphx +RUN apt-get -y install --no-install-recommends migraphx hipfft roctracer RUN apt-get -y install --no-install-recommends migraphx-dev RUN mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib -RUN cd /opt/rocm-$ROCM/lib && cp -dpr libMIOpen*.so* libamd*.so* libhip*.so* libhsa*.so* libmigraphx*.so* librocm*.so* librocblas*.so* /opt/rocm-dist/opt/rocm-$ROCM/lib/ +RUN cd /opt/rocm-$ROCM/lib && cp -dpr libMIOpen*.so* libamd*.so* libhip*.so* libhsa*.so* libmigraphx*.so* librocm*.so* librocblas*.so* libroctracer*.so* librocfft*.so* /opt/rocm-dist/opt/rocm-$ROCM/lib/ RUN cd /opt/rocm-dist/opt/ && ln -s rocm-$ROCM rocm RUN mkdir -p /opt/rocm-dist/etc/ld.so.conf.d/ @@ -69,7 +69,11 @@ RUN apt-get -y install libnuma1 WORKDIR /opt/frigate/ COPY --from=rootfs / / -COPY docker/rocm/rootfs/ / + +COPY docker/rocm/requirements-wheels-rocm.txt /requirements.txt +RUN python3 -m pip install --upgrade pip \ + && pip3 uninstall -y onnxruntime-openvino \ + && pip3 install -r /requirements.txt ####################################################################### FROM scratch AS rocm-dist @@ -101,6 +105,3 @@ ENV HSA_OVERRIDE_GFX_VERSION=$HSA_OVERRIDE_GFX_VERSION ####################################################################### FROM rocm-prelim-hsa-override$HSA_OVERRIDE as rocm-deps -# Request yolov8 download at startup -ENV DOWNLOAD_YOLOV8=1 - diff --git a/docker/rocm/requirements-wheels-rocm.txt b/docker/rocm/requirements-wheels-rocm.txt new file mode 100644 index 000000000..89d0e6096 --- /dev/null +++ b/docker/rocm/requirements-wheels-rocm.txt @@ -0,0 +1 @@ +onnxruntime-rocm @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v1.0.0/onnxruntime_rocm-1.17.3-cp39-cp39-linux_x86_64.whl \ No newline at end of file diff --git a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/dependencies.d/download-models b/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/dependencies.d/download-models deleted file mode 100644 index e69de29bb..000000000 diff --git a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/run b/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/run deleted file mode 100755 index f7b084ad7..000000000 --- a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/run +++ /dev/null @@ -1,20 +0,0 @@ -#!/command/with-contenv bash -# shellcheck shell=bash -# Compile YoloV8 ONNX files into ROCm MIGraphX files - -OVERRIDE=$(cd /opt/frigate && python3 -c 'import frigate.detectors.plugins.rocm as rocm; print(rocm.auto_override_gfx_version())') - -if ! test -z "$OVERRIDE"; then - echo "Using HSA_OVERRIDE_GFX_VERSION=${OVERRIDE}" - export HSA_OVERRIDE_GFX_VERSION=$OVERRIDE -fi - -for onnx in /config/model_cache/yolov8/*.onnx -do - mxr="${onnx%.onnx}.mxr" - if ! test -f $mxr; then - echo "processing $onnx into $mxr" - /opt/rocm/bin/migraphx-driver compile $onnx --optimize --gpu --enable-offload-copy --binary -o $mxr - fi -done - diff --git a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/type b/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/type deleted file mode 100644 index bdd22a185..000000000 --- a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/up b/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/up deleted file mode 100644 index 8fdcef491..000000000 --- a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/compile-rocm-models/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/compile-rocm-models/run diff --git a/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/frigate/dependencies.d/compile-rocm-models b/docker/rocm/rootfs/etc/s6-overlay/s6-rc.d/frigate/dependencies.d/compile-rocm-models deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 556068cea..174d7a572 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -9,6 +9,11 @@ Frigate supports multiple different detectors that work on different types of ha **Most Hardware** - [Coral EdgeTPU](#edge-tpu-detector): The Google Coral EdgeTPU is available in USB and m.2 format allowing for a wide range of compatibility with devices. +- [Hailo](#hailo-8l): The Hailo8 AI Acceleration module is available in m.2 format with a HAT for RPi devices, offering a wide range of compatibility with devices. + +**AMD** +- [ROCm](#amdrocm-gpu-detector): ROCm can run on AMD Discrete GPUs to provide efficient object detection. +- [ONNX](#onnx): ROCm will automatically be detected and used as a detector in the `-rocm` Frigate image when a supported ONNX model is configured. **Intel** - [OpenVino](#openvino-detector): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel CPUs to provide efficient object detection. @@ -16,7 +21,7 @@ Frigate supports multiple different detectors that work on different types of ha **Nvidia** - [TensortRT](#nvidia-tensorrt-detector): TensorRT can run on Nvidia GPUs, using one of many default models. -- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` Frigate image when a supported ONNX is configured. +- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` Frigate image when a supported ONNX model is configured. **Rockchip** - [RKNN](#rockchip-platform): RKNN models can run on Rockchip devices with included NPUs. @@ -312,6 +317,121 @@ model: height: 320 ``` +## AMD/ROCm GPU detector + +### Setup + +The `rocm` detector supports running YOLO-NAS models on AMD GPUs. Use a frigate docker image with `-rocm` suffix, for example `ghcr.io/blakeblackshear/frigate:stable-rocm`. + +### Docker settings for GPU access + +ROCm needs access to the `/dev/kfd` and `/dev/dri` devices. When docker or frigate is not run under root then also `video` (and possibly `render` and `ssl/_ssl`) groups should be added. + +When running docker directly the following flags should be added for device access: + +```bash +$ docker run --device=/dev/kfd --device=/dev/dri \ + ... +``` + +When using docker compose: + +```yaml +services: + frigate: +--- +devices: + - /dev/dri + - /dev/kfd +``` + +For reference on recommended settings see [running ROCm/pytorch in Docker](https://rocm.docs.amd.com/projects/install-on-linux/en/develop/how-to/3rd-party/pytorch-install.html#using-docker-with-pytorch-pre-installed). + +### Docker settings for overriding the GPU chipset + +Your GPU might work just fine without any special configuration but in many cases they need manual settings. AMD/ROCm software stack comes with a limited set of GPU drivers and for newer or missing models you will have to override the chipset version to an older/generic version to get things working. + +Also AMD/ROCm does not "officially" support integrated GPUs. It still does work with most of them just fine but requires special settings. One has to configure the `HSA_OVERRIDE_GFX_VERSION` environment variable. See the [ROCm bug report](https://github.com/ROCm/ROCm/issues/1743) for context and examples. + +For the rocm frigate build there is some automatic detection: + +- gfx90c -> 9.0.0 +- gfx1031 -> 10.3.0 +- gfx1103 -> 11.0.0 + +If you have something else you might need to override the `HSA_OVERRIDE_GFX_VERSION` at Docker launch. Suppose the version you want is `9.0.0`, then you should configure it from command line as: + +```bash +$ docker run -e HSA_OVERRIDE_GFX_VERSION=9.0.0 \ + ... +``` + +When using docker compose: + +```yaml +services: + frigate: +... +environment: + HSA_OVERRIDE_GFX_VERSION: "9.0.0" +``` + +Figuring out what version you need can be complicated as you can't tell the chipset name and driver from the AMD brand name. + +- first make sure that rocm environment is running properly by running `/opt/rocm/bin/rocminfo` in the frigate container -- it should list both the CPU and the GPU with their properties +- find the chipset version you have (gfxNNN) from the output of the `rocminfo` (see below) +- use a search engine to query what `HSA_OVERRIDE_GFX_VERSION` you need for the given gfx name ("gfxNNN ROCm HSA_OVERRIDE_GFX_VERSION") +- override the `HSA_OVERRIDE_GFX_VERSION` with relevant value +- if things are not working check the frigate docker logs + +#### Figuring out if AMD/ROCm is working and found your GPU + +```bash +$ docker exec -it frigate /opt/rocm/bin/rocminfo +``` + +#### Figuring out your AMD GPU chipset version: + +We unset the `HSA_OVERRIDE_GFX_VERSION` to prevent an existing override from messing up the result: + +```bash +$ docker exec -it frigate /bin/bash -c '(unset HSA_OVERRIDE_GFX_VERSION && /opt/rocm/bin/rocminfo |grep gfx)' +``` + +### Supported Models + +There is no default model provided, the following formats are supported: + +#### YOLO-NAS + +[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb). + +:::warning + +The pre-trained YOLO-NAS weights from DeciAI are subject to their license and can't be used commercially. For more information, see: https://docs.deci.ai/super-gradients/latest/LICENSE.YOLONAS.html + +::: + +The input image size in this notebook is set to 320x320. This results in lower CPU usage and faster inference times without impacting performance in most cases due to the way Frigate crops video frames to areas of interest before running detection. The notebook and config can be updated to 640x640 if desired. + +After placing the downloaded onnx model in your config folder, you can use the following configuration: + +```yaml +detectors: + onnx: + type: rocm + +model: + model_type: yolonas + width: 320 # <--- should match whatever was set in notebook + height: 320 # <--- should match whatever was set in notebook + input_pixel_format: bgr + path: /config/yolo_nas_s.onnx + labelmap_path: /labelmap/coco-80.txt +``` + +Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects. + ## ONNX ONNX is an open format for building machine learning models, Frigate supports running ONNX models on CPU, OpenVINO, and TensorRT. On startup Frigate will automatically try to use a GPU if one is available. @@ -475,7 +595,7 @@ $ cat /sys/kernel/debug/rknpu/load ## Hailo-8l -This detector is available if you are using the Raspberry Pi 5 with Hailo-8L AI Kit. This has not been tested using the Hailo-8L with other hardware. +This detector is available for use with Hailo-8 AI Acceleration Module. ### Configuration diff --git a/docs/docs/frigate/hardware.md b/docs/docs/frigate/hardware.md index 39fc4d84d..ef647e474 100644 --- a/docs/docs/frigate/hardware.md +++ b/docs/docs/frigate/hardware.md @@ -87,6 +87,10 @@ Inference speeds will vary greatly depending on the GPU and the model used. | Quadro P400 2GB | 20 - 25 ms | | Quadro P2000 | ~ 12 ms | +#### AMD GPUs + +With the [rocm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many AMD GPUs. + ### Community Supported: #### Nvidia Jetson diff --git a/frigate/detectors/plugins/hailo8l.py b/frigate/detectors/plugins/hailo8l.py index c2df04ffa..b66d78bd6 100644 --- a/frigate/detectors/plugins/hailo8l.py +++ b/frigate/detectors/plugins/hailo8l.py @@ -24,7 +24,6 @@ from typing_extensions import Literal from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig -from frigate.detectors.util import preprocess # Assuming this function is available # Set up logging logger = logging.getLogger(__name__) @@ -146,17 +145,9 @@ class HailoDetector(DetectionApi): f"[detect_raw] Converted tensor_input to numpy array: shape {tensor_input.shape}" ) - # Preprocess the tensor input using Frigate's preprocess function - processed_tensor = preprocess( - tensor_input, (1, self.h8l_model_height, self.h8l_model_width, 3), np.uint8 - ) + input_data = tensor_input logger.debug( - f"[detect_raw] Tensor data and shape after preprocessing: {processed_tensor} {processed_tensor.shape}" - ) - - input_data = processed_tensor - logger.debug( - f"[detect_raw] Input data for inference shape: {processed_tensor.shape}, dtype: {processed_tensor.dtype}" + f"[detect_raw] Input data for inference shape: {tensor_input.shape}, dtype: {tensor_input.dtype}" ) try: diff --git a/frigate/detectors/plugins/onnx.py b/frigate/detectors/plugins/onnx.py index 117cdae02..a0f5552d9 100644 --- a/frigate/detectors/plugins/onnx.py +++ b/frigate/detectors/plugins/onnx.py @@ -1,7 +1,6 @@ import logging import os -import cv2 import numpy as np from typing_extensions import Literal @@ -9,7 +8,6 @@ from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import ( BaseDetectorConfig, ModelTypeEnum, - PixelFormatEnum, ) logger = logging.getLogger(__name__) @@ -73,24 +71,13 @@ class ONNXDetector(DetectionApi): self.w = detector_config.model.width self.onnx_model_type = detector_config.model.model_type self.onnx_model_px = detector_config.model.input_pixel_format + self.onnx_model_shape = detector_config.model.input_tensor path = detector_config.model.path logger.info(f"ONNX: {path} loaded") def detect_raw(self, tensor_input): model_input_name = self.model.get_inputs()[0].name - model_input_shape = self.model.get_inputs()[0].shape - - # adjust input shape - if self.onnx_model_type == ModelTypeEnum.yolonas: - tensor_input = cv2.dnn.blobFromImage( - tensor_input[0], - 1.0, - (model_input_shape[3], model_input_shape[2]), - None, - swapRB=self.onnx_model_px == PixelFormatEnum.bgr, - ).astype(np.uint8) - tensor_output = self.model.run(None, {model_input_name: tensor_input}) if self.onnx_model_type == ModelTypeEnum.yolonas: diff --git a/frigate/detectors/plugins/rocm.py b/frigate/detectors/plugins/rocm.py index ea237a306..aa49e0544 100644 --- a/frigate/detectors/plugins/rocm.py +++ b/frigate/detectors/plugins/rocm.py @@ -9,8 +9,10 @@ from pydantic import Field from typing_extensions import Literal from frigate.detectors.detection_api import DetectionApi -from frigate.detectors.detector_config import BaseDetectorConfig -from frigate.detectors.util import preprocess +from frigate.detectors.detector_config import ( + BaseDetectorConfig, + ModelTypeEnum, +) logger = logging.getLogger(__name__) @@ -74,7 +76,16 @@ class ROCmDetector(DetectionApi): logger.error("AMD/ROCm: module loading failed, missing ROCm environment?") raise + if detector_config.conserve_cpu: + logger.info("AMD/ROCm: switching HIP to blocking mode to conserve CPU") + ctypes.CDLL("/opt/rocm/lib/libamdhip64.so").hipSetDeviceFlags(4) + + self.h = detector_config.model.height + self.w = detector_config.model.width + self.rocm_model_type = detector_config.model.model_type + self.rocm_model_px = detector_config.model.input_pixel_format path = detector_config.model.path + mxr_path = os.path.splitext(path)[0] + ".mxr" if path.endswith(".mxr"): logger.info(f"AMD/ROCm: loading parsed model from {mxr_path}") @@ -84,6 +95,7 @@ class ROCmDetector(DetectionApi): self.model = migraphx.load(mxr_path) else: logger.info(f"AMD/ROCm: loading model from {path}") + if path.endswith(".onnx"): self.model = migraphx.parse_onnx(path) elif ( @@ -95,30 +107,51 @@ class ROCmDetector(DetectionApi): self.model = migraphx.parse_tf(path) else: raise Exception(f"AMD/ROCm: unknown model format {path}") + logger.info("AMD/ROCm: compiling the model") + self.model.compile( migraphx.get_target("gpu"), offload_copy=True, fast_math=True ) + logger.info(f"AMD/ROCm: saving parsed model into {mxr_path}") + os.makedirs("/config/model_cache/rocm", exist_ok=True) migraphx.save(self.model, mxr_path) + logger.info("AMD/ROCm: model loaded") def detect_raw(self, tensor_input): model_input_name = self.model.get_parameter_names()[0] - model_input_shape = tuple( - self.model.get_parameter_shapes()[model_input_name].lens() - ) - tensor_input = preprocess(tensor_input, model_input_shape, np.float32) - detector_result = self.model.run({model_input_name: tensor_input})[0] - addr = ctypes.cast(detector_result.data_ptr(), ctypes.POINTER(ctypes.c_float)) - # ruff: noqa: F841 + tensor_output = np.ctypeslib.as_array( addr, shape=detector_result.get_shape().lens() ) - raise Exception( - "No models are currently supported for rocm. See the docs for more info." - ) + if self.rocm_model_type == ModelTypeEnum.yolonas: + predictions = tensor_output + + detections = np.zeros((20, 6), np.float32) + + for i, prediction in enumerate(predictions): + if i == 20: + break + (_, x_min, y_min, x_max, y_max, confidence, class_id) = prediction + # when running in GPU mode, empty predictions in the output have class_id of -1 + if class_id < 0: + break + detections[i] = [ + class_id, + confidence, + y_min / self.h, + x_min / self.w, + y_max / self.h, + x_max / self.w, + ] + return detections + else: + raise Exception( + f"{self.rocm_model_type} is currently not supported for rocm. See the docs for more info on supported models." + ) diff --git a/frigate/detectors/util.py b/frigate/detectors/util.py deleted file mode 100644 index 08e54e7cb..000000000 --- a/frigate/detectors/util.py +++ /dev/null @@ -1,36 +0,0 @@ -import logging - -import cv2 -import numpy as np - -logger = logging.getLogger(__name__) - - -def preprocess(tensor_input, model_input_shape, model_input_element_type): - model_input_shape = tuple(model_input_shape) - assert tensor_input.dtype == np.uint8, f"tensor_input.dtype: {tensor_input.dtype}" - if len(tensor_input.shape) == 3: - tensor_input = tensor_input[np.newaxis, :] - if model_input_element_type == np.uint8: - # nothing to do for uint8 model input - assert ( - model_input_shape == tensor_input.shape - ), f"model_input_shape: {model_input_shape}, tensor_input.shape: {tensor_input.shape}" - return tensor_input - assert ( - model_input_element_type == np.float32 - ), f"model_input_element_type: {model_input_element_type}" - # tensor_input must be nhwc - assert tensor_input.shape[3] == 3, f"tensor_input.shape: {tensor_input.shape}" - if tensor_input.shape[1:3] != model_input_shape[2:4]: - logger.warn( - f"preprocess: tensor_input.shape {tensor_input.shape} and model_input_shape {model_input_shape} do not match!" - ) - # cv2.dnn.blobFromImage is faster than running it through numpy - return cv2.dnn.blobFromImage( - tensor_input[0], - 1.0 / 255, - (model_input_shape[3], model_input_shape[2]), - None, - swapRB=False, - ) From ddf3a687a3d8e7504fe87382f91e8cf9b0e2e10b Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 19 Sep 2024 07:41:08 -0600 Subject: [PATCH 27/36] Use intel apt for out of tree drivers (#13829) --- docker/main/install_deps.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docker/main/install_deps.sh b/docker/main/install_deps.sh index 5fa661143..327053edd 100755 --- a/docker/main/install_deps.sh +++ b/docker/main/install_deps.sh @@ -63,12 +63,11 @@ fi # arch specific packages if [[ "${TARGETARCH}" == "amd64" ]]; then - # use debian bookworm for hwaccel packages + # use debian bookworm for amd / intel-i965 driver packages echo 'deb https://deb.debian.org/debian bookworm main contrib non-free' >/etc/apt/sources.list.d/debian-bookworm.list apt-get -qq update apt-get -qq install --no-install-recommends --no-install-suggests -y \ - intel-opencl-icd intel-media-va-driver-non-free i965-va-driver \ - libmfx-gen1.2 libmfx1 onevpl-tools intel-gpu-tools \ + i965-va-driver intel-gpu-tools onevpl-tools \ libva-drm2 \ mesa-va-drivers radeontop @@ -77,6 +76,17 @@ if [[ "${TARGETARCH}" == "amd64" ]]; then i965-va-driver-shaders rm -f /etc/apt/sources.list.d/debian-bookworm.list + + # use intel apt intel packages + wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics.gpg + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu jammy client" | tee /etc/apt/sources.list.d/intel-gpu-jammy.list + apt-get -qq update + apt-get -qq install --no-install-recommends --no-install-suggests -y \ + intel-opencl-icd intel-level-zero-gpu intel-media-va-driver-non-free \ + libmfx1 libmfxgen1 libvpl2 + + rm -f /usr/share/keyrings/intel-graphics.gpg + rm -f /etc/apt/sources.list.d/intel-gpu-jammy.list fi if [[ "${TARGETARCH}" == "arm64" ]]; then From 7c63cb5bca5ff84ae5906b5f174bc887ef22b762 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 19 Sep 2024 07:51:07 -0600 Subject: [PATCH 28/36] Do not handle object if data is None (#13830) --- frigate/timeline.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frigate/timeline.py b/frigate/timeline.py index 952bb36f4..e60638284 100644 --- a/frigate/timeline.py +++ b/frigate/timeline.py @@ -44,9 +44,10 @@ class TimelineProcessor(threading.Thread): continue if input_type == EventTypeEnum.tracked_object: - self.handle_object_detection( - camera, event_type, prev_event_data, event_data - ) + if prev_event_data is not None and event_data is not None: + self.handle_object_detection( + camera, event_type, prev_event_data, event_data + ) elif input_type == EventTypeEnum.api: self.handle_api_entry(camera, event_type, event_data) From 27e71eb14252fdfa3369b8836412031cef9a7cd6 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 19 Sep 2024 10:01:57 -0600 Subject: [PATCH 29/36] Improve navigation (#13833) * Fix infinite loop * Fix review page not opening to historical review items * Use query arg for search and remove unused recording opening * Retain query * Clean up typing --- web/src/components/input/InputWithTags.tsx | 38 ++--- web/src/hooks/use-overlay-state.tsx | 2 +- web/src/pages/Events.tsx | 13 +- web/src/pages/Explore.tsx | 159 +++++---------------- web/src/types/search.ts | 1 + web/src/utils/dateUtil.ts | 5 + web/src/views/search/SearchView.tsx | 1 - 7 files changed, 77 insertions(+), 142 deletions(-) diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index e4b02fa73..218f4e34e 100644 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -189,6 +189,8 @@ export default function InputWithTags({ let timestamp = 0; switch (type) { + case "query": + break; case "before": case "after": timestamp = convertLocalDateToTimestamp(value); @@ -584,24 +586,26 @@ export default function InputWithTags({ )} {Object.entries(filters).map(([filterType, filterValues]) => Array.isArray(filterValues) ? ( - filterValues.map((value, index) => ( - - {filterType.replaceAll("_", " ")}:{" "} - {value.replaceAll("_", " ")} - - - )) + {filterType.replaceAll("_", " ")}:{" "} + {value.replaceAll("_", " ")} + + + )) ) : ( { if (resp.status == 200 && resp.data) { + const startTime = resp.data.start_time - REVIEW_PADDING; + const date = new Date(startTime * 1000); + + setReviewFilter({ + after: getBeginningOfDayTimestamp(date), + before: getEndOfDayTimestamp(date), + }); setRecording( { camera: resp.data.camera, - startTime: resp.data.start_time - REVIEW_PADDING, + startTime, severity: resp.data.severity, }, true, diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 32c2e1579..e8ab01b6a 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -1,30 +1,24 @@ import { useApiFilterArgs } from "@/hooks/use-api-filter"; -import { useCameraPreviews } from "@/hooks/use-camera-previews"; -import { useOverlayState, useSearchEffect } from "@/hooks/use-overlay-state"; -import { FrigateConfig } from "@/types/frigateConfig"; -import { RecordingStartingPoint } from "@/types/record"; +import { useSearchEffect } from "@/hooks/use-overlay-state"; import { SearchFilter, SearchQuery, SearchResult } from "@/types/search"; -import { TimeRange } from "@/types/timeline"; -import { RecordingView } from "@/views/recording/RecordingView"; import SearchView from "@/views/search/SearchView"; import { useCallback, useEffect, useMemo, useState } from "react"; -import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; const API_LIMIT = 25; export default function Explore() { - const { data: config } = useSWR("config", { - revalidateOnFocus: false, - }); - // search field handler const [search, setSearch] = useState(""); - const [searchTerm, setSearchTerm] = useState(""); - const [recording, setRecording] = - useOverlayState("recording"); + const [searchFilter, setSearchFilter, searchSearchParams] = + useApiFilterArgs(); + + const searchTerm = useMemo( + () => searchSearchParams?.["query"] || "", + [searchSearchParams], + ); // search filter @@ -36,11 +30,13 @@ export default function Explore() { return searchTerm.split(":")[1]; }, [searchTerm]); - const [searchFilter, setSearchFilter, searchSearchParams] = - useApiFilterArgs(); - // search api + useSearchEffect("query", (query) => { + setSearch(query); + return false; + }); + useSearchEffect("similarity_search_id", (similarityId) => { setSearch(`similarity:${similarityId}`); // @ts-expect-error we want to clear this @@ -49,7 +45,16 @@ export default function Explore() { }); useEffect(() => { - setSearchTerm(search); + if (!searchTerm && !search) { + return; + } + + setSearchFilter({ + ...searchFilter, + query: search.length > 0 ? search : undefined, + }); + // only update when search is updated + // eslint-disable-next-line react-hooks/exhaustive-deps }, [search]); const searchQuery: SearchQuery = useMemo(() => { @@ -168,109 +173,19 @@ export default function Explore() { } }, [isReachingEnd, isLoadingMore, setSize, size, searchResults, searchQuery]); - // previews - - const previewTimeRange = useMemo(() => { - if (!searchResults) { - return { after: 0, before: 0 }; - } - - return { - after: Math.min(...searchResults.map((res) => res.start_time)), - before: Math.max( - ...searchResults.map((res) => res.end_time ?? Date.now() / 1000), - ), - }; - }, [searchResults]); - - const allPreviews = useCameraPreviews(previewTimeRange, { - autoRefresh: false, - fetchPreviews: searchResults != undefined, - }); - - // selection - - const onOpenSearch = useCallback( - (item: SearchResult) => { - setRecording({ - camera: item.camera, - startTime: item.start_time, - severity: "alert", - }); - }, - [setRecording], + return ( + setSearch(`similarity:${search.id}`)} + setSearchFilter={setSearchFilter} + onUpdateFilter={setSearchFilter} + loadMore={loadMore} + hasMore={!isReachingEnd} + /> ); - - const selectedReviewData = useMemo(() => { - if (!recording) { - return undefined; - } - - if (!config) { - return undefined; - } - - if (!searchResults) { - return undefined; - } - - const allCameras = searchFilter?.cameras ?? Object.keys(config.cameras); - - return { - camera: recording.camera, - start_time: recording.startTime, - allCameras: allCameras, - }; - - // previews will not update after item is selected - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [recording, searchResults]); - - const selectedTimeRange = useMemo(() => { - if (!recording) { - return undefined; - } - - const time = new Date(recording.startTime * 1000); - time.setUTCMinutes(0, 0, 0); - const start = time.getTime() / 1000; - time.setHours(time.getHours() + 2); - const end = time.getTime() / 1000; - return { - after: start, - before: end, - }; - }, [recording]); - - if (recording) { - if (selectedReviewData && selectedTimeRange) { - return ( - - ); - } - } else { - return ( - setSearch(`similarity:${search.id}`)} - setSearchFilter={setSearchFilter} - onUpdateFilter={setSearchFilter} - onOpenSearch={onOpenSearch} - loadMore={loadMore} - hasMore={!isReachingEnd} - /> - ); - } } diff --git a/web/src/types/search.ts b/web/src/types/search.ts index 141d3a72e..54cd7c948 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -29,6 +29,7 @@ export type SearchResult = { }; export type SearchFilter = { + query?: string; cameras?: string[]; labels?: string[]; subLabels?: string[]; diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts index fee8bd195..deb1dc850 100644 --- a/web/src/utils/dateUtil.ts +++ b/web/src/utils/dateUtil.ts @@ -285,6 +285,11 @@ export function endOfHourOrCurrentTime(timestamp: number) { return Math.min(timestamp, now.getTime() / 1000); } +export function getBeginningOfDayTimestamp(date: Date) { + date.setHours(0, 0, 0, 0); + return date.getTime() / 1000; +} + export function getEndOfDayTimestamp(date: Date) { date.setHours(23, 59, 59, 999); return date.getTime() / 1000; diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index b0ce92bec..bdde82115 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -33,7 +33,6 @@ type SearchViewProps = { setSimilaritySearch: (search: SearchResult) => void; setSearchFilter: (filter: SearchFilter) => void; onUpdateFilter: (filter: SearchFilter) => void; - onOpenSearch: (item: SearchResult) => void; loadMore: () => void; hasMore: boolean; }; From d498fabe726a6768ae74f6695662e4f5567b6090 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 19 Sep 2024 13:29:58 -0600 Subject: [PATCH 30/36] Update ffmpeg to 7 and update intel hwaccel docs (#13834) * Update ffmpeg to 7 and update intel hwaccel docs * Formatting * Redo early gen naming * Add gamma back in * Fix table * Add link to intel docs * Add hwaccel arg for disabling gamma * Formatting * Fix tests * Formatting * Fix nvidia --- docker/main/install_deps.sh | 16 +++++----- .../rootfs/usr/local/go2rtc/create_config.py | 16 +++++----- docker/rockchip/Dockerfile | 1 + .../configuration/hardware_acceleration.md | 29 ++++++++++++------- frigate/config.py | 20 ++++++------- frigate/const.py | 4 ++- frigate/ffmpeg_presets.py | 13 +++++++-- frigate/test/test_ffmpeg_presets.py | 2 +- 8 files changed, 60 insertions(+), 41 deletions(-) diff --git a/docker/main/install_deps.sh b/docker/main/install_deps.sh index 327053edd..9664c4574 100755 --- a/docker/main/install_deps.sh +++ b/docker/main/install_deps.sh @@ -40,25 +40,25 @@ apt-get -qq install --no-install-recommends --no-install-suggests -y \ # btbn-ffmpeg -> amd64 if [[ "${TARGETARCH}" == "amd64" ]]; then mkdir -p /usr/lib/ffmpeg/5.0 - mkdir -p /usr/lib/ffmpeg/6.0 + mkdir -p /usr/lib/ffmpeg/7.0 wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux64-gpl-5.1.tar.xz" tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay - wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-08-31-12-50/ffmpeg-n6.1.2-2-gb534cc666e-linux64-gpl-6.1.tar.xz" - tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/6.0 --strip-components 1 - rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/6.0/doc /usr/lib/ffmpeg/6.0/bin/ffplay + wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linux64-gpl-7.0.tar.xz" + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay fi # ffmpeg -> arm64 if [[ "${TARGETARCH}" == "arm64" ]]; then mkdir -p /usr/lib/ffmpeg/5.0 - mkdir -p /usr/lib/ffmpeg/6.0 + mkdir -p /usr/lib/ffmpeg/7.0 wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linuxarm64-gpl-5.1.tar.xz" tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay - wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-08-31-12-50/ffmpeg-n6.1.2-2-gb534cc666e-linuxarm64-gpl-6.1.tar.xz" - tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/6.0 --strip-components 1 - rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/6.0/doc /usr/lib/ffmpeg/6.0/bin/ffplay + wget -qO btbn-ffmpeg.tar.xz "hhttps://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linuxarm64-gpl-7.0.tar.xz" + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay fi # arch specific packages diff --git a/docker/main/rootfs/usr/local/go2rtc/create_config.py b/docker/main/rootfs/usr/local/go2rtc/create_config.py index d33e4da9e..a9abbe1d1 100644 --- a/docker/main/rootfs/usr/local/go2rtc/create_config.py +++ b/docker/main/rootfs/usr/local/go2rtc/create_config.py @@ -9,10 +9,12 @@ from pathlib import Path import yaml sys.path.insert(0, "/opt/frigate") -from frigate.const import BIRDSEYE_PIPE # noqa: E402 -from frigate.ffmpeg_presets import ( # noqa: E402 - parse_preset_hardware_acceleration_encode, +from frigate.const import ( + BIRDSEYE_PIPE, + DEFAULT_FFMPEG_VERSION, + INCLUDED_FFMPEG_VERSIONS, ) +from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode sys.path.remove("/opt/frigate") @@ -110,13 +112,11 @@ else: path = config.get("ffmpeg", {}).get("path", "default") if path == "default": if shutil.which("ffmpeg") is None: - ffmpeg_path = "/usr/lib/ffmpeg/6.0/bin/ffmpeg" + ffmpeg_path = f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg" else: ffmpeg_path = "ffmpeg" -elif path == "6.0": - ffmpeg_path = "/usr/lib/ffmpeg/6.0/bin/ffmpeg" -elif path == "5.0": - ffmpeg_path = "/usr/lib/ffmpeg/5.0/bin/ffmpeg" +elif path in INCLUDED_FFMPEG_VERSIONS: + ffmpeg_path = f"/usr/lib/ffmpeg/{path}/bin/ffmpeg" else: ffmpeg_path = f"{path}/bin/ffmpeg" diff --git a/docker/rockchip/Dockerfile b/docker/rockchip/Dockerfile index 9087efcd2..e1b43c255 100644 --- a/docker/rockchip/Dockerfile +++ b/docker/rockchip/Dockerfile @@ -24,3 +24,4 @@ RUN rm -rf /usr/lib/btbn-ffmpeg/bin/ffmpeg RUN rm -rf /usr/lib/btbn-ffmpeg/bin/ffprobe ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffmpeg /usr/lib/ffmpeg/6.0/bin/ ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffprobe /usr/lib/ffmpeg/6.0/bin/ +ENV PATH="/usr/lib/ffmpeg/6.0/bin/:${PATH}" diff --git a/docs/docs/configuration/hardware_acceleration.md b/docs/docs/configuration/hardware_acceleration.md index 0b62a0f08..867d555d8 100644 --- a/docs/docs/configuration/hardware_acceleration.md +++ b/docs/docs/configuration/hardware_acceleration.md @@ -65,24 +65,33 @@ Or map in all the `/dev/video*` devices. ## Intel-based CPUs +**Recommended hwaccel Preset** + +| CPU Generation | Intel Driver | Recommended Preset | Notes | +| -------------- | ------------ | ------------------ | ----------------------------------- | +| gen1 - gen7 | i965 | preset-vaapi | qsv is not supported | +| gen8 - gen12 | iHD | preset-vaapi | preset-intel-qsv-* can also be used | +| gen13+ | iHD / Xe | preset-intel-qsv-* | | +| Intel Arc GPU | iHD / Xe | preset-intel-qsv-* | | + +:::note + +The default driver is `iHD`. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file or [in the `frigate.yaml` for HA OS users](advanced.md#environment_vars). + +See [The Intel Docs](https://www.intel.com/content/www/us/en/support/articles/000005505/processors.html to figure out what generation your CPU is.) + +::: + ### Via VAAPI -VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. VAAPI is recommended for all generations of Intel-based CPUs. +VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. ```yaml ffmpeg: hwaccel_args: preset-vaapi ``` -:::note - -With some of the processors, like the J4125, the default driver `iHD` doesn't seem to work correctly for hardware acceleration. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file or [in the `frigate.yaml` for HA OS users](advanced.md#environment_vars). - -::: - -### Via Quicksync (>=10th Generation only) - -If VAAPI does not work for you, you can try QSV if your processor supports it. QSV must be set specifically based on the video encoding of the stream. +### Via Quicksync #### H.264 streams diff --git a/frigate/config.py b/frigate/config.py index ad96cebef..b1af9c51b 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -26,7 +26,9 @@ from frigate.const import ( CACHE_DIR, CACHE_SEGMENT_FORMAT, DEFAULT_DB_PATH, + DEFAULT_FFMPEG_VERSION, FREQUENCY_STATS_POINTS, + INCLUDED_FFMPEG_VERSIONS, MAX_PRE_CAPTURE, REGEX_CAMERA_NAME, YAML_EXT, @@ -896,27 +898,23 @@ class FfmpegConfig(FrigateBaseModel): def ffmpeg_path(self) -> str: if self.path == "default": if shutil.which("ffmpeg") is None: - return "/usr/lib/ffmpeg/6.0/bin/ffmpeg" + return f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg" else: return "ffmpeg" - elif self.path == "6.0": - return "/usr/lib/ffmpeg/6.0/bin/ffmpeg" - elif self.path == "5.0": - return "/usr/lib/ffmpeg/5.0/bin/ffmpeg" + elif self.path in INCLUDED_FFMPEG_VERSIONS: + return f"/usr/lib/ffmpeg/{self.path}/bin/ffmpeg" else: return f"{self.path}/bin/ffmpeg" @property def ffprobe_path(self) -> str: if self.path == "default": - if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59: - return "/usr/lib/ffmpeg/6.0/bin/ffprobe" + if shutil.which("ffprobe") is None: + return f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffprobe" else: return "ffprobe" - elif self.path == "6.0": - return "/usr/lib/ffmpeg/6.0/bin/ffprobe" - elif self.path == "5.0": - return "/usr/lib/ffmpeg/5.0/bin/ffprobe" + elif self.path in INCLUDED_FFMPEG_VERSIONS: + return f"/usr/lib/ffmpeg/{self.path}/bin/ffprobe" else: return f"{self.path}/bin/ffprobe" diff --git a/frigate/const.py b/frigate/const.py index c5cc41b3e..a0066774b 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -43,8 +43,10 @@ AUDIO_MIN_CONFIDENCE = 0.5 MAX_WAL_SIZE = 10 # MB -# Ffmpeg Presets +# Ffmpeg constants +DEFAULT_FFMPEG_VERSION = "7.0" +INCLUDED_FFMPEG_VERSIONS = ["7.0", "5.0"] FFMPEG_HWACCEL_NVIDIA = "preset-nvidia" FFMPEG_HWACCEL_VAAPI = "preset-vaapi" FFMPEG_HWACCEL_VULKAN = "preset-vulkan" diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 25dd809cb..574dc0177 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -91,10 +91,10 @@ PRESETS_HW_ACCEL_DECODE["preset-nvidia-mjpeg"] = PRESETS_HW_ACCEL_DECODE[ PRESETS_HW_ACCEL_SCALE = { "preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}", "preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}", - FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,format=nv12,eq=gamma=1.05", "preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", - FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2},hwdownload,format=nv12,eq=gamma=1.05", "preset-jetson-h264": "-r {0}", # scaled in decoder "preset-jetson-h265": "-r {0}", # scaled in decoder "preset-rk-h264": "-r {0} -vf scale_rkrga=w={1}:h={2}:format=yuv420p:force_original_aspect_ratio=0,hwmap=mode=read,format=yuv420p", @@ -185,6 +185,15 @@ def parse_preset_hardware_acceleration_scale( else: scale = PRESETS_HW_ACCEL_SCALE.get(arg, PRESETS_HW_ACCEL_SCALE["default"]) + if ( + ",hwdownload,format=nv12,eq=gamma=1.05" in scale + and os.environ.get("FFMPEG_DISABLE_GAMMA_EQUALIZER") is not None + ): + scale.replace( + ",hwdownload,format=nv12,eq=gamma=1.05", + ":format=nv12,hwdownload,format=nv12,format=yuv420p", + ) + scale = scale.format(fps, width, height).split(" ") scale.extend(detect_args) return scale diff --git a/frigate/test/test_ffmpeg_presets.py b/frigate/test/test_ffmpeg_presets.py index ac5e30a2d..0275469cd 100644 --- a/frigate/test/test_ffmpeg_presets.py +++ b/frigate/test/test_ffmpeg_presets.py @@ -78,7 +78,7 @@ class TestFfmpegPresets(unittest.TestCase): " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) ) assert ( - "fps=10,scale_cuda=w=2560:h=1920:format=nv12,hwdownload,format=nv12,format=yuv420p" + "fps=10,scale_cuda=w=2560:h=1920,hwdownload,format=nv12,eq=gamma=1.05" in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) ) From 6c43e5dba92e15bba3703c5f450c153e7cabc07b Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:14:20 -0500 Subject: [PATCH 31/36] Display warning when search embeddings models are being downloaded (#13840) --- web/src/pages/Explore.tsx | 52 +++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index e8ab01b6a..8accfd18f 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -3,6 +3,7 @@ import { useSearchEffect } from "@/hooks/use-overlay-state"; import { SearchFilter, SearchQuery, SearchResult } from "@/types/search"; import SearchView from "@/views/search/SearchView"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { TbExclamationCircle } from "react-icons/tb"; import useSWRInfinite from "swr/infinite"; const API_LIMIT = 25; @@ -116,10 +117,14 @@ export default function Explore() { // paging + // usually slow only on first run while downloading models + const [isSlowLoading, setIsSlowLoading] = useState(false); + const getKey = ( pageIndex: number, previousPageData: SearchResult[] | null, ): SearchQuery => { + if (isSlowLoading && !similaritySearch) return null; if (previousPageData && !previousPageData.length) return null; // reached the end if (!searchQuery) return null; @@ -143,6 +148,12 @@ export default function Explore() { { revalidateFirstPage: true, revalidateAll: false, + onLoadingSlow: () => { + if (!similaritySearch) { + setIsSlowLoading(true); + } + }, + loadingTimeout: 10000, }, ); @@ -174,18 +185,33 @@ export default function Explore() { }, [isReachingEnd, isLoadingMore, setSize, size, searchResults, searchQuery]); return ( - setSearch(`similarity:${search.id}`)} - setSearchFilter={setSearchFilter} - onUpdateFilter={setSearchFilter} - loadMore={loadMore} - hasMore={!isReachingEnd} - /> + <> + {isSlowLoading && !similaritySearch ? ( +
+
+

Search Unavailable

+ +

+ If this is your first time using Search, be patient while Frigate + downloads the necessary embeddings models. Check Frigate logs. +

+
+
+ ) : ( + setSearch(`similarity:${search.id}`)} + setSearchFilter={setSearchFilter} + onUpdateFilter={setSearchFilter} + loadMore={loadMore} + hasMore={!isReachingEnd} + /> + )} + ); } From 515f06ba6c25e905bbe54db32f4700962178eea0 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:36:07 -0500 Subject: [PATCH 32/36] small spacing tweaks and fix config editor schema load (#13841) --- .../overlay/detail/AnnotationSettingsPane.tsx | 8 +-- .../overlay/detail/ObjectLifecycle.tsx | 5 +- .../overlay/detail/ReviewDetailDialog.tsx | 2 +- web/src/pages/ConfigEditor.tsx | 64 +++++++++++-------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/web/src/components/overlay/detail/AnnotationSettingsPane.tsx b/web/src/components/overlay/detail/AnnotationSettingsPane.tsx index 64f41d4d4..e073f72a6 100644 --- a/web/src/components/overlay/detail/AnnotationSettingsPane.tsx +++ b/web/src/components/overlay/detail/AnnotationSettingsPane.tsx @@ -121,7 +121,7 @@ export function AnnotationSettingsPane({ } return ( -
+
Annotation Settings @@ -152,8 +152,8 @@ export function AnnotationSettingsPane({ render={({ field }) => ( Annotation Offset -
-
+
+
This data comes from your camera's detect feed but is @@ -161,7 +161,7 @@ export function AnnotationSettingsPane({ unlikely that the two streams are perfectly in sync. As a result, the bounding box and the footage will not line up perfectly. However, the annotation_offset{" "} - field in your config can be used to adjust this. + field can be used to adjust this.
- + {eventSequence.map((item, index) => ( diff --git a/web/src/components/overlay/detail/ReviewDetailDialog.tsx b/web/src/components/overlay/detail/ReviewDetailDialog.tsx index 44e569c7a..b90d438d0 100644 --- a/web/src/components/overlay/detail/ReviewDetailDialog.tsx +++ b/web/src/components/overlay/detail/ReviewDetailDialog.tsx @@ -234,7 +234,7 @@ export default function ReviewDetailDialog({ )} {pane == "details" && selectedEvent && ( -
+
)} diff --git a/web/src/pages/ConfigEditor.tsx b/web/src/pages/ConfigEditor.tsx index dc351e7f7..58a36b65d 100644 --- a/web/src/pages/ConfigEditor.tsx +++ b/web/src/pages/ConfigEditor.tsx @@ -31,6 +31,7 @@ function ConfigEditor() { const editorRef = useRef(null); const modelRef = useRef(null); const configRef = useRef(null); + const schemaConfiguredRef = useRef(false); const onHandleSaveConfig = useCallback( async (save_option: SaveOptions) => { @@ -79,50 +80,59 @@ function ConfigEditor() { return; } - if (modelRef.current != null) { - // we don't need to recreate the editor if it already exists - editorRef.current?.layout(); - return; + const modelUri = monaco.Uri.parse( + `a://b/api/config/schema_${Date.now()}.json`, + ); + + // Configure Monaco YAML schema only once + if (!schemaConfiguredRef.current) { + configureMonacoYaml(monaco, { + enableSchemaRequest: true, + hover: true, + completion: true, + validate: true, + format: true, + schemas: [ + { + uri: `${apiHost}api/config/schema.json`, + fileMatch: [String(modelUri)], + }, + ], + }); + schemaConfiguredRef.current = true; } - const modelUri = monaco.Uri.parse("a://b/api/config/schema.json"); - - if (monaco.editor.getModels().length > 0) { - modelRef.current = monaco.editor.getModel(modelUri); - } else { + if (!modelRef.current) { modelRef.current = monaco.editor.createModel(config, "yaml", modelUri); + } else { + modelRef.current.setValue(config); } - configureMonacoYaml(monaco, { - enableSchemaRequest: true, - hover: true, - completion: true, - validate: true, - format: true, - schemas: [ - { - uri: `${apiHost}api/config/schema.json`, - fileMatch: [String(modelUri)], - }, - ], - }); - const container = configRef.current; - if (container != null) { + if (container && !editorRef.current) { editorRef.current = monaco.editor.create(container, { language: "yaml", model: modelRef.current, scrollBeyondLastLine: false, theme: (systemTheme || theme) == "dark" ? "vs-dark" : "vs-light", }); + } else if (editorRef.current) { + editorRef.current.setModel(modelRef.current); } return () => { - configRef.current = null; - modelRef.current = null; + if (editorRef.current) { + editorRef.current.dispose(); + editorRef.current = null; + } + if (modelRef.current) { + modelRef.current.dispose(); + modelRef.current = null; + } + schemaConfiguredRef.current = false; }; - }); + }, [config, apiHost, systemTheme, theme]); // monitoring state From 535bf6e4b90dcf3a860d015a0c12476c8da63cfb Mon Sep 17 00:00:00 2001 From: Sean Kelly Date: Thu, 19 Sep 2024 18:21:04 -0700 Subject: [PATCH 33/36] Update install_deps.sh fix typo (#13842) --- docker/main/install_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/main/install_deps.sh b/docker/main/install_deps.sh index 9664c4574..e640a61d1 100755 --- a/docker/main/install_deps.sh +++ b/docker/main/install_deps.sh @@ -56,7 +56,7 @@ if [[ "${TARGETARCH}" == "arm64" ]]; then wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linuxarm64-gpl-5.1.tar.xz" tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay - wget -qO btbn-ffmpeg.tar.xz "hhttps://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linuxarm64-gpl-7.0.tar.xz" + wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linuxarm64-gpl-7.0.tar.xz" tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay fi From 1a51ce712c3e0364f5d99e640be79ab99fd19b5f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 20 Sep 2024 09:49:44 -0600 Subject: [PATCH 34/36] Simplify tensorrt building (#13851) * Simplify tensorrt building * Use engine context cache --- docker/tensorrt/Dockerfile.amd64 | 4 +--- frigate/detectors/plugins/onnx.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/tensorrt/Dockerfile.amd64 b/docker/tensorrt/Dockerfile.amd64 index 61d3264c9..3dcb42658 100644 --- a/docker/tensorrt/Dockerfile.amd64 +++ b/docker/tensorrt/Dockerfile.amd64 @@ -3,8 +3,6 @@ # https://askubuntu.com/questions/972516/debian-frontend-environment-variable ARG DEBIAN_FRONTEND=noninteractive -ARG TRT_BASE=nvcr.io/nvidia/tensorrt:23.03-py3 - # Make this a separate target so it can be built/cached optionally FROM wheels as trt-wheels ARG DEBIAN_FRONTEND @@ -15,7 +13,7 @@ COPY docker/tensorrt/requirements-amd64.txt /requirements-tensorrt.txt RUN mkdir -p /trt-wheels && pip3 wheel --wheel-dir=/trt-wheels -r /requirements-tensorrt.txt # Build CuDNN -FROM ${TRT_BASE} AS cudnn-deps +FROM wget AS cudnn-deps ARG COMPUTE_LEVEL diff --git a/frigate/detectors/plugins/onnx.py b/frigate/detectors/plugins/onnx.py index a0f5552d9..f3e15422b 100644 --- a/frigate/detectors/plugins/onnx.py +++ b/frigate/detectors/plugins/onnx.py @@ -49,7 +49,9 @@ class ONNXDetector(DetectionApi): "trt_timing_cache_enable": True, "trt_timing_cache_path": "/config/model_cache/tensorrt/ort", "trt_engine_cache_enable": True, + "trt_dump_ep_context_model": True, "trt_engine_cache_path": "/config/model_cache/tensorrt/ort/trt-engines", + "trt_ep_context_file_path": "/config/model_cache/tensorrt/ort", } ) elif provider == "OpenVINOExecutionProvider": From 176af55e8c86ecc363f6e3ed1dfb441dcb0a56c6 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:05:55 -0500 Subject: [PATCH 35/36] Fix similarity search (#13856) * add event_id param to api * exclude query from filtertype * update review pane link for similarity search * update filter group for similarity param and fix switch bug * unneeded prop * update query and input for similarity search param * use undefined instead of empty string for query with similarity search --- frigate/api/event.py | 7 +- .../components/filter/SearchFilterGroup.tsx | 6 +- web/src/components/input/InputWithTags.tsx | 95 ++++++++-------- .../overlay/detail/ReviewDetailDialog.tsx | 4 +- web/src/pages/Explore.tsx | 104 ++++++++---------- web/src/types/search.ts | 2 +- web/src/views/search/SearchView.tsx | 1 - 7 files changed, 100 insertions(+), 119 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index fd3c4ad0b..a49b8942d 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -353,7 +353,10 @@ def events_search(): after = request.args.get("after", type=float) before = request.args.get("before", type=float) - if not query: + # for similarity search + event_id = request.args.get("event_id", type=str) + + if not query and not event_id: return make_response( jsonify( { @@ -432,7 +435,7 @@ def events_search(): if search_type == "similarity": # Grab the ids of events that match the thumbnail image embeddings try: - search_event: Event = Event.get(Event.id == query) + search_event: Event = Event.get(Event.id == event_id) except DoesNotExist: return make_response( jsonify( diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 81090958e..834e9e99b 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -43,7 +43,6 @@ type SearchFilterGroupProps = { className: string; filters?: SearchFilters[]; filter?: SearchFilter; - searchTerm: string; filterList?: FilterList; onUpdateFilter: (filter: SearchFilter) => void; }; @@ -51,7 +50,6 @@ export default function SearchFilterGroup({ className, filters = DEFAULT_REVIEW_FILTERS, filter, - searchTerm, filterList, onUpdateFilter, }: SearchFilterGroupProps) { @@ -213,7 +211,7 @@ export default function SearchFilterGroup({ )} {config?.semantic_search?.enabled && filters.includes("source") && - !searchTerm.includes("similarity:") && ( + !filter?.search_type?.includes("similarity") && ( { const updatedSources = currentSearchSources ? [...currentSearchSources] diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 218f4e34e..c21dba733 100644 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -180,17 +180,11 @@ export default function InputWithTags({ const createFilter = useCallback( (type: FilterType, value: string) => { - if ( - allSuggestions[type as keyof SearchFilter]?.includes(value) || - type === "before" || - type === "after" - ) { + if (allSuggestions[type as FilterType]?.includes(value)) { const newFilters = { ...filters }; let timestamp = 0; switch (type) { - case "query": - break; case "before": case "after": timestamp = convertLocalDateToTimestamp(value); @@ -268,9 +262,7 @@ export default function InputWithTags({ (filterType: FilterType, filterValue: string) => { const trimmedValue = filterValue.trim(); if ( - allSuggestions[filterType as keyof SearchFilter]?.includes( - trimmedValue, - ) || + allSuggestions[filterType]?.includes(trimmedValue) || ((filterType === "before" || filterType === "after") && trimmedValue.match(/^\d{8}$/)) ) { @@ -429,14 +421,14 @@ export default function InputWithTags({ }, [currentFilterType, inputValue, updateSuggestions]); useEffect(() => { - if (search?.startsWith("similarity:")) { + if (filters?.search_type && filters?.search_type.includes("similarity")) { setIsSimilaritySearch(true); setInputValue(""); } else { setIsSimilaritySearch(false); setInputValue(search || ""); } - }, [search]); + }, [filters, search]); return ( <> @@ -585,56 +577,57 @@ export default function InputWithTags({ )} {Object.entries(filters).map(([filterType, filterValues]) => - Array.isArray(filterValues) ? ( - filterValues - .filter(() => filterType !== "query") - .map((value, index) => ( + Array.isArray(filterValues) + ? filterValues + .filter(() => filterType !== "query") + .filter(() => !filterValues.includes("similarity")) + .map((value, index) => ( + + {filterType.replaceAll("_", " ")}:{" "} + {value.replaceAll("_", " ")} + + + )) + : filterType !== "event_id" && ( - {filterType.replaceAll("_", " ")}:{" "} - {value.replaceAll("_", " ")} + {filterType}: + {filterType === "before" || filterType === "after" + ? new Date( + (filterType === "before" + ? (filterValues as number) + 1 + : (filterValues as number)) * 1000, + ).toLocaleDateString( + window.navigator?.language || "en-US", + ) + : filterValues} - )) - ) : ( - - {filterType}: - {filterType === "before" || filterType === "after" - ? new Date( - (filterType === "before" - ? (filterValues as number) + 1 - : (filterValues as number)) * 1000, - ).toLocaleDateString( - window.navigator?.language || "en-US", - ) - : filterValues} - - - ), + ), )}
diff --git a/web/src/components/overlay/detail/ReviewDetailDialog.tsx b/web/src/components/overlay/detail/ReviewDetailDialog.tsx index b90d438d0..6701fa6d7 100644 --- a/web/src/components/overlay/detail/ReviewDetailDialog.tsx +++ b/web/src/components/overlay/detail/ReviewDetailDialog.tsx @@ -370,7 +370,9 @@ function EventItem({ { - navigate(`/explore?similarity_search_id=${event.id}`); + navigate( + `/explore?search_type=similarity&event_id=${event.id}`, + ); }} > diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 8accfd18f..a1be7badc 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -1,5 +1,4 @@ import { useApiFilterArgs } from "@/hooks/use-api-filter"; -import { useSearchEffect } from "@/hooks/use-overlay-state"; import { SearchFilter, SearchQuery, SearchResult } from "@/types/search"; import SearchView from "@/views/search/SearchView"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -21,37 +20,22 @@ export default function Explore() { [searchSearchParams], ); - // search filter - - const similaritySearch = useMemo(() => { - if (!searchTerm.includes("similarity:")) { - return undefined; - } - - return searchTerm.split(":")[1]; - }, [searchTerm]); - - // search api - - useSearchEffect("query", (query) => { - setSearch(query); - return false; - }); - - useSearchEffect("similarity_search_id", (similarityId) => { - setSearch(`similarity:${similarityId}`); - // @ts-expect-error we want to clear this - setSearchFilter({ ...searchFilter, similarity_search_id: undefined }); - return false; - }); + const similaritySearch = useMemo( + () => searchSearchParams["search_type"] == "similarity", + [searchSearchParams], + ); useEffect(() => { if (!searchTerm && !search) { return; } + // switch back to normal search when query is entered setSearchFilter({ ...searchFilter, + search_type: + similaritySearch && search ? undefined : searchFilter?.search_type, + event_id: similaritySearch && search ? undefined : searchFilter?.event_id, query: search.length > 0 ? search : undefined, }); // only update when search is updated @@ -59,41 +43,18 @@ export default function Explore() { }, [search]); const searchQuery: SearchQuery = useMemo(() => { - if (similaritySearch) { - return [ - "events/search", - { - query: similaritySearch, - cameras: searchSearchParams["cameras"], - labels: searchSearchParams["labels"], - sub_labels: searchSearchParams["subLabels"], - zones: searchSearchParams["zones"], - before: searchSearchParams["before"], - after: searchSearchParams["after"], - include_thumbnails: 0, - search_type: "similarity", - }, - ]; + // no search parameters + if (searchSearchParams && Object.keys(searchSearchParams).length === 0) { + return null; } - if (searchTerm) { - return [ - "events/search", - { - query: searchTerm, - cameras: searchSearchParams["cameras"], - labels: searchSearchParams["labels"], - sub_labels: searchSearchParams["subLabels"], - zones: searchSearchParams["zones"], - before: searchSearchParams["before"], - after: searchSearchParams["after"], - search_type: searchSearchParams["search_type"], - include_thumbnails: 0, - }, - ]; - } - - if (searchSearchParams && Object.keys(searchSearchParams).length !== 0) { + // parameters, but no search term and not similarity + if ( + searchSearchParams && + Object.keys(searchSearchParams).length !== 0 && + !searchTerm && + !similaritySearch + ) { return [ "events", { @@ -112,7 +73,26 @@ export default function Explore() { ]; } - return null; + // parameters and search term + if (!similaritySearch) { + setSearch(searchTerm); + } + + return [ + "events/search", + { + query: similaritySearch ? undefined : searchTerm, + cameras: searchSearchParams["cameras"], + labels: searchSearchParams["labels"], + sub_labels: searchSearchParams["subLabels"], + zones: searchSearchParams["zones"], + before: searchSearchParams["before"], + after: searchSearchParams["after"], + search_type: searchSearchParams["search_type"], + event_id: searchSearchParams["event_id"], + include_thumbnails: 0, + }, + ]; }, [searchTerm, searchSearchParams, similaritySearch]); // paging @@ -205,7 +185,13 @@ export default function Explore() { searchResults={searchResults} isLoading={(isLoadingInitialData || isLoadingMore) ?? true} setSearch={setSearch} - setSimilaritySearch={(search) => setSearch(`similarity:${search.id}`)} + setSimilaritySearch={(search) => { + setSearchFilter({ + ...searchFilter, + search_type: ["similarity"], + event_id: search.id, + }); + }} setSearchFilter={setSearchFilter} onUpdateFilter={setSearchFilter} loadMore={loadMore} diff --git a/web/src/types/search.ts b/web/src/types/search.ts index 54cd7c948..c83e1aed2 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -56,7 +56,7 @@ export type SearchQueryParams = { }; export type SearchQuery = [string, SearchQueryParams] | null; -export type FilterType = keyof SearchFilter; +export type FilterType = Exclude; export type SavedSearchQuery = { name: string; diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index bdde82115..56257ee3e 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -281,7 +281,6 @@ export default function SearchView({ "w-full justify-between md:justify-start lg:justify-end", )} filter={searchFilter} - searchTerm={searchTerm} onUpdateFilter={onUpdateFilter} /> )} From 61a4a4bc2f68692fca50fe2add0f796a4f4e450f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 20 Sep 2024 16:20:11 -0600 Subject: [PATCH 36/36] Update openvino to 2024.3 (#13861) --- docker/main/requirements-wheels-post.txt | 4 ++-- docker/main/requirements-wheels.txt | 4 ++-- frigate/detectors/plugins/onnx.py | 10 ++++++++-- frigate/detectors/plugins/openvino.py | 6 ------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docker/main/requirements-wheels-post.txt b/docker/main/requirements-wheels-post.txt index c4ed33844..a1686f091 100644 --- a/docker/main/requirements-wheels-post.txt +++ b/docker/main/requirements-wheels-post.txt @@ -1,3 +1,3 @@ # ONNX -onnxruntime-openvino == 1.18.* ; platform_machine == 'x86_64' -onnxruntime == 1.18.* ; platform_machine == 'aarch64' \ No newline at end of file +onnxruntime-openvino == 1.19.* ; platform_machine == 'x86_64' +onnxruntime == 1.19.* ; platform_machine == 'aarch64' \ No newline at end of file diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 639d4b3c8..a704cde16 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -28,8 +28,8 @@ norfair == 2.2.* setproctitle == 1.3.* ws4py == 0.5.* unidecode == 1.3.* -# OpenVino & ONNX -openvino == 2024.1.* +# OpenVino (ONNX installed in wheels-post) +openvino == 2024.3.* # Embeddings chromadb == 0.5.0 onnx_clip == 4.0.* diff --git a/frigate/detectors/plugins/onnx.py b/frigate/detectors/plugins/onnx.py index f3e15422b..f690a232f 100644 --- a/frigate/detectors/plugins/onnx.py +++ b/frigate/detectors/plugins/onnx.py @@ -2,6 +2,7 @@ import logging import os import numpy as np +from pydantic import Field from typing_extensions import Literal from frigate.detectors.detection_api import DetectionApi @@ -17,6 +18,7 @@ DETECTOR_KEY = "onnx" class ONNXDetectorConfig(BaseDetectorConfig): type: Literal[DETECTOR_KEY] + device: str = Field(default="AUTO", title="Device Type") class ONNXDetector(DetectionApi): @@ -36,7 +38,11 @@ class ONNXDetector(DetectionApi): path = detector_config.model.path logger.info(f"ONNX: loading {detector_config.model.path}") - providers = ort.get_available_providers() + providers = ( + ["CPUExecutionProvider"] + if detector_config.device == "CPU" + else ort.get_available_providers() + ) options = [] for provider in providers: @@ -59,7 +65,7 @@ class ONNXDetector(DetectionApi): options.append( { "cache_dir": "/config/model_cache/openvino/ort", - "device_type": "GPU", + "device_type": detector_config.device, } ) else: diff --git a/frigate/detectors/plugins/openvino.py b/frigate/detectors/plugins/openvino.py index 897b84430..5dc998487 100644 --- a/frigate/detectors/plugins/openvino.py +++ b/frigate/detectors/plugins/openvino.py @@ -30,12 +30,6 @@ class OvDetector(DetectionApi): self.h = detector_config.model.height self.w = detector_config.model.width - if detector_config.device == "AUTO": - logger.warning( - "OpenVINO AUTO device type is not currently supported. Attempting to use GPU instead." - ) - detector_config.device = "GPU" - if not os.path.isfile(detector_config.model.path): logger.error(f"OpenVino model file {detector_config.model.path} not found.") raise FileNotFoundError