Compare commits

..
Author SHA1 Message Date
Josh Hawkins 95c67f04d5 fix typos 2026-07-10 08:03:51 -05:00
Josh Hawkins 6dc121dc81 update autotracking faq 2026-07-10 07:42:01 -05:00
Josh Hawkins c97fe7ca62 update plus docs 2026-07-09 11:29:13 -05:00
Josh Hawkins 8b9e08f6a6 add enable http link for reolinks 2026-07-08 19:10:22 -05:00
Josh Hawkins 1d19e8adb7 add faqitem component 2026-07-08 17:32:03 -05:00
Josh Hawkins d13a93f4bf improve faq grouping 2026-07-08 16:48:24 -05:00
Josh Hawkins 8edcbf1907 clarify 2026-07-08 16:15:46 -05:00
Josh Hawkins 9289253ba8 update face recognition docs 2026-07-08 16:14:20 -05:00
35 changed files with 98 additions and 324 deletions
+5 -7
View File
@@ -10,8 +10,7 @@ body:
Before submitting, read the [beta documentation][docs].
[docs]: https://docs-dev.frigate.video/
[discussions]: https://github.com/blakeblackshear/frigate/discussions
[docs]: https://deploy-preview-19787--frigate-docs.netlify.app/
- type: textarea
id: description
attributes:
@@ -23,8 +22,8 @@ body:
id: version
attributes:
label: Beta Version
description: Visible on the System Metrics page in the Web UI. Please include the full version including the build identifier (eg. 0.18.0-beta1, 0.18.0-8b72c7a, etc.)
placeholder: "0.18.0-beta1"
description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.17.0-beta1)
placeholder: "0.17.0-beta1"
validations:
required: true
- type: dropdown
@@ -72,12 +71,11 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxomox via VM
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
@@ -87,12 +87,11 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxomox via VM
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
@@ -73,12 +73,11 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxomox via VM
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
@@ -53,12 +53,11 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxomox via VM
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
@@ -73,12 +73,11 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxmox via VM
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
@@ -69,12 +69,11 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxomox via VM
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
+2 -6
View File
@@ -6,7 +6,7 @@ body:
value: |
Use this form to submit a reproducible bug in Frigate or Frigate's UI.
**⚠️ If you are running a beta version (0.18.0-beta or similar), please use the [Beta Support template](https://github.com/blakeblackshear/frigate/discussions/new?category=beta-support) instead.**
**⚠️ If you are running a beta version (0.17.0-beta or similar), please use the [Beta Support template](https://github.com/blakeblackshear/frigate/discussions/new?category=beta-support) instead.**
Before submitting your bug report, please ask the AI with the "Ask AI" button on the [official documentation site][ai] about your issue, [search the discussions][discussions], look at recent open and closed [pull requests][prs], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your bug has already been fixed by the developers or reported by the community.
@@ -116,13 +116,9 @@ body:
attributes:
label: Install method
options:
- Home Assistant App
- Home Assistant Add-on
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via installation script
- Proxomox via VM
- Windows WSL2
validations:
required: true
- type: dropdown
+1 -1
View File
@@ -63,7 +63,7 @@ RUN --mount=type=bind,source=docker/main/build_intel_media_driver.sh,target=/dep
FROM scratch AS go2rtc
ARG TARGETARCH
WORKDIR /rootfs/usr/local/go2rtc/bin
ADD --link --chmod=755 "https://github.com/AlexxIT/go2rtc/releases/download/v1.9.14/go2rtc_linux_${TARGETARCH}" go2rtc
ADD --link --chmod=755 "https://github.com/AlexxIT/go2rtc/releases/download/v1.9.13/go2rtc_linux_${TARGETARCH}" go2rtc
FROM wget AS tempio
ARG TARGETARCH
@@ -820,7 +820,7 @@ classification:
interval: None
# Optional: Restream configuration
# Uses https://github.com/AlexxIT/go2rtc (v1.9.14)
# Uses https://github.com/AlexxIT/go2rtc (v1.9.13)
# NOTE: The default go2rtc API port (1984) must be used,
# changing this port for the integrated go2rtc instance is not supported.
go2rtc:
+5 -5
View File
@@ -72,10 +72,10 @@ Variables prefixed with `FRIGATE_` can be referenced in config fields that suppo
Navigate to <NavPath path="Settings > System > Environment variables" /> to add or edit environment variables.
| Field | Description |
| ----------------- | --------------------------------------------------------- |
| **Variable name** | The environment variable name (e.g., `FRIGATE_MQTT_USER`) |
| **Value** | The value for the variable |
| Field | Description |
| --------- | --------------------------------------------------------- |
| **Key** | The environment variable name (e.g., `FRIGATE_MQTT_USER`) |
| **Value** | The value for the variable |
Variables defined here can be referenced elsewhere in your configuration using the `{FRIGATE_VARIABLE_NAME}` syntax.
@@ -354,7 +354,7 @@ To do this:
### Custom go2rtc version
Frigate currently includes go2rtc v1.9.14, there may be certain cases where you want to run a different version of go2rtc.
Frigate currently includes go2rtc v1.9.13, there may be certain cases where you want to run a different version of go2rtc.
To do this:
+3 -3
View File
@@ -204,7 +204,7 @@ go2rtc:
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password#video=copy#audio=copy#audio=opus"
your_reolink_camera_sub:
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
# example for connecting to a Reolink camera that supports two way talk
# example for connectin to a Reolink camera that supports two way talk
your_reolink_camera_twt:
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password#video=copy#audio=copy#audio=opus"
- "rtsp://username:password@reolink_ip/Preview_01_sub"
@@ -249,7 +249,7 @@ cameras:
:::note
Unifi G5s cameras and newer need a Unifi Protect server to enable rtsps stream, it's not possible to enable it in standalone mode.
Unifi G5s cameras and newer need a Unifi Protect server to enable rtsps stream, it's not posible to enable it in standalone mode.
:::
@@ -264,7 +264,7 @@ go2rtc:
- rtspx://192.168.1.1:7441/abcdefghijk
```
[See the go2rtc docs for more information](https://github.com/AlexxIT/go2rtc/tree/v1.9.14#source-rtsp)
[See the go2rtc docs for more information](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-rtsp)
In the Unifi 2.0 update Unifi Protect Cameras had a change in audio sample rate which causes issues for ffmpeg. The input rate needs to be set for record if used directly with unifi protect.
+1 -1
View File
@@ -25,7 +25,7 @@ This guide is mainly useful if you are **upgrading from an older version and hav
If you added your cameras with the wizard, go2rtc is already configured. You can skip straight to [troubleshooting](/troubleshooting/go2rtc). The steps below are for upgrading users with existing cameras that aren't using go2rtc yet, or for anyone who prefers to configure a stream by hand.
Configure go2rtc to connect to your camera by adding the stream you want to use for live view. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.9.14#module-streams), not just rtsp.
Configure go2rtc to connect to your camera by adding the stream you want to use for live view. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#module-streams), not just rtsp.
:::tip
@@ -477,7 +477,7 @@ Error marking filters as finished
Restarting ffmpeg...
```
you should try to upgrade to FFmpeg 7. This can be done using this config option:
you should try to uprade to FFmpeg 7. This can be done using this config option:
```yaml
ffmpeg:
+2 -2
View File
@@ -743,13 +743,13 @@ config:
quant_img_RGB2BGR: true
```
Explanation of the parameters:
Explanation of the paramters:
- `soc`: A list of all SoCs you want to build the rknn model for. If you don't specify this parameter, the script tries to find out your SoC and builds the rknn model for this one.
- `quantization`: true: 8 bit integer (i8) quantization, false: 16 bit float (fp16). Default: false.
- `output_name`: The output name of the model. The following variables are available:
- `quant`: "i8" or "fp16" depending on the config
- `input_basename`: the basename of the input model (e.g. "my_model" if the input model is called "my_model.onnx")
- `input_basename`: the basename of the input model (e.g. "my_model" if the input model is calles "my_model.onnx")
- `soc`: the SoC this model was build for (e.g. "rk3588")
- `tk_version`: Version of `rknn-toolkit2` (e.g. "2.3.0")
- **example**: Specifying `output_name = "frigate-{quant}-{input_basename}-{soc}-v{tk_version}"` could result in a model called `frigate-i8-my_model-rk3588-v2.3.0.rknn`.
+2 -2
View File
@@ -11,7 +11,7 @@ import NavPath from "@site/src/components/NavPath";
Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://<frigate_host>:8554/<camera_name>`. Port 8554 must be open. [This allows you to use a video feed for detection in Frigate and Home Assistant live view at the same time without having to make two separate connections to the camera](#reduce-connections-to-camera). The video feed is copied from the original video feed directly to avoid re-encoding. This feed does not include any annotation by Frigate.
Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc/tree/v1.9.14) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc/tree/v1.9.14#configuration) for more advanced configurations and features.
Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc/tree/v1.9.13) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#configuration) for more advanced configurations and features.
:::note
@@ -236,7 +236,7 @@ Enabling arbitrary exec sources allows execution of arbitrary commands through g
## Advanced Restream Configurations
The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.9.14#source-exec) source in go2rtc can be used for custom ffmpeg commands and other applications. An example is below:
The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-exec) source in go2rtc can be used for custom ffmpeg commands and other applications. An example is below:
:::warning
@@ -43,7 +43,7 @@ Let's look at an example use case: I want to record any cars that enter my drive
One might simply think "Why not just run object detection any time there is motion around the driveway area and notify if the bounding box is in that zone?"
With that approach, what video is related to the car that entered the driveway? Did it come from the left or right? Was it parked across the street for an hour before turning into the driveway? One approach is to just record 24/7 or for motion (on any changed pixels) and not attempt to do that at all. This is what most other NVRs do. Just don't even try to identify a start and end for that object since it's hard and you will be wrong some portion of the time.
With that approach, what video is related to the car that entered the driveway? Did it come from the left or right? Was it parked across the street for an hour before turning into the driveway? One approach is to just record 24/7 or for motion (on any changed changed pixels) and not attempt to do that at all. This is what most other NVRs do. Just don't even try to identify a start and end for that object since it's hard and you will be wrong some portion of the time.
Couldn't you just look at when motion stopped and started? Motion for a video feed is nothing more than looking for pixels that are different than they were in previous frames. If the car entered the driveway while someone was mowing the grass, how would you know which motion was for the car and which was for the person when they mow along the driveway or street? What if another car was driving the other direction on the street? Or what if its a windy day and the bush by your mailbox is blowing around?
+9 -9
View File
@@ -32,7 +32,7 @@ The following models are downloaded automatically the first time their associate
| [License plate recognition](/configuration/license_plate_recognition) | PaddleOCR (detection, classification, recognition) + YOLOv9 plate detector | GitHub |
| [Bird classification](/configuration/bird_classification) | MobileNetV2 bird model + label map | GitHub |
| [Custom classification](/configuration/custom_classification/state_classification) (training) | MobileNetV2 ImageNet base weights (via Keras) | Google storage |
| [Audio transcription](/configuration/advanced/system) | Whisper or Sherpa-ONNX streaming model | HuggingFace / OpenAI |
| [Audio transcription](/configuration/advanced/system) | Whisper or Sherpa-ONNX streaming model | HuggingFace / OpenAI |
### Hardware-Specific Detector Models
@@ -91,13 +91,13 @@ See [Frigate+](/integrations/plus) for details.
When a Generative AI provider is configured, Frigate sends images and prompts to the configured provider for event descriptions, chat, and camera monitoring. Available providers:
| Provider | Internet Required |
| ------------- | --------------------------------------------------------------- |
| OpenAI | Yes, connects to OpenAI API (or custom base URL) |
| Google Gemini | Yes, connects to Google Generative AI API |
| Azure OpenAI | Yes, connects to your Azure endpoint |
| Ollama | Depends: typically local (`localhost:11434`), but can be remote |
| llama.cpp | No, runs entirely locally |
| Provider | Internet Required |
| ------------- | ---------------------------------------------------------------- |
| OpenAI | Yes, connects to OpenAI API (or custom base URL) |
| Google Gemini | Yes, connects to Google Generative AI API |
| Azure OpenAI | Yes, connects to your Azure endpoint |
| Ollama | Depends: typically local (`localhost:11434`), but can be remote |
| llama.cpp | No, runs entirely locally |
Disable Generative AI by removing the `genai` configuration from your cameras. See [Generative AI](/configuration/genai/genai_config) for details.
@@ -131,7 +131,7 @@ For [WebRTC live streaming](/configuration/live), Frigate uses STUN for NAT trav
## Home Assistant Supervisor
When running as a Home Assistant App, the go2rtc startup script queries the local Supervisor API (`http://supervisor/`) to discover the host IP address and WebRTC port. This is a local network call to the Home Assistant host, not an internet connection.
When running as a Home Assistant add-on, the go2rtc startup script queries the local Supervisor API (`http://supervisor/`) to discover the host IP address and WebRTC port. This is a local network call to the Home Assistant host, not an internet connection.
## What Does NOT Require Internet
+1 -1
View File
@@ -122,7 +122,7 @@ Use `http://<frigate_device_ip>:8971` as the URL for the integration so that aut
The above URL assumes you have [disabled TLS](../configuration/tls).
By default, TLS is enabled and Frigate will be using a self-signed certificate. HomeAssistant will fail to connect HTTPS to port 8971 since it fails to verify the self-signed certificate.
Either disable TLS and use HTTP from HomeAssistant, or configure Frigate to be accessible with a valid certificate.
Either disable TLS and use HTTP from HomeAssistant, or configure Frigate to be acessible with a valid certificate.
:::
+1 -1
View File
@@ -280,7 +280,7 @@ Same data available at `/api/stats` published at a configurable interval.
### `frigate/camera_activity`
Returns data about each camera, its current features, and if it is detecting motion, objects, etc. Can be triggered by publishing to `frigate/onConnect`
Returns data about each camera, its current features, and if it is detecting motion, objects, etc. Can be triggered by publising to `frigate/onConnect`
### `frigate/profile/set`
+1 -1
View File
@@ -76,7 +76,7 @@ This is an issue due to outdated gasket driver when being used with new linux ke
### Not detected on Raspberry Pi5
A kernel update to the RPi5 means an update to config.txt is required, see [the raspberry pi forum for more info](https://forums.raspberrypi.com/viewtopic.php?t=363682&sid=cb59b026a412f0dc041595951273a9ca&start=25)
A kernel update to the RPi5 means an upate to config.txt is required, see [the raspberry pi forum for more info](https://forums.raspberrypi.com/viewtopic.php?t=363682&sid=cb59b026a412f0dc041595951273a9ca&start=25)
Specifically, add the following to config.txt
+2 -2
View File
@@ -220,7 +220,7 @@ go2rtc:
`/dev/dri/renderD128` is the usual render node; on a system with more than one GPU you may need `renderD129` (or higher), and the device must be passed into the container (e.g. `devices: - /dev/dri:/dev/dri` in Docker Compose).
If you use a **different hardware acceleration backend**, you will likely need to specify its device in the same way, using the option that matches that backend instead of `-vaapi_device`. See the [go2rtc FFmpeg source documentation](https://github.com/AlexxIT/go2rtc/tree/v1.9.14#source-ffmpeg) and the upstream report ([go2rtc issue #1984](https://github.com/AlexxIT/go2rtc/issues/1984)) for background and other examples.
If you use a **different hardware acceleration backend**, you will likely need to specify its device in the same way, using the option that matches that backend instead of `-vaapi_device`. See the [go2rtc FFmpeg source documentation](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-ffmpeg) and the upstream report ([go2rtc issue #1984](https://github.com/AlexxIT/go2rtc/issues/1984)) for background and other examples.
:::tip
@@ -230,6 +230,6 @@ If you don't transcode in go2rtc with hardware acceleration, this does not affec
## Home Assistant and port access
When running Frigate as a Home Assistant App, the go2rtc API (port `1984`), the RTSP restream (port `8554`), and WebRTC (port `8555`) are **disabled and hidden by default**. To use them (for example to reach the go2rtc web interface for troubleshooting, or to open a go2rtc stream externally in an app like VLC), go to <NavPath path="Settings > Apps > Frigate > Configuration > Network" />, click **Show disabled ports**, enable the port you need, and save. Use the host's IP address rather than an mDNS name like `homeassistant.local`.
When running Frigate as a Home Assistant add-on, the go2rtc API (port `1984`), the RTSP restream (port `8554`), and WebRTC (port `8555`) are **disabled and hidden by default**. To use them (for example to reach the go2rtc web interface for troubleshooting, or to open a go2rtc stream externally in an app like VLC), go to <NavPath path="Settings > Add-ons > Frigate > Configuration > Network" />, click **Show disabled ports**, enable the port you need, and save. Use the host's IP address rather than an mDNS name like `homeassistant.local`.
If live view works in the Frigate UI but not in Home Assistant, the most common cause is the go2rtc stream name not matching the camera name: name the primary go2rtc stream exactly like the camera, or add a `live -> streams` mapping, so the integration can resolve the restream.
+1 -1
View File
@@ -91,7 +91,7 @@ To act on many objects at once, Ctrl/Cmd-click or right-click to start a selecti
1. Semantic Search is used in conjunction with the other filters available on the Explore page. Use a combination of traditional filtering and Semantic Search for the best results.
2. Use the thumbnail search type when searching for particular objects in the scene. Use the description search type when attempting to discern the intent of your object.
3. Because of how the AI models Frigate uses have been trained, the comparison between text and image embedding distances generally means that with multi-modal (`thumbnail` and `description`) searches, results matching `description` will appear first, even if a `thumbnail` embedding may be a better match. Play with the "Search Type" setting to help find what you are looking for. Note that if you are generating descriptions for specific objects or zones only, this may cause search results to prioritize the objects with descriptions even if the ones without them are more relevant.
3. Because of how the AI models Frigate uses have been trained, the comparison between text and image embedding distances generally means that with multi-modal (`thumbnail` and `description`) searches, results matching `description` will appear first, even if a `thumbnail` embedding may be a better match. Play with the "Search Type" setting to help find what you are looking for. Note that if you are generating descriptions for specific objects or zones only, this may cause search results to prioritize the objects with descriptions even if the the ones without them are more relevant.
4. Make your search language and tone closely match exactly what you're looking for. If you are using thumbnail search, **phrase your query as an image caption**. Searching for "red car" may not work as well as "red sedan driving down a residential street on a sunny day".
5. Semantic search on thumbnails tends to return better results when matching large subjects that take up most of the frame. Small things like "cat" tend to not work well.
6. Experiment! Find a tracked object you want to test and start typing keywords and phrases to see what works for you.
+1 -1
View File
@@ -132,7 +132,7 @@ const sidebars: SidebarsConfig = {
{
type: "link",
label: "Go2RTC Configuration Reference",
href: "https://github.com/AlexxIT/go2rtc/tree/v1.9.14#configuration",
href: "https://github.com/AlexxIT/go2rtc/tree/v1.9.13#configuration",
} as PropSidebarItemLink,
],
},
+18 -35
View File
@@ -200,9 +200,6 @@ class EmbeddingMaintainer(threading.Thread):
)
for model_config in self.config.classification.custom.values():
if not model_config.enabled:
continue
self.realtime_processors.append(
CustomStateClassificationProcessor(
self.config, model_config, self.requestor, self.metrics
@@ -335,25 +332,6 @@ class EmbeddingMaintainer(threading.Thread):
for processor in self.post_processors:
processor.update_config(topic, payload)
def _remove_custom_classification_processor(self, model_name: str) -> None:
"""Shut down and drop any running processor for a custom model."""
remaining = []
for processor in self.realtime_processors:
if (
isinstance(
processor,
(
CustomStateClassificationProcessor,
CustomObjectClassificationProcessor,
),
)
and processor.model_config.name == model_name
):
processor.shutdown()
else:
remaining.append(processor)
self.realtime_processors = remaining
def _handle_custom_classification_update(
self, topic: str, model_config: Any
) -> None:
@@ -361,7 +339,23 @@ class EmbeddingMaintainer(threading.Thread):
model_name = topic.split("/")[-1]
if model_config is None:
self._remove_custom_classification_processor(model_name)
remaining = []
for processor in self.realtime_processors:
if (
isinstance(
processor,
(
CustomStateClassificationProcessor,
CustomObjectClassificationProcessor,
),
)
and processor.model_config.name == model_name
):
processor.shutdown()
else:
remaining.append(processor)
self.realtime_processors = remaining
logger.info(
f"Successfully removed classification processor for model: {model_name}"
)
@@ -369,13 +363,6 @@ class EmbeddingMaintainer(threading.Thread):
self.config.classification.custom[model_name] = model_config
# A disabled model must not run; tear down any existing processor and
# do not register a new one.
if not model_config.enabled:
self._remove_custom_classification_processor(model_name)
logger.info(f"Disabled classification processor for model: {model_name}")
return
# Check if processor already exists
for processor in self.realtime_processors:
if isinstance(
@@ -715,11 +702,7 @@ class EmbeddingMaintainer(threading.Thread):
and "license_plate" not in camera_config.objects.track
)
has_enabled_custom = any(
c.enabled for c in self.config.classification.custom.values()
)
if not dedicated_lpr_enabled and not has_enabled_custom:
if not dedicated_lpr_enabled and len(self.config.classification.custom) == 0:
# no active features that use this data
return
-106
View File
@@ -1,106 +0,0 @@
"""Tests that disabled custom classification models are not registered or run."""
import sys
import unittest
from unittest.mock import MagicMock
# Mock TFLite before importing the maintainer / classification modules
_MOCK_MODULES = [
"tflite_runtime",
"tflite_runtime.interpreter",
"ai_edge_litert",
"ai_edge_litert.interpreter",
]
for mod in _MOCK_MODULES:
if mod not in sys.modules:
sys.modules[mod] = MagicMock()
from frigate.data_processing.real_time.custom_classification import ( # noqa: E402
CustomObjectClassificationProcessor,
)
from frigate.embeddings.maintainer import EmbeddingMaintainer # noqa: E402
class TestCustomClassificationEnabledGating(unittest.TestCase):
"""A model with enabled: false must not keep a processor registered."""
def _make_maintainer(self) -> EmbeddingMaintainer:
# Bypass the heavy __init__; only the attributes touched by the
# config update path are needed for these tests.
maintainer = EmbeddingMaintainer.__new__(EmbeddingMaintainer)
maintainer.realtime_processors = []
maintainer.config = MagicMock()
maintainer.config.classification.custom = {}
maintainer.requestor = MagicMock()
maintainer.metrics = MagicMock()
maintainer.event_metadata_publisher = MagicMock()
return maintainer
def _make_model_config(self, name: str, enabled: bool) -> MagicMock:
model_config = MagicMock()
model_config.name = name
model_config.enabled = enabled
model_config.state_config = None
return model_config
def _make_processor(self, name: str) -> MagicMock:
processor = MagicMock(spec=CustomObjectClassificationProcessor)
processor.model_config = MagicMock()
processor.model_config.name = name
return processor
def test_disabled_update_tears_down_existing_processor(self):
"""Toggling a running model to disabled shuts down and drops its processor."""
maintainer = self._make_maintainer()
processor = self._make_processor("atli")
maintainer.realtime_processors = [processor]
maintainer._handle_custom_classification_update(
"config/classification/custom/atli",
self._make_model_config("atli", enabled=False),
)
processor.shutdown.assert_called_once()
self.assertEqual(maintainer.realtime_processors, [])
def test_disabled_update_does_not_register_processor(self):
"""A disabled model that has no processor is never registered."""
maintainer = self._make_maintainer()
maintainer._handle_custom_classification_update(
"config/classification/custom/atli",
self._make_model_config("atli", enabled=False),
)
self.assertEqual(maintainer.realtime_processors, [])
def test_disabled_update_leaves_other_processors_untouched(self):
"""Disabling one model must not affect other running processors."""
maintainer = self._make_maintainer()
other = self._make_processor("simbi")
maintainer.realtime_processors = [other]
maintainer._handle_custom_classification_update(
"config/classification/custom/atli",
self._make_model_config("atli", enabled=False),
)
other.shutdown.assert_not_called()
self.assertEqual(maintainer.realtime_processors, [other])
def test_removed_model_tears_down_processor(self):
"""A None payload (model deleted) still shuts down its processor."""
maintainer = self._make_maintainer()
processor = self._make_processor("atli")
maintainer.realtime_processors = [processor]
maintainer._handle_custom_classification_update(
"config/classification/custom/atli", None
)
processor.shutdown.assert_called_once()
self.assertEqual(maintainer.realtime_processors, [])
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -123,7 +123,7 @@
"error": {
"failed": "Failed to queue export: {{error}}",
"endTimeMustAfterStartTime": "End time must be after start time",
"noValidTimeSelected": "No valid time range selected"
"noVaildTimeSelected": "No valid time range selected"
}
},
"fromTimeline": {
+1 -1
View File
@@ -76,7 +76,7 @@
},
"offset": {
"label": "Annotation Offset",
"desc": "This data comes from your camera's detect feed but is overlaid on images from the record feed. It is unlikely that the two streams are perfectly in sync. As a result, the bounding box and the footage will not line up perfectly. You can use this setting to offset the annotations forward or backward in time to better align them with the recorded footage.",
"desc": "This data comes from your camera's detect feed but is overlayed on images from the the record feed. It is unlikely that the two streams are perfectly in sync. As a result, the bounding box and the footage will not line up perfectly. You can use this setting to offset the annotations forward or backward in time to better align them with the recorded footage.",
"millisecondsToOffset": "Milliseconds to offset detect annotations by. <em>Default: 0</em>",
"tips": "Lower the value if the video playback is ahead of the boxes and path points, and increase the value if the video playback is behind them. This value can be negative.",
"toast": {
+1 -8
View File
@@ -1490,14 +1490,7 @@
"keyLabel": "Key",
"valueLabel": "Value",
"keyPlaceholder": "New key",
"remove": "Remove",
"providerNameLabel": "Provider name",
"providerNamePlaceholder": "e.g., openai",
"variableNameLabel": "Variable name",
"variableNamePlaceholder": "e.g., MY_VARIABLE",
"loggerNameLabel": "Logger name",
"loggerNamePlaceholder": "e.g., frigate.record",
"keyPatternError": "Use only letters, numbers, hyphens, and underscores (no spaces)"
"remove": "Remove"
},
"knownPlates": {
"namePlaceholder": "e.g., Wife's Car",
@@ -7,13 +7,7 @@ const environmentVars: SectionConfigOverrides = {
advancedFields: [],
uiSchema: {
additionalProperties: {
"ui:options": {
size: "lg",
additionalPropertyKeyLabel:
"configForm.additionalProperties.variableNameLabel",
additionalPropertyKeyPlaceholder:
"configForm.additionalProperties.variableNamePlaceholder",
},
"ui:options": { size: "lg" },
},
},
},
@@ -9,15 +9,7 @@ const genai: SectionConfigOverrides = {
uiSchema: {
"ui:options": { disableNestedCard: true },
"*": {
"ui:options": {
disableNestedCard: true,
additionalPropertyKeyLabel:
"configForm.additionalProperties.providerNameLabel",
additionalPropertyKeyPlaceholder:
"configForm.additionalProperties.providerNamePlaceholder",
additionalPropertyKeyPattern: "^[a-zA-Z0-9_-]+$",
preventKeyRename: true,
},
"ui:options": { disableNestedCard: true },
"ui:order": [
"provider",
"api_key",
@@ -12,13 +12,7 @@ const logger: SectionConfigOverrides = {
},
logs: {
additionalProperties: {
"ui:options": {
enumI18nPrefix: "logger.logLevel",
additionalPropertyKeyLabel:
"configForm.additionalProperties.loggerNameLabel",
additionalPropertyKeyPlaceholder:
"configForm.additionalProperties.loggerNamePlaceholder",
},
"ui:options": { enumI18nPrefix: "logger.logLevel" },
},
},
},
@@ -6,14 +6,12 @@ import {
StrictRJSFSchema,
WrapIfAdditionalTemplateProps,
} from "@rjsf/utils";
import { useEffect, useMemo, useState, type FocusEvent } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { LuTrash2 } from "react-icons/lu";
import type { ConfigFormContext } from "@/types/configForm";
export function WrapIfAdditionalTemplate<
T = unknown,
@@ -32,7 +30,6 @@ export function WrapIfAdditionalTemplate<
onKeyRenameBlur,
readonly,
required,
registry,
schema,
uiSchema,
} = props;
@@ -41,55 +38,6 @@ export function WrapIfAdditionalTemplate<
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
const uiOptions = getUiOptions(uiSchema);
const keyIsReadonly = uiOptions.additionalPropertyKeyReadonly === true;
const keyLabelKey =
typeof uiOptions.additionalPropertyKeyLabel === "string"
? uiOptions.additionalPropertyKeyLabel
: undefined;
const keyPlaceholderKey =
typeof uiOptions.additionalPropertyKeyPlaceholder === "string"
? uiOptions.additionalPropertyKeyPlaceholder
: undefined;
const keyPattern =
typeof uiOptions.additionalPropertyKeyPattern === "string"
? uiOptions.additionalPropertyKeyPattern
: undefined;
const preventKeyRename = uiOptions.preventKeyRename === true;
const formContext = registry?.formContext as ConfigFormContext | undefined;
// optionally, lock the key once it's been saved
const baseline = formContext?.baselineFormData;
const keyLocked =
preventKeyRename &&
typeof label === "string" &&
!!baseline &&
Object.prototype.hasOwnProperty.call(baseline, label);
// controlled key value so we can validate live and block invalid renames.
const [keyValue, setKeyValue] = useState<string>(label ?? "");
useEffect(() => {
setKeyValue(label ?? "");
}, [label]);
const keyRegex = useMemo(
() => (keyPattern ? new RegExp(keyPattern) : undefined),
[keyPattern],
);
const keyError = useMemo(() => {
if (!keyRegex || keyLocked) return null;
if (!keyRegex.test(keyValue)) {
return t("configForm.additionalProperties.keyPatternError", {
ns: "views/settings",
defaultValue:
"Use only letters, numbers, hyphens, and underscores (no spaces)",
});
}
return null;
}, [keyRegex, keyLocked, keyValue, t]);
if (!additional) {
return (
<div className={classNames} style={style}>
@@ -99,26 +47,20 @@ export function WrapIfAdditionalTemplate<
}
const keyId = `${id}-key`;
const keyLabel = keyLabelKey
? t(keyLabelKey, { ns: "views/settings" })
: t("configForm.additionalProperties.keyLabel", { ns: "views/settings" });
const keyLabel = t("configForm.additionalProperties.keyLabel", {
ns: "views/settings",
});
const valueLabel = t("configForm.additionalProperties.valueLabel", {
ns: "views/settings",
});
const keyPlaceholder = keyPlaceholderKey
? t(keyPlaceholderKey, { ns: "views/settings" })
: t("configForm.additionalProperties.keyPlaceholder", {
ns: "views/settings",
});
const keyPlaceholder = t("configForm.additionalProperties.keyPlaceholder", {
ns: "views/settings",
});
const removeLabel = t("configForm.additionalProperties.remove", {
ns: "views/settings",
});
const commitKeyRename = (e: FocusEvent<HTMLInputElement>) => {
if (readonly) return;
if (keyError) return;
onKeyRenameBlur?.(e);
};
const uiOptions = getUiOptions(uiSchema);
const keyIsReadonly = uiOptions.additionalPropertyKeyReadonly === true;
return (
<div
@@ -128,30 +70,23 @@ export function WrapIfAdditionalTemplate<
{!keyIsReadonly && (
<div className="col-span-12 space-y-2 md:col-span-2">
{displayLabel && <Label htmlFor={keyId}>{keyLabel}</Label>}
{keyLocked ? (
{keyIsReadonly ? (
<div
id={keyId}
className="flex items-center break-all text-sm text-primary-variant"
className="flex items-center text-sm text-muted-foreground"
>
{label}
</div>
) : (
<>
<Input
id={keyId}
name={keyId}
required={required}
value={keyValue}
placeholder={keyPlaceholder}
disabled={disabled || readonly}
onChange={(e) => setKeyValue(e.target.value)}
onBlur={!readonly ? commitKeyRename : undefined}
aria-invalid={keyError ? true : undefined}
/>
{keyError && (
<p className="text-xs text-destructive">{keyError}</p>
)}
</>
<Input
id={keyId}
name={keyId}
required={required}
defaultValue={label}
placeholder={keyPlaceholder}
disabled={disabled || readonly}
onBlur={!readonly ? onKeyRenameBlur : undefined}
/>
)}
</div>
)}
+2 -2
View File
@@ -134,7 +134,7 @@ export default function ExportDialog({
}
if (!range) {
toast.error(t("export.toast.error.noValidTimeSelected"), {
toast.error(t("export.toast.error.noVaildTimeSelected"), {
position: "top-center",
});
return false;
@@ -665,7 +665,7 @@ export function ExportContent({
}
if (!range) {
toast.error(t("export.toast.error.noValidTimeSelected"), {
toast.error(t("export.toast.error.noVaildTimeSelected"), {
position: "top-center",
});
return;
@@ -149,7 +149,7 @@ export default function MobileReviewSettingsDrawer({
if (!range) {
toast.error(
t("export.toast.error.noValidTimeSelected", {
t("export.toast.error.noVaildTimeSelected", {
ns: "components/dialog",
}),
{
@@ -305,7 +305,7 @@ export default function MotionSearchView({
const handleExportPreview = useCallback(() => {
if (!exportRange) {
toast.error(
t("export.toast.error.noValidTimeSelected", {
t("export.toast.error.noVaildTimeSelected", {
ns: "components/dialog",
}),
{
@@ -351,7 +351,7 @@ export default function MotionSearchView({
const handleExportSave = useCallback(() => {
if (!exportRange || !selectedCamera) {
toast.error(
t("export.toast.error.noValidTimeSelected", {
t("export.toast.error.noVaildTimeSelected", {
ns: "components/dialog",
}),
{