From 5d038b5c759b97f925b7b73cdfaf521e9e63cde2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 26 Oct 2025 05:39:09 -0600 Subject: [PATCH 01/78] Update PWA requirements and add usage section (#20562) Added VPN as a secure context option for PWA installation and included a usage section. --- docs/docs/configuration/pwa.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/pwa.md b/docs/docs/configuration/pwa.md index fd1aae554c..fbcbdff62a 100644 --- a/docs/docs/configuration/pwa.md +++ b/docs/docs/configuration/pwa.md @@ -11,7 +11,7 @@ This adds features including the ability to deep link directly into the app. In order to install Frigate as a PWA, the following requirements must be met: -- Frigate must be accessed via a secure context (localhost, secure https, etc.) +- Frigate must be accessed via a secure context (localhost, secure https, VPN, etc.) - On Android, Firefox, Chrome, Edge, Opera, and Samsung Internet Browser all support installing PWAs. - On iOS 16.4 and later, PWAs can be installed from the Share menu in Safari, Chrome, Edge, Firefox, and Orion. @@ -22,3 +22,7 @@ Installation varies slightly based on the device that is being used: - Desktop: Use the install button typically found in right edge of the address bar - Android: Use the `Install as App` button in the more options menu for Chrome, and the `Add app to Home screen` button for Firefox - iOS: Use the `Add to Homescreen` button in the share menu + +## Usage + +Once setup, the Frigate app can be used wherever it has access to Frigate. This means it can be setup as local-only, VPN-only, or fully accessible depending on your needs. From 6fd7f862f51bdd2efc0634d6db7b92ca75547fbb Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 26 Oct 2025 05:56:01 -0600 Subject: [PATCH 02/78] Update coral docs / links (#20674) * Revise GPU and AI accelerator recommendations Updated hardware recommendations for AI acceleration. * Revise PCIe Coral driver installation instructions Updated instructions for PCIe Coral driver installation. * Revise Coral driver installation instructions Updated driver installation instructions for PCIe and M.2 versions of Google Coral. * Change PCIe Coral driver link in getting_started.md Updated the link for PCIe Coral driver instructions. * Change PCIe Coral driver link in installation guide Updated the link for PCIe Coral driver instructions. * Update Coral TPU recommendation in hardware documentation Added a warning about the Coral TPU's recommendation status for new Frigate installations and suggested alternatives. --- README.md | 2 +- docs/docs/frigate/hardware.md | 8 +++++++- docs/docs/frigate/installation.md | 2 +- docs/docs/guides/getting_started.md | 2 +- docs/docs/troubleshooting/edgetpu.md | 3 +-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 35e8cb7e97..825a62884d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A complete and local NVR designed for [Home Assistant](https://www.home-assistant.io) with AI object detection. Uses OpenCV and Tensorflow to perform realtime object detection locally for IP cameras. -Use of a GPU or AI accelerator such as a [Google Coral](https://coral.ai/products/) or [Hailo](https://hailo.ai/) is highly recommended. AI accelerators will outperform even the best CPUs with very little overhead. +Use of a GPU, Integrated GPU, or AI accelerator such as a [Hailo](https://hailo.ai/) is highly recommended. Dedicated hardware will outperform even the best CPUs with very little overhead. - Tight integration with Home Assistant via a [custom component](https://github.com/blakeblackshear/frigate-hass-integration) - Designed to minimize resource use and maximize performance by only looking for objects when and where it is necessary diff --git a/docs/docs/frigate/hardware.md b/docs/docs/frigate/hardware.md index fcb98573f8..61c3df7121 100644 --- a/docs/docs/frigate/hardware.md +++ b/docs/docs/frigate/hardware.md @@ -104,10 +104,16 @@ In real-world deployments, even with multiple cameras running concurrently, Frig ### Google Coral TPU +:::warning + +The Coral is no longer recommended for new Frigate installations, except in deployments with particularly low power requirements or hardware incapable of utilizing alternative AI accelerators for object detection. Instead, we suggest using one of the numerous other supported object detectors. Frigate will continue to provide support for the Coral TPU for as long as practicably possible given its still one of the most power-efficient devices for executing object detection models. + +::: + Frigate supports both the USB and M.2 versions of the Google Coral. - The USB version is compatible with the widest variety of hardware and does not require a driver on the host machine. However, it does lack the automatic throttling features of the other versions. -- The PCIe and M.2 versions require installation of a driver on the host. Follow the instructions for your version from https://coral.ai +- The PCIe and M.2 versions require installation of a driver on the host. https://github.com/jnicolson/gasket-builder should be used. A single Coral can handle many cameras using the default model and will be sufficient for the majority of users. You can calculate the maximum performance of your Coral based on the inference speed reported by Frigate. With an inference speed of 10, your Coral will top out at `1000/10=100`, or 100 frames per second. If your detection fps is regularly getting close to that, you should first consider tuning motion masks. If those are already properly configured, a second Coral may be needed. diff --git a/docs/docs/frigate/installation.md b/docs/docs/frigate/installation.md index 88decf7c98..ecd15ef610 100644 --- a/docs/docs/frigate/installation.md +++ b/docs/docs/frigate/installation.md @@ -200,7 +200,7 @@ services: shm_size: "512mb" # update for your cameras based on calculation above devices: - /dev/bus/usb:/dev/bus/usb # Passes the USB Coral, needs to be modified for other versions - - /dev/apex_0:/dev/apex_0 # Passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux + - /dev/apex_0:/dev/apex_0 # Passes a PCIe Coral, follow driver instructions here https://github.com/jnicolson/gasket-builder - /dev/video11:/dev/video11 # For Raspberry Pi 4B - /dev/dri/renderD128:/dev/dri/renderD128 # For intel hwaccel, needs to be updated for your hardware volumes: diff --git a/docs/docs/guides/getting_started.md b/docs/docs/guides/getting_started.md index 89176ad4b9..3b07d8d5ba 100644 --- a/docs/docs/guides/getting_started.md +++ b/docs/docs/guides/getting_started.md @@ -202,7 +202,7 @@ services: ... devices: - /dev/bus/usb:/dev/bus/usb # passes the USB Coral, needs to be modified for other versions - - /dev/apex_0:/dev/apex_0 # passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux + - /dev/apex_0:/dev/apex_0 # passes a PCIe Coral, follow driver instructions here https://github.com/jnicolson/gasket-builder ... ``` diff --git a/docs/docs/troubleshooting/edgetpu.md b/docs/docs/troubleshooting/edgetpu.md index f5cb3587fc..af94a3d84a 100644 --- a/docs/docs/troubleshooting/edgetpu.md +++ b/docs/docs/troubleshooting/edgetpu.md @@ -68,8 +68,7 @@ The USB Coral can become stuck and need to be restarted, this can happen for a n The most common reason for the PCIe Coral not being detected is that the driver has not been installed. This process varies based on what OS and kernel that is being run. -- In most cases [the Coral docs](https://coral.ai/docs/m2/get-started/#2-install-the-pcie-driver-and-edge-tpu-runtime) show how to install the driver for the PCIe based Coral. -- For some newer Linux distros (for example, Ubuntu 22.04+), https://github.com/jnicolson/gasket-builder can be used to build and install the latest version of the driver. +- In most cases https://github.com/jnicolson/gasket-builder can be used to build and install the latest version of the driver. ## Attempting to load TPU as pci & Fatal Python error: Illegal instruction From 17d2bc240ade44a655a7ed3d0952794f1efbf391 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 4 Nov 2025 09:56:28 -0700 Subject: [PATCH 03/78] Update recommended hardware to list more models (#20777) * Update recommended hardware to list more models * Update hardware.md with new Intel models and links --- docs/docs/frigate/hardware.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/docs/frigate/hardware.md b/docs/docs/frigate/hardware.md index 61c3df7121..3298a5910e 100644 --- a/docs/docs/frigate/hardware.md +++ b/docs/docs/frigate/hardware.md @@ -36,9 +36,11 @@ If the EQ13 is out of stock, the link below may take you to a suggested alternat ::: -| Name | Coral Inference Speed | Coral Compatibility | Notes | -| ------------------------------------------------------------------------------------------------------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------- | -| Beelink EQ13 (Amazon) | 5-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. | +| Name | Capabilities | Notes | +| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | --------------------------------------------------- | +| Beelink EQ13 (Amazon) | Can run object detection on several 1080p cameras with low-medium activity | Dual gigabit NICs for easy isolated camera network. | +| Intel 1120p ([Amazon](https://www.amazon.com/Beelink-i3-1220P-Computer-Display-Gigabit/dp/B0DDCKT9YP) | Can handle a large number of 1080p cameras with high activity | | +| Intel 125H ([Amazon](https://www.amazon.com/MINISFORUM-Pro-125H-Barebone-Computer-HDMI2-1/dp/B0FH21FSZM) | Can handle a significant number of 1080p cameras with high activity | Includes NPU for more efficient detection in 0.17+ | ## Detectors From 5cf2ae0121b60a97fc66d6f77fef0c254f39d3e1 Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Wed, 5 Nov 2025 20:23:45 +0800 Subject: [PATCH 04/78] docs: remove webrtc not support H.265 tips (#20769) --- docs/docs/configuration/live.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index 11339d5848..93c795d2f3 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -15,7 +15,7 @@ The jsmpeg live view will use more browser and client GPU resources. Using go2rt | ------ | ------------------------------------- | ---------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | jsmpeg | same as `detect -> fps`, capped at 10 | 720p | no | no | Resolution is configurable, but go2rtc is recommended if you want higher resolutions and better frame rates. jsmpeg is Frigate's default without go2rtc configured. | | mse | native | native | yes (depends on audio codec) | yes | iPhone requires iOS 17.1+, Firefox is h.264 only. This is Frigate's default when go2rtc is configured. | -| webrtc | native | native | yes (depends on audio codec) | yes | Requires extra configuration, doesn't support h.265. Frigate attempts to use WebRTC when MSE fails or when using a camera's two-way talk feature. | +| webrtc | native | native | yes (depends on audio codec) | yes | Requires extra configuration. Frigate attempts to use WebRTC when MSE fails or when using a camera's two-way talk feature. | ### Camera Settings Recommendations @@ -127,7 +127,8 @@ WebRTC works by creating a TCP or UDP connection on port `8555`. However, it req ``` - For access through Tailscale, the Frigate system's Tailscale IP must be added as a WebRTC candidate. Tailscale IPs all start with `100.`, and are reserved within the `100.64.0.0/10` CIDR block. -- Note that WebRTC does not support H.265. + +- Note that some browsers may not support H.265 (HEVC). You can check your browser's current version for H.265 compatibility [here](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#codecs-madness). :::tip From 3620ef27dba9225e361d028d85a8ca7d49950767 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 8 Nov 2025 12:21:15 -0700 Subject: [PATCH 05/78] Update hailo installation instructions (#20847) * Update hailo docs installation * Adjust section separation --- docs/docs/frigate/installation.md | 109 ++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/docs/docs/frigate/installation.md b/docs/docs/frigate/installation.md index ecd15ef610..ce8a28b13a 100644 --- a/docs/docs/frigate/installation.md +++ b/docs/docs/frigate/installation.md @@ -94,6 +94,10 @@ $ python -c 'print("{:.2f}MB".format(((1280 * 720 * 1.5 * 20 + 270480) / 1048576 The shm size cannot be set per container for Home Assistant add-ons. However, this is probably not required since by default Home Assistant Supervisor allocates `/dev/shm` with half the size of your total memory. If your machine has 8GB of memory, chances are that Frigate will have access to up to 4GB without any additional configuration. +## Extra Steps for Specific Hardware + +The following sections contain additional setup steps that are only required if you are using specific hardware. If you are not using any of these hardware types, you can skip to the [Docker](#docker) installation section. + ### Raspberry Pi 3/4 By default, the Raspberry Pi limits the amount of memory available to the GPU. In order to use ffmpeg hardware acceleration, you must increase the available memory by setting `gpu_mem` to the maximum recommended value in `config.txt` as described in the [official docs](https://www.raspberrypi.org/documentation/computers/config_txt.html#memory-options). @@ -106,14 +110,107 @@ The Hailo-8 and Hailo-8L AI accelerators are available in both M.2 and HAT form #### Installation -For Raspberry Pi 5 users with the AI Kit, installation is straightforward. Simply follow this [guide](https://www.raspberrypi.com/documentation/accessories/ai-kit.html#ai-kit-installation) to install the driver and software. +:::warning + +The Raspberry Pi kernel includes an older version of the Hailo driver that is incompatible with Frigate. You **must** follow the installation steps below to install the correct driver version, and you **must** disable the built-in kernel driver as described in step 1. + +::: + +1. **Disable the built-in Hailo driver (Raspberry Pi only)**: + + :::note + + If you are **not** using a Raspberry Pi, skip this step and proceed directly to step 2. + + ::: + + If you are using a Raspberry Pi, you need to blacklist the built-in kernel Hailo driver to prevent conflicts. First, check if the driver is currently loaded: + + ```bash + lsmod | grep hailo + ``` + + If it shows `hailo_pci`, unload it: + + ```bash + sudo rmmod hailo_pci + ``` + + Now blacklist the driver to prevent it from loading on boot: + + ```bash + echo "blacklist hailo_pci" | sudo tee /etc/modprobe.d/blacklist-hailo_pci.conf + ``` + + Update initramfs to ensure the blacklist takes effect: + + ```bash + sudo update-initramfs -u + ``` + + Reboot your Raspberry Pi: + + ```bash + sudo reboot + ``` + + After rebooting, verify the built-in driver is not loaded: + + ```bash + lsmod | grep hailo + ``` + + This command should return no results. If it still shows `hailo_pci`, the blacklist did not take effect properly and you may need to check for other Hailo packages installed via apt that are loading the driver. + +2. **Run the installation script**: + + Download the installation script: + + ```bash + wget https://raw.githubusercontent.com/blakeblackshear/frigate/dev/docker/hailo8l/user_installation.sh + ``` + + Make it executable: + + ```bash + sudo chmod +x user_installation.sh + ``` + + Run the script: + + ```bash + ./user_installation.sh + ``` + + The script will: + + - Install necessary build dependencies + - Clone and build the Hailo driver from the official repository + - Install the driver + - Download and install the required firmware + - Set up udev rules + +3. **Reboot your system**: + + After the script completes successfully, reboot to load the firmware: + + ```bash + sudo reboot + ``` + +4. **Verify the installation**: + + After rebooting, verify that the Hailo device is available: + + ```bash + ls -l /dev/hailo0 + ``` -For other installations, follow these steps for installation: + You should see the device listed. You can also verify the driver is loaded: -1. Install the driver from the [Hailo GitHub repository](https://github.com/hailo-ai/hailort-drivers). A convenient script for Linux is available to clone the repository, build the driver, and install it. -2. Copy or download [this script](https://github.com/blakeblackshear/frigate/blob/dev/docker/hailo8l/user_installation.sh). -3. Ensure it has execution permissions with `sudo chmod +x user_installation.sh` -4. Run the script with `./user_installation.sh` + ```bash + lsmod | grep hailo_pci + ``` #### Setup From 9589c5fc24cb47cc08f4c6ca8d0d43fbc17d9359 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:15:38 -0600 Subject: [PATCH 06/78] Fix rf-detr heading (#20963) The link earlier in the file was referencing "#downloading-rf-detr-model" --- docs/docs/configuration/object_detectors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index e0faaf7fc6..b930f72fb2 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -1002,7 +1002,7 @@ COPY --from=build /dfine/output/dfine_${MODEL_SIZE}_obj2coco.onnx /dfine-${MODEL EOF ``` -### Download RF-DETR Model +### Downloading RF-DETR Model RF-DETR can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=Nano` in the first line to `Nano`, `Small`, or `Medium` size. From 914ff4f1e5c2a15e93c8b61d317c9e5ffa141ae3 Mon Sep 17 00:00:00 2001 From: h-leth Date: Sat, 22 Nov 2025 19:41:13 +0100 Subject: [PATCH 07/78] add comment about unifi g5 and newer cams (#21003) --- docs/docs/configuration/camera_specific.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/configuration/camera_specific.md b/docs/docs/configuration/camera_specific.md index 334e3682bd..608cf3a09e 100644 --- a/docs/docs/configuration/camera_specific.md +++ b/docs/docs/configuration/camera_specific.md @@ -227,6 +227,12 @@ cameras: ### Unifi Protect Cameras +:::note + +Unifi G5s cameras and newer need a Unifi Protect server to enable rtsps stream, it's not posible to enable it in standalone mode. + +::: + Unifi protect cameras require the rtspx stream to be used with go2rtc. To utilize a Unifi protect camera, modify the rtsps link to begin with rtspx. Additionally, remove the "?enableSrtp" from the end of the Unifi link. From 592c245dcd25919e7d39bdae2462847c6323850f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:27:16 -0600 Subject: [PATCH 08/78] Fixes (#21061) * require admin role to delete users * explicitly prevent deletion of admin user * Recordings playback fixes * Remove nvidia pyindex * Update version --------- Co-authored-by: Nicolas Mowen --- Makefile | 2 +- docker/main/requirements.txt | 1 - frigate/api/auth.py | 10 +- web/src/components/player/HlsVideoPlayer.tsx | 4 +- .../player/dynamic/DynamicVideoController.ts | 41 ++----- .../player/dynamic/DynamicVideoPlayer.tsx | 111 ++++++++++-------- web/src/utils/videoUtil.ts | 54 +++++++++ 7 files changed, 139 insertions(+), 84 deletions(-) diff --git a/Makefile b/Makefile index fa692b681b..04eee68d57 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ default_target: local COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1) -VERSION = 0.16.2 +VERSION = 0.16.3 IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) BOARDS= #Initialized empty diff --git a/docker/main/requirements.txt b/docker/main/requirements.txt index 3ae420d078..f1ba7d9ada 100644 --- a/docker/main/requirements.txt +++ b/docker/main/requirements.txt @@ -1,2 +1 @@ scikit-build == 0.18.* -nvidia-pyindex diff --git a/frigate/api/auth.py b/frigate/api/auth.py index 9459c4ac88..95586e955e 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -447,8 +447,14 @@ def create_user( return JSONResponse(content={"username": body.username}) -@router.delete("/users/{username}") -def delete_user(username: str): +@router.delete("/users/{username}", dependencies=[Depends(require_role(["admin"]))]) +def delete_user(request: Request, username: str): + # Prevent deletion of the built-in admin user + if username == "admin": + return JSONResponse( + content={"message": "Cannot delete admin user"}, status_code=403 + ) + User.delete_by_id(username) return JSONResponse(content={"success": True}) diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 4b1bfe5efc..38896f8422 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -6,7 +6,7 @@ import { useState, } from "react"; import Hls from "hls.js"; -import { isAndroid, isDesktop, isMobile } from "react-device-detect"; +import { isDesktop, isMobile } from "react-device-detect"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import VideoControls from "./VideoControls"; import { VideoResolutionType } from "@/types/live"; @@ -21,7 +21,7 @@ import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record"; import { useTranslation } from "react-i18next"; // Android native hls does not seek correctly -const USE_NATIVE_HLS = !isAndroid; +const USE_NATIVE_HLS = false; const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const; const unsupportedErrorCodes = [ MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, diff --git a/web/src/components/player/dynamic/DynamicVideoController.ts b/web/src/components/player/dynamic/DynamicVideoController.ts index 0683481c6a..b68191e165 100644 --- a/web/src/components/player/dynamic/DynamicVideoController.ts +++ b/web/src/components/player/dynamic/DynamicVideoController.ts @@ -2,7 +2,10 @@ import { Recording } from "@/types/record"; import { DynamicPlayback } from "@/types/playback"; import { PreviewController } from "../PreviewPlayer"; import { TimeRange, ObjectLifecycleSequence } from "@/types/timeline"; -import { calculateInpointOffset } from "@/utils/videoUtil"; +import { + calculateInpointOffset, + calculateSeekPosition, +} from "@/utils/videoUtil"; type PlayerMode = "playback" | "scrubbing"; @@ -68,38 +71,20 @@ export class DynamicVideoController { return; } - if ( - this.recordings.length == 0 || - time < this.recordings[0].start_time || - time > this.recordings[this.recordings.length - 1].end_time - ) { - this.setNoRecording(true); - return; - } - if (this.playerMode != "playback") { this.playerMode = "playback"; } - let seekSeconds = 0; - (this.recordings || []).every((segment) => { - // if the next segment is past the desired time, stop calculating - if (segment.start_time > time) { - return false; - } - - if (segment.end_time < time) { - seekSeconds += segment.end_time - segment.start_time; - return true; - } - - seekSeconds += - segment.end_time - segment.start_time - (segment.end_time - time); - return true; - }); + const seekSeconds = calculateSeekPosition( + time, + this.recordings, + this.inpointOffset, + ); - // adjust for HLS inpoint offset - seekSeconds -= this.inpointOffset; + if (seekSeconds === undefined) { + this.setNoRecording(true); + return; + } if (seekSeconds != 0) { this.playerController.currentTime = seekSeconds; diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index 836203ca74..5967fc6dca 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -13,7 +13,10 @@ import { VideoResolutionType } from "@/types/live"; import axios from "axios"; import { cn } from "@/lib/utils"; import { useTranslation } from "react-i18next"; -import { calculateInpointOffset } from "@/utils/videoUtil"; +import { + calculateInpointOffset, + calculateSeekPosition, +} from "@/utils/videoUtil"; import { isFirefox } from "react-device-detect"; /** @@ -99,10 +102,10 @@ export default function DynamicVideoPlayer({ const [isLoading, setIsLoading] = useState(false); const [isBuffering, setIsBuffering] = useState(false); const [loadingTimeout, setLoadingTimeout] = useState(); - const [source, setSource] = useState({ - playlist: `${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`, - startPosition: startTimestamp ? timeRange.after - startTimestamp : 0, - }); + + // Don't set source until recordings load - we need accurate startPosition + // to avoid hls.js clamping to video end when startPosition exceeds duration + const [source, setSource] = useState(undefined); // start at correct time @@ -174,7 +177,7 @@ export default function DynamicVideoPlayer({ ); useEffect(() => { - if (!controller || !recordings?.length) { + if (!recordings?.length) { if (recordings?.length == 0) { setNoRecording(true); } @@ -182,10 +185,6 @@ export default function DynamicVideoPlayer({ return; } - if (playerRef.current) { - playerRef.current.autoplay = !isScrubbing; - } - let startPosition = undefined; if (startTimestamp) { @@ -193,14 +192,12 @@ export default function DynamicVideoPlayer({ recordingParams.after, (recordings || [])[0], ); - const idealStartPosition = Math.max( - 0, - startTimestamp - timeRange.after - inpointOffset, - ); - if (idealStartPosition >= recordings[0].start_time - timeRange.after) { - startPosition = idealStartPosition; - } + startPosition = calculateSeekPosition( + startTimestamp, + recordings, + inpointOffset, + ); } setSource({ @@ -208,6 +205,18 @@ export default function DynamicVideoPlayer({ startPosition, }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [recordings]); + + useEffect(() => { + if (!controller || !recordings?.length) { + return; + } + + if (playerRef.current) { + playerRef.current.autoplay = !isScrubbing; + } + setLoadingTimeout(setTimeout(() => setIsLoading(true), 1000)); controller.newPlayback({ @@ -215,7 +224,7 @@ export default function DynamicVideoPlayer({ timeRange, }); - // we only want this to change when recordings update + // we only want this to change when controller or recordings update // eslint-disable-next-line react-hooks/exhaustive-deps }, [controller, recordings]); @@ -253,38 +262,40 @@ export default function DynamicVideoPlayer({ return ( <> - { - if (isScrubbing) { - playerRef.current?.pause(); - } - - if (loadingTimeout) { - clearTimeout(loadingTimeout); - } - - setNoRecording(false); - }} - setFullResolution={setFullResolution} - onUploadFrame={onUploadFrameToPlus} - toggleFullscreen={toggleFullscreen} - onError={(error) => { - if (error == "stalled" && !isScrubbing) { - setIsBuffering(true); - } - }} - /> + {source && ( + { + if (isScrubbing) { + playerRef.current?.pause(); + } + + if (loadingTimeout) { + clearTimeout(loadingTimeout); + } + + setNoRecording(false); + }} + setFullResolution={setFullResolution} + onUploadFrame={onUploadFrameToPlus} + toggleFullscreen={toggleFullscreen} + onError={(error) => { + if (error == "stalled" && !isScrubbing) { + setIsBuffering(true); + } + }} + /> + )} recordings[recordings.length - 1].end_time + ) { + return undefined; + } + + let seekSeconds = 0; + + (recordings || []).every((segment) => { + // if the next segment is past the desired time, stop calculating + if (segment.start_time > timestamp) { + return false; + } + + if (segment.end_time < timestamp) { + // Add the full duration of this segment + seekSeconds += segment.end_time - segment.start_time; + return true; + } + + // We're in this segment - calculate position within it + seekSeconds += + segment.end_time - segment.start_time - (segment.end_time - timestamp); + return true; + }); + + // Adjust for HLS inpoint offset + seekSeconds -= inpointOffset; + + return seekSeconds >= 0 ? seekSeconds : undefined; +} From 9f95a5f31f1f895da3aed757ddfd8ea9de808ca6 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 1 Dec 2025 08:21:27 -0600 Subject: [PATCH 09/78] version bump in docs (#21111) --- docs/docs/frigate/updating.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/frigate/updating.md b/docs/docs/frigate/updating.md index d95ae83c52..a164f9296f 100644 --- a/docs/docs/frigate/updating.md +++ b/docs/docs/frigate/updating.md @@ -5,7 +5,7 @@ title: Updating # Updating Frigate -The current stable version of Frigate is **0.16.2**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.16.2). +The current stable version of Frigate is **0.16.3**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.16.3). Keeping Frigate up to date ensures you benefit from the latest features, performance improvements, and bug fixes. The update process varies slightly depending on your installation method (Docker, Home Assistant Addon, etc.). Below are instructions for the most common setups. @@ -33,21 +33,21 @@ If you’re running Frigate via Docker (recommended method), follow these steps: 2. **Update and Pull the Latest Image**: - If using Docker Compose: - - Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.16.2` instead of `0.15.2`). For example: + - Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.16.3` instead of `0.15.2`). For example: ```yaml services: frigate: - image: ghcr.io/blakeblackshear/frigate:0.16.2 + image: ghcr.io/blakeblackshear/frigate:0.16.3 ``` - Then pull the image: ```bash - docker pull ghcr.io/blakeblackshear/frigate:0.16.2 + docker pull ghcr.io/blakeblackshear/frigate:0.16.3 ``` - **Note for `stable` Tag Users**: If your `docker-compose.yml` uses the `stable` tag (e.g., `ghcr.io/blakeblackshear/frigate:stable`), you don’t need to update the tag manually. The `stable` tag always points to the latest stable release after pulling. - If using `docker run`: - - Pull the image with the appropriate tag (e.g., `0.16.2`, `0.16.2-tensorrt`, or `stable`): + - Pull the image with the appropriate tag (e.g., `0.16.3`, `0.16.3-tensorrt`, or `stable`): ```bash - docker pull ghcr.io/blakeblackshear/frigate:0.16.2 + docker pull ghcr.io/blakeblackshear/frigate:0.16.3 ``` 3. **Start the Container**: From a43d294bd15da93821ca9587151ac768408ab70f Mon Sep 17 00:00:00 2001 From: munit85 <49924807+munit85@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:47:01 -0800 Subject: [PATCH 10/78] Add Axis Q-6155E camera configuration details (#21105) * Add Axis Q-6155E camera configuration details Added Axis Q-6155E camera details with ONVIF service port information. * Update Axis Q-6155E ONVIF autotracking support details Added the reason for autotracking not working --- docs/docs/configuration/cameras.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index d6a8915c31..2805f1b81b 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -98,6 +98,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | Amcrest IP4M-S2112EW-AI | ✅ | ❌ | FOV relative movement not supported. | | Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. | | Annke CZ504 | ✅ | ✅ | Annke support provide specific firmware ([V5.7.1 build 250227](https://github.com/pierrepinon/annke_cz504/raw/refs/heads/main/digicap_V5-7-1_build_250227.dav)) to fix issue with ONVIF "TranslationSpaceFov" | +| Axis Q-6155E | ✅ | ❌ | ONVIF service port: 80; Camera does not support MoveStatus. | Ctronics PTZ | ✅ | ❌ | | | Dahua | ✅ | ✅ | Some low-end Dahuas (lite series, among others) have been reported to not support autotracking | | Dahua DH-SD2A500HB | ✅ | ❌ | | From e47e82f4be2d45d97c082c12781a3f172d2a5d75 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:15:12 -0600 Subject: [PATCH 11/78] Pin onnx in rfdetr model generation command (#21127) * pin onnx in rfdetr model generation command * Apply suggestion from @NickM-27 Co-authored-by: Nicolas Mowen --------- Co-authored-by: Nicolas Mowen --- docs/docs/configuration/object_detectors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index b930f72fb2..33c95adbfa 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -1007,12 +1007,12 @@ EOF RF-DETR can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=Nano` in the first line to `Nano`, `Small`, or `Medium` size. ```sh -docker build . --build-arg MODEL_SIZE=Nano --output . -f- <<'EOF' +docker build . --build-arg MODEL_SIZE=Nano --rm --output . -f- <<'EOF' FROM python:3.11 AS build RUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/* COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/ WORKDIR /rfdetr -RUN uv pip install --system rfdetr[onnxexport] torch==2.8.0 onnxscript +RUN uv pip install --system rfdetr[onnxexport] torch==2.8.0 onnx==1.19.1 onnxscript ARG MODEL_SIZE RUN python3 -c "from rfdetr import RFDETR${MODEL_SIZE}; x = RFDETR${MODEL_SIZE}(resolution=320); x.export(simplify=True)" FROM scratch From 7167cf57c57e24475d19f773e10f0db26d0958be Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:20:50 -0600 Subject: [PATCH 12/78] pin cryptography version to fix vapid issues (#21126) --- docker/main/requirements-wheels.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 2764eca43a..dce124897a 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -8,6 +8,7 @@ fastapi == 0.115.* uvicorn == 0.30.* slowapi == 0.1.* joserfc == 1.0.* +cryptography == 44.0.* pathvalidate == 3.2.* markupsafe == 3.0.* python-multipart == 0.0.12 From 90344540b30529d7d2d38e304577418281965b5a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 6 Dec 2025 09:16:23 -0600 Subject: [PATCH 13/78] Fix jetson build (#21173) --- docker/main/build_pysqlite3.sh | 12 +++++++++--- docker/tensorrt/Dockerfile.arm64 | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docker/main/build_pysqlite3.sh b/docker/main/build_pysqlite3.sh index c84c6fcf71..14d0cde44c 100755 --- a/docker/main/build_pysqlite3.sh +++ b/docker/main/build_pysqlite3.sh @@ -5,21 +5,27 @@ set -euxo pipefail SQLITE3_VERSION="3.46.1" PYSQLITE3_VERSION="0.5.3" +# Install libsqlite3-dev if not present (needed for some base images like NVIDIA TensorRT) +if ! dpkg -l | grep -q libsqlite3-dev; then + echo "Installing libsqlite3-dev for compilation..." + apt-get update && apt-get install -y libsqlite3-dev && rm -rf /var/lib/apt/lists/* +fi + # Fetch the pre-built sqlite amalgamation instead of building from source if [[ ! -d "sqlite" ]]; then mkdir sqlite cd sqlite - + # Download the pre-built amalgamation from sqlite.org # For SQLite 3.46.1, the amalgamation version is 3460100 SQLITE_AMALGAMATION_VERSION="3460100" - + wget https://www.sqlite.org/2024/sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}.zip -O sqlite-amalgamation.zip unzip sqlite-amalgamation.zip mv sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}/* . rmdir sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION} rm sqlite-amalgamation.zip - + cd ../ fi diff --git a/docker/tensorrt/Dockerfile.arm64 b/docker/tensorrt/Dockerfile.arm64 index 0ae9c38e9c..dd3c5de5e3 100644 --- a/docker/tensorrt/Dockerfile.arm64 +++ b/docker/tensorrt/Dockerfile.arm64 @@ -112,7 +112,7 @@ RUN apt-get update \ && apt-get install -y protobuf-compiler libprotobuf-dev \ && rm -rf /var/lib/apt/lists/* RUN --mount=type=bind,source=docker/tensorrt/requirements-models-arm64.txt,target=/requirements-tensorrt-models.txt \ - pip3 wheel --wheel-dir=/trt-model-wheels -r /requirements-tensorrt-models.txt + pip3 wheel --wheel-dir=/trt-model-wheels --no-deps -r /requirements-tensorrt-models.txt FROM wget AS jetson-ffmpeg ARG DEBIAN_FRONTEND @@ -145,7 +145,8 @@ COPY --from=trt-wheels /etc/TENSORRT_VER /etc/TENSORRT_VER RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \ --mount=type=bind,from=trt-model-wheels,source=/trt-model-wheels,target=/deps/trt-model-wheels \ pip3 uninstall -y onnxruntime \ - && pip3 install -U /deps/trt-wheels/*.whl /deps/trt-model-wheels/*.whl \ + && pip3 install -U /deps/trt-wheels/*.whl \ + && pip3 install -U /deps/trt-model-wheels/*.whl \ && ldconfig WORKDIR /opt/frigate/ From c3f596327e82f973706c48684f32f8a0ce5fa4fc Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Sun, 7 Dec 2025 22:38:41 +0800 Subject: [PATCH 14/78] docs: fix the missing quotes in the Reolink example within the documentation (#21178) --- docs/docs/configuration/camera_specific.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/camera_specific.md b/docs/docs/configuration/camera_specific.md index 608cf3a09e..89f8f08cfc 100644 --- a/docs/docs/configuration/camera_specific.md +++ b/docs/docs/configuration/camera_specific.md @@ -188,10 +188,10 @@ go2rtc: # 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 + - "rtsp://username:password@reolink_ip/Preview_01_sub" your_reolink_camera_twt_sub: - "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password" - - "rtsp://username:password@reolink_ip/Preview_01_sub + - "rtsp://username:password@reolink_ip/Preview_01_sub" # example for connecting to a Reolink NVR your_reolink_camera_via_nvr: - "ffmpeg:http://reolink_nvr_ip/flv?port=1935&app=bcs&stream=channel3_main.bcs&user=username&password=password" # channel numbers are 0-15 From 1de7519d1a2a3a0381f95eae084805d954355065 Mon Sep 17 00:00:00 2001 From: User873902 <92607223+User873902@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:33:10 -0500 Subject: [PATCH 15/78] Update camera_specific.md for Wyze Cameras (Thingino) (#21221) * Update camera_specific.md Wyze Cameras alternative firmware considerations. * Update docs/docs/configuration/camera_specific.md Co-authored-by: Nicolas Mowen * Update docs/docs/configuration/camera_specific.md * Update camera_specific.md Moved Wyze Camera section --------- Co-authored-by: Nicolas Mowen --- docs/docs/configuration/camera_specific.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/camera_specific.md b/docs/docs/configuration/camera_specific.md index 89f8f08cfc..75fda5b884 100644 --- a/docs/docs/configuration/camera_specific.md +++ b/docs/docs/configuration/camera_specific.md @@ -258,6 +258,10 @@ ffmpeg: TP-Link VIGI cameras need some adjustments to the main stream settings on the camera itself to avoid issues. The stream needs to be configured as `H264` with `Smart Coding` set to `off`. Without these settings you may have problems when trying to watch recorded footage. For example Firefox will stop playback after a few seconds and show the following error message: `The media playback was aborted due to a corruption problem or because the media used features your browser did not support.`. +### Wyze Wireless Cameras + +Some community members have found better performance on Wyze cameras by using an alternative firmware known as [Thingino](https://thingino.com/). + ## USB Cameras (aka Webcams) To use a USB camera (webcam) with Frigate, the recommendation is to use go2rtc's [FFmpeg Device](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg-device) support: @@ -290,5 +294,3 @@ cameras: width: 1024 height: 576 ``` - - From 0a293aebabf0be8d278d40370e40fb2b3a1f3ee0 Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Thu, 11 Dec 2025 21:31:52 +0800 Subject: [PATCH 16/78] docs: update OpenVINO D-FINE configuration default device (#21231) * docs: remove OpenVINO D-FINE configuration device * docs: change D-FINE model detectors default device --- docs/docs/configuration/object_detectors.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 33c95adbfa..7016bf4b63 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -395,7 +395,7 @@ After placing the downloaded onnx model in your config/model_cache folder, you c detectors: ov: type: openvino - device: GPU + device: CPU model: model_type: dfine @@ -431,10 +431,10 @@ When using Docker Compose: ```yaml services: frigate: ---- -devices: - - /dev/dri - - /dev/kfd + ... + 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). @@ -462,9 +462,9 @@ When using Docker Compose: ```yaml services: frigate: - -environment: - HSA_OVERRIDE_GFX_VERSION: "10.0.0" + ... + environment: + HSA_OVERRIDE_GFX_VERSION: "10.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. From 1d5c2466a8adda927424d982ec77a06dc2c7b02d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 12 Dec 2025 13:25:22 -0700 Subject: [PATCH 17/78] Update HIKVISION camera link in hardware documentation (#21256) --- docs/docs/frigate/hardware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/frigate/hardware.md b/docs/docs/frigate/hardware.md index 3298a5910e..f5577a780d 100644 --- a/docs/docs/frigate/hardware.md +++ b/docs/docs/frigate/hardware.md @@ -18,7 +18,7 @@ Here are some of the cameras I recommend: - Loryta(Dahua) IPC-T549M-ALED-S3 (affiliate link) - Loryta(Dahua) IPC-T54IR-AS (affiliate link) - Amcrest IP5M-T1179EW-AI-V3 (affiliate link) -- HIKVISION DS-2CD2387G2P-LSU/SL ColorVu 8MP Panoramic Turret IP Camera (affiliate link) +- HIKVISION DS-2CD2387G2P-LSU/SL ColorVu 8MP Panoramic Turret IP Camera (affiliate link) I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website. From 51ee6f26e674880b89a5f1f32a919dba401eb922 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:00:48 -0600 Subject: [PATCH 18/78] Fix yolov9 coral docs labelmap path (#21278) --- docs/docs/configuration/object_detectors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 2305c6850c..282310be56 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -175,7 +175,7 @@ model: width: 320 # <--- should match the imgsize of the model, typically 320 height: 320 # <--- should match the imgsize of the model, typically 320 path: /config/model_cache/yolov9-s-relu6-best_320_int8_edgetpu.tflite - labelmap_path: /labelmap/labels-coco-17.txt + labelmap_path: /config/labels-coco17.txt ``` Note that the labelmap uses a subset of the complete COCO label set that has only 17 objects. From e1545a8db8692a4fe8adfefa22a0a5677bc93c2f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:41:38 -0600 Subject: [PATCH 19/78] Miscellaneous Fixes (0.17 beta) (#21279) * Fix Safari popover issue in classification wizard * use name for key instead of title prevents duplicate key warnings when users mix vaapi and qsv * update auth api endpoint descriptions and docs * tweak headings * fix note * clarify classification docs * Fix cuda birdseye --------- Co-authored-by: Nicolas Mowen --- docs/docs/configuration/authentication.md | 39 +++++++ .../state_classification.md | 6 +- docs/static/frigate-api.yaml | 105 ++++++++++++++++-- frigate/api/auth.py | 56 ++++++++-- frigate/ffmpeg_presets.py | 2 +- web/src/components/Statusbar.tsx | 4 +- .../wizard/Step1NameAndDefine.tsx | 4 +- 7 files changed, 190 insertions(+), 26 deletions(-) diff --git a/docs/docs/configuration/authentication.md b/docs/docs/configuration/authentication.md index 1d1581b2c3..17718c4057 100644 --- a/docs/docs/configuration/authentication.md +++ b/docs/docs/configuration/authentication.md @@ -270,3 +270,42 @@ To use role-based access control, you must connect to Frigate via the **authenti 1. Log in as an **admin** user via port `8971`. 2. Navigate to **Settings > Users**. 3. Edit a user’s role by selecting **admin** or **viewer**. + +## API Authentication Guide + +### Getting a Bearer Token + +To use the Frigate API, you need to authenticate first. Follow these steps to obtain a Bearer token: + +#### 1. Login + +Make a POST request to `/login` with your credentials: + +```bash +curl -i -X POST https://frigate_ip:8971/api/login \ + -H "Content-Type: application/json" \ + -d '{"user": "admin", "password": "your_password"}' +``` + +:::note + +You may need to include `-k` in the argument list in these steps (eg: `curl -k -i -X POST ...`) if your Frigate instance is using a self-signed certificate. + +::: + +The response will contain a cookie with the JWT token. + +#### 2. Using the Bearer Token + +Once you have the token, include it in the Authorization header for subsequent requests: + +```bash +curl -H "Authorization: Bearer " https://frigate_ip:8971/api/profile +``` + +#### 3. Token Lifecycle + +- Tokens are valid for the configured session length +- Tokens are automatically refreshed when you visit the `/auth` endpoint +- Tokens are invalidated when the user's password is changed +- Use `/logout` to clear your session cookie diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index 927fe91afc..801d5d9052 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -60,11 +60,9 @@ Choose one or more cameras and draw a rectangle over the area of interest for ea ### Step 3: Assign Training Examples -The system will automatically generate example images from your camera feeds. You'll be guided through each class one at a time to select which images represent that state. +The system will automatically generate example images from your camera feeds. You'll be guided through each class one at a time to select which images represent that state. It's not strictly required to select all images you see. If a state is missing from the samples, you can train it from the Recent tab later. -**Important**: All images must be assigned to a state before training can begin. This includes images that may not be optimal, such as when people temporarily block the view, sun glare is present, or other distractions occur. Assign these images to the state that is actually present (based on what you know the state to be), not based on the distraction. This training helps the model correctly identify the state even when such conditions occur during inference. - -Once all images are assigned, training will begin automatically. +Once some images are assigned, training will begin automatically. ### Improving the Model diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml index f9981f8de0..1bd5e95b86 100644 --- a/docs/static/frigate-api.yaml +++ b/docs/static/frigate-api.yaml @@ -14,7 +14,11 @@ paths: get: tags: - Auth - summary: Auth + summary: Authenticate request + description: |- + Authenticates the current request based on proxy headers or JWT token. + Returns user role and permissions for camera access. + This endpoint verifies authentication credentials and manages JWT token refresh. operationId: auth_auth_get responses: "200": @@ -22,11 +26,21 @@ paths: content: application/json: schema: {} + "202": + description: Authentication Accepted + content: + application/json: + schema: {} + "401": + description: Authentication Failed /profile: get: tags: - Auth - summary: Profile + summary: Get user profile + description: |- + Returns the current authenticated user's profile including username, role, and allowed cameras. + This endpoint requires authentication and returns information about the user's permissions. operationId: profile_profile_get responses: "200": @@ -34,11 +48,16 @@ paths: content: application/json: schema: {} + "401": + description: Unauthorized /logout: get: tags: - Auth - summary: Logout + summary: Logout user + description: |- + Logs out the current user by clearing the session cookie. + After logout, subsequent requests will require re-authentication. operationId: logout_logout_get responses: "200": @@ -46,11 +65,22 @@ paths: content: application/json: schema: {} + "303": + description: See Other (redirects to login page) /login: post: tags: - Auth - summary: Login + summary: Login with credentials + description: |- + Authenticates a user with username and password. + Returns a JWT token as a secure HTTP-only cookie that can be used for subsequent API requests. + The JWT token can also be retrieved from the response and used as a Bearer token in the Authorization header. + + Example using Bearer token: + ``` + curl -H "Authorization: Bearer " https://frigate_ip:8971/api/profile + ``` operationId: login_login_post requestBody: required: true @@ -64,6 +94,11 @@ paths: content: application/json: schema: {} + "401": + description: Login Failed - Invalid credentials + content: + application/json: + schema: {} "422": description: Validation Error content: @@ -74,7 +109,10 @@ paths: get: tags: - Auth - summary: Get Users + summary: Get all users + description: |- + Returns a list of all users with their usernames and roles. + Requires admin role. Each user object contains the username and assigned role. operationId: get_users_users_get responses: "200": @@ -82,10 +120,19 @@ paths: content: application/json: schema: {} + "403": + description: Forbidden - Admin role required post: tags: - Auth - summary: Create User + summary: Create new user + description: |- + Creates a new user with the specified username, password, and role. + Requires admin role. Password must meet strength requirements: + - Minimum 8 characters + - At least one uppercase letter + - At least one digit + - At least one special character (!@#$%^&*(),.?":{}\|<>) operationId: create_user_users_post requestBody: required: true @@ -99,6 +146,13 @@ paths: content: application/json: schema: {} + "400": + description: Bad Request - Invalid username or role + content: + application/json: + schema: {} + "403": + description: Forbidden - Admin role required "422": description: Validation Error content: @@ -109,7 +163,10 @@ paths: delete: tags: - Auth - summary: Delete User + summary: Delete user + description: |- + Deletes a user by username. The built-in admin user cannot be deleted. + Requires admin role. Returns success message or error if user not found. operationId: delete_user_users__username__delete parameters: - name: username @@ -118,12 +175,15 @@ paths: schema: type: string title: Username + description: The username of the user to delete responses: "200": description: Successful Response content: application/json: schema: {} + "403": + description: Forbidden - Cannot delete admin user or admin role required "422": description: Validation Error content: @@ -134,7 +194,17 @@ paths: put: tags: - Auth - summary: Update Password + summary: Update user password + description: |- + Updates a user's password. Users can only change their own password unless they have admin role. + Requires the current password to verify identity for non-admin users. + Password must meet strength requirements: + - Minimum 8 characters + - At least one uppercase letter + - At least one digit + - At least one special character (!@#$%^&*(),.?":{}\|<>) + + If user changes their own password, a new JWT cookie is automatically issued. operationId: update_password_users__username__password_put parameters: - name: username @@ -143,6 +213,7 @@ paths: schema: type: string title: Username + description: The username of the user whose password to update requestBody: required: true content: @@ -155,6 +226,14 @@ paths: content: application/json: schema: {} + "400": + description: Bad Request - Current password required or password doesn't meet requirements + "401": + description: Unauthorized - Current password is incorrect + "403": + description: Forbidden - Viewers can only update their own password + "404": + description: Not Found - User not found "422": description: Validation Error content: @@ -165,7 +244,10 @@ paths: put: tags: - Auth - summary: Update Role + summary: Update user role + description: |- + Updates a user's role. The built-in admin user's role cannot be modified. + Requires admin role. Valid roles are defined in the configuration. operationId: update_role_users__username__role_put parameters: - name: username @@ -174,6 +256,7 @@ paths: schema: type: string title: Username + description: The username of the user whose role to update requestBody: required: true content: @@ -186,6 +269,10 @@ paths: content: application/json: schema: {} + "400": + description: Bad Request - Invalid role + "403": + description: Forbidden - Cannot modify admin user's role or admin role required "422": description: Validation Error content: diff --git a/frigate/api/auth.py b/frigate/api/auth.py index d913173d08..cae80dc5b0 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -549,7 +549,12 @@ def resolve_role( # Endpoints -@router.get("/auth", dependencies=[Depends(allow_public())]) +@router.get( + "/auth", + dependencies=[Depends(allow_public())], + summary="Authenticate request", + description="Authenticates the current request based on proxy headers or JWT token. Returns user role and permissions for camera access.", +) def auth(request: Request): auth_config: AuthConfig = request.app.frigate_config.auth proxy_config: ProxyConfig = request.app.frigate_config.proxy @@ -689,7 +694,12 @@ def auth(request: Request): return fail_response -@router.get("/profile", dependencies=[Depends(allow_any_authenticated())]) +@router.get( + "/profile", + dependencies=[Depends(allow_any_authenticated())], + summary="Get user profile", + description="Returns the current authenticated user's profile including username, role, and allowed cameras.", +) def profile(request: Request): username = request.headers.get("remote-user", "anonymous") role = request.headers.get("remote-role", "viewer") @@ -703,7 +713,12 @@ def profile(request: Request): ) -@router.get("/logout", dependencies=[Depends(allow_public())]) +@router.get( + "/logout", + dependencies=[Depends(allow_public())], + summary="Logout user", + description="Logs out the current user by clearing the session cookie.", +) def logout(request: Request): auth_config: AuthConfig = request.app.frigate_config.auth response = RedirectResponse("/login", status_code=303) @@ -714,7 +729,12 @@ def logout(request: Request): limiter = Limiter(key_func=get_remote_addr) -@router.post("/login", dependencies=[Depends(allow_public())]) +@router.post( + "/login", + dependencies=[Depends(allow_public())], + summary="Login with credentials", + description="Authenticates a user with username and password. Returns a JWT token as a secure HTTP-only cookie that can be used for subsequent API requests. The token can also be retrieved and used as a Bearer token in the Authorization header.", +) @limiter.limit(limit_value=rateLimiter.get_limit) def login(request: Request, body: AppPostLoginBody): JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name @@ -752,7 +772,12 @@ def login(request: Request, body: AppPostLoginBody): return JSONResponse(content={"message": "Login failed"}, status_code=401) -@router.get("/users", dependencies=[Depends(require_role(["admin"]))]) +@router.get( + "/users", + dependencies=[Depends(require_role(["admin"]))], + summary="Get all users", + description="Returns a list of all users with their usernames and roles. Requires admin role.", +) def get_users(): exports = ( User.select(User.username, User.role).order_by(User.username).dicts().iterator() @@ -760,7 +785,12 @@ def get_users(): return JSONResponse([e for e in exports]) -@router.post("/users", dependencies=[Depends(require_role(["admin"]))]) +@router.post( + "/users", + dependencies=[Depends(require_role(["admin"]))], + summary="Create new user", + description="Creates a new user with the specified username, password, and role. Requires admin role. Password must meet strength requirements.", +) def create_user( request: Request, body: AppPostUsersBody, @@ -789,7 +819,12 @@ def create_user( return JSONResponse(content={"username": body.username}) -@router.delete("/users/{username}", dependencies=[Depends(require_role(["admin"]))]) +@router.delete( + "/users/{username}", + dependencies=[Depends(require_role(["admin"]))], + summary="Delete user", + description="Deletes a user by username. The built-in admin user cannot be deleted. Requires admin role.", +) def delete_user(request: Request, username: str): # Prevent deletion of the built-in admin user if username == "admin": @@ -802,7 +837,10 @@ def delete_user(request: Request, username: str): @router.put( - "/users/{username}/password", dependencies=[Depends(allow_any_authenticated())] + "/users/{username}/password", + dependencies=[Depends(allow_any_authenticated())], + summary="Update user password", + description="Updates a user's password. Users can only change their own password unless they have admin role. Requires the current password to verify identity. Password must meet strength requirements (minimum 8 characters, uppercase letter, digit, and special character).", ) async def update_password( request: Request, @@ -887,6 +925,8 @@ async def update_password( @router.put( "/users/{username}/role", dependencies=[Depends(require_role(["admin"]))], + summary="Update user role", + description="Updates a user's role. The built-in admin user's role cannot be modified. Requires admin role.", ) async def update_role( request: Request, diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index d7724c6485..36f7828fc8 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -153,7 +153,7 @@ def get_gpu_arg(self, preset: str, gpu: int) -> str: FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v main -level:v 4.1 -async_depth:v 1 {2}", - FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -hwaccel device {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", + FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -hwaccel cuda -hwaccel_device {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}", FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", diff --git a/web/src/components/Statusbar.tsx b/web/src/components/Statusbar.tsx index 0ac6d10a4a..ab22a11434 100644 --- a/web/src/components/Statusbar.tsx +++ b/web/src/components/Statusbar.tsx @@ -116,10 +116,10 @@ export default function Statusbar() { } return ( - + {" "}
{t("wizard.step1.classificationType")} - + - + { if (!open) { @@ -527,7 +532,10 @@ function CustomTimeSelector({ {formattedEnd} - + !globalObjectMasksArray.includes(mask), + ); + } + queryString = filteredMask .map((pointsArray) => { const coordinates = flattenPoints(parseCoordinates(pointsArray)).join( From 39af85625e8f8f1caec1e4a69eaba3952cb61856 Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Mon, 15 Dec 2025 23:59:13 +0800 Subject: [PATCH 21/78] feat: add train classification download weights file endpoint (#21294) * feat: add train classification download weights file endpoint: "TF_KERAS_MOBILENET_V2_ENDPOINT" * refactor: custom weights file url --- frigate/util/classification.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/frigate/util/classification.py b/frigate/util/classification.py index 1f4213315b..03229cc737 100644 --- a/frigate/util/classification.py +++ b/frigate/util/classification.py @@ -22,6 +22,7 @@ from frigate.log import redirect_output_to_logger from frigate.models import Event, Recordings, ReviewSegment from frigate.types import ModelStatusTypesEnum +from frigate.util.downloader import ModelDownloader from frigate.util.file import get_event_thumbnail_bytes from frigate.util.image import get_image_from_recording from frigate.util.process import FrigateProcess @@ -121,6 +122,10 @@ def get_dataset_image_count(model_name: str) -> int: class ClassificationTrainingProcess(FrigateProcess): def __init__(self, model_name: str) -> None: + self.BASE_WEIGHT_URL = os.environ.get( + "TF_KERAS_MOBILENET_V2_WEIGHTS_URL", + "", + ) super().__init__( stop_event=None, priority=PROCESS_PRIORITY_LOW, @@ -179,11 +184,23 @@ def __train_classification_model(self) -> bool: ) return False + weights_path = "imagenet" + # Download MobileNetV2 weights if not present + if self.BASE_WEIGHT_URL: + weights_path = os.path.join( + MODEL_CACHE_DIR, "MobileNet", "mobilenet_v2_weights.h5" + ) + if not os.path.exists(weights_path): + logger.info("Downloading MobileNet V2 weights file") + ModelDownloader.download_from_url( + self.BASE_WEIGHT_URL, weights_path + ) + # Start with imagenet base model with 35% of channels in each layer base_model = MobileNetV2( input_shape=(224, 224, 3), include_top=False, - weights="imagenet", + weights=weights_path, alpha=0.35, ) base_model.trainable = False # Freeze pre-trained layers From f543d0ab31090a62c7836f4e9a2fb62e8fcb3d03 Mon Sep 17 00:00:00 2001 From: Issy Szemeti <48881813+issy@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:18:41 +0000 Subject: [PATCH 22/78] Fix layout shift with camera filter (#21298) --- web/src/pages/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index a490a046af..151718d19d 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -438,7 +438,7 @@ export default function Settings() { return (
- + {t("menu.settings", { ns: "common" })} {CAMERA_SELECT_BUTTON_PAGES.includes(page) && ( From 818cccb2e345b25a185406dcf649d986c2b7944b Mon Sep 17 00:00:00 2001 From: Issy Szemeti <48881813+issy@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:42:11 +0000 Subject: [PATCH 23/78] Settings page layout shift - follow up (#21300) * Fix layout shift with camera filter * Move min height --- web/src/pages/Settings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 151718d19d..1d44125cb9 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -437,8 +437,8 @@ export default function Settings() { return (
-
- +
+ {t("menu.settings", { ns: "common" })} {CAMERA_SELECT_BUTTON_PAGES.includes(page) && ( From e7d047715d17f73ea841e6bdc02ec7380ee56c53 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 16 Dec 2025 08:11:53 -0600 Subject: [PATCH 24/78] Miscellaneous Fixes (0.17 beta) (#21301) * Wait for config to load before evaluating route access Fix race condition where custom role users are temporarily denied access after login while config is still loading. Defer route rendering in DefaultAppView until config is available so the complete role list is known before ProtectedRoute evaluates permissions * Use batching for state classification generation * Ignore incorrect scoring images if they make it through the deletion * Delete unclassified images * mitigate tensorflow atexit crash by pre-importing tflite/tensorflow on main thread Pre-import Interpreter in embeddings maintainer and add defensive lazy imports in classification processors to avoid worker-thread tensorflow imports causing "can't register atexit after shutdown" * don't require old password for users with admin role when changing passwords * don't render actions menu if no options are available * Remove hwaccel arg as it is not used for encoding * change password button text --------- Co-authored-by: Nicolas Mowen --- frigate/api/auth.py | 10 +-- frigate/data_processing/real_time/bird.py | 12 +-- .../real_time/custom_classification.py | 19 +++-- frigate/embeddings/maintainer.py | 23 ++++++ frigate/ffmpeg_presets.py | 2 +- frigate/util/classification.py | 79 +++++++++++++------ web/public/locales/en/views/settings.json | 4 +- web/src/App.tsx | 20 +++-- .../wizard/Step3ChooseExamples.tsx | 32 +++++++- .../overlay/detail/DetailActionsMenu.tsx | 23 ++++++ .../classification/ModelTrainingView.tsx | 6 ++ 11 files changed, 174 insertions(+), 56 deletions(-) diff --git a/frigate/api/auth.py b/frigate/api/auth.py index 95ee4f9dce..d3b50067c3 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -893,13 +893,9 @@ async def update_password( except DoesNotExist: return JSONResponse(content={"message": "User not found"}, status_code=404) - # Require old_password when: - # 1. Non-admin user is changing another user's password (admin only action) - # 2. Any user is changing their own password - is_changing_own_password = current_username == username - is_non_admin = current_role != "admin" - - if is_changing_own_password or is_non_admin: + # Require old_password when non-admin user is changing any password + # Admin users changing passwords do NOT need to provide the current password + if current_role != "admin": if not body.old_password: return JSONResponse( content={"message": "Current password is required"}, diff --git a/frigate/data_processing/real_time/bird.py b/frigate/data_processing/real_time/bird.py index e599ab0fb1..8d6e1b2dcf 100644 --- a/frigate/data_processing/real_time/bird.py +++ b/frigate/data_processing/real_time/bird.py @@ -19,11 +19,6 @@ from ..types import DataProcessorMetrics from .api import RealTimeProcessorApi -try: - from tflite_runtime.interpreter import Interpreter -except ModuleNotFoundError: - from tensorflow.lite.python.interpreter import Interpreter - logger = logging.getLogger(__name__) @@ -35,7 +30,7 @@ def __init__( metrics: DataProcessorMetrics, ): super().__init__(config, metrics) - self.interpreter: Interpreter = None + self.interpreter: Any | None = None self.sub_label_publisher = sub_label_publisher self.tensor_input_details: dict[str, Any] = None self.tensor_output_details: dict[str, Any] = None @@ -82,6 +77,11 @@ def __download_models(self, path: str) -> None: @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: + try: + from tflite_runtime.interpreter import Interpreter + except ModuleNotFoundError: + from tensorflow.lite.python.interpreter import Interpreter + self.interpreter = Interpreter( model_path=os.path.join(MODEL_CACHE_DIR, "bird/bird.tflite"), num_threads=2, diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py index 25ec3bb863..dd011b48e1 100644 --- a/frigate/data_processing/real_time/custom_classification.py +++ b/frigate/data_processing/real_time/custom_classification.py @@ -29,11 +29,6 @@ from ..types import DataProcessorMetrics from .api import RealTimeProcessorApi -try: - from tflite_runtime.interpreter import Interpreter -except ModuleNotFoundError: - from tensorflow.lite.python.interpreter import Interpreter - logger = logging.getLogger(__name__) MAX_OBJECT_CLASSIFICATIONS = 16 @@ -52,7 +47,7 @@ def __init__( self.requestor = requestor self.model_dir = os.path.join(MODEL_CACHE_DIR, self.model_config.name) self.train_dir = os.path.join(CLIPS_DIR, self.model_config.name, "train") - self.interpreter: Interpreter | None = None + self.interpreter: Any | None = None self.tensor_input_details: dict[str, Any] | None = None self.tensor_output_details: dict[str, Any] | None = None self.labelmap: dict[int, str] = {} @@ -74,6 +69,11 @@ def __init__( @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: + try: + from tflite_runtime.interpreter import Interpreter + except ModuleNotFoundError: + from tensorflow.lite.python.interpreter import Interpreter + model_path = os.path.join(self.model_dir, "model.tflite") labelmap_path = os.path.join(self.model_dir, "labelmap.txt") @@ -345,7 +345,7 @@ def __init__( self.model_config = model_config self.model_dir = os.path.join(MODEL_CACHE_DIR, self.model_config.name) self.train_dir = os.path.join(CLIPS_DIR, self.model_config.name, "train") - self.interpreter: Interpreter | None = None + self.interpreter: Any | None = None self.sub_label_publisher = sub_label_publisher self.requestor = requestor self.tensor_input_details: dict[str, Any] | None = None @@ -368,6 +368,11 @@ def __init__( @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: + try: + from tflite_runtime.interpreter import Interpreter + except ModuleNotFoundError: + from tensorflow.lite.python.interpreter import Interpreter + model_path = os.path.join(self.model_dir, "model.tflite") labelmap_path = os.path.join(self.model_dir, "labelmap.txt") diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 78a251c424..33d09dcc34 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -146,6 +146,29 @@ def __init__( self.detected_license_plates: dict[str, dict[str, Any]] = {} self.genai_client = get_genai_client(config) + # Pre-import TensorFlow/tflite on main thread to avoid atexit registration issues + # when importing from worker threads later (e.g., during dynamic config updates) + if ( + self.config.classification.bird.enabled + or len(self.config.classification.custom) > 0 + ): + try: + from tflite_runtime.interpreter import Interpreter # noqa: F401 + except ModuleNotFoundError: + try: + from tensorflow.lite.python.interpreter import ( # noqa: F401 + Interpreter, + ) + + logger.debug( + "Pre-imported TensorFlow Interpreter on main thread for classification models" + ) + except Exception as e: + logger.warning( + f"Failed to pre-import TensorFlow Interpreter: {e}. " + "Classification models may fail to load if added dynamically." + ) + # model runners to share between realtime and post processors if self.config.lpr.enabled: lpr_model_runner = LicensePlateModelRunner( diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 36f7828fc8..43272a6d1f 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -153,7 +153,7 @@ def get_gpu_arg(self, preset: str, gpu: int) -> str: FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v main -level:v 4.1 -async_depth:v 1 {2}", - FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -hwaccel cuda -hwaccel_device {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", + FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}", FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", diff --git a/frigate/util/classification.py b/frigate/util/classification.py index 03229cc737..7777af51cf 100644 --- a/frigate/util/classification.py +++ b/frigate/util/classification.py @@ -499,6 +499,10 @@ def _extract_keyframes( """ Extract keyframes from recordings at specified timestamps and crop to specified regions. + This implementation batches work by running multiple ffmpeg snapshot commands + concurrently, which significantly reduces total runtime compared to + processing each timestamp serially. + Args: ffmpeg_path: Path to ffmpeg binary timestamps: List of timestamp dicts from _select_balanced_timestamps @@ -508,15 +512,21 @@ def _extract_keyframes( Returns: List of paths to successfully extracted and cropped keyframe images """ - keyframe_paths = [] + from concurrent.futures import ThreadPoolExecutor, as_completed + + if not timestamps: + return [] + + # Limit the number of concurrent ffmpeg processes so we don't overload the host. + max_workers = min(5, len(timestamps)) - for idx, ts_info in enumerate(timestamps): + def _process_timestamp(idx: int, ts_info: dict) -> tuple[int, str | None]: camera = ts_info["camera"] timestamp = ts_info["timestamp"] if camera not in camera_crops: logger.warning(f"No crop coordinates for camera {camera}") - continue + return idx, None norm_x1, norm_y1, norm_x2, norm_y2 = camera_crops[camera] @@ -533,7 +543,7 @@ def _extract_keyframes( .get() ) except Exception: - continue + return idx, None relative_time = timestamp - recording.start_time @@ -547,38 +557,57 @@ def _extract_keyframes( height=None, ) - if image_data: - nparr = np.frombuffer(image_data, np.uint8) - img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + if not image_data: + return idx, None - if img is not None: - height, width = img.shape[:2] + nparr = np.frombuffer(image_data, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + + if img is None: + return idx, None + + height, width = img.shape[:2] - x1 = int(norm_x1 * width) - y1 = int(norm_y1 * height) - x2 = int(norm_x2 * width) - y2 = int(norm_y2 * height) + x1 = int(norm_x1 * width) + y1 = int(norm_y1 * height) + x2 = int(norm_x2 * width) + y2 = int(norm_y2 * height) - x1_clipped = max(0, min(x1, width)) - y1_clipped = max(0, min(y1, height)) - x2_clipped = max(0, min(x2, width)) - y2_clipped = max(0, min(y2, height)) + x1_clipped = max(0, min(x1, width)) + y1_clipped = max(0, min(y1, height)) + x2_clipped = max(0, min(x2, width)) + y2_clipped = max(0, min(y2, height)) - if x2_clipped > x1_clipped and y2_clipped > y1_clipped: - cropped = img[y1_clipped:y2_clipped, x1_clipped:x2_clipped] - resized = cv2.resize(cropped, (224, 224)) + if x2_clipped <= x1_clipped or y2_clipped <= y1_clipped: + return idx, None - output_path = os.path.join(output_dir, f"frame_{idx:04d}.jpg") - cv2.imwrite(output_path, resized) - keyframe_paths.append(output_path) + cropped = img[y1_clipped:y2_clipped, x1_clipped:x2_clipped] + resized = cv2.resize(cropped, (224, 224)) + output_path = os.path.join(output_dir, f"frame_{idx:04d}.jpg") + cv2.imwrite(output_path, resized) + return idx, output_path except Exception as e: logger.debug( f"Failed to extract frame from {recording.path} at {relative_time}s: {e}" ) - continue + return idx, None + + keyframes_with_index: list[tuple[int, str]] = [] + + with ThreadPoolExecutor(max_workers=max_workers) as executor: + future_to_idx = { + executor.submit(_process_timestamp, idx, ts_info): idx + for idx, ts_info in enumerate(timestamps) + } + + for future in as_completed(future_to_idx): + _, path = future.result() + if path: + keyframes_with_index.append((future_to_idx[future], path)) - return keyframe_paths + keyframes_with_index.sort(key=lambda item: item[0]) + return [path for _, path in keyframes_with_index] def _select_distinct_images( diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 2adcc53ff0..1946a1c62e 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -679,7 +679,7 @@ "desc": "Manage this Frigate instance's user accounts." }, "addUser": "Add User", - "updatePassword": "Update Password", + "updatePassword": "Reset Password", "toast": { "success": { "createUser": "User {{user}} created successfully", @@ -700,7 +700,7 @@ "role": "Role", "noUsers": "No users found.", "changeRole": "Change user role", - "password": "Password", + "password": "Reset Password", "deleteUser": "Delete user" }, "dialog": { diff --git a/web/src/App.tsx b/web/src/App.tsx index 2fbfa4c99d..b458d9ec3c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -14,6 +14,7 @@ import ProtectedRoute from "@/components/auth/ProtectedRoute"; import { AuthProvider } from "@/context/auth-context"; import useSWR from "swr"; import { FrigateConfig } from "./types/frigateConfig"; +import ActivityIndicator from "@/components/indicators/activity-indicator"; const Live = lazy(() => import("@/pages/Live")); const Events = lazy(() => import("@/pages/Events")); @@ -50,6 +51,13 @@ function DefaultAppView() { const { data: config } = useSWR("config", { revalidateOnFocus: false, }); + + // Compute required roles for main routes, ensuring we have config first + // to prevent race condition where custom roles are temporarily unavailable + const mainRouteRoles = config?.auth?.roles + ? Object.keys(config.auth.roles) + : undefined; + return (
{isDesktop && } @@ -68,13 +76,11 @@ function DefaultAppView() { + mainRouteRoles ? ( + + ) : ( + + ) } > } /> diff --git a/web/src/components/classification/wizard/Step3ChooseExamples.tsx b/web/src/components/classification/wizard/Step3ChooseExamples.tsx index c4978a1b88..d15e45b8c5 100644 --- a/web/src/components/classification/wizard/Step3ChooseExamples.tsx +++ b/web/src/components/classification/wizard/Step3ChooseExamples.tsx @@ -141,7 +141,37 @@ export default function Step3ChooseExamples({ ); await Promise.all(categorizePromises); - // Step 2.5: Create empty folders for classes that don't have any images + // Step 2.5: Delete any unselected images from train folder + // For state models, all images must be classified, so unselected images should be removed + // For object models, unselected images are assigned to "none" so they're already categorized + if (step1Data.modelType === "state") { + try { + // Fetch current train images to see what's left after categorization + const trainImagesResponse = await axios.get( + `/classification/${step1Data.modelName}/train`, + ); + const remainingTrainImages = trainImagesResponse.data || []; + + const categorizedImageNames = new Set(Object.keys(classifications)); + const unselectedImages = remainingTrainImages.filter( + (imageName) => !categorizedImageNames.has(imageName), + ); + + if (unselectedImages.length > 0) { + await axios.post( + `/classification/${step1Data.modelName}/train/delete`, + { + ids: unselectedImages, + }, + ); + } + } catch (error) { + // Silently fail - unselected images will remain but won't cause issues + // since the frontend filters out images that don't match expected format + } + } + + // Step 2.6: Create empty folders for classes that don't have any images // This ensures all classes are available in the dataset view later const classesWithImages = new Set( Object.values(classifications).filter((c) => c && c !== "none"), diff --git a/web/src/components/overlay/detail/DetailActionsMenu.tsx b/web/src/components/overlay/detail/DetailActionsMenu.tsx index ee4184d0f1..87f77eaf82 100644 --- a/web/src/components/overlay/detail/DetailActionsMenu.tsx +++ b/web/src/components/overlay/detail/DetailActionsMenu.tsx @@ -49,6 +49,29 @@ export default function DetailActionsMenu({ search.data?.type === "audio" ? null : [`review/event/${search.id}`], ); + // don't render menu at all if no options are available + const hasSemanticSearchOption = + config?.semantic_search.enabled && + setSimilarity !== undefined && + search.data?.type === "object"; + + const hasReviewItem = !!(reviewItem && reviewItem.id); + + const hasAdminTriggerOption = + isAdmin && + config?.semantic_search.enabled && + search.data?.type === "object"; + + if ( + !search.has_snapshot && + !search.has_clip && + !hasSemanticSearchOption && + !hasReviewItem && + !hasAdminTriggerOption + ) { + return null; + } + return ( diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index 02abc021b9..ea9facd089 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -866,6 +866,12 @@ function TrainGrid({ }; }) .filter((data) => { + // Ignore images that don't match the expected format (event-camera-timestamp-state-score.webp) + // Expected format has 5 parts when split by "-", and score should be a valid number + if (data.score === undefined || isNaN(data.score) || !data.name) { + return false; + } + if (!trainFilter) { return true; } From c292cd207d523255d7d1de5e0f061afcc801454d Mon Sep 17 00:00:00 2001 From: Issy Szemeti <48881813+issy@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:28:35 +0000 Subject: [PATCH 25/78] Align node versions used in GHA PR workflow (#21302) * Add node/npm version config to package.json * Bump npm version/fix node version format * Version range * Use package.json for github actions node version * Unification * Move it all to the bottom * Remove this * Bump versions in docs * Add volta config here too * Revert changes * Revert this --- .github/workflows/pull_request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3540738382..c4d8aa7a03 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,9 +19,9 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-node@master + - uses: actions/setup-node@v6 with: - node-version: 16.x + node-version: 20.x - run: npm install working-directory: ./web - name: Lint @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-node@master + - uses: actions/setup-node@v6 with: node-version: 20.x - run: npm install @@ -78,7 +78,7 @@ jobs: uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-node@master + - uses: actions/setup-node@v6 with: node-version: 20.x - name: Install devcontainer cli From 78eace258eec51e962bd6230601ce30ca8f5c829 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 16 Dec 2025 21:35:43 -0700 Subject: [PATCH 26/78] Miscellaneous Fixes (0.17 Beta) (#21320) * Exclude D-FINE from using CUDA Graphs * fix objects count in detail stream * Add debugging for classification models * validate idb stored stream name and reset if invalid fixes https://github.com/blakeblackshear/frigate/discussions/21311 * ensure jina loading takes place in the main thread to prevent lazily importing tensorflow in another thread later reverts atexit changes in https://github.com/blakeblackshear/frigate/pull/21301 and fixes https://github.com/blakeblackshear/frigate/discussions/21306 * revert old atexit change in bird too * revert types * ensure we bail in the live mode hook for empty camera groups prevent infinite rendering on camera groups with no cameras --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- .../object_classification.md | 20 ++++++++++++ .../state_classification.md | 31 +++++++++++++++++++ frigate/data_processing/real_time/bird.py | 12 +++---- .../real_time/custom_classification.py | 14 ++++----- frigate/detectors/detection_runners.py | 1 + frigate/embeddings/maintainer.py | 23 -------------- frigate/embeddings/onnx/jina_v1_embedding.py | 3 ++ frigate/embeddings/onnx/jina_v2_embedding.py | 3 ++ web/src/components/timeline/DetailStream.tsx | 6 ++-- web/src/hooks/use-camera-live-mode.ts | 2 +- web/src/views/live/LiveCameraView.tsx | 22 ++++++++++--- 11 files changed, 93 insertions(+), 44 deletions(-) diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index 3d59b74f97..f94e5e0ec0 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -94,3 +94,23 @@ When choosing which objects to classify, start with a small number of visually d - **Preprocessing**: Ensure examples reflect object crops similar to Frigate’s boxes; keep the subject centered. - **Labels**: Keep label names short and consistent; include a `none` class if you plan to ignore uncertain predictions for sub labels. - **Threshold**: Tune `threshold` per model to reduce false assignments. Start at `0.8` and adjust based on validation. + +## Debugging Classification Models + +To troubleshoot issues with object classification models, enable debug logging to see detailed information about classification attempts, scores, and consensus calculations. + +Enable debug logs for classification models by adding `frigate.data_processing.real_time.custom_classification: debug` to your `logger` configuration. These logs are verbose, so only keep this enabled when necessary. Restart Frigate after this change. + +```yaml +logger: + default: info + logs: + frigate.data_processing.real_time.custom_classification: debug +``` + +The debug logs will show: + +- Classification probabilities for each attempt +- Whether scores meet the threshold requirement +- Consensus calculations and when assignments are made +- Object classification history and weighted scores diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index 801d5d9052..2b7d16da17 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -70,3 +70,34 @@ Once some images are assigned, training will begin automatically. - **Data collection**: Use the model's Recent Classifications tab to gather balanced examples across times of day and weather. - **When to train**: Focus on cases where the model is entirely incorrect or flips between states when it should not. There's no need to train additional images when the model is already working consistently. - **Selecting training images**: Images scoring below 100% due to new conditions (e.g., first snow of the year, seasonal changes) or variations (e.g., objects temporarily in view, insects at night) are good candidates for training, as they represent scenarios different from the default state. Training these lower-scoring images that differ from existing training data helps prevent overfitting. Avoid training large quantities of images that look very similar, especially if they already score 100% as this can lead to overfitting. + +## Debugging Classification Models + +To troubleshoot issues with state classification models, enable debug logging to see detailed information about classification attempts, scores, and state verification. + +Enable debug logs for classification models by adding `frigate.data_processing.real_time.custom_classification: debug` to your `logger` configuration. These logs are verbose, so only keep this enabled when necessary. Restart Frigate after this change. + +```yaml +logger: + default: info + logs: + frigate.data_processing.real_time.custom_classification: debug +``` + +The debug logs will show: + +- Classification probabilities for each attempt +- Whether scores meet the threshold requirement +- State verification progress (consecutive detections needed) +- When state changes are published + +### Recent Classifications + +For state classification, images are only added to recent classifications under specific circumstances: + +- **First detection**: The first classification attempt for a camera is always saved +- **State changes**: Images are saved when the detected state differs from the current verified state +- **Pending verification**: Images are saved when there's a pending state change being verified (requires 3 consecutive identical states) +- **Low confidence**: Images with scores below 100% are saved even if the state matches the current state (useful for training) + +Images are **not** saved when the state is stable (detected state matches current state) **and** the score is 100%. This prevents unnecessary storage of redundant high-confidence classifications. diff --git a/frigate/data_processing/real_time/bird.py b/frigate/data_processing/real_time/bird.py index 8d6e1b2dcf..e599ab0fb1 100644 --- a/frigate/data_processing/real_time/bird.py +++ b/frigate/data_processing/real_time/bird.py @@ -19,6 +19,11 @@ from ..types import DataProcessorMetrics from .api import RealTimeProcessorApi +try: + from tflite_runtime.interpreter import Interpreter +except ModuleNotFoundError: + from tensorflow.lite.python.interpreter import Interpreter + logger = logging.getLogger(__name__) @@ -30,7 +35,7 @@ def __init__( metrics: DataProcessorMetrics, ): super().__init__(config, metrics) - self.interpreter: Any | None = None + self.interpreter: Interpreter = None self.sub_label_publisher = sub_label_publisher self.tensor_input_details: dict[str, Any] = None self.tensor_output_details: dict[str, Any] = None @@ -77,11 +82,6 @@ def __download_models(self, path: str) -> None: @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: - try: - from tflite_runtime.interpreter import Interpreter - except ModuleNotFoundError: - from tensorflow.lite.python.interpreter import Interpreter - self.interpreter = Interpreter( model_path=os.path.join(MODEL_CACHE_DIR, "bird/bird.tflite"), num_threads=2, diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py index dd011b48e1..a2f88ee93b 100644 --- a/frigate/data_processing/real_time/custom_classification.py +++ b/frigate/data_processing/real_time/custom_classification.py @@ -29,6 +29,11 @@ from ..types import DataProcessorMetrics from .api import RealTimeProcessorApi +try: + from tflite_runtime.interpreter import Interpreter +except ModuleNotFoundError: + from tensorflow.lite.python.interpreter import Interpreter + logger = logging.getLogger(__name__) MAX_OBJECT_CLASSIFICATIONS = 16 @@ -47,7 +52,7 @@ def __init__( self.requestor = requestor self.model_dir = os.path.join(MODEL_CACHE_DIR, self.model_config.name) self.train_dir = os.path.join(CLIPS_DIR, self.model_config.name, "train") - self.interpreter: Any | None = None + self.interpreter: Interpreter = None self.tensor_input_details: dict[str, Any] | None = None self.tensor_output_details: dict[str, Any] | None = None self.labelmap: dict[int, str] = {} @@ -345,7 +350,7 @@ def __init__( self.model_config = model_config self.model_dir = os.path.join(MODEL_CACHE_DIR, self.model_config.name) self.train_dir = os.path.join(CLIPS_DIR, self.model_config.name, "train") - self.interpreter: Any | None = None + self.interpreter: Interpreter = None self.sub_label_publisher = sub_label_publisher self.requestor = requestor self.tensor_input_details: dict[str, Any] | None = None @@ -368,11 +373,6 @@ def __init__( @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: - try: - from tflite_runtime.interpreter import Interpreter - except ModuleNotFoundError: - from tensorflow.lite.python.interpreter import Interpreter - model_path = os.path.join(self.model_dir, "model.tflite") labelmap_path = os.path.join(self.model_dir, "labelmap.txt") diff --git a/frigate/detectors/detection_runners.py b/frigate/detectors/detection_runners.py index 89ebb35eb6..56b49ec67d 100644 --- a/frigate/detectors/detection_runners.py +++ b/frigate/detectors/detection_runners.py @@ -170,6 +170,7 @@ def is_model_supported(model_type: str) -> bool: return model_type not in [ ModelTypeEnum.yolonas.value, + ModelTypeEnum.dfine.value, EnrichmentModelTypeEnum.paddleocr.value, EnrichmentModelTypeEnum.jina_v1.value, EnrichmentModelTypeEnum.jina_v2.value, diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 33d09dcc34..78a251c424 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -146,29 +146,6 @@ def __init__( self.detected_license_plates: dict[str, dict[str, Any]] = {} self.genai_client = get_genai_client(config) - # Pre-import TensorFlow/tflite on main thread to avoid atexit registration issues - # when importing from worker threads later (e.g., during dynamic config updates) - if ( - self.config.classification.bird.enabled - or len(self.config.classification.custom) > 0 - ): - try: - from tflite_runtime.interpreter import Interpreter # noqa: F401 - except ModuleNotFoundError: - try: - from tensorflow.lite.python.interpreter import ( # noqa: F401 - Interpreter, - ) - - logger.debug( - "Pre-imported TensorFlow Interpreter on main thread for classification models" - ) - except Exception as e: - logger.warning( - f"Failed to pre-import TensorFlow Interpreter: {e}. " - "Classification models may fail to load if added dynamically." - ) - # model runners to share between realtime and post processors if self.config.lpr.enabled: lpr_model_runner = LicensePlateModelRunner( diff --git a/frigate/embeddings/onnx/jina_v1_embedding.py b/frigate/embeddings/onnx/jina_v1_embedding.py index e64d8da39c..519247f3c3 100644 --- a/frigate/embeddings/onnx/jina_v1_embedding.py +++ b/frigate/embeddings/onnx/jina_v1_embedding.py @@ -186,6 +186,9 @@ def __init__( download_func=self._download_model, ) self.downloader.ensure_model_files() + # Avoid lazy loading in worker threads: block until downloads complete + # and load the model on the main thread during initialization. + self._load_model_and_utils() else: self.downloader = None ModelDownloader.mark_files_state( diff --git a/frigate/embeddings/onnx/jina_v2_embedding.py b/frigate/embeddings/onnx/jina_v2_embedding.py index 44cc6c12bb..fd4323f85d 100644 --- a/frigate/embeddings/onnx/jina_v2_embedding.py +++ b/frigate/embeddings/onnx/jina_v2_embedding.py @@ -65,6 +65,9 @@ def __init__( download_func=self._download_model, ) self.downloader.ensure_model_files() + # Avoid lazy loading in worker threads: block until downloads complete + # and load the model on the main thread during initialization. + self._load_model_and_utils() else: self.downloader = None ModelDownloader.mark_files_state( diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index dc87e1f0f7..e28c841b12 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -345,9 +345,9 @@ function ReviewGroup({ } const reviewInfo = useMemo(() => { - const objectCount = fetchedEvents - ? fetchedEvents.length - : (review.data.objects ?? []).length; + const detectionsCount = + review.data?.detections?.length ?? (review.data?.objects ?? []).length; + const objectCount = fetchedEvents ? fetchedEvents.length : detectionsCount; return `${t("detail.trackedObject", { count: objectCount })}`; }, [review, t, fetchedEvents]); diff --git a/web/src/hooks/use-camera-live-mode.ts b/web/src/hooks/use-camera-live-mode.ts index 0b189a1f2d..288c0ea09e 100644 --- a/web/src/hooks/use-camera-live-mode.ts +++ b/web/src/hooks/use-camera-live-mode.ts @@ -54,7 +54,7 @@ export default function useCameraLiveMode( }>({}); useEffect(() => { - if (!cameras) return; + if (!cameras || cameras.length === 0) return; const mseSupported = "MediaSource" in window || "ManagedMediaSource" in window; diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index a9c62f6238..5de52d2438 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -147,10 +147,11 @@ export default function LiveCameraView({ // supported features - const [streamName, setStreamName] = useUserPersistence( - `${camera.name}-stream`, - Object.values(camera.live.streams)[0], - ); + const [streamName, setStreamName, streamNameLoaded] = + useUserPersistence( + `${camera.name}-stream`, + Object.values(camera.live.streams)[0], + ); const isRestreamed = useMemo( () => @@ -159,6 +160,19 @@ export default function LiveCameraView({ [config, streamName], ); + // validate stored stream name and reset if now invalid + + useEffect(() => { + if (!streamNameLoaded) return; + + const available = Object.values(camera.live.streams || {}); + if (available.length === 0) return; + + if (streamName != null && !available.includes(streamName)) { + setStreamName(available[0]); + } + }, [streamNameLoaded, camera.live.streams, streamName, setStreamName]); + const { data: cameraMetadata } = useSWR( isRestreamed ? `go2rtc/streams/${streamName}` : null, { From 3edfd905de644fa6e9f4b513a9386d9a7fbc7a9f Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Wed, 17 Dec 2025 08:01:20 -0600 Subject: [PATCH 27/78] consider anonymous user authenticated (#21335) * consider anonymous user authenticated * simplify and update comments --- frigate/api/auth.py | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/frigate/api/auth.py b/frigate/api/auth.py index d3b50067c3..c6b0cef239 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -143,17 +143,6 @@ async def admin_checker(request: Request): return admin_checker -def _is_authenticated(request: Request) -> bool: - """ - Helper to determine if a request is from an authenticated user. - - Returns True if the request has a valid authenticated user (not anonymous). - Port 5000 internal requests are considered anonymous despite having admin role. - """ - username = request.headers.get("remote-user") - return username is not None and username != "anonymous" - - def allow_public(): """ Override dependency to allow unauthenticated access to an endpoint. @@ -173,27 +162,24 @@ async def public_checker(request: Request): def allow_any_authenticated(): """ - Override dependency to allow any authenticated user (bypass admin requirement). + Override dependency to allow any request that passed through the /auth endpoint. Allows: - - Port 5000 internal requests (have admin role despite anonymous user) - - Any authenticated user with a real username (not "anonymous") + - Port 5000 internal requests (remote-user: "anonymous", remote-role: "admin") + - Authenticated users with JWT tokens (remote-user: username) + - Unauthenticated requests when auth is disabled (remote-user: "anonymous") Rejects: - - Port 8971 requests with anonymous user (auth disabled, no proxy auth) + - Requests with no remote-user header (did not pass through /auth endpoint) Example: @router.get("/authenticated-endpoint", dependencies=[Depends(allow_any_authenticated())]) """ async def auth_checker(request: Request): - # Port 5000 requests have admin role and should be allowed - role = request.headers.get("remote-role") - if role == "admin": - return - - # Otherwise require a real authenticated user (not anonymous) - if not _is_authenticated(request): + # Ensure a remote-user has been set by the /auth endpoint + username = request.headers.get("remote-user") + if username is None: raise HTTPException(status_code=401, detail="Authentication required") return From 13957fec00ede000f9cd5b67eb7544115b4fd1fd Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Thu, 18 Dec 2025 06:26:11 +0800 Subject: [PATCH 28/78] classification i18n fix (#21331) * fix: fix classification pages none label i18n * fix: fix README_CN formatting issue --- README_CN.md | 9 +++++---- web/public/locales/en/views/classificationModel.json | 5 +++-- web/src/components/card/ClassificationCard.tsx | 6 +++++- .../components/overlay/ClassificationSelectionDialog.tsx | 2 +- web/src/components/overlay/dialog/TrainFilterDialog.tsx | 4 +++- web/src/views/classification/ModelTrainingView.tsx | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README_CN.md b/README_CN.md index 3c37f64ead..f2433b2489 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,14 +4,14 @@ # Frigate NVR™ - 一个具有实时目标检测的本地 NVR -[English](https://github.com/blakeblackshear/frigate) | \[简体中文\] - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - 翻译状态 +[English](https://github.com/blakeblackshear/frigate) | \[简体中文\] + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + 一个完整的本地网络视频录像机(NVR),专为[Home Assistant](https://www.home-assistant.io)设计,具备 AI 目标/物体检测功能。使用 OpenCV 和 TensorFlow 在本地为 IP 摄像头执行实时物体检测。 强烈推荐使用 GPU 或者 AI 加速器(例如[Google Coral 加速器](https://coral.ai/products/) 或者 [Hailo](https://hailo.ai/)等)。它们的运行效率远远高于现在的顶级 CPU,并且功耗也极低。 @@ -38,6 +38,7 @@ ## 协议 本项目采用 **MIT 许可证**授权。 + **代码部分**:本代码库中的源代码、配置文件和文档均遵循 [MIT 许可证](LICENSE)。您可以自由使用、修改和分发这些代码,但必须保留原始版权声明。 **商标部分**:“Frigate”名称、“Frigate NVR”品牌以及 Frigate 的 Logo 为 **Frigate LLC 的商标**,**不在** MIT 许可证覆盖范围内。 diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json index 0e9095e490..fa3ec03cff 100644 --- a/web/public/locales/en/views/classificationModel.json +++ b/web/public/locales/en/views/classificationModel.json @@ -1,7 +1,9 @@ { "documentTitle": "Classification Models - Frigate", "details": { - "scoreInfo": "Score represents the average classification confidence across all detections of this object." + "scoreInfo": "Score represents the average classification confidence across all detections of this object.", + "none": "None", + "unknown": "Unknown" }, "button": { "deleteClassificationAttempts": "Delete Classification Images", @@ -83,7 +85,6 @@ "aria": "Select Recent Classifications" }, "categories": "Classes", - "none": "None", "createCategory": { "new": "Create New Class" }, diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index 4e5f224b03..e8bb9f9cbe 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -161,7 +161,11 @@ export const ClassificationCard = forwardRef< )} >
- {data.name == "unknown" ? t("details.unknown") : data.name} + {data.name == "unknown" + ? t("details.unknown") + : data.name == "none" + ? t("details.none") + : data.name}
{data.score != undefined && (
onCategorizeImage(category)} > {category === "none" - ? t("none") + ? t("details.none") : category.replaceAll("_", " ")} ))} diff --git a/web/src/components/overlay/dialog/TrainFilterDialog.tsx b/web/src/components/overlay/dialog/TrainFilterDialog.tsx index 46d802a196..b44b766fb9 100644 --- a/web/src/components/overlay/dialog/TrainFilterDialog.tsx +++ b/web/src/components/overlay/dialog/TrainFilterDialog.tsx @@ -170,7 +170,9 @@ export function ClassFilterContent({ { diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index ea9facd089..f3b555af21 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -707,7 +707,7 @@ function LibrarySelector({ className="flex-grow cursor-pointer capitalize" onClick={() => setPageToggle(id)} > - {id === "none" ? t("none") : id.replaceAll("_", " ")} + {id === "none" ? t("details.none") : id.replaceAll("_", " ")} ({dataset?.[id].length}) From ae009b98618f63f7ab0284c9ff35d6cb6da5990e Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:52:27 -0600 Subject: [PATCH 29/78] Miscellaneous Fixes (0.17 beta) (#21336) * fix coral docs * add note about sub label object classification with person * Catch OSError for deleting classification image * add docs for dummy camera debugging * add to sidebar * fix formatting * fix * avx instructions are required for classification * break text on classification card to prevent button overflow * Ensure there is no NameError when processing * Don't use region for state classification models * fix spelling * Handle attribute based models * Catch case of non-trained model that doesn't add infinite number of classification images * Actually train object classification models automatically --------- Co-authored-by: Nicolas Mowen --- .../object_classification.md | 8 ++ .../state_classification.md | 2 + docs/docs/configuration/object_detectors.md | 12 +-- docs/docs/troubleshooting/dummy-camera.md | 60 +++++++++++++ docs/sidebars.ts | 1 + .../real_time/custom_classification.py | 55 +++++++----- .../locales/en/views/classificationModel.json | 2 +- .../components/card/ClassificationCard.tsx | 87 ++++++++++--------- .../wizard/Step3ChooseExamples.tsx | 8 +- web/src/pages/FaceLibrary.tsx | 19 +++- web/src/types/classification.ts | 6 ++ web/src/types/event.ts | 7 ++ .../classification/ModelTrainingView.tsx | 44 +++++++++- 13 files changed, 235 insertions(+), 76 deletions(-) create mode 100644 docs/docs/troubleshooting/dummy-camera.md diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index f94e5e0ec0..37d9082859 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -11,6 +11,8 @@ Object classification models are lightweight and run very fast on CPU. Inference Training the model does briefly use a high amount of system resources for about 1–3 minutes per training run. On lower-power devices, training may take longer. +A CPU with AVX instructions is required for training and inference. + ## Classes Classes are the categories your model will learn to distinguish between. Each class represents a distinct visual category that the model will predict. @@ -35,6 +37,12 @@ For object classification: - Ideal when multiple attributes can coexist independently. - Example: Detecting if a `person` in a construction yard is wearing a helmet or not. +:::note + +A tracked object can only have a single sub label. If you are using Face Recognition and you configure an object classification model for `person` using the sub label type, your sub label may not be assigned correctly as it depends on which enrichment completes its analysis first. Consider using the `attribute` type instead. + +::: + ## Assignment Requirements Sub labels and attributes are only assigned when both conditions are met: diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index 2b7d16da17..6b95a2567a 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -11,6 +11,8 @@ State classification models are lightweight and run very fast on CPU. Inference Training the model does briefly use a high amount of system resources for about 1–3 minutes per training run. On lower-power devices, training may take longer. +A CPU with AVX instructions is required for training and inference. + ## Classes Classes are the different states an area on your camera can be in. Each class represents a distinct visual state that the model will learn to recognize. diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 282310be56..229dd698c1 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -146,16 +146,16 @@ detectors: ### EdgeTPU Supported Models -| Model | Notes | -| ------------------------------------- | ------------------------------------------- | -| [MobileNet v2](#ssdlite-mobilenet-v2) | Default model | -| [YOLOv9](#yolo-v9) | More accurate but slower than default model | +| Model | Notes | +| ----------------------- | ------------------------------------------- | +| [Mobiledet](#mobiledet) | Default model | +| [YOLOv9](#yolov9) | More accurate but slower than default model | -#### SSDLite MobileNet v2 +#### Mobiledet A TensorFlow Lite model is provided in the container at `/edgetpu_model.tflite` and is used by this detector type by default. To provide your own model, bind mount the file into the container and provide the path with `model.path`. -#### YOLO v9 +#### YOLOv9 [YOLOv9](https://github.com/dbro/frigate-detector-edgetpu-yolo9/releases/download/v1.0/yolov9-s-relu6-best_320_int8_edgetpu.tflite) models that are compiled for Tensorflow Lite and properly quantized are supported, but not included by default. To provide your own model, bind mount the file into the container and provide the path with `model.path`. Note that the model may require a custom label file (eg. [use this 17 label file](https://raw.githubusercontent.com/dbro/frigate-detector-edgetpu-yolo9/refs/heads/main/labels-coco17.txt) for the model linked above.) diff --git a/docs/docs/troubleshooting/dummy-camera.md b/docs/docs/troubleshooting/dummy-camera.md new file mode 100644 index 0000000000..7e7c26ae92 --- /dev/null +++ b/docs/docs/troubleshooting/dummy-camera.md @@ -0,0 +1,60 @@ +--- +id: dummy-camera +title: Troubleshooting Detection +--- + +When investigating object detection or tracking problems, it can be helpful to replay an exported video as a temporary "dummy" camera. This lets you reproduce issues locally, iterate on configuration (detections, zones, enrichment settings), and capture logs and clips for analysis. + +## When to use + +- Replaying an exported clip to reproduce incorrect detections +- Testing configuration changes (model settings, trackers, filters) against a known clip +- Gathering deterministic logs and recordings for debugging or issue reports + +## Example Config + +Place the clip you want to replay in a location accessible to Frigate (for example `/media/frigate/` or the repository `debug/` folder when developing). Then add a temporary camera to your `config/config.yml` like this: + +```yaml +cameras: + test: + ffmpeg: + inputs: + - path: /media/frigate/car-stopping.mp4 + input_args: -re -stream_loop -1 -fflags +genpts + roles: + - detect + detect: + enabled: true + record: + enabled: false + snapshots: + enabled: false +``` + +- `-re -stream_loop -1` tells `ffmpeg` to play the file in realtime and loop indefinitely, which is useful for long debugging sessions. +- `-fflags +genpts` helps generate presentation timestamps when they are missing in the file. + +## Steps + +1. Export or copy the clip you want to replay to the Frigate host (e.g., `/media/frigate/` or `debug/clips/`). +2. Add the temporary camera to `config/config.yml` (example above). Use a unique name such as `test` or `replay_camera` so it's easy to remove later. + - If you're debugging a specific camera, copy the settings from that camera (frame rate, model/enrichment settings, zones, etc.) into the temporary camera so the replay closely matches the original environment. Leave `record` and `snapshots` disabled unless you are specifically debugging recording or snapshot behavior. +3. Restart Frigate. +4. Observe the Debug view in the UI and logs as the clip is replayed. Watch detections, zones, or any feature you're looking to debug, and note any errors in the logs to reproduce the issue. +5. Iterate on camera or enrichment settings (model, fps, zones, filters) and re-check the replay until the behavior is resolved. +6. Remove the temporary camera from your config after debugging to avoid spurious telemetry or recordings. + +## Variables to consider in object tracking + +- The exported video will not always line up exactly with how it originally ran through Frigate (or even with the last loop). Different frames may be used on replay, which can change detections and tracking. +- Motion detection depends on the frames used; small frame shifts can change motion regions and therefore what gets passed to the detector. +- Object detection is not deterministic: models and post-processing can yield different results across runs, so you may not get identical detections or track IDs every time. + +When debugging, treat the replay as a close approximation rather than a byte-for-byte replay. Capture multiple runs, enable recording if helpful, and examine logs and saved event clips to understand variability. + +## Troubleshooting + +- No video: verify the path is correct and accessible from the Frigate process/container. +- FFmpeg errors: check the log output for ffmpeg-specific flags and adjust `input_args` accordingly for your file/container. You may also need to disable hardware acceleration (`hwaccel_args: ""`) for the dummy camera. +- No detections: confirm the camera `roles` include `detect`, and model/detector configuration is enabled. diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 1f5d0572fd..4c8effeecd 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -132,6 +132,7 @@ const sidebars: SidebarsConfig = { "troubleshooting/gpu", "troubleshooting/edgetpu", "troubleshooting/memory", + "troubleshooting/dummy-camera", ], Development: [ "development/contributing", diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py index a2f88ee93b..ee72e145c6 100644 --- a/frigate/data_processing/real_time/custom_classification.py +++ b/frigate/data_processing/real_time/custom_classification.py @@ -229,28 +229,34 @@ def process_frame(self, frame_data: dict[str, Any], frame: np.ndarray): if not should_run: return - x, y, x2, y2 = calculate_region( - frame.shape, - crop[0], - crop[1], - crop[2], - crop[3], - 224, - 1.0, - ) - rgb = cv2.cvtColor(frame, cv2.COLOR_YUV2RGB_I420) - frame = rgb[ - y:y2, - x:x2, - ] + height, width = rgb.shape[:2] + + # Convert normalized crop coordinates to pixel values + x1 = int(camera_config.crop[0] * width) + y1 = int(camera_config.crop[1] * height) + x2 = int(camera_config.crop[2] * width) + y2 = int(camera_config.crop[3] * height) + + # Clip coordinates to frame boundaries + x1 = max(0, min(x1, width)) + y1 = max(0, min(y1, height)) + x2 = max(0, min(x2, width)) + y2 = max(0, min(y2, height)) + + if x2 <= x1 or y2 <= y1: + logger.warning( + f"Invalid crop coordinates for {camera}: [{x1}, {y1}, {x2}, {y2}]" + ) + return - if frame.shape != (224, 224): - try: - resized_frame = cv2.resize(frame, (224, 224)) - except Exception: - logger.warning("Failed to resize image for state classification") - return + frame = rgb[y1:y2, x1:x2] + + try: + resized_frame = cv2.resize(frame, (224, 224)) + except Exception: + logger.warning("Failed to resize image for state classification") + return if self.interpreter is None: # When interpreter is None, always save (score is 0.0, which is < 1.0) @@ -513,6 +519,13 @@ def process_frame(self, obj_data, frame): 0.0, max_files=save_attempts, ) + + # Still track history even when model doesn't exist to respect MAX_OBJECT_CLASSIFICATIONS + # Add an entry with "unknown" label so the history limit is enforced + if object_id not in self.classification_history: + self.classification_history[object_id] = [] + + self.classification_history[object_id].append(("unknown", 0.0, now)) return input = np.expand_dims(resized_crop, axis=0) @@ -654,5 +667,5 @@ def write_classification_attempt( if len(files) > max_files: os.unlink(os.path.join(folder, files[-1])) - except FileNotFoundError: + except (FileNotFoundError, OSError): pass diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json index fa3ec03cff..96bf0284bd 100644 --- a/web/public/locales/en/views/classificationModel.json +++ b/web/public/locales/en/views/classificationModel.json @@ -74,7 +74,7 @@ }, "renameCategory": { "title": "Rename Class", - "desc": "Enter a new name for {{name}}. You will be required to retrain the model for the name change to take affect." + "desc": "Enter a new name for {{name}}. You will be required to retrain the model for the name change to take effect." }, "description": { "invalidName": "Invalid name. Names can only include letters, numbers, spaces, apostrophes, underscores, and hyphens." diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index e8bb9f9cbe..360bb11bff 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -4,8 +4,8 @@ import { cn } from "@/lib/utils"; import { ClassificationItemData, ClassificationThreshold, + ClassifiedEvent, } from "@/types/classification"; -import { Event } from "@/types/event"; import { forwardRef, useMemo, useRef, useState } from "react"; import { isDesktop, isIOS, isMobile, isMobileOnly } from "react-device-detect"; import { useTranslation } from "react-i18next"; @@ -160,7 +160,7 @@ export const ClassificationCard = forwardRef< data.score != undefined ? "text-xs" : "text-sm", )} > -
+
{data.name == "unknown" ? t("details.unknown") : data.name == "none" @@ -190,7 +190,7 @@ export const ClassificationCard = forwardRef< type GroupedClassificationCardProps = { group: ClassificationItemData[]; - event?: Event; + classifiedEvent?: ClassifiedEvent; threshold?: ClassificationThreshold; selectedItems: string[]; i18nLibrary: string; @@ -201,7 +201,7 @@ type GroupedClassificationCardProps = { }; export function GroupedClassificationCard({ group, - event, + classifiedEvent, threshold, selectedItems, i18nLibrary, @@ -236,14 +236,15 @@ export function GroupedClassificationCard({ const bestTyped: ClassificationItemData = best; return { ...bestTyped, - name: event - ? event.sub_label && event.sub_label !== "none" - ? event.sub_label - : t(noClassificationLabel) - : bestTyped.name, - score: event?.data?.sub_label_score, + name: + classifiedEvent?.label && classifiedEvent.label !== "none" + ? classifiedEvent.label + : classifiedEvent + ? t(noClassificationLabel) + : bestTyped.name, + score: classifiedEvent?.score, }; - }, [group, event, noClassificationLabel, t]); + }, [group, classifiedEvent, noClassificationLabel, t]); const bestScoreStatus = useMemo(() => { if (!bestItem?.score || !threshold) { @@ -329,36 +330,38 @@ export function GroupedClassificationCard({ )} > - {event?.sub_label && event.sub_label !== "none" - ? event.sub_label + {classifiedEvent?.label && classifiedEvent.label !== "none" + ? classifiedEvent.label : t(noClassificationLabel)} - {event?.sub_label && event.sub_label !== "none" && ( -
-
{`${Math.round((event.data.sub_label_score || 0) * 100)}%`}
- - - - - - {t("details.scoreInfo", { ns: i18nLibrary })} - - -
- )} + {classifiedEvent?.label && + classifiedEvent.label !== "none" && + classifiedEvent.score !== undefined && ( +
+
{`${Math.round((classifiedEvent.score || 0) * 100)}%`}
+ + + + + + {t("details.scoreInfo", { ns: i18nLibrary })} + + +
+ )}
{time && ( @@ -372,14 +375,14 @@ export function GroupedClassificationCard({
{isDesktop && (
- {event && ( + {classifiedEvent && (
{ - navigate(`/explore?event_id=${event.id}`); + navigate(`/explore?event_id=${classifiedEvent.id}`); }} > diff --git a/web/src/components/classification/wizard/Step3ChooseExamples.tsx b/web/src/components/classification/wizard/Step3ChooseExamples.tsx index d15e45b8c5..ec45f4b4d2 100644 --- a/web/src/components/classification/wizard/Step3ChooseExamples.tsx +++ b/web/src/components/classification/wizard/Step3ChooseExamples.tsx @@ -186,15 +186,17 @@ export default function Step3ChooseExamples({ await Promise.all(emptyFolderPromises); // Step 3: Determine if we should train - // For state models, we need ALL states to have examples - // For object models, we need at least 2 classes with images + // For state models, we need ALL states to have examples (at least 2 states) + // For object models, we need at least 1 class with images (the rest go to "none") const allStatesHaveExamplesForTraining = step1Data.modelType !== "state" || step1Data.classes.every((className) => classesWithImages.has(className), ); const shouldTrain = - allStatesHaveExamplesForTraining && classesWithImages.size >= 2; + step1Data.modelType === "object" + ? classesWithImages.size >= 1 + : allStatesHaveExamplesForTraining && classesWithImages.size >= 2; // Step 4: Kick off training only if we have enough classes with images if (shouldTrain) { diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 0f281895e1..0a2789c00f 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -68,7 +68,10 @@ import { ClassificationCard, GroupedClassificationCard, } from "@/components/card/ClassificationCard"; -import { ClassificationItemData } from "@/types/classification"; +import { + ClassificationItemData, + ClassifiedEvent, +} from "@/types/classification"; export default function FaceLibrary() { const { t } = useTranslation(["views/faceLibrary"]); @@ -922,10 +925,22 @@ function FaceAttemptGroup({ [onRefresh, t], ); + // Create ClassifiedEvent from Event (face recognition uses sub_label) + const classifiedEvent: ClassifiedEvent | undefined = useMemo(() => { + if (!event || !event.sub_label || event.sub_label === "none") { + return undefined; + } + return { + id: event.id, + label: event.sub_label, + score: event.data?.sub_label_score, + }; + }, [event]); + return ( { + if (!event || !model.object_config) { + return undefined; + } + + const classificationType = model.object_config.classification_type; + + if (classificationType === "attribute") { + // For attribute type, look at event.data[model.name] + const attributeValue = event.data[model.name] as string | undefined; + const attributeScore = event.data[`${model.name}_score`] as + | number + | undefined; + + if (attributeValue && attributeValue !== "none") { + return { + id: event.id, + label: attributeValue, + score: attributeScore, + }; + } + } else { + // For sub_label type, use event.sub_label + if (event.sub_label && event.sub_label !== "none") { + return { + id: event.id, + label: event.sub_label, + score: event.data?.sub_label_score, + }; + } + } + + return undefined; + }, + [model], + ); + // selection const [selectedEvent, setSelectedEvent] = useState(); @@ -1095,11 +1135,13 @@ function ObjectTrainGrid({ > {Object.entries(groups).map(([key, group]) => { const event = events?.find((ev) => ev.id == key); + const classifiedEvent = createClassifiedEvent(event); + return (
Date: Thu, 18 Dec 2025 22:08:45 +0800 Subject: [PATCH 30/78] fix: temp directory is only created when there are review_items. (#21344) --- frigate/util/classification.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frigate/util/classification.py b/frigate/util/classification.py index 7777af51cf..e723cca0e9 100644 --- a/frigate/util/classification.py +++ b/frigate/util/classification.py @@ -355,8 +355,6 @@ def collect_state_classification_examples( cameras: Dict mapping camera names to normalized crop coordinates [x1, y1, x2, y2] (0-1) """ dataset_dir = os.path.join(CLIPS_DIR, model_name, "dataset") - temp_dir = os.path.join(dataset_dir, "temp") - os.makedirs(temp_dir, exist_ok=True) # Step 1: Get review items for the cameras camera_names = list(cameras.keys()) @@ -371,6 +369,10 @@ def collect_state_classification_examples( logger.warning(f"No review items found for cameras: {camera_names}") return + # The temp directory is only created when there are review_items. + temp_dir = os.path.join(dataset_dir, "temp") + os.makedirs(temp_dir, exist_ok=True) + # Step 2: Create balanced timestamp selection (100 samples) timestamps = _select_balanced_timestamps(review_items, target_count=100) From 6a0e31dcf9e6f87b7a305f621d9b0808330acc98 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:35:47 -0600 Subject: [PATCH 31/78] Add object classification attributes to Tracked Object Details (#21348) * attributes endpoint * event endpoints * add attributes to more filters * add to suggestions and query in explore * support attributes in search input * i18n * add object type filter to endpoint * add attributes to tracked object details pane * add generic multi select dialog * save object attributes endpoint * add group by param to fetch attributes endpoint * add attribute editing to tracked object details * docs * fix docs * update openapi spec to match python --- .../object_classification.md | 2 +- .../state_classification.md | 2 +- docs/static/frigate-api.yaml | 74 +++++++ frigate/api/classification.py | 54 ++++++ .../api/defs/query/events_query_parameters.py | 3 + frigate/api/defs/request/events_body.py | 7 + frigate/api/event.py | 150 +++++++++++++++ web/public/locales/en/components/filter.json | 4 + web/public/locales/en/views/explore.json | 7 + web/public/locales/en/views/search.json | 1 + web/src/components/input/InputWithTags.tsx | 2 +- .../overlay/detail/SearchDetailDialog.tsx | 182 +++++++++++++++++- .../overlay/dialog/AttributeSelectDialog.tsx | 123 ++++++++++++ .../overlay/dialog/MultiSelectDialog.tsx | 96 +++++++++ .../overlay/dialog/SearchFilterDialog.tsx | 90 ++++++++- web/src/pages/Explore.tsx | 3 + web/src/types/search.ts | 4 + web/src/views/search/SearchView.tsx | 10 + 18 files changed, 808 insertions(+), 6 deletions(-) create mode 100644 web/src/components/overlay/dialog/AttributeSelectDialog.tsx create mode 100644 web/src/components/overlay/dialog/MultiSelectDialog.tsx diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index 37d9082859..52056a0070 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -3,7 +3,7 @@ id: object_classification title: Object Classification --- -Object classification allows you to train a custom MobileNetV2 classification model to run on tracked objects (persons, cars, animals, etc.) to identify a finer category or attribute for that object. +Object classification allows you to train a custom MobileNetV2 classification model to run on tracked objects (persons, cars, animals, etc.) to identify a finer category or attribute for that object. Classification results are visible in the Tracked Object Details pane in Explore, through the `frigate/tracked_object_details` MQTT topic, in Home Assistant sensors via the official Frigate integration, or through the event endpoints in the HTTP API. ## Minimum System Requirements diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index 6b95a2567a..196ec78de6 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -3,7 +3,7 @@ id: state_classification title: State Classification --- -State classification allows you to train a custom MobileNetV2 classification model on a fixed region of your camera frame(s) to determine a current state. The model can be configured to run on a schedule and/or when motion is detected in that region. +State classification allows you to train a custom MobileNetV2 classification model on a fixed region of your camera frame(s) to determine a current state. The model can be configured to run on a schedule and/or when motion is detected in that region. Classification results are available through the `frigate//classification/` MQTT topic and in Home Assistant sensors via the official Frigate integration. ## Minimum System Requirements diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml index 624688965f..1cfe1b91f4 100644 --- a/docs/static/frigate-api.yaml +++ b/docs/static/frigate-api.yaml @@ -616,6 +616,32 @@ paths: application/json: schema: $ref: "#/components/schemas/HTTPValidationError" + /classification/attributes: + get: + tags: + - Classification + summary: Get custom classification attributes + description: |- + Returns custom classification attributes for a given object type. + Only includes models with classification_type set to 'attribute'. + By default returns a flat sorted list of all attribute labels. + If group_by_model is true, returns attributes grouped by model name. + operationId: get_custom_attributes_classification_attributes_get + parameters: + - name: object_type + in: query + schema: + type: string + - name: group_by_model + in: query + schema: + type: boolean + default: false + responses: + "200": + description: Successful Response + "422": + description: Validation Error /classification/{name}/dataset: get: tags: @@ -2912,6 +2938,42 @@ paths: application/json: schema: $ref: "#/components/schemas/HTTPValidationError" + /events/{event_id}/attributes: + post: + tags: + - Events + summary: Set custom classification attributes + description: |- + Sets an event's custom classification attributes for all attribute-type + models that apply to the event's object type. + Returns a success message or an error if the event is not found. + operationId: set_attributes_events__event_id__attributes_post + parameters: + - name: event_id + in: path + required: true + schema: + type: string + title: Event Id + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EventsAttributesBody" + responses: + "200": + description: Successful Response + content: + application/json: + schema: + $ref: "#/components/schemas/GenericResponse" + "422": + description: Validation Error + content: + application/json: + schema: + $ref: "#/components/schemas/HTTPValidationError" /events/{event_id}/description: post: tags: @@ -4959,6 +5021,18 @@ components: required: - subLabel title: EventsSubLabelBody + EventsAttributesBody: + properties: + attributes: + type: object + title: Attributes + description: Object with model names as keys and attribute values + additionalProperties: + type: string + type: object + required: + - attributes + title: EventsAttributesBody ExportModel: properties: id: diff --git a/frigate/api/classification.py b/frigate/api/classification.py index deafaf956b..18e590ce10 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -31,6 +31,7 @@ from frigate.api.defs.tags import Tags from frigate.config import FrigateConfig from frigate.config.camera import DetectConfig +from frigate.config.classification import ObjectClassificationType from frigate.const import CLIPS_DIR, FACE_DIR, MODEL_CACHE_DIR from frigate.embeddings import EmbeddingsContext from frigate.models import Event @@ -622,6 +623,59 @@ def get_classification_dataset(name: str): ) +@router.get( + "/classification/attributes", + summary="Get custom classification attributes", + description="""Returns custom classification attributes for a given object type. + Only includes models with classification_type set to 'attribute'. + By default returns a flat sorted list of all attribute labels. + If group_by_model is true, returns attributes grouped by model name.""", +) +def get_custom_attributes( + request: Request, object_type: str = None, group_by_model: bool = False +): + models_with_attributes = {} + + for ( + model_key, + model_config, + ) in request.app.frigate_config.classification.custom.items(): + if ( + not model_config.enabled + or not model_config.object_config + or model_config.object_config.classification_type + != ObjectClassificationType.attribute + ): + continue + + model_objects = getattr(model_config.object_config, "objects", []) or [] + if object_type is not None and object_type not in model_objects: + continue + + dataset_dir = os.path.join(CLIPS_DIR, sanitize_filename(model_key), "dataset") + if not os.path.exists(dataset_dir): + continue + + attributes = [] + for category_name in os.listdir(dataset_dir): + category_dir = os.path.join(dataset_dir, category_name) + if os.path.isdir(category_dir) and category_name != "none": + attributes.append(category_name) + + if attributes: + model_name = model_config.name or model_key + models_with_attributes[model_name] = sorted(attributes) + + if group_by_model: + return JSONResponse(content=models_with_attributes) + else: + # Flatten to a unique sorted list + all_attributes = set() + for attributes in models_with_attributes.values(): + all_attributes.update(attributes) + return JSONResponse(content=sorted(list(all_attributes))) + + @router.get( "/classification/{name}/train", summary="Get classification train images", diff --git a/frigate/api/defs/query/events_query_parameters.py b/frigate/api/defs/query/events_query_parameters.py index 187dd3f918..8e5a5391a9 100644 --- a/frigate/api/defs/query/events_query_parameters.py +++ b/frigate/api/defs/query/events_query_parameters.py @@ -12,6 +12,7 @@ class EventsQueryParams(BaseModel): labels: Optional[str] = "all" sub_label: Optional[str] = "all" sub_labels: Optional[str] = "all" + attributes: Optional[str] = "all" zone: Optional[str] = "all" zones: Optional[str] = "all" limit: Optional[int] = 100 @@ -58,6 +59,8 @@ class EventsSearchQueryParams(BaseModel): limit: Optional[int] = 50 cameras: Optional[str] = "all" labels: Optional[str] = "all" + sub_labels: Optional[str] = "all" + attributes: Optional[str] = "all" zones: Optional[str] = "all" after: Optional[float] = None before: Optional[float] = None diff --git a/frigate/api/defs/request/events_body.py b/frigate/api/defs/request/events_body.py index 6110e34f5f..50754e92ab 100644 --- a/frigate/api/defs/request/events_body.py +++ b/frigate/api/defs/request/events_body.py @@ -24,6 +24,13 @@ class EventsLPRBody(BaseModel): ) +class EventsAttributesBody(BaseModel): + attributes: List[str] = Field( + title="Selected classification attributes for the event", + default_factory=list, + ) + + class EventsDescriptionBody(BaseModel): description: Union[str, None] = Field(title="The description of the event") diff --git a/frigate/api/event.py b/frigate/api/event.py index fc78ac0e5e..ea5cfb29cd 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -37,6 +37,7 @@ RegenerateQueryParameters, ) from frigate.api.defs.request.events_body import ( + EventsAttributesBody, EventsCreateBody, EventsDeleteBody, EventsDescriptionBody, @@ -55,6 +56,7 @@ from frigate.api.defs.response.generic_response import GenericResponse from frigate.api.defs.tags import Tags from frigate.comms.event_metadata_updater import EventMetadataTypeEnum +from frigate.config.classification import ObjectClassificationType from frigate.const import CLIPS_DIR, TRIGGER_DIR from frigate.embeddings import EmbeddingsContext from frigate.models import Event, ReviewSegment, Timeline, Trigger @@ -99,6 +101,8 @@ def events( if sub_labels == "all" and sub_label != "all": sub_labels = sub_label + attributes = unquote(params.attributes) + zone = params.zone zones = params.zones @@ -187,6 +191,17 @@ def events( sub_label_clause = reduce(operator.or_, sub_label_clauses) clauses.append((sub_label_clause)) + if attributes != "all": + # Custom classification results are stored as data[model_name] = result_value + filtered_attributes = attributes.split(",") + attribute_clauses = [] + + for attr in filtered_attributes: + attribute_clauses.append(Event.data.cast("text") % f'*:"{attr}"*') + + attribute_clause = reduce(operator.or_, attribute_clauses) + clauses.append(attribute_clause) + if recognized_license_plate != "all": filtered_recognized_license_plates = recognized_license_plate.split(",") @@ -492,6 +507,8 @@ def events_search( # Filters cameras = params.cameras labels = params.labels + sub_labels = params.sub_labels + attributes = params.attributes zones = params.zones after = params.after before = params.before @@ -566,6 +583,38 @@ def events_search( if labels != "all": event_filters.append((Event.label << labels.split(","))) + if sub_labels != "all": + # use matching so joined sub labels are included + # for example a sub label 'bob' would get events + # with sub labels 'bob' and 'bob, john' + sub_label_clauses = [] + filtered_sub_labels = sub_labels.split(",") + + if "None" in filtered_sub_labels: + filtered_sub_labels.remove("None") + sub_label_clauses.append((Event.sub_label.is_null())) + + for label in filtered_sub_labels: + sub_label_clauses.append( + (Event.sub_label.cast("text") == label) + ) # include exact matches + + # include this label when part of a list + sub_label_clauses.append((Event.sub_label.cast("text") % f"*{label},*")) + sub_label_clauses.append((Event.sub_label.cast("text") % f"*, {label}*")) + + event_filters.append((reduce(operator.or_, sub_label_clauses))) + + if attributes != "all": + # Custom classification results are stored as data[model_name] = result_value + filtered_attributes = attributes.split(",") + attribute_clauses = [] + + for attr in filtered_attributes: + attribute_clauses.append(Event.data.cast("text") % f'*:"{attr}"*') + + event_filters.append(reduce(operator.or_, attribute_clauses)) + if zones != "all": zone_clauses = [] filtered_zones = zones.split(",") @@ -1351,6 +1400,107 @@ async def set_plate( ) +@router.post( + "/events/{event_id}/attributes", + response_model=GenericResponse, + dependencies=[Depends(require_role(["admin"]))], + summary="Set custom classification attributes", + description=( + "Sets an event's custom classification attributes for all attribute-type " + "models that apply to the event's object type." + ), +) +async def set_attributes( + request: Request, + event_id: str, + body: EventsAttributesBody, +): + try: + event: Event = Event.get(Event.id == event_id) + await require_camera_access(event.camera, request=request) + except DoesNotExist: + return JSONResponse( + content=({"success": False, "message": f"Event {event_id} not found."}), + status_code=404, + ) + + object_type = event.label + selected_attributes = set(body.attributes or []) + applied_updates: list[dict[str, str | float | None]] = [] + + for ( + model_key, + model_config, + ) in request.app.frigate_config.classification.custom.items(): + # Only apply to enabled attribute classifiers that target this object type + if ( + not model_config.enabled + or not model_config.object_config + or model_config.object_config.classification_type + != ObjectClassificationType.attribute + or object_type not in (model_config.object_config.objects or []) + ): + continue + + # Get available labels from dataset directory + dataset_dir = os.path.join(CLIPS_DIR, sanitize_filename(model_key), "dataset") + available_labels = set() + + if os.path.exists(dataset_dir): + for category_name in os.listdir(dataset_dir): + category_dir = os.path.join(dataset_dir, category_name) + if os.path.isdir(category_dir): + available_labels.add(category_name) + + if not available_labels: + logger.warning( + "No dataset found for custom attribute model %s at %s", + model_key, + dataset_dir, + ) + continue + + # Find all selected attributes that apply to this model + model_name = model_config.name or model_key + matching_attrs = selected_attributes & available_labels + + if matching_attrs: + # Publish updates for each selected attribute + for attr in matching_attrs: + request.app.event_metadata_updater.publish( + (event_id, model_name, attr, 1.0), + EventMetadataTypeEnum.attribute.value, + ) + applied_updates.append( + {"model": model_name, "label": attr, "score": 1.0} + ) + else: + # Clear this model's attribute + request.app.event_metadata_updater.publish( + (event_id, model_name, None, None), + EventMetadataTypeEnum.attribute.value, + ) + applied_updates.append({"model": model_name, "label": None, "score": None}) + + if len(applied_updates) == 0: + return JSONResponse( + content={ + "success": False, + "message": "No matching attributes found for this object type.", + }, + status_code=400, + ) + + return JSONResponse( + content={ + "success": True, + "message": f"Updated {len(applied_updates)} attribute(s)", + "applied": applied_updates, + }, + status_code=200, + ) + + @router.post( "/events/{event_id}/description", response_model=GenericResponse, diff --git a/web/public/locales/en/components/filter.json b/web/public/locales/en/components/filter.json index 177234bed0..e9ae5c769b 100644 --- a/web/public/locales/en/components/filter.json +++ b/web/public/locales/en/components/filter.json @@ -38,6 +38,10 @@ "label": "Sub Labels", "all": "All Sub Labels" }, + "attributes": { + "label": "Classification Attributes", + "all": "All Attributes" + }, "score": "Score", "estimatedSpeed": "Estimated Speed ({{unit}})", "features": { diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index 6c938c1092..ff95e2fc62 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -104,12 +104,14 @@ "regenerate": "A new description has been requested from {{provider}}. Depending on the speed of your provider, the new description may take some time to regenerate.", "updatedSublabel": "Successfully updated sub label.", "updatedLPR": "Successfully updated license plate.", + "updatedAttributes": "Successfully updated attributes.", "audioTranscription": "Successfully requested audio transcription. Depending on the speed of your Frigate server, the transcription may take some time to complete." }, "error": { "regenerate": "Failed to call {{provider}} for a new description: {{errorMessage}}", "updatedSublabelFailed": "Failed to update sub label: {{errorMessage}}", "updatedLPRFailed": "Failed to update license plate: {{errorMessage}}", + "updatedAttributesFailed": "Failed to update attributes: {{errorMessage}}", "audioTranscription": "Failed to request audio transcription: {{errorMessage}}" } } @@ -125,6 +127,10 @@ "desc": "Enter a new license plate value for this {{label}}", "descNoLabel": "Enter a new license plate value for this tracked object" }, + "editAttributes": { + "title": "Edit attributes", + "desc": "Select classification attributes for this {{label}}" + }, "snapshotScore": { "label": "Snapshot Score" }, @@ -136,6 +142,7 @@ "label": "Score" }, "recognizedLicensePlate": "Recognized License Plate", + "attributes": "Classification Attributes", "estimatedSpeed": "Estimated Speed", "objects": "Objects", "camera": "Camera", diff --git a/web/public/locales/en/views/search.json b/web/public/locales/en/views/search.json index 22da7721fe..dae622c70e 100644 --- a/web/public/locales/en/views/search.json +++ b/web/public/locales/en/views/search.json @@ -16,6 +16,7 @@ "labels": "Labels", "zones": "Zones", "sub_labels": "Sub Labels", + "attributes": "Attributes", "search_type": "Search Type", "time_range": "Time Range", "before": "Before", diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 298537136c..70f1cd0c9b 100755 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -399,7 +399,7 @@ export default function InputWithTags({ newFilters.sort = value as SearchSortType; break; default: - // Handle array types (cameras, labels, subLabels, zones) + // Handle array types (cameras, labels, sub_labels, attributes, zones) if (!newFilters[type]) newFilters[type] = []; if (Array.isArray(newFilters[type])) { if (!(newFilters[type] as string[]).includes(value)) { diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index 1c46213df3..392e929eb8 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -84,6 +84,7 @@ import { LuInfo } from "react-icons/lu"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import { FaPencilAlt } from "react-icons/fa"; import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog"; +import AttributeSelectDialog from "@/components/overlay/dialog/AttributeSelectDialog"; import { Trans, useTranslation } from "react-i18next"; import { useIsAdmin } from "@/hooks/use-is-admin"; import { getTranslatedLabel } from "@/utils/i18n"; @@ -297,6 +298,7 @@ type DialogContentComponentProps = { isPopoverOpen: boolean; setIsPopoverOpen: (open: boolean) => void; dialogContainer: HTMLDivElement | null; + setShowNavigationButtons: React.Dispatch>; }; function DialogContentComponent({ @@ -314,6 +316,7 @@ function DialogContentComponent({ isPopoverOpen, setIsPopoverOpen, dialogContainer, + setShowNavigationButtons, }: DialogContentComponentProps) { if (page === "tracking_details") { return ( @@ -399,6 +402,7 @@ function DialogContentComponent({ config={config} setSearch={setSearch} setInputFocused={setInputFocused} + setShowNavigationButtons={setShowNavigationButtons} />
@@ -415,6 +419,7 @@ function DialogContentComponent({ config={config} setSearch={setSearch} setInputFocused={setInputFocused} + setShowNavigationButtons={setShowNavigationButtons} /> ); @@ -459,6 +464,7 @@ export default function SearchDetailDialog({ const [isOpen, setIsOpen] = useState(search != undefined); const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [showNavigationButtons, setShowNavigationButtons] = useState(false); const dialogContentRef = useRef(null); const [dialogContainer, setDialogContainer] = useState( null, @@ -540,9 +546,9 @@ export default function SearchDetailDialog({ onOpenChange={handleOpenChange} enableHistoryBack={true} > - {isDesktop && onPrevious && onNext && ( + {isDesktop && onPrevious && onNext && showNavigationButtons && ( -
+
@@ -664,12 +671,14 @@ type ObjectDetailsTabProps = { config?: FrigateConfig; setSearch: (search: SearchResult | undefined) => void; setInputFocused: React.Dispatch>; + setShowNavigationButtons?: React.Dispatch>; }; function ObjectDetailsTab({ search, config, setSearch, setInputFocused, + setShowNavigationButtons, }: ObjectDetailsTabProps) { const { t, i18n } = useTranslation([ "views/explore", @@ -678,6 +687,15 @@ function ObjectDetailsTab({ ]); const apiHost = useApiHost(); + const hasCustomClassificationModels = useMemo( + () => Object.keys(config?.classification?.custom ?? {}).length > 0, + [config], + ); + const { data: modelAttributes } = useSWR>( + hasCustomClassificationModels && search + ? `classification/attributes?object_type=${encodeURIComponent(search.label)}&group_by_model=true` + : null, + ); // mutation / revalidation @@ -708,6 +726,7 @@ function ObjectDetailsTab({ const [desc, setDesc] = useState(search?.data.description); const [isSubLabelDialogOpen, setIsSubLabelDialogOpen] = useState(false); const [isLPRDialogOpen, setIsLPRDialogOpen] = useState(false); + const [isAttributesDialogOpen, setIsAttributesDialogOpen] = useState(false); const [isEditingDesc, setIsEditingDesc] = useState(false); const originalDescRef = useRef(null); @@ -722,6 +741,19 @@ function ObjectDetailsTab({ // we have to make sure the current selected search item stays in sync useEffect(() => setDesc(search?.data.description ?? ""), [search]); + useEffect(() => setIsAttributesDialogOpen(false), [search?.id]); + + useEffect(() => { + const anyDialogOpen = + isSubLabelDialogOpen || isLPRDialogOpen || isAttributesDialogOpen; + setShowNavigationButtons?.(!anyDialogOpen); + }, [ + isSubLabelDialogOpen, + isLPRDialogOpen, + isAttributesDialogOpen, + setShowNavigationButtons, + ]); + const formattedDate = useFormattedTimestamp( search?.start_time ?? 0, config?.ui.time_format == "24hour" @@ -807,6 +839,41 @@ function ObjectDetailsTab({ } }, [search]); + // Extract current attribute selections grouped by model + const selectedAttributesByModel = useMemo(() => { + if (!search || !modelAttributes) { + return {}; + } + + const dataAny = search.data as Record; + const selections: Record = {}; + + // Initialize all models with null + Object.keys(modelAttributes).forEach((modelName) => { + selections[modelName] = null; + }); + + // Find which attribute is selected for each model + Object.keys(modelAttributes).forEach((modelName) => { + const value = dataAny[modelName]; + if ( + typeof value === "string" && + modelAttributes[modelName].includes(value) + ) { + selections[modelName] = value; + } + }); + + return selections; + }, [search, modelAttributes]); + + // Get flat list of selected attributes for display + const eventAttributes = useMemo(() => { + return Object.values(selectedAttributesByModel) + .filter((attr): attr is string => attr !== null) + .sort((a, b) => a.localeCompare(b)); + }, [selectedAttributesByModel]); + const isEventsKey = useCallback((key: unknown): boolean => { const candidate = Array.isArray(key) ? key[0] : key; const EVENTS_KEY_PATTERNS = ["events", "events/search", "events/explore"]; @@ -1048,6 +1115,74 @@ function ObjectDetailsTab({ [search, apiHost, mutate, setSearch, t, mapSearchResults, isEventsKey], ); + const handleAttributesSave = useCallback( + (selectedAttributes: string[]) => { + if (!search) return; + + axios + .post(`${apiHost}api/events/${search.id}/attributes`, { + attributes: selectedAttributes, + }) + .then((response) => { + const applied = Array.isArray(response.data?.applied) + ? (response.data.applied as { + model?: string; + label?: string | null; + score?: number | null; + }[]) + : []; + + toast.success(t("details.item.toast.success.updatedAttributes"), { + position: "top-center", + }); + + const applyUpdatedAttributes = (event: SearchResult) => { + if (event.id !== search.id) return event; + + const updatedData: Record = { ...event.data }; + + applied.forEach(({ model, label, score }) => { + if (!model) return; + updatedData[model] = label ?? null; + updatedData[`${model}_score`] = score ?? null; + }); + + return { ...event, data: updatedData } as SearchResult; + }; + + mutate( + (key) => isEventsKey(key), + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, applyUpdatedAttributes), + { + optimisticData: true, + rollbackOnError: true, + revalidate: false, + }, + ); + + setSearch(applyUpdatedAttributes(search)); + setIsAttributesDialogOpen(false); + }) + .catch((error) => { + const errorMessage = + error.response?.data?.message || + error.response?.data?.detail || + "Unknown error"; + + toast.error( + t("details.item.toast.error.updatedAttributesFailed", { + errorMessage, + }), + { + position: "top-center", + }, + ); + }); + }, + [search, apiHost, mutate, t, mapSearchResults, isEventsKey, setSearch], + ); + // speech transcription const onTranscribe = useCallback(() => { @@ -1295,6 +1430,38 @@ function ObjectDetailsTab({
)} + + {hasCustomClassificationModels && + modelAttributes && + Object.keys(modelAttributes).length > 0 && ( +
+
+ {t("details.attributes")} + {isAdmin && ( + + + + setIsAttributesDialogOpen(true)} + /> + + + + + {t("button.edit", { ns: "common" })} + + + + )} +
+
+ {eventAttributes.length > 0 + ? eventAttributes.join(", ") + : t("label.none", { ns: "common" })} +
+
+ )}
@@ -1595,6 +1762,17 @@ function ObjectDetailsTab({ defaultValue={search?.data.recognized_license_plate || ""} allowEmpty={true} /> +
); diff --git a/web/src/components/overlay/dialog/AttributeSelectDialog.tsx b/web/src/components/overlay/dialog/AttributeSelectDialog.tsx new file mode 100644 index 0000000000..b2ddc48ea2 --- /dev/null +++ b/web/src/components/overlay/dialog/AttributeSelectDialog.tsx @@ -0,0 +1,123 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; +import { useCallback, useEffect, useState } from "react"; +import { isDesktop } from "react-device-detect"; +import { useTranslation } from "react-i18next"; + +type AttributeSelectDialogProps = { + open: boolean; + setOpen: (open: boolean) => void; + title: string; + description: string; + onSave: (selectedAttributes: string[]) => void; + selectedAttributes: Record; // model -> selected attribute + modelAttributes: Record; // model -> available attributes + className?: string; +}; + +export default function AttributeSelectDialog({ + open, + setOpen, + title, + description, + onSave, + selectedAttributes, + modelAttributes, + className, +}: AttributeSelectDialogProps) { + const { t } = useTranslation(); + const [internalSelection, setInternalSelection] = useState< + Record + >({}); + + useEffect(() => { + if (open) { + setInternalSelection({ ...selectedAttributes }); + } + }, [open, selectedAttributes]); + + const handleSave = useCallback(() => { + // Convert from model->attribute map to flat list of attributes + const attributes = Object.values(internalSelection).filter( + (attr): attr is string => attr !== null, + ); + onSave(attributes); + }, [internalSelection, onSave]); + + const handleToggle = useCallback((modelName: string, attribute: string) => { + setInternalSelection((prev) => { + const currentSelection = prev[modelName]; + // If clicking the currently selected attribute, deselect it + if (currentSelection === attribute) { + return { ...prev, [modelName]: null }; + } + // Otherwise, select this attribute for this model + return { ...prev, [modelName]: attribute }; + }); + }, []); + + return ( + + e.preventDefault()} + > + + {title} + {description} + +
+
+ {Object.entries(modelAttributes).map(([modelName, attributes]) => ( +
+
+ {modelName} +
+
+ {attributes.map((attribute) => ( +
+ + + handleToggle(modelName, attribute) + } + /> +
+ ))} +
+
+ ))} +
+
+ + + + +
+
+ ); +} diff --git a/web/src/components/overlay/dialog/MultiSelectDialog.tsx b/web/src/components/overlay/dialog/MultiSelectDialog.tsx new file mode 100644 index 0000000000..b30ac9cf53 --- /dev/null +++ b/web/src/components/overlay/dialog/MultiSelectDialog.tsx @@ -0,0 +1,96 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; +import { isMobile } from "react-device-detect"; +import { useTranslation } from "react-i18next"; +import FilterSwitch from "@/components/filter/FilterSwitch"; + +type MultiSelectDialogProps = { + open: boolean; + title: string; + description?: string; + setOpen: (open: boolean) => void; + onSave: (selectedItems: string[]) => void; + selectedItems: string[]; + availableItems: string[]; + allowEmpty?: boolean; +}; + +export default function MultiSelectDialog({ + open, + title, + description, + setOpen, + onSave, + selectedItems = [], + availableItems = [], + allowEmpty = false, +}: MultiSelectDialogProps) { + const { t } = useTranslation("common"); + const [internalSelection, setInternalSelection] = + useState(selectedItems); + + // Reset internal selection when dialog opens + const handleOpenChange = (isOpen: boolean) => { + if (isOpen) { + setInternalSelection(selectedItems); + } + setOpen(isOpen); + }; + + const toggleItem = (item: string) => { + setInternalSelection((prev) => + prev.includes(item) ? prev.filter((i) => i !== item) : [...prev, item], + ); + }; + + const handleSave = () => { + if (!allowEmpty && internalSelection.length === 0) { + return; + } + onSave(internalSelection); + setOpen(false); + }; + + return ( + + + + {title} + {description && {description}} + +
+ {availableItems.map((item) => ( + toggleItem(item)} + /> + ))} +
+ + + + +
+
+ ); +} diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx index 3ee2052d02..eb1188257a 100644 --- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx +++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx @@ -65,6 +65,13 @@ export default function SearchFilterDialog({ const { t } = useTranslation(["components/filter"]); const [currentFilter, setCurrentFilter] = useState(filter ?? {}); const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]); + const hasCustomClassificationModels = useMemo( + () => Object.keys(config?.classification?.custom ?? {}).length > 0, + [config], + ); + const { data: allAttributes } = useSWR( + hasCustomClassificationModels ? "classification/attributes" : null, + ); const { data: allRecognizedLicensePlates } = useSWR( "recognized_license_plates", ); @@ -91,8 +98,10 @@ export default function SearchFilterDialog({ (currentFilter.max_speed ?? 150) < 150 || (currentFilter.zones?.length ?? 0) > 0 || (currentFilter.sub_labels?.length ?? 0) > 0 || + (hasCustomClassificationModels && + (currentFilter.attributes?.length ?? 0) > 0) || (currentFilter.recognized_license_plate?.length ?? 0) > 0), - [currentFilter], + [currentFilter, hasCustomClassificationModels], ); const trigger = ( @@ -133,6 +142,15 @@ export default function SearchFilterDialog({ setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels }) } /> + {hasCustomClassificationModels && ( + + setCurrentFilter({ ...currentFilter, attributes: newAttributes }) + } + /> + )} ); } + +type AttributeFilterContentProps = { + allAttributes?: string[]; + attributes: string[] | undefined; + setAttributes: (labels: string[] | undefined) => void; +}; +export function AttributeFilterContent({ + allAttributes, + attributes, + setAttributes, +}: AttributeFilterContentProps) { + const { t } = useTranslation(["components/filter"]); + const sortedAttributes = useMemo( + () => + [...(allAttributes || [])].sort((a, b) => + a.toLowerCase().localeCompare(b.toLowerCase()), + ), + [allAttributes], + ); + return ( +
+ +
{t("attributes.label")}
+
+ + { + if (isChecked) { + setAttributes(undefined); + } + }} + /> +
+
+ {sortedAttributes.map((item) => ( + { + if (isChecked) { + const updatedAttributes = attributes ? [...attributes] : []; + + updatedAttributes.push(item); + setAttributes(updatedAttributes); + } else { + const updatedAttributes = attributes ? [...attributes] : []; + + // can not deselect the last item + if (updatedAttributes.length > 1) { + updatedAttributes.splice(updatedAttributes.indexOf(item), 1); + setAttributes(updatedAttributes); + } + } + }} + /> + ))} +
+
+ ); +} diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 53ebd0401e..8f50e982e8 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -31,6 +31,7 @@ const SEARCH_FILTER_ARRAY_KEYS = [ "cameras", "labels", "sub_labels", + "attributes", "recognized_license_plate", "zones", ]; @@ -122,6 +123,7 @@ export default function Explore() { cameras: searchSearchParams["cameras"], labels: searchSearchParams["labels"], sub_labels: searchSearchParams["sub_labels"], + attributes: searchSearchParams["attributes"], recognized_license_plate: searchSearchParams["recognized_license_plate"], zones: searchSearchParams["zones"], @@ -158,6 +160,7 @@ export default function Explore() { cameras: searchSearchParams["cameras"], labels: searchSearchParams["labels"], sub_labels: searchSearchParams["sub_labels"], + attributes: searchSearchParams["attributes"], recognized_license_plate: searchSearchParams["recognized_license_plate"], zones: searchSearchParams["zones"], diff --git a/web/src/types/search.ts b/web/src/types/search.ts index 8fb81dfc63..d47e955847 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -5,6 +5,7 @@ const SEARCH_FILTERS = [ "general", "zone", "sub", + "attribute", "source", "sort", ] as const; @@ -16,6 +17,7 @@ export const DEFAULT_SEARCH_FILTERS: SearchFilters[] = [ "general", "zone", "sub", + "attribute", "source", "sort", ]; @@ -71,6 +73,7 @@ export type SearchFilter = { cameras?: string[]; labels?: string[]; sub_labels?: string[]; + attributes?: string[]; recognized_license_plate?: string[]; zones?: string[]; before?: number; @@ -95,6 +98,7 @@ export type SearchQueryParams = { cameras?: string[]; labels?: string[]; sub_labels?: string[]; + attributes?: string[]; recognized_license_plate?: string[]; zones?: string[]; before?: string; diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 426b7e209d..a373acc82c 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -143,6 +143,13 @@ export default function SearchView({ }, [config, searchFilter, allowedCameras]); const { data: allSubLabels } = useSWR("sub_labels"); + const hasCustomClassificationModels = useMemo( + () => Object.keys(config?.classification?.custom ?? {}).length > 0, + [config], + ); + const { data: allAttributes } = useSWR( + hasCustomClassificationModels ? "classification/attributes" : null, + ); const { data: allRecognizedLicensePlates } = useSWR( "recognized_license_plates", ); @@ -182,6 +189,7 @@ export default function SearchView({ labels: Object.values(allLabels || {}), zones: Object.values(allZones || {}), sub_labels: allSubLabels, + ...(hasCustomClassificationModels && { attributes: allAttributes }), search_type: ["thumbnail", "description"] as SearchSource[], time_range: config?.ui.time_format == "24hour" @@ -204,9 +212,11 @@ export default function SearchView({ allLabels, allZones, allSubLabels, + allAttributes, allRecognizedLicensePlates, searchFilter, allowedCameras, + hasCustomClassificationModels, ], ); From e636449d56bc60693ac1a72216fff5dfea23646c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 18 Dec 2025 15:12:10 -0700 Subject: [PATCH 32/78] Miscellaneous fixes (0.17 beta) (#21350) * Fix genai callbacks in MQTT * Cleanup cursor pointer for classification cards * Cleanup * Handle unknown SOCs for RKNN converter by only using known SOCs * don't allow "none" as a classification class name * change internal port user to admin and default unspecified username to viewer * keep 5000 as anonymous user * suppress tensorflow logging during classification training * Always apply base log level suppressions for noisy third-party libraries even if no specific logConfig is provided * remove decorator and specifically suppress TFLite delegate creation messages --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- docker/main/Dockerfile | 12 ++++++- docs/static/frigate-api.yaml | 2 +- frigate/api/auth.py | 12 +++---- frigate/comms/mqtt.py | 3 +- frigate/const.py | 3 ++ frigate/data_processing/real_time/bird.py | 15 +++++---- .../real_time/custom_classification.py | 28 ++++++++-------- frigate/detectors/plugins/cpu_tfl.py | 13 ++++---- frigate/detectors/plugins/rknn.py | 8 ++--- frigate/embeddings/onnx/face_embedding.py | 15 +++++---- frigate/events/audio.py | 16 ++++----- frigate/log.py | 33 +++++++++++++++++++ frigate/util/classification.py | 25 ++++++++------ frigate/util/process.py | 5 +++ frigate/util/rknn_converter.py | 12 +++++++ .../locales/en/views/classificationModel.json | 1 + .../components/card/ClassificationCard.tsx | 7 +++- .../wizard/Step1NameAndDefine.tsx | 10 +++++- web/src/pages/FaceLibrary.tsx | 1 + .../classification/ModelTrainingView.tsx | 2 ++ 20 files changed, 156 insertions(+), 67 deletions(-) diff --git a/docker/main/Dockerfile b/docker/main/Dockerfile index 8dbf5ff869..055a1458f8 100644 --- a/docker/main/Dockerfile +++ b/docker/main/Dockerfile @@ -237,8 +237,18 @@ ENV PYTHONWARNINGS="ignore:::numpy.core.getlimits" # Set HailoRT to disable logging ENV HAILORT_LOGGER_PATH=NONE -# TensorFlow error only +# TensorFlow C++ logging suppression (must be set before import) +# TF_CPP_MIN_LOG_LEVEL: 0=all, 1=INFO+, 2=WARNING+, 3=ERROR+ (we use 3 for errors only) ENV TF_CPP_MIN_LOG_LEVEL=3 +# Suppress verbose logging from TensorFlow C++ code +ENV TF_CPP_MIN_VLOG_LEVEL=3 +# Disable oneDNN optimization messages ("optimized with oneDNN...") +ENV TF_ENABLE_ONEDNN_OPTS=0 +# Suppress AutoGraph verbosity during conversion +ENV AUTOGRAPH_VERBOSITY=0 +# Google Logging (GLOG) suppression for TensorFlow components +ENV GLOG_minloglevel=3 +ENV GLOG_logtostderr=0 ENV PATH="/usr/local/go2rtc/bin:/usr/local/tempio/bin:/usr/local/nginx/sbin:${PATH}" diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml index 1cfe1b91f4..f1a00fe61b 100644 --- a/docs/static/frigate-api.yaml +++ b/docs/static/frigate-api.yaml @@ -25,7 +25,7 @@ paths: description: Authentication Accepted (no response body, different headers depending on auth method) headers: remote-user: - description: Authenticated username or "anonymous" in proxy-only mode + description: Authenticated username or "viewer" in proxy-only mode schema: type: string remote-role: diff --git a/frigate/api/auth.py b/frigate/api/auth.py index c6b0cef239..7ba845f45f 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -167,7 +167,7 @@ def allow_any_authenticated(): Allows: - Port 5000 internal requests (remote-user: "anonymous", remote-role: "admin") - Authenticated users with JWT tokens (remote-user: username) - - Unauthenticated requests when auth is disabled (remote-user: "anonymous") + - Unauthenticated requests when auth is disabled (remote-user: "viewer") Rejects: - Requests with no remote-user header (did not pass through /auth endpoint) @@ -550,7 +550,7 @@ def resolve_role( "description": "Authentication Accepted (no response body)", "headers": { "remote-user": { - "description": 'Authenticated username or "anonymous" in proxy-only mode', + "description": 'Authenticated username or "viewer" in proxy-only mode', "schema": {"type": "string"}, }, "remote-role": { @@ -592,12 +592,12 @@ def auth(request: Request): # if auth is disabled, just apply the proxy header map and return success if not auth_config.enabled: # pass the user header value from the upstream proxy if a mapping is specified - # or use anonymous if none are specified + # or use viewer if none are specified user_header = proxy_config.header_map.user success_response.headers["remote-user"] = ( - request.headers.get(user_header, default="anonymous") + request.headers.get(user_header, default="viewer") if user_header - else "anonymous" + else "viewer" ) # parse header and resolve a valid role @@ -712,7 +712,7 @@ def auth(request: Request): description="Returns the current authenticated user's profile including username, role, and allowed cameras. This endpoint requires authentication and returns information about the user's permissions.", ) def profile(request: Request): - username = request.headers.get("remote-user", "anonymous") + username = request.headers.get("remote-user", "viewer") role = request.headers.get("remote-role", "viewer") all_camera_names = set(request.app.frigate_config.cameras.keys()) diff --git a/frigate/comms/mqtt.py b/frigate/comms/mqtt.py index 0af56e2592..68ae698d9f 100644 --- a/frigate/comms/mqtt.py +++ b/frigate/comms/mqtt.py @@ -225,7 +225,8 @@ def _start(self) -> None: "birdseye_mode", "review_alerts", "review_detections", - "genai", + "object_descriptions", + "review_descriptions", ] for name in self.config.cameras.keys(): diff --git a/frigate/const.py b/frigate/const.py index 11e89886fb..41c24f0874 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -77,6 +77,9 @@ FFMPEG_HWACCEL_AMF = "preset-amd-amf" FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"] +# RKNN constants +SUPPORTED_RK_SOCS = ["rk3562", "rk3566", "rk3568", "rk3576", "rk3588"] + # Regex constants REGEX_CAMERA_NAME = r"^[a-zA-Z0-9_-]+$" diff --git a/frigate/data_processing/real_time/bird.py b/frigate/data_processing/real_time/bird.py index e599ab0fb1..7851c09972 100644 --- a/frigate/data_processing/real_time/bird.py +++ b/frigate/data_processing/real_time/bird.py @@ -13,7 +13,7 @@ ) from frigate.config import FrigateConfig from frigate.const import MODEL_CACHE_DIR -from frigate.log import redirect_output_to_logger +from frigate.log import suppress_stderr_during from frigate.util.object import calculate_region from ..types import DataProcessorMetrics @@ -80,13 +80,14 @@ def __download_models(self, path: str) -> None: except Exception as e: logger.error(f"Failed to download {path}: {e}") - @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: - self.interpreter = Interpreter( - model_path=os.path.join(MODEL_CACHE_DIR, "bird/bird.tflite"), - num_threads=2, - ) - self.interpreter.allocate_tensors() + # Suppress TFLite delegate creation messages that bypass Python logging + with suppress_stderr_during("tflite_interpreter_init"): + self.interpreter = Interpreter( + model_path=os.path.join(MODEL_CACHE_DIR, "bird/bird.tflite"), + num_threads=2, + ) + self.interpreter.allocate_tensors() self.tensor_input_details = self.interpreter.get_input_details() self.tensor_output_details = self.interpreter.get_output_details() diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py index ee72e145c6..fac0ecc3d8 100644 --- a/frigate/data_processing/real_time/custom_classification.py +++ b/frigate/data_processing/real_time/custom_classification.py @@ -21,7 +21,7 @@ ObjectClassificationType, ) from frigate.const import CLIPS_DIR, MODEL_CACHE_DIR -from frigate.log import redirect_output_to_logger +from frigate.log import suppress_stderr_during from frigate.types import TrackedObjectUpdateTypesEnum from frigate.util.builtin import EventsPerSecond, InferenceSpeed, load_labels from frigate.util.object import box_overlaps, calculate_region @@ -72,7 +72,6 @@ def __init__( self.last_run = datetime.datetime.now().timestamp() self.__build_detector() - @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: try: from tflite_runtime.interpreter import Interpreter @@ -89,11 +88,13 @@ def __build_detector(self) -> None: self.labelmap = {} return - self.interpreter = Interpreter( - model_path=model_path, - num_threads=2, - ) - self.interpreter.allocate_tensors() + # Suppress TFLite delegate creation messages that bypass Python logging + with suppress_stderr_during("tflite_interpreter_init"): + self.interpreter = Interpreter( + model_path=model_path, + num_threads=2, + ) + self.interpreter.allocate_tensors() self.tensor_input_details = self.interpreter.get_input_details() self.tensor_output_details = self.interpreter.get_output_details() self.labelmap = load_labels(labelmap_path, prefill=0) @@ -377,7 +378,6 @@ def __init__( self.__build_detector() - @redirect_output_to_logger(logger, logging.DEBUG) def __build_detector(self) -> None: model_path = os.path.join(self.model_dir, "model.tflite") labelmap_path = os.path.join(self.model_dir, "labelmap.txt") @@ -389,11 +389,13 @@ def __build_detector(self) -> None: self.labelmap = {} return - self.interpreter = Interpreter( - model_path=model_path, - num_threads=2, - ) - self.interpreter.allocate_tensors() + # Suppress TFLite delegate creation messages that bypass Python logging + with suppress_stderr_during("tflite_interpreter_init"): + self.interpreter = Interpreter( + model_path=model_path, + num_threads=2, + ) + self.interpreter.allocate_tensors() self.tensor_input_details = self.interpreter.get_input_details() self.tensor_output_details = self.interpreter.get_output_details() self.labelmap = load_labels(labelmap_path, prefill=0) diff --git a/frigate/detectors/plugins/cpu_tfl.py b/frigate/detectors/plugins/cpu_tfl.py index 37cc10777c..00351f5192 100644 --- a/frigate/detectors/plugins/cpu_tfl.py +++ b/frigate/detectors/plugins/cpu_tfl.py @@ -5,7 +5,7 @@ from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig -from frigate.log import redirect_output_to_logger +from frigate.log import suppress_stderr_during from ..detector_utils import tflite_detect_raw, tflite_init @@ -28,12 +28,13 @@ class CpuDetectorConfig(BaseDetectorConfig): class CpuTfl(DetectionApi): type_key = DETECTOR_KEY - @redirect_output_to_logger(logger, logging.DEBUG) def __init__(self, detector_config: CpuDetectorConfig): - interpreter = Interpreter( - model_path=detector_config.model.path, - num_threads=detector_config.num_threads or 3, - ) + # Suppress TFLite delegate creation messages that bypass Python logging + with suppress_stderr_during("tflite_interpreter_init"): + interpreter = Interpreter( + model_path=detector_config.model.path, + num_threads=detector_config.num_threads or 3, + ) tflite_init(self, interpreter) diff --git a/frigate/detectors/plugins/rknn.py b/frigate/detectors/plugins/rknn.py index 70186824b7..c16df507ec 100644 --- a/frigate/detectors/plugins/rknn.py +++ b/frigate/detectors/plugins/rknn.py @@ -8,7 +8,7 @@ import numpy as np from pydantic import Field -from frigate.const import MODEL_CACHE_DIR +from frigate.const import MODEL_CACHE_DIR, SUPPORTED_RK_SOCS from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detection_runners import RKNNModelRunner from frigate.detectors.detector_config import BaseDetectorConfig, ModelTypeEnum @@ -19,8 +19,6 @@ DETECTOR_KEY = "rknn" -supported_socs = ["rk3562", "rk3566", "rk3568", "rk3576", "rk3588"] - supported_models = { ModelTypeEnum.yologeneric: "^frigate-fp16-yolov9-[cemst]$", ModelTypeEnum.yolonas: "^deci-fp16-yolonas_[sml]$", @@ -82,9 +80,9 @@ def get_soc(self): except FileNotFoundError: raise Exception("Make sure to run docker in privileged mode.") - if soc not in supported_socs: + if soc not in SUPPORTED_RK_SOCS: raise Exception( - f"Your SoC is not supported. Your SoC is: {soc}. Currently these SoCs are supported: {supported_socs}." + f"Your SoC is not supported. Your SoC is: {soc}. Currently these SoCs are supported: {SUPPORTED_RK_SOCS}." ) return soc diff --git a/frigate/embeddings/onnx/face_embedding.py b/frigate/embeddings/onnx/face_embedding.py index e661f8d376..04d756897c 100644 --- a/frigate/embeddings/onnx/face_embedding.py +++ b/frigate/embeddings/onnx/face_embedding.py @@ -8,7 +8,7 @@ from frigate.const import MODEL_CACHE_DIR from frigate.detectors.detection_runners import get_optimized_runner from frigate.embeddings.types import EnrichmentModelTypeEnum -from frigate.log import redirect_output_to_logger +from frigate.log import suppress_stderr_during from frigate.util.downloader import ModelDownloader from ...config import FaceRecognitionConfig @@ -57,17 +57,18 @@ def __init__(self): self._load_model_and_utils() logger.debug(f"models are already downloaded for {self.model_name}") - @redirect_output_to_logger(logger, logging.DEBUG) def _load_model_and_utils(self): if self.runner is None: if self.downloader: self.downloader.wait_for_download() - self.runner = Interpreter( - model_path=os.path.join(MODEL_CACHE_DIR, "facedet/facenet.tflite"), - num_threads=2, - ) - self.runner.allocate_tensors() + # Suppress TFLite delegate creation messages that bypass Python logging + with suppress_stderr_during("tflite_interpreter_init"): + self.runner = Interpreter( + model_path=os.path.join(MODEL_CACHE_DIR, "facedet/facenet.tflite"), + num_threads=2, + ) + self.runner.allocate_tensors() self.tensor_input_details = self.runner.get_input_details() self.tensor_output_details = self.runner.get_output_details() diff --git a/frigate/events/audio.py b/frigate/events/audio.py index 1aa227719e..e88f2ae71d 100644 --- a/frigate/events/audio.py +++ b/frigate/events/audio.py @@ -34,7 +34,7 @@ AudioTranscriptionRealTimeProcessor, ) from frigate.ffmpeg_presets import parse_preset_input -from frigate.log import LogPipe, redirect_output_to_logger +from frigate.log import LogPipe, suppress_stderr_during from frigate.object_detection.base import load_labels from frigate.util.builtin import get_ffmpeg_arg_list from frigate.util.process import FrigateProcess @@ -367,17 +367,17 @@ def run(self) -> None: class AudioTfl: - @redirect_output_to_logger(logger, logging.DEBUG) def __init__(self, stop_event: threading.Event, num_threads=2): self.stop_event = stop_event self.num_threads = num_threads self.labels = load_labels("/audio-labelmap.txt", prefill=521) - self.interpreter = Interpreter( - model_path="/cpu_audio_model.tflite", - num_threads=self.num_threads, - ) - - self.interpreter.allocate_tensors() + # Suppress TFLite delegate creation messages that bypass Python logging + with suppress_stderr_during("tflite_interpreter_init"): + self.interpreter = Interpreter( + model_path="/cpu_audio_model.tflite", + num_threads=self.num_threads, + ) + self.interpreter.allocate_tensors() self.tensor_input_details = self.interpreter.get_input_details() self.tensor_output_details = self.interpreter.get_output_details() diff --git a/frigate/log.py b/frigate/log.py index f2171ffe00..717cce19f1 100644 --- a/frigate/log.py +++ b/frigate/log.py @@ -80,10 +80,15 @@ def apply_log_levels(default: str, log_levels: dict[str, LogLevel]) -> None: log_levels = { "absl": LogLevel.error, "httpx": LogLevel.error, + "h5py": LogLevel.error, + "keras": LogLevel.error, "matplotlib": LogLevel.error, "tensorflow": LogLevel.error, + "tensorflow.python": LogLevel.error, "werkzeug": LogLevel.error, "ws4py": LogLevel.error, + "PIL": LogLevel.warning, + "numba": LogLevel.warning, **log_levels, } @@ -318,3 +323,31 @@ def wrapper(*args: tuple, **kwargs: dict[str, Any]) -> Any: return result return wrapper + + +@contextmanager +def suppress_stderr_during(operation_name: str) -> Generator[None, None, None]: + """ + Context manager to suppress stderr output during a specific operation. + + Useful for silencing LLVM debug output, CUDA messages, and other native + library logging that cannot be controlled via Python logging or environment + variables. Completely redirects file descriptor 2 (stderr) to /dev/null. + + Usage: + with suppress_stderr_during("model_conversion"): + converter = tf.lite.TFLiteConverter.from_keras_model(model) + tflite_model = converter.convert() + + Args: + operation_name: Name of the operation for debugging purposes + """ + original_stderr_fd = os.dup(2) + devnull = os.open(os.devnull, os.O_WRONLY) + try: + os.dup2(devnull, 2) + yield + finally: + os.dup2(original_stderr_fd, 2) + os.close(devnull) + os.close(original_stderr_fd) diff --git a/frigate/util/classification.py b/frigate/util/classification.py index e723cca0e9..f4206b3461 100644 --- a/frigate/util/classification.py +++ b/frigate/util/classification.py @@ -19,7 +19,7 @@ PROCESS_PRIORITY_LOW, UPDATE_MODEL_STATE, ) -from frigate.log import redirect_output_to_logger +from frigate.log import redirect_output_to_logger, suppress_stderr_during from frigate.models import Event, Recordings, ReviewSegment from frigate.types import ModelStatusTypesEnum from frigate.util.downloader import ModelDownloader @@ -250,15 +250,20 @@ def __train_classification_model(self) -> bool: logger.debug(f"Converting {self.model_name} to TFLite...") # convert model to tflite - converter = tf.lite.TFLiteConverter.from_keras_model(model) - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.representative_dataset = ( - self.__generate_representative_dataset_factory(dataset_dir) - ) - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.uint8 - converter.inference_output_type = tf.uint8 - tflite_model = converter.convert() + # Suppress stderr during conversion to avoid LLVM debug output + # (fully_quantize, inference_type, MLIR optimization messages, etc) + with suppress_stderr_during("tflite_conversion"): + converter = tf.lite.TFLiteConverter.from_keras_model(model) + converter.optimizations = [tf.lite.Optimize.DEFAULT] + converter.representative_dataset = ( + self.__generate_representative_dataset_factory(dataset_dir) + ) + converter.target_spec.supported_ops = [ + tf.lite.OpsSet.TFLITE_BUILTINS_INT8 + ] + converter.inference_input_type = tf.uint8 + converter.inference_output_type = tf.uint8 + tflite_model = converter.convert() # write model model_path = os.path.join(model_dir, "model.tflite") diff --git a/frigate/util/process.py b/frigate/util/process.py index 0bef991fc8..1613c1e431 100644 --- a/frigate/util/process.py +++ b/frigate/util/process.py @@ -65,10 +65,15 @@ def pre_run_setup(self, logConfig: LoggerConfig | None = None) -> None: logging.basicConfig(handlers=[], force=True) logging.getLogger().addHandler(QueueHandler(self.__log_queue)) + # Always apply base log level suppressions for noisy third-party libraries + # even if no specific logConfig is provided if logConfig: frigate.log.apply_log_levels( logConfig.default.value.upper(), logConfig.logs ) + else: + # Apply default INFO level with standard library suppressions + frigate.log.apply_log_levels("INFO", {}) self._setup_memray() diff --git a/frigate/util/rknn_converter.py b/frigate/util/rknn_converter.py index 8b2fd0050b..f7ebbf5e65 100644 --- a/frigate/util/rknn_converter.py +++ b/frigate/util/rknn_converter.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Optional +from frigate.const import SUPPORTED_RK_SOCS from frigate.util.file import FileLock logger = logging.getLogger(__name__) @@ -68,9 +69,20 @@ def is_rknn_compatible(model_path: str, model_type: str | None = None) -> bool: True if the model is RKNN-compatible, False otherwise """ soc = get_soc_type() + if soc is None: return False + # Check if the SoC is actually a supported RK device + # This prevents false positives on non-RK devices (e.g., macOS Docker) + # where /proc/device-tree/compatible might exist but contain non-RK content + if soc not in SUPPORTED_RK_SOCS: + logger.debug( + f"SoC '{soc}' is not a supported RK device for RKNN conversion. " + f"Supported SoCs: {SUPPORTED_RK_SOCS}" + ) + return False + if not model_type: model_type = get_rknn_model_type(model_path) diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json index 96bf0284bd..a07114b5cb 100644 --- a/web/public/locales/en/views/classificationModel.json +++ b/web/public/locales/en/views/classificationModel.json @@ -139,6 +139,7 @@ "nameOnlyNumbers": "Model name cannot contain only numbers", "classRequired": "At least 1 class is required", "classesUnique": "Class names must be unique", + "noneNotAllowed": "The class 'none' is not allowed", "stateRequiresTwoClasses": "State models require at least 2 classes", "objectLabelRequired": "Please select an object label", "objectTypeRequired": "Please select a classification type" diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index 360bb11bff..bf91d89c22 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -40,6 +40,7 @@ type ClassificationCardProps = { data: ClassificationItemData; threshold?: ClassificationThreshold; selected: boolean; + clickable: boolean; i18nLibrary: string; showArea?: boolean; count?: number; @@ -56,6 +57,7 @@ export const ClassificationCard = forwardRef< data, threshold, selected, + clickable, i18nLibrary, showArea = true, count, @@ -101,11 +103,12 @@ export const ClassificationCard = forwardRef<
{ const isMeta = e.metaKey || e.ctrlKey; @@ -289,6 +292,7 @@ export function GroupedClassificationCard({ data={bestItem} threshold={threshold} selected={selectedItems.includes(bestItem.filename)} + clickable={true} i18nLibrary={i18nLibrary} count={group.length} onClick={(_, meta) => { @@ -413,6 +417,7 @@ export function GroupedClassificationCard({ data={data} threshold={threshold} selected={false} + clickable={false} i18nLibrary={i18nLibrary} onClick={() => {}} > diff --git a/web/src/components/classification/wizard/Step1NameAndDefine.tsx b/web/src/components/classification/wizard/Step1NameAndDefine.tsx index d5ee430eb9..a4cdc4867f 100644 --- a/web/src/components/classification/wizard/Step1NameAndDefine.tsx +++ b/web/src/components/classification/wizard/Step1NameAndDefine.tsx @@ -94,7 +94,14 @@ export default function Step1NameAndDefine({ objectLabel: z.string().optional(), objectType: z.enum(["sub_label", "attribute"]).optional(), classes: z - .array(z.string()) + .array( + z + .string() + .refine( + (val) => val.trim().toLowerCase() !== "none", + t("wizard.step1.errors.noneNotAllowed"), + ), + ) .min(1, t("wizard.step1.errors.classRequired")) .refine( (classes) => { @@ -467,6 +474,7 @@ export default function Step1NameAndDefine({ )}
+ )} /> diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 0a2789c00f..628928562b 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -1026,6 +1026,7 @@ function FaceGrid({ filepath: `clips/faces/${pageToggle}/${image}`, }} selected={selectedFaces.includes(image)} + clickable={selectedFaces.length > 0} i18nLibrary="views/faceLibrary" onClick={(data, meta) => onClickFaces([data.filename], meta)} > diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index 3d106ffbc9..31e6f6d532 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -804,6 +804,7 @@ function DatasetGrid({ name: "", }} showArea={false} + clickable={selectedImages.length > 0} selected={selectedImages.includes(image)} i18nLibrary="views/classificationModel" onClick={(data, _) => onClickImages([data.filename], true)} @@ -962,6 +963,7 @@ function StateTrainGrid({ data={data} threshold={threshold} selected={selectedImages.includes(data.filename)} + clickable={selectedImages.length > 0} i18nLibrary="views/classificationModel" showArea={false} onClick={(data, meta) => onClickImages([data.filename], meta)} From 60052e5f9f27e7bba76232bddd4cb5aaab1b4d41 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:59:26 -0600 Subject: [PATCH 33/78] Miscellaneous Fixes (0.17 beta) (#21355) * remove footer messages and add update topic to motion tuner view restart after changing values is no longer required * add cache key and activity indicator for loading classification wizard images * Always mark model as untrained when a classname is changed * clarify object classification docs * add debug logs for individual lpr replace_rules * update memray docs * memray tweaks * Don't fail for audio transcription when semantic search is not enabled * Fix incorrect mismatch for object vs sub label * Check if the video is currently playing when deciding to seek due to misalignment * Refactor timeline event handling to allow multiple timeline entries per update * Check if zones have actually changed (not just count) for event state update * show event icon on mobile * move div inside conditional --------- Co-authored-by: Nicolas Mowen --- .../object_classification.md | 2 + docs/docs/troubleshooting/memory.md | 46 +++++++------- frigate/api/classification.py | 7 +++ .../common/license_plate/mixin.py | 5 +- .../post/audio_transcription.py | 5 +- frigate/events/maintainer.py | 2 +- frigate/timeline.py | 60 +++++++++++++------ .../components/card/ClassificationCard.tsx | 52 ++++++++-------- .../wizard/Step3ChooseExamples.tsx | 16 ++++- web/src/views/recording/RecordingView.tsx | 5 +- web/src/views/settings/MotionTunerView.tsx | 28 ++------- 11 files changed, 138 insertions(+), 90 deletions(-) diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index 52056a0070..70fd1fbbd1 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -95,6 +95,8 @@ The system will automatically generate example images from detected objects matc When choosing which objects to classify, start with a small number of visually distinct classes and ensure your training samples match camera viewpoints and distances typical for those objects. +If examples for some of your classes do not appear in the grid, you can continue configuring the model without them. New images will begin to appear in the Recent Classifications view. When your missing classes are seen, classify them from this view and retrain your model. + ### Improving the Model - **Problem framing**: Keep classes visually distinct and relevant to the chosen object types. diff --git a/docs/docs/troubleshooting/memory.md b/docs/docs/troubleshooting/memory.md index b8ef5367d8..338037c7aa 100644 --- a/docs/docs/troubleshooting/memory.md +++ b/docs/docs/troubleshooting/memory.md @@ -9,8 +9,20 @@ Frigate includes built-in memory profiling using [memray](https://bloomberg.gith Memory profiling is controlled via the `FRIGATE_MEMRAY_MODULES` environment variable. Set it to a comma-separated list of module names you want to profile: +```yaml +# docker-compose example +services: + frigate: + ... + environment: + - FRIGATE_MEMRAY_MODULES=frigate.embeddings,frigate.capture +``` + ```bash -export FRIGATE_MEMRAY_MODULES="frigate.review_segment_manager,frigate.capture" +# docker run example +docker run -e FRIGATE_MEMRAY_MODULES="frigate.embeddings" \ + ... + --name frigate ``` ### Module Names @@ -24,11 +36,12 @@ Frigate processes are named using a module-based naming scheme. Common module na - `frigate.output` - Output processing - `frigate.audio_manager` - Audio processing - `frigate.embeddings` - Embeddings processing +- `frigate.embeddings_manager` - Embeddings manager You can also specify the full process name (including camera-specific identifiers) if you want to profile a specific camera: ```bash -export FRIGATE_MEMRAY_MODULES="frigate.capture:front_door" +FRIGATE_MEMRAY_MODULES=frigate.capture:front_door ``` When you specify a module name (e.g., `frigate.capture`), all processes with that module prefix will be profiled. For example, `frigate.capture` will profile all camera capture processes. @@ -55,11 +68,20 @@ After a process exits normally, you'll find HTML reports in `/config/memray_repo If a process crashes or you want to generate a report from an existing binary file, you can manually create the HTML report: +- Run `memray` inside the Frigate container: + ```bash -memray flamegraph /config/memray_reports/.bin +docker-compose exec frigate memray flamegraph /config/memray_reports/.bin +# or +docker exec -it memray flamegraph /config/memray_reports/.bin ``` -This will generate an HTML file that you can open in your browser. +- You can also copy the `.bin` file to the host and run `memray` locally if you have it installed: + +```bash +docker cp :/config/memray_reports/.bin /tmp/ +memray flamegraph /tmp/.bin +``` ## Understanding the Reports @@ -110,20 +132,4 @@ The interactive HTML reports allow you to: - Check that memray is properly installed (included by default in Frigate) - Verify the process actually started and ran (check process logs) -## Example Usage - -```bash -# Enable profiling for review and capture modules -export FRIGATE_MEMRAY_MODULES="frigate.review_segment_manager,frigate.capture" - -# Start Frigate -# ... let it run for a while ... - -# Check for reports -ls -lh /config/memray_reports/ - -# If a process crashed, manually generate report -memray flamegraph /config/memray_reports/frigate_capture_front_door.bin -``` - For more information about memray and interpreting reports, see the [official memray documentation](https://bloomberg.github.io/memray/). diff --git a/frigate/api/classification.py b/frigate/api/classification.py index 18e590ce10..f60cfd3c38 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -40,6 +40,7 @@ collect_state_classification_examples, get_dataset_image_count, read_training_metadata, + write_training_metadata, ) from frigate.util.file import get_event_snapshot @@ -842,6 +843,12 @@ def rename_classification_category( try: os.rename(old_folder, new_folder) + + # Mark dataset as ready to train by resetting training metadata + # This ensures the dataset is marked as changed after renaming + sanitized_name = sanitize_filename(name) + write_training_metadata(sanitized_name, 0) + return JSONResponse( content=( { diff --git a/frigate/data_processing/common/license_plate/mixin.py b/frigate/data_processing/common/license_plate/mixin.py index a2509d4fad..b56c66a19a 100644 --- a/frigate/data_processing/common/license_plate/mixin.py +++ b/frigate/data_processing/common/license_plate/mixin.py @@ -374,6 +374,9 @@ def _process_license_plate( combined_plate = re.sub( pattern, replacement, combined_plate ) + logger.debug( + f"{camera}: Processing replace rule: '{pattern}' -> '{replacement}', result: '{combined_plate}'" + ) except re.error as e: logger.warning( f"{camera}: Invalid regex in replace_rules '{pattern}': {e}" @@ -381,7 +384,7 @@ def _process_license_plate( if combined_plate != original_combined: logger.debug( - f"{camera}: Rules applied: '{original_combined}' -> '{combined_plate}'" + f"{camera}: All rules applied: '{original_combined}' -> '{combined_plate}'" ) # Compute the combined area for qualifying boxes diff --git a/frigate/data_processing/post/audio_transcription.py b/frigate/data_processing/post/audio_transcription.py index b7b6cb021b..558ab433e5 100644 --- a/frigate/data_processing/post/audio_transcription.py +++ b/frigate/data_processing/post/audio_transcription.py @@ -131,8 +131,9 @@ def process_data( }, ) - # Embed the description - self.embeddings.embed_description(event_id, transcription) + # Embed the description if semantic search is enabled + if self.config.semantic_search.enabled: + self.embeddings.embed_description(event_id, transcription) except DoesNotExist: logger.debug("No recording found for audio transcription post-processing") diff --git a/frigate/events/maintainer.py b/frigate/events/maintainer.py index 2b0fc4193a..0d1a1b025b 100644 --- a/frigate/events/maintainer.py +++ b/frigate/events/maintainer.py @@ -46,7 +46,7 @@ def should_update_state(prev_event: Event, current_event: Event) -> bool: if prev_event["sub_label"] != current_event["sub_label"]: return True - if len(prev_event["current_zones"]) < len(current_event["current_zones"]): + if set(prev_event["current_zones"]) != set(current_event["current_zones"]): return True return False diff --git a/frigate/timeline.py b/frigate/timeline.py index a2d59b88ee..cf2f5e8c75 100644 --- a/frigate/timeline.py +++ b/frigate/timeline.py @@ -86,11 +86,11 @@ def handle_object_detection( event_data: dict[Any, Any], ) -> bool: """Handle object detection.""" - save = False camera_config = self.config.cameras[camera] event_id = event_data["id"] - timeline_entry = { + # Base timeline entry data that all entries will share + base_entry = { Timeline.timestamp: event_data["frame_time"], Timeline.camera: camera, Timeline.source: "tracked_object", @@ -123,40 +123,64 @@ def handle_object_detection( e[Timeline.data]["sub_label"] = event_data["sub_label"] if event_type == EventStateEnum.start: + timeline_entry = base_entry.copy() timeline_entry[Timeline.class_type] = "visible" - save = True + self.insert_or_save(timeline_entry, prev_event_data, event_data) elif event_type == EventStateEnum.update: + # Check all conditions and create timeline entries for each change + entries_to_save = [] + + # Check for zone changes + prev_zones = set(prev_event_data["current_zones"]) + current_zones = set(event_data["current_zones"]) + zones_changed = prev_zones != current_zones + + # Only save "entered_zone" events when the object is actually IN zones if ( - len(prev_event_data["current_zones"]) < len(event_data["current_zones"]) + zones_changed and not event_data["stationary"] + and len(current_zones) > 0 ): - timeline_entry[Timeline.class_type] = "entered_zone" - timeline_entry[Timeline.data]["zones"] = event_data["current_zones"] - save = True - elif prev_event_data["stationary"] != event_data["stationary"]: - timeline_entry[Timeline.class_type] = ( + zone_entry = base_entry.copy() + zone_entry[Timeline.class_type] = "entered_zone" + zone_entry[Timeline.data] = base_entry[Timeline.data].copy() + zone_entry[Timeline.data]["zones"] = event_data["current_zones"] + entries_to_save.append(zone_entry) + + # Check for stationary status change + if prev_event_data["stationary"] != event_data["stationary"]: + stationary_entry = base_entry.copy() + stationary_entry[Timeline.class_type] = ( "stationary" if event_data["stationary"] else "active" ) - save = True - elif prev_event_data["attributes"] == {} and event_data["attributes"] != {}: - timeline_entry[Timeline.class_type] = "attribute" - timeline_entry[Timeline.data]["attribute"] = list( + stationary_entry[Timeline.data] = base_entry[Timeline.data].copy() + entries_to_save.append(stationary_entry) + + # Check for new attributes + if prev_event_data["attributes"] == {} and event_data["attributes"] != {}: + attribute_entry = base_entry.copy() + attribute_entry[Timeline.class_type] = "attribute" + attribute_entry[Timeline.data] = base_entry[Timeline.data].copy() + attribute_entry[Timeline.data]["attribute"] = list( event_data["attributes"].keys() )[0] if len(event_data["current_attributes"]) > 0: - timeline_entry[Timeline.data]["attribute_box"] = to_relative_box( + attribute_entry[Timeline.data]["attribute_box"] = to_relative_box( camera_config.detect.width, camera_config.detect.height, event_data["current_attributes"][0]["box"], ) - save = True + entries_to_save.append(attribute_entry) + + # Save all entries + for entry in entries_to_save: + self.insert_or_save(entry, prev_event_data, event_data) + elif event_type == EventStateEnum.end: + timeline_entry = base_entry.copy() timeline_entry[Timeline.class_type] = "gone" - save = True - - if save: self.insert_or_save(timeline_entry, prev_event_data, event_data) def handle_api_entry( diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index bf91d89c22..a8ed12f241 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -233,7 +233,7 @@ export function GroupedClassificationCard({ }); if (!best) { - return group.at(-1); + best = group.at(-1)!; } const bestTyped: ClassificationItemData = best; @@ -377,30 +377,34 @@ export function GroupedClassificationCard({ )}
- {isDesktop && ( -
- {classifiedEvent && ( - - -
{ - navigate(`/explore?event_id=${classifiedEvent.id}`); - }} - > - -
-
- - - {t("details.item.button.viewInExplore", { - ns: "views/explore", - })} - - -
+ {classifiedEvent && ( +
+ + +
{ + navigate(`/explore?event_id=${classifiedEvent.id}`); + }} + > + +
+
+ + + {t("details.item.button.viewInExplore", { + ns: "views/explore", + })} + + +
)} diff --git a/web/src/components/classification/wizard/Step3ChooseExamples.tsx b/web/src/components/classification/wizard/Step3ChooseExamples.tsx index ec45f4b4d2..e3dd04afc7 100644 --- a/web/src/components/classification/wizard/Step3ChooseExamples.tsx +++ b/web/src/components/classification/wizard/Step3ChooseExamples.tsx @@ -45,6 +45,12 @@ export default function Step3ChooseExamples({ const [isProcessing, setIsProcessing] = useState(false); const [currentClassIndex, setCurrentClassIndex] = useState(0); const [selectedImages, setSelectedImages] = useState>(new Set()); + const [cacheKey, setCacheKey] = useState(Date.now()); + const [loadedImages, setLoadedImages] = useState>(new Set()); + + const handleImageLoad = useCallback((imageName: string) => { + setLoadedImages((prev) => new Set(prev).add(imageName)); + }, []); const { data: trainImages, mutate: refreshTrainImages } = useSWR( hasGenerated ? `classification/${step1Data.modelName}/train` : null, @@ -332,6 +338,8 @@ export default function Step3ChooseExamples({ setHasGenerated(true); toast.success(t("wizard.step3.generateSuccess")); + // Update cache key to force image reload + setCacheKey(Date.now()); await refreshTrainImages(); } catch (error) { const axiosError = error as { @@ -565,10 +573,16 @@ export default function Step3ChooseExamples({ )} onClick={() => toggleImageSelection(imageName)} > + {!loadedImages.has(imageName) && ( +
+ +
+ )} {`Example handleImageLoad(imageName)} />
); diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 5b4d5328c3..839186d8ea 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -309,7 +309,10 @@ export function RecordingView({ currentTimeRange.after <= currentTime && currentTimeRange.before >= currentTime ) { - mainControllerRef.current?.seekToTimestamp(currentTime, true); + mainControllerRef.current?.seekToTimestamp( + currentTime, + mainControllerRef.current.isPlaying(), + ); } else { updateSelectedSegment(currentTime, true); } diff --git a/web/src/views/settings/MotionTunerView.tsx b/web/src/views/settings/MotionTunerView.tsx index 4bcd9cdc5c..25d5f14699 100644 --- a/web/src/views/settings/MotionTunerView.tsx +++ b/web/src/views/settings/MotionTunerView.tsx @@ -4,7 +4,7 @@ import useSWR from "swr"; import axios from "axios"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage"; -import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; import { @@ -20,7 +20,6 @@ import { toast } from "sonner"; import { Separator } from "@/components/ui/separator"; import { Link } from "react-router-dom"; import { LuExternalLink } from "react-icons/lu"; -import { StatusBarMessagesContext } from "@/context/statusbar-provider"; import { Trans, useTranslation } from "react-i18next"; import { useDocDomain } from "@/hooks/use-doc-domain"; import { cn } from "@/lib/utils"; @@ -48,8 +47,6 @@ export default function MotionTunerView({ const [changedValue, setChangedValue] = useState(false); const [isLoading, setIsLoading] = useState(false); - const { addMessage, removeMessage } = useContext(StatusBarMessagesContext)!; - const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera); const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera); const { send: sendImproveContrast } = useImproveContrast(selectedCamera); @@ -119,7 +116,10 @@ export default function MotionTunerView({ axios .put( `config/set?cameras.${selectedCamera}.motion.threshold=${motionSettings.threshold}&cameras.${selectedCamera}.motion.contour_area=${motionSettings.contour_area}&cameras.${selectedCamera}.motion.improve_contrast=${motionSettings.improve_contrast ? "True" : "False"}`, - { requires_restart: 0 }, + { + requires_restart: 0, + update_topic: `config/cameras/${selectedCamera}/motion`, + }, ) .then((res) => { if (res.status === 200) { @@ -164,23 +164,7 @@ export default function MotionTunerView({ const onCancel = useCallback(() => { setMotionSettings(origMotionSettings); setChangedValue(false); - removeMessage("motion_tuner", `motion_tuner_${selectedCamera}`); - }, [origMotionSettings, removeMessage, selectedCamera]); - - useEffect(() => { - if (changedValue) { - addMessage( - "motion_tuner", - t("motionDetectionTuner.unsavedChanges", { camera: selectedCamera }), - undefined, - `motion_tuner_${selectedCamera}`, - ); - } else { - removeMessage("motion_tuner", `motion_tuner_${selectedCamera}`); - } - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [changedValue, selectedCamera]); + }, [origMotionSettings]); useEffect(() => { document.title = t("documentTitle.motionTuner"); From 8a4d5f34da556817c8c5c7f133441fa306c6a70c Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Sat, 20 Dec 2025 20:45:31 +0800 Subject: [PATCH 34/78] fix: fix system enrichments view classification i18n (#21366) --- web/public/locales/en/views/system.json | 5 ++++- web/src/views/system/EnrichmentMetrics.tsx | 21 ++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/web/public/locales/en/views/system.json b/web/public/locales/en/views/system.json index 73c6d65b50..ada23cdc26 100644 --- a/web/public/locales/en/views/system.json +++ b/web/public/locales/en/views/system.json @@ -192,7 +192,10 @@ "review_description_events_per_second": "Review Description", "object_description": "Object Description", "object_description_speed": "Object Description Speed", - "object_description_events_per_second": "Object Description" + "object_description_events_per_second": "Object Description", + "classification": "{{name}} Classification", + "classification_speed": "{{name}} Classification Speed", + "classification_events_per_second": "{{name}} Classification Events Per Second" } } } diff --git a/web/src/views/system/EnrichmentMetrics.tsx b/web/src/views/system/EnrichmentMetrics.tsx index 75ab593a02..29eda8fa27 100644 --- a/web/src/views/system/EnrichmentMetrics.tsx +++ b/web/src/views/system/EnrichmentMetrics.tsx @@ -88,11 +88,20 @@ export default function EnrichmentMetrics({ Object.entries(stats.embeddings).forEach(([rawKey, stat]) => { const key = rawKey.replaceAll("_", " "); - if (!(key in series)) { + const classificationIndex = rawKey.indexOf("_classification_"); + const seriesName = + classificationIndex === -1 + ? t("enrichments.embeddings." + rawKey) + : t( + `enrichments.embeddings.${rawKey.substring(classificationIndex + 1)}`, + { + name: rawKey.substring(0, classificationIndex), + }, + ); series[key] = { rawKey, - name: t("enrichments.embeddings." + rawKey), + name: seriesName, metrics: getThreshold(rawKey), data: [], }; @@ -133,8 +142,14 @@ export default function EnrichmentMetrics({ isSpeed = false; } + let categoryName = ""; // Get translated category name - const categoryName = t("enrichments.embeddings." + categoryKey); + if (categoryKey.endsWith("_classification")) { + const name = categoryKey.replace("_classification", ""); + categoryName = t("enrichments.embeddings.classification", { name }); + } else { + categoryName = t("enrichments.embeddings." + categoryKey); + } if (!(categoryKey in grouped)) { grouped[categoryKey] = { From 54f4af3c6ae562dfa7035c91683763f80fae2c2f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Dec 2025 17:30:34 -0700 Subject: [PATCH 35/78] Miscellaneous fixes (#21373) * Send preferred language for report service * make object lifecycle scrollable in tracking details * fix info popovers in live camera drawer * ensure metrics are initialized if genai is enabled * docs * ollama cloud model docs * Ensure object descriptions get claened up --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- .../object_classification.md | 8 +- .../state_classification.md | 2 + docs/docs/configuration/genai.md | 6 +- frigate/app.py | 4 + .../post/object_descriptions.py | 18 +- .../post/review_descriptions.py | 1 + frigate/embeddings/maintainer.py | 2 + frigate/genai/__init__.py | 4 + .../overlay/detail/SearchDetailDialog.tsx | 11 +- .../overlay/detail/TrackingDetails.tsx | 192 +++++++++--------- web/src/views/live/LiveCameraView.tsx | 6 +- 11 files changed, 144 insertions(+), 110 deletions(-) diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index 70fd1fbbd1..da8c4d8873 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -33,9 +33,9 @@ For object classification: - Example: `cat` → `Leo`, `Charlie`, `None`. - **Attribute**: - - Added as metadata to the object (visible in /events): `: `. + - Added as metadata to the object, visible in the Tracked Object Details pane in Explore, `frigate/events` MQTT messages, and the HTTP API response as `: `. - Ideal when multiple attributes can coexist independently. - - Example: Detecting if a `person` in a construction yard is wearing a helmet or not. + - Example: Detecting if a `person` in a construction yard is wearing a helmet or not, and if they are wearing a yellow vest or not. :::note @@ -81,6 +81,8 @@ classification: classification_type: sub_label # or: attribute ``` +An optional config, `save_attempts`, can be set as a key under the model name. This defines the number of classification attempts to save in the Recent Classifications tab. For object classification models, the default is 200. + ## Training the model Creating and training the model is done within the Frigate UI using the `Classification` page. The process consists of two steps: @@ -89,6 +91,8 @@ Creating and training the model is done within the Frigate UI using the `Classif Enter a name for your model, select the object label to classify (e.g., `person`, `dog`, `car`), choose the classification type (sub label or attribute), and define your classes. Include a `none` class for objects that don't fit any specific category. +For example: To classify your two cats, create a model named "Our Cats" and create two classes, "Charlie" and "Leo". Create a third class, "none", for other neighborhood cats that are not your own. + ### Step 2: Assign Training Examples The system will automatically generate example images from detected objects matching your selected label. You'll be guided through each class one at a time to select which images represent that class. Any images not assigned to a specific class will automatically be assigned to `none` when you complete the last class. Once all images are processed, training will begin automatically. diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index 196ec78de6..1ffdf90115 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -48,6 +48,8 @@ classification: crop: [0, 180, 220, 400] ``` +An optional config, `save_attempts`, can be set as a key under the model name. This defines the number of classification attempts to save in the Recent Classifications tab. For state classification models, the default is 100. + ## Training the model Creating and training the model is done within the Frigate UI using the `Classification` page. The process consists of three steps: diff --git a/docs/docs/configuration/genai.md b/docs/docs/configuration/genai.md index 018dc2050c..f9a3e1de07 100644 --- a/docs/docs/configuration/genai.md +++ b/docs/docs/configuration/genai.md @@ -56,7 +56,7 @@ Parallel requests also come with some caveats. You will need to set `OLLAMA_NUM_ ### Supported Models -You must use a vision capable model with Frigate. Current model variants can be found [in their model library](https://ollama.com/library). At the time of writing, this includes `llava`, `llava-llama3`, `llava-phi3`, and `moondream`. Note that Frigate will not automatically download the model you specify in your config, you must download the model to your local instance of Ollama first i.e. by running `ollama pull llava:7b` on your Ollama server/Docker container. Note that the model specified in Frigate's config must match the downloaded model tag. +You must use a vision capable model with Frigate. Current model variants can be found [in their model library](https://ollama.com/library). Note that Frigate will not automatically download the model you specify in your config, you must download the model to your local instance of Ollama first i.e. by running `ollama pull llava:7b` on your Ollama server/Docker container. Note that the model specified in Frigate's config must match the downloaded model tag. :::note @@ -64,6 +64,10 @@ You should have at least 8 GB of RAM available (or VRAM if running on GPU) to ru ::: +#### Ollama Cloud models + +Ollama also supports [cloud models](https://ollama.com/cloud), where your local Ollama instance handles requests from Frigate, but model inference is performed in the cloud. Set up Ollama locally, sign in with your Ollama account, and specify the cloud model name in your Frigate config. For more details, see the Ollama cloud model [docs](https://docs.ollama.com/cloud). + ### Configuration ```yaml diff --git a/frigate/app.py b/frigate/app.py index 30259ad3da..fac7a08d95 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -100,6 +100,10 @@ def __init__( ) if ( config.semantic_search.enabled + or any( + c.objects.genai.enabled or c.review.genai.enabled + for c in config.cameras.values() + ) or config.lpr.enabled or config.face_recognition.enabled or len(config.classification.custom) > 0 diff --git a/frigate/data_processing/post/object_descriptions.py b/frigate/data_processing/post/object_descriptions.py index 1f4608bc38..7bd38bfa8c 100644 --- a/frigate/data_processing/post/object_descriptions.py +++ b/frigate/data_processing/post/object_descriptions.py @@ -131,6 +131,8 @@ def __handle_frame_finalize( ) ): self._process_genai_description(event, camera_config, thumbnail) + else: + self.cleanup_event(event.id) def __regenerate_description(self, event_id: str, source: str, force: bool) -> None: """Regenerate the description for an event.""" @@ -204,6 +206,17 @@ def handle_request(self, topic: str, data: dict[str, Any]) -> str | None: ) return None + def cleanup_event(self, event_id: str) -> None: + """Clean up tracked event data to prevent memory leaks. + + This should be called when an event ends, regardless of whether + genai processing is triggered. + """ + if event_id in self.tracked_events: + del self.tracked_events[event_id] + if event_id in self.early_request_sent: + del self.early_request_sent[event_id] + def _read_and_crop_snapshot(self, event: Event) -> bytes | None: """Read, decode, and crop the snapshot image.""" @@ -299,9 +312,8 @@ def _process_genai_description( ), ).start() - # Delete tracked events based on the event_id - if event.id in self.tracked_events: - del self.tracked_events[event.id] + # Clean up tracked events and early request state + self.cleanup_event(event.id) def _genai_embed_description(self, event: Event, thumbnails: list[bytes]) -> None: """Embed the description for an event.""" diff --git a/frigate/data_processing/post/review_descriptions.py b/frigate/data_processing/post/review_descriptions.py index 7932d56f4e..0b12aa1a00 100644 --- a/frigate/data_processing/post/review_descriptions.py +++ b/frigate/data_processing/post/review_descriptions.py @@ -311,6 +311,7 @@ def handle_request(self, topic, request_data): start_ts, end_ts, events_with_context, + self.config.review.genai.preferred_language, self.config.review.genai.debug_save_thumbnails, ) else: diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 78a251c424..21d6a56342 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -522,6 +522,8 @@ def _process_finalized(self) -> None: ) elif isinstance(processor, ObjectDescriptionProcessor): if not updated_db: + # Still need to cleanup tracked events even if not processing + processor.cleanup_event(event_id) continue processor.process_data( diff --git a/frigate/genai/__init__.py b/frigate/genai/__init__.py index 910fc13b9b..5e1a742798 100644 --- a/frigate/genai/__init__.py +++ b/frigate/genai/__init__.py @@ -178,6 +178,7 @@ def generate_review_summary( start_ts: float, end_ts: float, events: list[dict[str, Any]], + preferred_language: str | None, debug_save: bool, ) -> str | None: """Generate a summary of review item descriptions over a period of time.""" @@ -232,6 +233,9 @@ def generate_review_summary( for event in events: timeline_summary_prompt += f"\n{event}\n" + if preferred_language: + timeline_summary_prompt += f"\nProvide your answer in {preferred_language}" + if debug_save: with open( os.path.join( diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index 392e929eb8..e3ae19159c 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -599,9 +599,14 @@ export default function SearchDetailDialog({ { if (isPopoverOpen) { diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx index 28a4624872..42535d5e11 100644 --- a/web/src/components/overlay/detail/TrackingDetails.tsx +++ b/web/src/components/overlay/detail/TrackingDetails.tsx @@ -526,7 +526,7 @@ export function TrackingDetails({
1 && aspectRatio < 1.5 + ? "lg:basis-3/5" + : "lg:basis-2/5", )} > {isDesktop && tabs && ( @@ -632,121 +635,114 @@ export function TrackingDetails({ )}
{config?.cameras[event.camera]?.onvif.autotracking .enabled_in_config && ( -
+
{t("trackingDetails.autoTrackingTips")}
)} -
-
-
+
+
+
{ + e.stopPropagation(); + // event.start_time is detect time, convert to record + handleSeekToTime( + (event.start_time ?? 0) + annotationOffset / 1000, + ); + }} + role="button" + >
{ - e.stopPropagation(); - // event.start_time is detect time, convert to record - handleSeekToTime( - (event.start_time ?? 0) + annotationOffset / 1000, - ); - }} - role="button" + className={cn( + "relative ml-2 rounded-full bg-muted-foreground p-2", + )} > -
- {getIconForLabel( - event.sub_label ? event.label + "-verified" : event.label, - "size-4 text-white", - )} -
-
- {label} -
- {formattedStart ?? ""} - {event.end_time != null ? ( - <> - {formattedEnd} - ) : ( -
- -
- )} -
- {event.data?.recognized_license_plate && ( - <> - · -
- - {event.data.recognized_license_plate} - -
- + {getIconForLabel( + event.sub_label ? event.label + "-verified" : event.label, + "size-4 text-white", + )} +
+
+ {label} +
+ {formattedStart ?? ""} + {event.end_time != null ? ( + <> - {formattedEnd} + ) : ( +
+ +
)}
+ {event.data?.recognized_license_plate && ( + <> + · +
+ + {event.data.recognized_license_plate} + +
+ + )}
+
-
- {!eventSequence ? ( - - ) : eventSequence.length === 0 ? ( -
- {t("detail.noObjectDetailData", { ns: "views/events" })} -
- ) : ( +
+ {!eventSequence ? ( + + ) : eventSequence.length === 0 ? ( +
+ {t("detail.noObjectDetailData", { ns: "views/events" })} +
+ ) : ( +
+ className="absolute -top-2 left-6 z-0 w-0.5 -translate-x-1/2 bg-secondary-foreground" + style={{ bottom: lineBottomOffsetPx }} + /> + {isWithinEventRange && (
- {isWithinEventRange && ( -
- )} -
- {eventSequence.map((item, idx) => { - return ( -
{ - rowRefs.current[idx] = el; - }} - > - handleLifecycleClick(item)} - setSelectedZone={setSelectedZone} - getZoneColor={getZoneColor} - effectiveTime={effectiveTime} - isTimelineActive={isWithinEventRange} - /> -
- ); - })} -
+ )} +
+ {eventSequence.map((item, idx) => { + return ( +
{ + rowRefs.current[idx] = el; + }} + > + handleLifecycleClick(item)} + setSelectedZone={setSelectedZone} + getZoneColor={getZoneColor} + effectiveTime={effectiveTime} + isTimelineActive={isWithinEventRange} + /> +
+ ); + })}
- )} -
+
+ )}
diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index 5de52d2438..418c74068c 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -1444,7 +1444,7 @@ function FrigateCameraFeatures({ ns: "components/dialog", })}
- +
@@ -1531,7 +1531,7 @@ function FrigateCameraFeatures({ <>
{t("stream.audio.unavailable")}
- +
@@ -1575,7 +1575,7 @@ function FrigateCameraFeatures({ <>
{t("stream.twoWayTalk.unavailable")}
- +
From f74df040bbd68bb1e746662e7dafcda417579148 Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Mon, 22 Dec 2025 19:56:19 +0800 Subject: [PATCH 36/78] fix: fix password setting overlay time i18n (#21387) --- web/src/components/overlay/SetPasswordDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/overlay/SetPasswordDialog.tsx b/web/src/components/overlay/SetPasswordDialog.tsx index 95c4e73bec..c6d861a6ba 100644 --- a/web/src/components/overlay/SetPasswordDialog.tsx +++ b/web/src/components/overlay/SetPasswordDialog.tsx @@ -54,7 +54,7 @@ export default function SetPasswordDialog({ config?.auth?.refresh_time ?? undefined; const refreshTimeLabel = refreshSeconds ? formatSecondsToDuration(refreshSeconds) - : "30 minutes"; + : t("time.30minutes", { ns: "common" }); // visibility toggles for password fields const [showOldPassword, setShowOldPassword] = useState(false); From f862ef5d0cf0c5c91fbe28fc2d55a655b744c61c Mon Sep 17 00:00:00 2001 From: apocaliss92 Date: Mon, 22 Dec 2025 16:13:37 +0100 Subject: [PATCH 37/78] Add Scrypted - Frigate bridge plugin information (#21365) --- docs/docs/integrations/third_party_extensions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/integrations/third_party_extensions.md b/docs/docs/integrations/third_party_extensions.md index adaee2780f..c26c8a13a8 100644 --- a/docs/docs/integrations/third_party_extensions.md +++ b/docs/docs/integrations/third_party_extensions.md @@ -38,3 +38,7 @@ This is a fork (with fixed errors and new features) of [original Double Take](ht ## [Periscope](https://github.com/maksz42/periscope) [Periscope](https://github.com/maksz42/periscope) is a lightweight Android app that turns old devices into live viewers for Frigate. It works on Android 2.2 and above, including Android TV. It supports authentication and HTTPS. + +## [Scrypted - Frigate bridge plugin](https://github.com/apocaliss92/scrypted-frigate-bridge) + +[Scrypted - Frigate bridge](https://github.com/apocaliss92/scrypted-frigate-bridge) is an plugin that allows to ingest Frigate detections, motion, videoclips on Scrypted as well as provide templates to export rebroadcast configurations on Frigate. From a4ece9dae324fd6ee3ac7c047da10a2e21f75c3f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 24 Dec 2025 08:03:09 -0600 Subject: [PATCH 38/78] Miscellaneous Fixes (0.17 beta) (#21396) * use fallback timeout for opening media source covers the case where there is no active connection to the go2rtc stream and the camera takes a long time to start * Add review thumbnail URL to integration docs * fix weekday starting point on explore when set to monday in UI settings * only show allowed cameras and groups in camera filter button * Reset the wizard state after closing with model * remove footnote about 0.17 * 0.17 * add triggers to note * add slovak * Ensure genai client exists * Correctly catch JSONDecodeError * clarify docs for none class * version bump on updating page * fix ExportRecordingsBody to allow optional name field fixes https://github.com/blakeblackshear/frigate/discussions/21413 because of https://github.com/blakeblackshear/frigate-hass-integration/pull/1021 * Catch remote protocol error from ollama --------- Co-authored-by: Nicolas Mowen --- .../object_classification.md | 6 ++-- docs/docs/frigate/updating.md | 16 +++++----- docs/docs/integrations/home-assistant.md | 6 ++++ docs/docs/plus/index.md | 16 +++++----- .../defs/request/export_recordings_body.py | 4 +-- frigate/embeddings/maintainer.py | 8 +++-- frigate/genai/ollama.py | 9 ++++-- frigate/stats/util.py | 3 +- .../ClassificationModelWizardDialog.tsx | 7 ++++- .../filter/CalendarFilterButton.tsx | 3 ++ .../components/filter/CamerasFilterButton.tsx | 29 +++++++++++++++++-- web/src/components/player/MsePlayer.tsx | 4 +-- web/src/components/ui/calendar-range.tsx | 11 +++++-- web/src/lib/const.ts | 1 + 14 files changed, 87 insertions(+), 36 deletions(-) diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index da8c4d8873..0fc3ee8147 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -39,7 +39,7 @@ For object classification: :::note -A tracked object can only have a single sub label. If you are using Face Recognition and you configure an object classification model for `person` using the sub label type, your sub label may not be assigned correctly as it depends on which enrichment completes its analysis first. Consider using the `attribute` type instead. +A tracked object can only have a single sub label. If you are using Triggers or Face Recognition and you configure an object classification model for `person` using the sub label type, your sub label may not be assigned correctly as it depends on which enrichment completes its analysis first. Consider using the `attribute` type instead. ::: @@ -89,9 +89,9 @@ Creating and training the model is done within the Frigate UI using the `Classif ### Step 1: Name and Define -Enter a name for your model, select the object label to classify (e.g., `person`, `dog`, `car`), choose the classification type (sub label or attribute), and define your classes. Include a `none` class for objects that don't fit any specific category. +Enter a name for your model, select the object label to classify (e.g., `person`, `dog`, `car`), choose the classification type (sub label or attribute), and define your classes. Frigate will automatically include a `none` class for objects that don't fit any specific category. -For example: To classify your two cats, create a model named "Our Cats" and create two classes, "Charlie" and "Leo". Create a third class, "none", for other neighborhood cats that are not your own. +For example: To classify your two cats, create a model named "Our Cats" and create two classes, "Charlie" and "Leo". A third class, "none", will be created automatically for other neighborhood cats that are not your own. ### Step 2: Assign Training Examples diff --git a/docs/docs/frigate/updating.md b/docs/docs/frigate/updating.md index d95ae83c52..61cb80f133 100644 --- a/docs/docs/frigate/updating.md +++ b/docs/docs/frigate/updating.md @@ -5,7 +5,7 @@ title: Updating # Updating Frigate -The current stable version of Frigate is **0.16.2**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.16.2). +The current stable version of Frigate is **0.17.0**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.17.0). Keeping Frigate up to date ensures you benefit from the latest features, performance improvements, and bug fixes. The update process varies slightly depending on your installation method (Docker, Home Assistant Addon, etc.). Below are instructions for the most common setups. @@ -33,21 +33,21 @@ If you’re running Frigate via Docker (recommended method), follow these steps: 2. **Update and Pull the Latest Image**: - If using Docker Compose: - - Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.16.2` instead of `0.15.2`). For example: + - Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.17.0` instead of `0.16.3`). For example: ```yaml services: frigate: - image: ghcr.io/blakeblackshear/frigate:0.16.2 + image: ghcr.io/blakeblackshear/frigate:0.17.0 ``` - Then pull the image: ```bash - docker pull ghcr.io/blakeblackshear/frigate:0.16.2 + docker pull ghcr.io/blakeblackshear/frigate:0.17.0 ``` - **Note for `stable` Tag Users**: If your `docker-compose.yml` uses the `stable` tag (e.g., `ghcr.io/blakeblackshear/frigate:stable`), you don’t need to update the tag manually. The `stable` tag always points to the latest stable release after pulling. - If using `docker run`: - - Pull the image with the appropriate tag (e.g., `0.16.2`, `0.16.2-tensorrt`, or `stable`): + - Pull the image with the appropriate tag (e.g., `0.17.0`, `0.17.0-tensorrt`, or `stable`): ```bash - docker pull ghcr.io/blakeblackshear/frigate:0.16.2 + docker pull ghcr.io/blakeblackshear/frigate:0.17.0 ``` 3. **Start the Container**: @@ -105,8 +105,8 @@ If an update causes issues: 1. Stop Frigate. 2. Restore your backed-up config file and database. 3. Revert to the previous image version: - - For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.15.2`) in your `docker run` command. - - For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.15.2`), and re-run `docker compose up -d`. + - For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.16.3`) in your `docker run` command. + - For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.16.3`), and re-run `docker compose up -d`. - For Home Assistant: Reinstall the previous addon version manually via the repository if needed and restart the addon. 4. Verify the old version is running again. diff --git a/docs/docs/integrations/home-assistant.md b/docs/docs/integrations/home-assistant.md index 169a7ad31f..46453b55a1 100644 --- a/docs/docs/integrations/home-assistant.md +++ b/docs/docs/integrations/home-assistant.md @@ -245,6 +245,12 @@ To load a preview gif of a review item: https://HA_URL/api/frigate/notifications//review_preview.gif ``` +To load the thumbnail of a review item: + +``` +https://HA_URL/api/frigate/notifications///review_thumbnail.webp +``` + ## RTSP stream diff --git a/docs/docs/plus/index.md b/docs/docs/plus/index.md index fa8f86f9ca..d75c12f92f 100644 --- a/docs/docs/plus/index.md +++ b/docs/docs/plus/index.md @@ -15,13 +15,11 @@ There are three model types offered in Frigate+, `mobiledet`, `yolonas`, and `yo Not all model types are supported by all detectors, so it's important to choose a model type to match your detector as shown in the table under [supported detector types](#supported-detector-types). You can test model types for compatibility and speed on your hardware by using the base models. -| Model Type | Description | -| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `mobiledet` | Based on the same architecture as the default model included with Frigate. Runs on Google Coral devices and CPUs. | -| `yolonas` | A newer architecture that offers slightly higher accuracy and improved detection of small objects. Runs on Intel, NVidia GPUs, and AMD GPUs. | -| `yolov9` | A leading SOTA (state of the art) object detection model with similar performance to yolonas, but on a wider range of hardware options. Runs on Intel, NVidia GPUs, AMD GPUs, Hailo, MemryX\*, Apple Silicon\*, and Rockchip NPUs. | - -_\* Support coming in 0.17_ +| Model Type | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `mobiledet` | Based on the same architecture as the default model included with Frigate. Runs on Google Coral devices and CPUs. | +| `yolonas` | A newer architecture that offers slightly higher accuracy and improved detection of small objects. Runs on Intel, NVidia GPUs, and AMD GPUs. | +| `yolov9` | A leading SOTA (state of the art) object detection model with similar performance to yolonas, but on a wider range of hardware options. Runs on Intel, NVidia GPUs, AMD GPUs, Hailo, MemryX, Apple Silicon, and Rockchip NPUs. | ### YOLOv9 Details @@ -39,7 +37,7 @@ If you have a Hailo device, you will need to specify the hardware you have when #### Rockchip (RKNN) Support -For 0.16, YOLOv9 onnx models will need to be manually converted. First, you will need to configure Frigate to use the model id for your YOLOv9 onnx model so it downloads the model to your `model_cache` directory. From there, you can follow the [documentation](/configuration/object_detectors.md#converting-your-own-onnx-model-to-rknn-format) to convert it. Automatic conversion is coming in 0.17. +For 0.16, YOLOv9 onnx models will need to be manually converted. First, you will need to configure Frigate to use the model id for your YOLOv9 onnx model so it downloads the model to your `model_cache` directory. From there, you can follow the [documentation](/configuration/object_detectors.md#converting-your-own-onnx-model-to-rknn-format) to convert it. Automatic conversion is available in 0.17 and later. ## Supported detector types @@ -55,7 +53,7 @@ Currently, Frigate+ models support CPU (`cpu`), Google Coral (`edgetpu`), OpenVi | [Hailo8/Hailo8L/Hailo8R](/configuration/object_detectors#hailo-8) | `hailo8l` | `yolov9` | | [Rockchip NPU](/configuration/object_detectors#rockchip-platform)\* | `rknn` | `yolov9` | -_\* Requires manual conversion in 0.16. Automatic conversion coming in 0.17._ +_\* Requires manual conversion in 0.16. Automatic conversion available in 0.17 and later._ ## Improving your model diff --git a/frigate/api/defs/request/export_recordings_body.py b/frigate/api/defs/request/export_recordings_body.py index eb6c151555..19fc2f0194 100644 --- a/frigate/api/defs/request/export_recordings_body.py +++ b/frigate/api/defs/request/export_recordings_body.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Optional, Union from pydantic import BaseModel, Field from pydantic.json_schema import SkipJsonSchema @@ -16,5 +16,5 @@ class ExportRecordingsBody(BaseModel): source: PlaybackSourceEnum = Field( default=PlaybackSourceEnum.recordings, title="Playback source" ) - name: str = Field(title="Friendly name", default=None, max_length=256) + name: Optional[str] = Field(title="Friendly name", default=None, max_length=256) image_path: Union[str, SkipJsonSchema[None]] = None diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 21d6a56342..c74bc2310c 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -203,7 +203,9 @@ def __init__( # post processors self.post_processors: list[PostProcessorApi] = [] - if any(c.review.genai.enabled_in_config for c in self.config.cameras.values()): + if self.genai_client is not None and any( + c.review.genai.enabled_in_config for c in self.config.cameras.values() + ): self.post_processors.append( ReviewDescriptionProcessor( self.config, self.requestor, self.metrics, self.genai_client @@ -244,7 +246,9 @@ def __init__( ) self.post_processors.append(semantic_trigger_processor) - if any(c.objects.genai.enabled_in_config for c in self.config.cameras.values()): + if self.genai_client is not None and any( + c.objects.genai.enabled_in_config for c in self.config.cameras.values() + ): self.post_processors.append( ObjectDescriptionProcessor( self.config, diff --git a/frigate/genai/ollama.py b/frigate/genai/ollama.py index 9f9c8a750f..a54141e4dc 100644 --- a/frigate/genai/ollama.py +++ b/frigate/genai/ollama.py @@ -3,7 +3,7 @@ import logging from typing import Any, Optional -from httpx import TimeoutException +from httpx import RemoteProtocolError, TimeoutException from ollama import Client as ApiClient from ollama import ResponseError @@ -68,7 +68,12 @@ def _send(self, prompt: str, images: list[bytes]) -> Optional[str]: f"Ollama tokens used: eval_count={result.get('eval_count')}, prompt_eval_count={result.get('prompt_eval_count')}" ) return result["response"].strip() - except (TimeoutException, ResponseError, ConnectionError) as e: + except ( + TimeoutException, + ResponseError, + RemoteProtocolError, + ConnectionError, + ) as e: logger.warning("Ollama returned an error: %s", str(e)) return None diff --git a/frigate/stats/util.py b/frigate/stats/util.py index 17b45d1d48..410350d968 100644 --- a/frigate/stats/util.py +++ b/frigate/stats/util.py @@ -42,11 +42,10 @@ def get_latest_version(config: FrigateConfig) -> str: "https://api.github.com/repos/blakeblackshear/frigate/releases/latest", timeout=10, ) + response = request.json() except (RequestException, JSONDecodeError): return "unknown" - response = request.json() - if request.ok and response and "tag_name" in response: return str(response.get("tag_name").replace("v", "")) else: diff --git a/web/src/components/classification/ClassificationModelWizardDialog.tsx b/web/src/components/classification/ClassificationModelWizardDialog.tsx index 06bf1f8509..0c43b99425 100644 --- a/web/src/components/classification/ClassificationModelWizardDialog.tsx +++ b/web/src/components/classification/ClassificationModelWizardDialog.tsx @@ -137,6 +137,11 @@ export default function ClassificationModelWizardDialog({ onClose(); }; + const handleSuccessClose = () => { + dispatch({ type: "RESET" }); + onClose(); + }; + return ( )} diff --git a/web/src/components/filter/CalendarFilterButton.tsx b/web/src/components/filter/CalendarFilterButton.tsx index 876eb9ab05..9f052b73d3 100644 --- a/web/src/components/filter/CalendarFilterButton.tsx +++ b/web/src/components/filter/CalendarFilterButton.tsx @@ -18,6 +18,7 @@ import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import { useTranslation } from "react-i18next"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; +import { useUserPersistence } from "@/hooks/use-user-persistence"; type CalendarFilterButtonProps = { reviewSummary?: ReviewSummary; @@ -105,6 +106,7 @@ export function CalendarRangeFilterButton({ const { t } = useTranslation(["components/filter"]); const { data: config } = useSWR("config"); const timezone = useTimezone(config); + const [weekStartsOn] = useUserPersistence("weekStartsOn", 0); const [open, setOpen] = useState(false); const selectedDate = useFormattedRange( @@ -138,6 +140,7 @@ export function CalendarRangeFilterButton({ initialDateTo={range?.to} timezone={timezone} showCompare={false} + weekStartsOn={weekStartsOn} onUpdate={(range) => { updateSelectedRange(range.range); setOpen(false); diff --git a/web/src/components/filter/CamerasFilterButton.tsx b/web/src/components/filter/CamerasFilterButton.tsx index baeccf06f5..cc89e13cf5 100644 --- a/web/src/components/filter/CamerasFilterButton.tsx +++ b/web/src/components/filter/CamerasFilterButton.tsx @@ -13,6 +13,7 @@ import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import FilterSwitch from "./FilterSwitch"; import { FaVideo } from "react-icons/fa"; import { useTranslation } from "react-i18next"; +import { useAllowedCameras } from "@/hooks/use-allowed-cameras"; type CameraFilterButtonProps = { allCameras: string[]; @@ -35,6 +36,30 @@ export function CamerasFilterButton({ const [currentCameras, setCurrentCameras] = useState( selectedCameras, ); + const allowedCameras = useAllowedCameras(); + + // Filter cameras to only include those the user has access to + const filteredCameras = useMemo( + () => allCameras.filter((camera) => allowedCameras.includes(camera)), + [allCameras, allowedCameras], + ); + + // Filter groups to only include those with at least one allowed camera + const filteredGroups = useMemo( + () => + groups + .map(([name, config]) => { + const allowedGroupCameras = config.cameras.filter((camera) => + allowedCameras.includes(camera), + ); + return [name, { ...config, cameras: allowedGroupCameras }] as [ + string, + CameraGroupConfig, + ]; + }) + .filter(([, config]) => config.cameras.length > 0), + [groups, allowedCameras], + ); const buttonText = useMemo(() => { if (isMobile) { @@ -79,8 +104,8 @@ export function CamerasFilterButton({ ); const content = ( { if (wsRef.current) { onDisconnect(); @@ -290,7 +290,7 @@ function MSEPlayer({ type: "mse", value: codecs(MediaSource.isTypeSupported), }, - 3000, + (fallbackTimeout ?? 3) * 1000, ).catch(() => { if (wsRef.current) { onDisconnect(); diff --git a/web/src/components/ui/calendar-range.tsx b/web/src/components/ui/calendar-range.tsx index f439cb082f..09641926ac 100644 --- a/web/src/components/ui/calendar-range.tsx +++ b/web/src/components/ui/calendar-range.tsx @@ -35,6 +35,8 @@ export interface DateRangePickerProps { showCompare?: boolean; /** timezone */ timezone?: string; + /** First day of the week: 0 = Sunday, 1 = Monday */ + weekStartsOn?: number; } const getDateAdjustedForTimezone = ( @@ -91,6 +93,7 @@ export function DateRangePicker({ onUpdate, onReset, showCompare = true, + weekStartsOn = 0, }: DateRangePickerProps) { const [isOpen, setIsOpen] = useState(false); @@ -150,7 +153,9 @@ export function DateRangePicker({ if (!preset) throw new Error(`Unknown date range preset: ${presetName}`); const from = new TZDate(new Date(), timezone); const to = new TZDate(new Date(), timezone); - const first = from.getDate() - from.getDay(); + const dayOfWeek = from.getDay(); + const daysFromWeekStart = (dayOfWeek - weekStartsOn + 7) % 7; + const first = from.getDate() - daysFromWeekStart; switch (preset.name) { case "today": @@ -184,8 +189,8 @@ export function DateRangePicker({ to.setHours(23, 59, 59, 999); break; case "lastWeek": - from.setDate(from.getDate() - 7 - from.getDay()); - to.setDate(to.getDate() - to.getDay() - 1); + from.setDate(first - 7); + to.setDate(first - 1); from.setHours(0, 0, 0, 0); to.setHours(23, 59, 59, 999); break; diff --git a/web/src/lib/const.ts b/web/src/lib/const.ts index 07fe268c1f..ad35d5ae35 100644 --- a/web/src/lib/const.ts +++ b/web/src/lib/const.ts @@ -23,5 +23,6 @@ export const supportedLanguageKeys = [ "lt", "uk", "cs", + "sk", "hu", ]; From bb3991f62b71f021e167bfbb127bb2b930e6af86 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:16:55 +0100 Subject: [PATCH 39/78] Translated using Weblate (Turkish) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (121 of 121 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (130 of 130 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (92 of 92 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (128 of 128 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (120 of 120 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (41 of 41 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (214 of 214 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (654 of 654 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Emircanos Co-authored-by: Hosted Weblate Co-authored-by: pcislocked Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/tr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/tr/ Translation: Frigate NVR/common Translation: Frigate NVR/components-auth Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-events Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-live Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/tr/common.json | 2 +- web/public/locales/tr/components/auth.json | 2 +- web/public/locales/tr/components/filter.json | 4 + .../locales/tr/views/classificationModel.json | 41 +++-- web/public/locales/tr/views/events.json | 6 +- web/public/locales/tr/views/explore.json | 23 ++- web/public/locales/tr/views/faceLibrary.json | 4 +- web/public/locales/tr/views/live.json | 6 +- web/public/locales/tr/views/search.json | 3 +- web/public/locales/tr/views/settings.json | 164 +++++++++--------- web/public/locales/tr/views/system.json | 7 +- 11 files changed, 141 insertions(+), 121 deletions(-) diff --git a/web/public/locales/tr/common.json b/web/public/locales/tr/common.json index 6dd7d79075..311cca81f6 100644 --- a/web/public/locales/tr/common.json +++ b/web/public/locales/tr/common.json @@ -231,7 +231,7 @@ "hide": "{{item}} öğesini gizle", "show": "{{item}} öğesini göster", "ID": "ID", - "none": "Yok", + "none": "Hiçbiri", "all": "Tümü" }, "notFound": { diff --git a/web/public/locales/tr/components/auth.json b/web/public/locales/tr/components/auth.json index 5d99dcd754..b66836baef 100644 --- a/web/public/locales/tr/components/auth.json +++ b/web/public/locales/tr/components/auth.json @@ -11,6 +11,6 @@ "unknownError": "Bilinmeyen hata. Günlükleri kontrol edin." }, "user": "Kullanıcı Adı", - "firstTimeLogin": "İlk kez giriş yapmayı mı deniyorsunuz? Giriş bilgileri Frigate loglarında görüntülenir." + "firstTimeLogin": "İlk kez giriş yapmayı mı deniyorsunuz? Giriş bilgileri Frigate günlüklerinde görüntülenir." } } diff --git a/web/public/locales/tr/components/filter.json b/web/public/locales/tr/components/filter.json index c71110bebc..ef178d4a04 100644 --- a/web/public/locales/tr/components/filter.json +++ b/web/public/locales/tr/components/filter.json @@ -133,5 +133,9 @@ "all": { "title": "Tüm Sınıflar" } + }, + "attributes": { + "label": "Sınıflandırma Özellikleri", + "all": "Tüm Özellikler" } } diff --git a/web/public/locales/tr/views/classificationModel.json b/web/public/locales/tr/views/classificationModel.json index 535e5b13b2..2081188aa4 100644 --- a/web/public/locales/tr/views/classificationModel.json +++ b/web/public/locales/tr/views/classificationModel.json @@ -1,7 +1,9 @@ { "documentTitle": "Sınıflandırma Modelleri - Frigate", "details": { - "scoreInfo": "Skor, modelin nesneyi tespit ettiği tüm durumlar için ortalama güven düzeyini gösterir." + "scoreInfo": "Skor, modelin nesneyi tespit ettiği tüm durumlar için ortalama güven düzeyini gösterir.", + "none": "Hiçbiri", + "unknown": "Bilinmiyor" }, "button": { "deleteClassificationAttempts": "Sınıflandırma Fotoğraflarını Sil", @@ -27,7 +29,7 @@ }, "error": { "deleteImageFailed": "Silinemedi: {{errorMessage}}", - "deleteModelFailed": "Model silinirken hata oluştu: {{errorMessage}}", + "deleteModelFailed": "Model silinemedi: {{errorMessage}}", "categorizeFailed": "Görsel sınıflandırılamadı: {{errorMessage}}", "trainingFailed": "Model eğitimi başarısız oldu. Ayrıntılar için Frigate günlüklerini kontrol edin.", "deleteCategoryFailed": "Sınıf silinemedi: {{errorMessage}}", @@ -60,10 +62,10 @@ }, "renameCategory": { "title": "Sınıfı Yeniden Adlandır", - "desc": "{{name}} için yeni bir ad girin. Ad değişikliğinin etkili olabilmesi için modeli yeniden eğitmeniz gerekecektir." + "desc": "{{name}} için yeni bir ad girin. Ad değişikliğinin geçerli olması için modeli yeniden eğitmeniz gerekecektir." }, "description": { - "invalidName": "Geçersiz ad. Ad yalnızca harfler, rakamlar, boşluklar, kesme işaretleri (’), alt çizgiler (_) ve tireler (-) içerebilir." + "invalidName": "Geçersiz isim. İsimler; yalnızca harf, rakam, boşluk, kesme işareti (’), alt çizgi(_) ve tire (-) içerebilir." }, "train": { "title": "Son Sınıflandırmalar", @@ -107,14 +109,14 @@ "wizard": { "title": "Yeni Sınıflandırma Oluştur", "steps": { - "nameAndDefine": "Adlandır ve Tanımla", + "nameAndDefine": "İsim ver ve Tanımla", "stateArea": "Durum Alanı", "chooseExamples": "Örnekleri Seç" }, "step1": { - "description": "State modelleri, sabit kamera alanlarındaki değişiklikleri (ör. kapının açılması/kapanması) izler. Nesne modelleri ise algılanan nesnelere ek sınıflandırmalar ekler (ör. bilinen hayvanlar, kuryeler vb.).", - "name": "Ad", - "namePlaceholder": "Model adını girin...", + "description": "Durum modelleri, sabit kamera alanlarındaki değişiklikleri (ör. kapının açılması/kapanması) izler. Nesne modelleri ise algılanan nesnelere ek sınıflandırmalar ekler (ör. bilinen hayvanlar, kuryeler vb.).", + "name": "İsim", + "namePlaceholder": "Model ismi girin...", "type": "Tür", "typeState": "Durum", "typeObject": "Nesne", @@ -122,24 +124,25 @@ "objectLabelPlaceholder": "Nesne türünü seçin...", "classificationType": "Sınıflandırma Türü", "classificationTypeTip": "Sınıflandırma türleri hakkında bilgi edinin", - "classificationTypeDesc": "Alt etiketleri, nesne etiketine ek metin ekler (örneğin: “Person: UPS”). Öznitelikler (attributes) ise nesne meta verilerinde ayrı olarak saklanan ve aranabilir metadata bilgileridir.", + "classificationTypeDesc": "Alt etiketler, nesne etiketine ek olarak saklanır (örneğin: “Person: UPS”). Öznitelikler(attributes) ise nesne meta verilerinde saklanan aranabilir meta verilerdir.", "classificationSubLabel": "Alt Etiket", "classificationAttribute": "Özellik", "classes": "Sınıflar", "states": "Durumlar", "classesTip": "Sınıflar hakkında bilgi edinin", "classesStateDesc": "Kamera alanınızın içinde bulunabileceği farklı durumları tanımlayın. Örneğin: bir garaj kapısı için ‘açık’ ve ‘kapalı’.", - "classesObjectDesc": "Algılanan nesneleri sınıflandırmak için farklı kategorileri tanımlayın. Örneğin: Bir kişi sınıflandırması için ‘teslimat_görevlisi’, ‘sakin’, ‘yabancı’.", - "classPlaceholder": "Sınıf adını girin...", + "classesObjectDesc": "Algılanan nesneleri sınıflandırmak için farklı kategorileri tanımlayın. Örneğin: Bir kişi sınıflandırması için \"kurye\", \"bahçıvan\" veya \"yabancı\" olabilir.", + "classPlaceholder": "Sınıf ismi girin...", "errors": { - "nameRequired": "Model adı gerekli", - "nameLength": "Model adı 64 karakter veya daha az olmalıdır", - "nameOnlyNumbers": "Model adı yalnızca rakamlardan oluşamaz", + "nameRequired": "Model ismi gereklidir", + "nameLength": "Model ismi en fazla 64 karakter olmalıdır", + "nameOnlyNumbers": "Model ismi yalnızca rakamlardan oluşamaz", "classRequired": "En az 1 sınıf gereklidir", - "classesUnique": "Sınıf adları benzersiz olmalıdır", + "classesUnique": "Sınıf isimleri benzersiz olmalıdır", "stateRequiresTwoClasses": "Durum modelleri en az 2 sınıf gerektirir", "objectLabelRequired": "Lütfen bir nesne etiketi seçin", - "objectTypeRequired": "Lütfen bir sınıflandırma türü seçin" + "objectTypeRequired": "Lütfen bir sınıflandırma türü seçin", + "noneNotAllowed": "'none' sınıfına izin verilmiyor" } }, "step2": { @@ -152,11 +155,11 @@ "step3": { "selectImagesPrompt": "{{className}} etiketli tüm görselleri seç", "selectImagesDescription": "Görselleri seçmek için üzerlerine tıklayın. Bu sınıfla işiniz bittiğinde Devam Et’e tıklayın.", - "allImagesRequired_one": "Lütfen tüm görselleri sınıflandırın. {{count}} görsel kaldı.", + "allImagesRequired_one": "Lütfen tüm görselleri sınıflandırın. Bir görsel kaldı.", "allImagesRequired_other": "Lütfen tüm görselleri sınıflandırın. {{count}} görsel kaldı.", "generating": { "title": "Örnek Görseller Oluşturuluyor", - "description": "Frigate kayıtlarınızdan temsilî görüntüler çekiliyor. Bu işlem biraz zaman alabilir…" + "description": "Frigate kayıtlarınızdan temsili görüntüler alınıyor. Bu işlem biraz zaman alabilir…" }, "training": { "title": "Model Eğitiliyor", @@ -177,7 +180,7 @@ "generateSuccess": "Örnek görseller başarıyla oluşturuldu", "missingStatesWarning": { "title": "Eksik Durum Örnekleri", - "description": "En iyi sonuçlar için tavsiye edilir: Tüm durumlar (state) için örnekler seçin. Tüm durumlar için örnek seçmeden devam edebilirsiniz, ancak model, tüm durumlara ait görüntüler eklenene kadar eğitilmeyecektir. Devam ettikten sonra, eksik durumlar için görüntüleri sınıflandırmak ve ardından modeli eğitmek için Son Sınıflandırmalar (Recent Classifications) görünümünü kullanın." + "description": "En iyi sonuçlar için her bir durum için örnek görseller seçmeniz tavsiye edilir. Tüm durumlar için görsel seçmeden devam edebilirsiniz fakat tüm durumlar için görseller seçilmedikçe model eğitilemeyecektir. Son Sınıflandırmalar arayüzünü kullanarak görselleri sınıflandırmak üzere görüntüleyebilir, yeterince görsel seçildikten sonra da modeli eğitebilirsiniz." } } }, diff --git a/web/public/locales/tr/views/events.json b/web/public/locales/tr/views/events.json index e15c11b382..ae9a0d463d 100644 --- a/web/public/locales/tr/views/events.json +++ b/web/public/locales/tr/views/events.json @@ -1,10 +1,10 @@ { "camera": "kamera", - "alerts": "Alarmlar", + "alerts": "Uyarılar", "detections": "Tespitler", "empty": { "detection": "İncelenecek tespit öğesi yok", - "alert": "İncelenecek alarm öğesi yok", + "alert": "İncelenecek uyarı öğesi yok", "motion": "Hareket verisi bulunamadı" }, "timeline": "Zaman şeridi", @@ -54,7 +54,7 @@ }, "objectTrack": { "trackedPoint": "Takip edilen nokta", - "clickToSeek": "Bu zamana gitmek için tıklayın" + "clickToSeek": "Bu zamana atlamak için tıklayın" }, "normalActivity": "Normal", "needsReview": "İnceleme Gerekiyor", diff --git a/web/public/locales/tr/views/explore.json b/web/public/locales/tr/views/explore.json index 6909c849a5..de2055e1af 100644 --- a/web/public/locales/tr/views/explore.json +++ b/web/public/locales/tr/views/explore.json @@ -19,13 +19,15 @@ "updatedSublabel": "Alt etiket başarıyla gücellendi.", "regenerate": "Yeni bir açıklama {{provider}} sağlayıcısından talep edildi. Sağlayıcının hızına bağlı olarak yeni açıklamanın oluşturulması biraz zaman alabilir.", "updatedLPR": "Plaka başarıyla güncellendi.", - "audioTranscription": "Ses dökümü başarıyla istendi. Frigate sunucunuzun hızına bağlı olarak döküm işlemi tamamlanması biraz zaman alabilir." + "audioTranscription": "Ses dökümü başarıyla istendi. Frigate sunucun­uzun hızına bağlı olarak döküm işlemi tamamlanması biraz zaman alabilir.", + "updatedAttributes": "Özellikler başarıyla güncellendi." }, "error": { "updatedSublabelFailed": "Alt etiket güncellenemedi: {{errorMessage}}", "regenerate": "{{provider}} sağlayıcısından yeni açıklama talep edilemedi: {{errorMessage}}", "updatedLPRFailed": "Plaka güncellenemedi: {{errorMessage}}", - "audioTranscription": "Ses çözümlemesi talep edilemedi: {{errorMessage}}" + "audioTranscription": "Ses çözümlemesi talep edilemedi: {{errorMessage}}", + "updatedAttributesFailed": "Öznitelikler güncellenemedi: {{errorMessage}}" } } }, @@ -73,7 +75,12 @@ }, "score": { "label": "Skor" - } + }, + "editAttributes": { + "title": "Özellikleri düzenle", + "desc": "Bu {{label}} için sınıflandırma özelliklerini seçin" + }, + "attributes": "Sınıflandırma Özellikleri" }, "generativeAI": "Üretken Yapay Zeka", "exploreIsUnavailable": { @@ -203,7 +210,7 @@ "aria": "Temiz anlık görüntüyü indir" }, "viewTrackingDetails": { - "label": "İzleme ayrıntılarını görüntüle", + "label": "Takip ayrıntılarını görüntüle", "aria": "Takip ayrıntılarını göster" }, "showObjectDetails": { @@ -249,7 +256,7 @@ "trackedPoint": "Takip edilen nokta", "lifecycleItemDesc": { "visible": "{{label}} tespit edildi", - "entered_zone": "{{label}} {{zones}} bölgesine girdi", + "entered_zone": "{{label}}, {{zones}} bölgesine girdi", "active": "{{label}} etkin hale geldi", "stationary": "{{label}} sabit hale geldi", "attribute": { @@ -274,11 +281,11 @@ }, "offset": { "label": "Etiket Kaydırma Değeri", - "desc": "Bu veriler kameranızın algılama akışından gelir ancak kayıt akışındaki görüntülerin üzerine bindirilir. İki akışın tamamen senkronize olması pek olası değildir. Bu nedenle sınır kutusu ile görüntü birebir hizalı olmayabilir. Bu ayarı kullanarak anotasyonları zamansal olarak ileri veya geri kaydırabilir ve kaydedilmiş görüntülerle daha iyi hizalayabilirsiniz.", - "millisecondsToOffset": "Algılama anotasyonlarının kaydırılacağı milisaniye değeri. Varsayılan: 0", + "desc": "Bu veriler kameranızın algılama akışından gelir ancak kayıt akışındaki görüntülerin üzerine bindirilir. İki akış tamamen eşzamanlı olmayabilir, bu durum da sınır kutusu ile görüntünün hizasını kaydırabilir. Bu ayarı kullanarak zaman senkronunu ileri veya geri kaydırarak kayıt akışını ve etiketlemeleri hizalayabilirsiniz.", + "millisecondsToOffset": "Algılama etiketlemelerinin kaydırılacağı milisaniye değeri. Varsayılan: 0", "tips": "Videonun oynatımı kutulardan ve yol noktalarından öndeyse değeri düşürün; geride kalıyorsa değeri artırın. Bu değer negatif olabilir.", "toast": { - "success": "{{camera}} için anotasyon zaman kaydırması yapılandırma dosyasına kaydedildi." + "success": "{{camera}} için etiketleme zaman kaydırması yapılandırma dosyasına kaydedildi." } } }, diff --git a/web/public/locales/tr/views/faceLibrary.json b/web/public/locales/tr/views/faceLibrary.json index 7f2a1ddb3d..1417eff6dc 100644 --- a/web/public/locales/tr/views/faceLibrary.json +++ b/web/public/locales/tr/views/faceLibrary.json @@ -3,7 +3,7 @@ "description": { "placeholder": "Bu koleksiyona bir isim verin", "addFace": "İlk görselinizi yükleyerek Yüz Kütüphanesi’ne yeni bir koleksiyon ekleyin.", - "invalidName": "Geçersiz ad. Ad yalnızca harfler, rakamlar, boşluklar, kesme işaretleri (’), alt çizgiler (_) ve tireler (-) içerebilir." + "invalidName": "Geçersiz isim. İsimler; yalnızca harf, rakam, boşluk, kesme işareti (’), alt çizgi(_) ve tire (-) içerebilir." }, "details": { "person": "İnsan", @@ -13,7 +13,7 @@ "face": "Yüz Detayları", "scoreInfo": "Alt etiket skoru, tanınan tüm yüzlerin güvenilirlik değerlerinin ağırlıklı ortalamasından elde edilir, dolayısıyla fotoğraf üzerinde gösterilen skordan farklı olabilir.", "subLabelScore": "Alt Etiket Puanı", - "unknown": "Bilinmeyen" + "unknown": "Bilinmiyor" }, "documentTitle": "Yüz Kütüphanesi - Frigate", "uploadFaceImage": { diff --git a/web/public/locales/tr/views/live.json b/web/public/locales/tr/views/live.json index ad7a7e5b55..929b271819 100644 --- a/web/public/locales/tr/views/live.json +++ b/web/public/locales/tr/views/live.json @@ -54,7 +54,7 @@ }, "title": "Yayın", "debug": { - "picker": "Debug modunda akış seçimi kullanılamaz. Debug görünümü her zaman “detect” rolüne atanmış akışı kullanır." + "picker": "Hata ayıklama modunda akış seçimi kullanılamaz. Hata ayıklama görünümü her zaman tespit(detect) rolüne atanmış akışı kullanır." } }, "cameraSettings": { @@ -172,13 +172,13 @@ "disable": "Canlı Ses Çözümlemeyi Kapat" }, "snapshot": { - "takeSnapshot": "Anlık Ekran Görüntüsünü İndir", + "takeSnapshot": "Anlık görüntüyü indir", "noVideoSource": "Anlık görüntü için kullanılabilir bir video kaynağı bulunamadı.", "captureFailed": "Anlık görüntü yakalanamadı.", "downloadStarted": "Anlık görüntü indirme işlemi başlatıldı." }, "noCameras": { - "title": "Hiç Kamera Yapılandırılmamış", + "title": "Yapılandırılmış Kamera Yok", "description": "Frigate’e bir kamera bağlayarak başlayın.", "buttonText": "Kamera Ekle", "restricted": { diff --git a/web/public/locales/tr/views/search.json b/web/public/locales/tr/views/search.json index 17ba98899b..2de2edf477 100644 --- a/web/public/locales/tr/views/search.json +++ b/web/public/locales/tr/views/search.json @@ -23,7 +23,8 @@ "has_clip": "Klibi var", "min_speed": "Min. Hız", "sub_labels": "Alt Etiketler", - "max_speed": "Maks. Hız" + "max_speed": "Maks. Hız", + "attributes": "Özellikler" }, "searchType": { "description": "Açıklama", diff --git a/web/public/locales/tr/views/settings.json b/web/public/locales/tr/views/settings.json index 52ddc6060f..33ffdd792f 100644 --- a/web/public/locales/tr/views/settings.json +++ b/web/public/locales/tr/views/settings.json @@ -26,7 +26,7 @@ "cameras": "Kamera Ayarları", "enrichments": "Zenginleştirmeler", "triggers": "Tetikler", - "cameraManagement": "Yönet", + "cameraManagement": "Yönetim", "cameraReview": "İncele", "roles": "Roller" }, @@ -39,15 +39,15 @@ }, "playAlertVideos": { "label": "Alarm Videolarını Oynat", - "desc": "Varsayılan olarak canlı görüntü panelinde gösterilen son alarmlar ufak videolar olarak oynatılır. Bu tarayıcı/cihazda video yerine sabit resim göstermek için bu seçeneği kapatın." + "desc": "Varsayılan olarak canlı görüntü panelinde gösterilen son uyarılar ufak videolar olarak oynatılır. Bu tarayıcı/cihazda video yerine sabit resim göstermek için bu seçeneği kapatın." }, "title": "Canlı Görüntü Paneli", "displayCameraNames": { - "label": "Kamera Adlarını Her Zaman Göster", + "label": "Kamera Adlarını Daima Göster", "desc": "Çok kameralı canlı izleme panelinde, kamera adlarını her zaman bir etiket içinde göster." }, "liveFallbackTimeout": { - "label": "Canlı Oynatıcı Yedekleme Zaman Aşımı", + "label": "Canlı Oynatıcı Yedeğe Geçiş Zaman Aşımı", "desc": "Bir kameranın yüksek kaliteli canlı akışı kullanılamadığında, belirtilen saniye kadar sonra düşük bant genişliği moduna geç. Varsayılan: 3." } }, @@ -251,7 +251,7 @@ "mustNotBeSameWithCamera": "Alan adı kamera adıyla aynı olmamalıdır.", "alreadyExists": "Bu kamera için bu ada sahip bir alan zaten mevcut.", "mustNotContainPeriod": "Alan adı nokta içermemelidir.", - "mustHaveAtLeastOneLetter": "Bölge adı en az bir harf içermelidir." + "mustHaveAtLeastOneLetter": "Bölge ismi en az bir harf içermelidir." } }, "distance": { @@ -307,7 +307,7 @@ "name": { "inputPlaceHolder": "Bir isim girin…", "title": "İsim", - "tips": "Ad en az 2 karakter olmalı, en az bir harf içermeli ve bu kameradaki bir kamera adıyla veya başka bir bölge adıyla aynı olmamalıdır." + "tips": "İsim 2 karakter veya daha uzun olmalı, en az bir harf içermeli ve bu kameradaki bir kamera ismi veya başka bir bölge ismiyle çakışmamalıdır." }, "inertia": { "title": "Eylemsizlik", @@ -515,7 +515,7 @@ "changeRole": "Kullanıcı rolünü değiştir", "deleteUser": "Kullanıcıyı sil", "role": "Rol", - "password": "Parola" + "password": "Parola Sıfırla" }, "dialog": { "form": { @@ -540,10 +540,10 @@ }, "notMatch": "Parolalar eşleşmiyor", "match": "Parolalar eşleşiyor", - "show": "Şifreyi göster", - "hide": "Şifreyi gizle", + "show": "Parolay⁸ göster", + "hide": "Parolayı gizle", "requirements": { - "title": "Şifre gereksinimleri:", + "title": "Parola gereksinimleri:", "length": "En az 8 karakter", "uppercase": "En az bir büyük harf", "digit": "En az bir rakam", @@ -560,8 +560,8 @@ "usernameIsRequired": "Kullanıcı adı gereklidir", "passwordIsRequired": "Parola gereklidir", "currentPassword": { - "title": "Mevcut Şifre", - "placeholder": "Mevcut şifrenizi girin" + "title": "Mevcut Parola", + "placeholder": "Mevcut parolanızı girin" } }, "createUser": { @@ -581,10 +581,11 @@ "desc": "Bu hesabı güvenli hale getirmek güçlü bir parola belirleyin.", "cannotBeEmpty": "Parola boş olamaz", "doNotMatch": "Parolalar eşleşmiyor", - "currentPasswordRequired": "Mevcut şifre gerekli", - "incorrectCurrentPassword": "Mevcut şifre yanlış", - "passwordVerificationFailed": "Şifre doğrulanamadı", - "multiDeviceWarning": "Oturum açtığınız diğer tüm cihazlarda {{refresh_time}} içinde yeniden oturum açmanız gerekecektir. Ayrıca, JWT gizli anahtarınızı döndürerek tüm kullanıcıların hemen yeniden kimlik doğrulaması yapmasını da sağlayabilirsiniz." + "currentPasswordRequired": "Mevcut parola gereklidir", + "incorrectCurrentPassword": "Mevcut parola yanlış", + "passwordVerificationFailed": "Parola doğrulanamadı", + "multiDeviceWarning": "Oturum açtığınız diğer tüm cihazların {{refresh_time}} süresi içinde yeniden oturum açması gerekecektir.", + "multiDeviceAdmin": "JWT gizli anahtarınızı yenileyerek tüm kullanıcıları derhal yeniden doğrulama yapmaya zorlayabilirsiniz." }, "changeRole": { "title": "Kullanıcı Rolünü Değiştir", @@ -600,7 +601,7 @@ "select": "Bir rol seçin" } }, - "updatePassword": "Parola Belirle" + "updatePassword": "Parola Sıfırla" }, "notification": { "title": "Bildirimler", @@ -769,13 +770,13 @@ "form": { "name": { "error": { - "invalidCharacters": "Alan yalnızca harf, rakam, alt çizgi ve tire içerebilir.", - "minLength": "Alan en az 2 karakter uzunluğunda olmalıdır.", + "invalidCharacters": "Girdi yalnızca harf, rakam, alt çizgi ve tire içerebilir.", + "minLength": "Girdi en az 2 karakter uzunluğunda olmalıdır.", "alreadyExists": "Bu kamerada aynı isimle bir tetik zaten mevcut." }, "title": "İsim", - "placeholder": "Bu tetikleyiciye ad verin", - "description": "Bu tetikleyiciyi tanımlamak için benzersiz bir ad veya açıklama girin" + "placeholder": "Bu tetikleyiciye isim verin", + "description": "Bu tetikleyiciyi tanımlamak için benzersiz bir isim veya açıklama girin" }, "enabled": { "description": "Bu tetiği açın veya kapatın" @@ -806,7 +807,7 @@ }, "actions": { "title": "Eylemler", - "desc": "Varsayılan olarak, Frigate tüm tetikleyiciler için bir MQTT mesajı gönderir. Alt etiketler, tetikleyici adını nesne etiketine ekler. Nitelikler, izlenen nesne meta verilerinde ayrı olarak depolanan aranabilir meta verilerdir.", + "desc": "Varsayılan olarak, Frigate tüm tetikleyici isimlerini bir MQTT mesajı olarak gönderir. Alt etiketler, tetikleyici ismini nesne etiketine ekler. Nitelikler, izlenen nesne meta verilerinde ayrı olarak depolanan aranabilir meta verilerdir.", "error": { "min": "En az bir eylem seçilmelidir." } @@ -880,7 +881,7 @@ "description": "Bu tetikleyici için eşik değerini ve eylemleri yapılandırın." }, "steps": { - "nameAndType": "Ad ve Tür", + "nameAndType": "İsim ve Tür", "configureData": "Verileri Yapılandır", "thresholdAndActions": "Eşik ve Eylemler" } @@ -890,7 +891,7 @@ "title": "Kamera Ekle", "description": "Aşağıdaki adımları izleyerek Frigate kurulumunuza yeni bir kamera ekleyin.", "steps": { - "nameAndConnection": "Ad & Bağlantı", + "nameAndConnection": "İsim & Bağlantı", "probeOrSnapshot": "Probe veya Anlık Görüntü", "streamConfiguration": "Akış Yapılandırması", "validationAndTesting": "Doğrulama ve Test" @@ -911,13 +912,13 @@ }, "step1": { "description": "Kamera bilgilerinizi girin ve kamerayı taramayı (probe) ya da markayı manuel olarak seçmeyi tercih edin.", - "cameraName": "Kamera Adı", - "cameraNamePlaceholder": "ör. front_door veya Arka Bahçe Genel Görünümü", - "host": "Ana Makine / IP Adresi", + "cameraName": "Kamera İsmi", + "cameraNamePlaceholder": "örn. onkapi, veya Arka Bahçe Genel Görünümü", + "host": "Ana makine adı veya IP Adresi", "port": "Port", "username": "Kullanıcı adı", "usernamePlaceholder": "İsteğe bağlı", - "password": "Şifre", + "password": "Parola", "passwordPlaceholder": "İsteğe bağlı", "selectTransport": "İletişim protokolünü seçin", "cameraBrand": "Kamera Markası", @@ -925,7 +926,7 @@ "customUrl": "Özel Akış URL’si", "brandInformation": "Marka Bilgileri", "brandUrlFormat": "RTSP URL formatı şu şekilde olan kameralar için: {{exampleUrl}}", - "customUrlPlaceholder": "rtsp://kullanıcıadı:şifre@host:port/path", + "customUrlPlaceholder": "rtsp://kullanıcıadı:parola@host:port/path", "connectionSettings": "Bağlantı Ayarları", "detectionMethod": "Akış Algılama Yöntemi", "onvifPort": "ONVIF Portu", @@ -935,19 +936,19 @@ "onvifPortDescription": "ONVIF'i destekleyen kameralarda bu genellikle 80 veya 8080'dir.", "useDigestAuth": "Digest kimlik doğrulamasını kullan", "errors": { - "nameRequired": "Kamera adı gerekli", - "nameLength": "Kamera adı 64 karakter veya daha az olmalıdır", - "invalidCharacters": "Kamera adı geçersiz karakterler içeriyor", - "nameExists": "Kamera adı zaten mevcut", + "nameRequired": "Kamera ismi gereklidir", + "nameLength": "Kamera ismi en fazla 64 karakter olmalıdır", + "invalidCharacters": "Kamera ismi geçersiz karakterler içeriyor", + "nameExists": "Kamera ismi zaten mevcut", "customUrlRtspRequired": "Özel URL'ler \"rtsp://\" ile başlamalıdır. RTSP olmayan kamera akışları için manuel yapılandırma gereklidir.", "brandOrCustomUrlRequired": "Bir kamera markası seçip host/IP adresi girin ya da özel bir URL kullanmak için ‘Diğer’ seçeneğini tercih edin" }, - "useDigestAuthDescription": "ONVIF için HTTP digest kimlik doğrulamasını kullanın. Bazı kameralar, standart yönetici kullanıcısı yerine özel bir ONVIF kullanıcı adı/şifresi gerektirebilir." + "useDigestAuthDescription": "ONVIF için HTTP digest kimlik doğrulamasını kullanın. Bazı kameralar, standart yönetici kullanıcısı yerine özel bir ONVIF kullanıcı adı/parola kullanılmasını gerektirebilir." }, "step2": { "description": "Mevcut akışları bulmak için kamerayı tarayın veya seçtiğiniz algılama yöntemine göre manuel ayarları yapılandırın.", "testSuccess": "Bağlantı testi başarılı!", - "testFailed": "Bağlantı testi başarısız oldu. Lütfen tüm alanları kontrol edip tekrar deneyin.", + "testFailed": "Bağlantı testi başarısız oldu. Lütfen bilgileri kontrol edip tekrar deneyin.", "testFailedTitle": "Test Başarısız", "streamDetails": "Akış Ayrıntıları", "probing": "Kamera taranıyor...", @@ -986,15 +987,15 @@ }, "step3": { "description": "Akış rollerini yapılandırın ve kameranız için ek akışlar ekleyin.", - "streamsTitle": "Kamera Yayınları", - "addStream": "Yayın Ekle", - "addAnotherStream": "Başka Bir Yayın Ekle", - "streamTitle": "Yayın {{number}}", - "streamUrl": "Yayın URL'si", - "streamUrlPlaceholder": "rtsp://kullanıcıadı:şifre@host:port/path", - "selectStream": "Bir yayın seçin", + "streamsTitle": "Kamera Akışları", + "addStream": "Akış Ekle", + "addAnotherStream": "Başka Bir Akış Ekle", + "streamTitle": "Akış {{number}}", + "streamUrl": "Akış URL'si", + "streamUrlPlaceholder": "rtsp://kullanıcıadı:parola@host:port/path", + "selectStream": "Bir akış seçin", "searchCandidates": "Yayınları arayın...", - "noStreamFound": "Yayın bulunamadı", + "noStreamFound": "Akış bulunamadı", "url": "URL", "resolution": "Çözünürlük", "selectResolution": "Çözünürlüğü seçin", @@ -1014,10 +1015,10 @@ "notConnected": "Bağlı Değil", "featuresTitle": "Özellikler", "go2rtc": "Kameraya olan bağlantıları azaltın", - "detectRoleWarning": "Devam edebilmek için en az bir akışın \"algılama\" rolüne sahip olması gerekir.", + "detectRoleWarning": "Devam edebilmek için en az bir akışın algılama (detect) rolüne sahip olması gerekir.", "rolesPopover": { - "title": "Yayın Rolleri", - "detect": "Nesne tespiti için ana besleme.", + "title": "Akış Rolleri", + "detect": "Nesne algılama için ana besleme.", "record": "Yapılandırma ayarlarına göre video akışının bölümlerini kaydeder.", "audio": "Ses tabanlı algılama için besleme." }, @@ -1035,25 +1036,25 @@ "none": "Hiçbiri", "error": "Hata", "description": "Yeni kameranızı kaydetmeden önce son doğrulama ve analiz. Kaydetmeden önce her akışı bağlayın.", - "validationTitle": "Yayın Doğrulaması", - "connectAllStreams": "Tüm Yayınları Bağla", + "validationTitle": "Akış Doğrulaması", + "connectAllStreams": "Tüm Akışlara Bağlan", "reconnectionSuccess": "Yeniden bağlantı başarılı.", - "reconnectionPartial": "Bazı yayınlara yeniden bağlanılamadı.", - "streamUnavailable": "Yayın önizlemesi kullanılamıyor", + "reconnectionPartial": "Bazı Akışlara yeniden bağlanılamadı.", + "streamUnavailable": "Akış önizlemesi kullanılamıyor", "reload": "Yeniden yükle", "connecting": "Bağlanıyor...", - "streamTitle": "Yayın {{number}}", + "streamTitle": "Akış {{number}}", "valid": "Geçerli", "failed": "Başarısız", "notTested": "Test edilmedi", "connectStream": "Bağlan", "connectingStream": "Bağlanıyor", - "streamValidated": "{{number}} yayını başarıyla doğrulandı", - "streamValidationFailed": "Yayın {{number}} doğrulaması başarısız oldu", + "streamValidated": "{{number}} nolu akış başarıyla doğrulandı", + "streamValidationFailed": "{{number}} nolu akış doğrulanamadı", "saveAndApply": "Yeni Kamerayı Kaydet", "saveError": "Geçersiz yapılandırma. Lütfen ayarlarınızı kontrol edin.", "issues": { - "title": "Yayın Doğrulaması", + "title": "Akış Doğrulaması", "videoCodecGood": "Video kodeği {{codec}}.", "audioCodecGood": "Ses kodeği {{codec}}.", "resolutionHigh": "{{resolution}} çözünürlüğü kaynak kullanımının artmasına neden olabilir.", @@ -1063,7 +1064,8 @@ "audioCodecRequired": "Ses algılamayı desteklemek için bir ses akışı gereklidir.", "restreamingWarning": "Kayıt akışı için kameraya olan bağlantıları azaltmak CPU kullanımını bir miktar artırabilir.", "brands": { - "reolink-rtsp": "Reolink RTSP önerilmez. Kameranın donanım yazılımı ayarlarında HTTP'yi etkinleştirin ve sihirbazı yeniden başlatın." + "reolink-rtsp": "Reolink RTSP önerilmez. Kameranın ayarlarında HTTP'yi etkinleştirin ve sihirbazı baştan başlatın.", + "reolink-http": "Reolink HTTP akışları daha iyi uyumluluk için FFmpeg kullanmalıdır. Bu akış için 'Akış uyumluluk modunu kullan' seçeneğini etkinleştirin." }, "dahua": { "substreamWarning": "Alt akış 1 düşük çözünürlüğe kilitlenmiştir. Birçok Dahua / Amcrest / EmpireTech kamera, kamera ayarlarında etkinleştirilmesi gereken ek alt akışları destekler. Mevcutsa, bu akışları kontrol edip kullanmanız önerilir." @@ -1088,27 +1090,27 @@ "add": "Kamera Ekle", "edit": "Kamerayı Düzenle", "description": "Yayınlar ve roller dahil olmak üzere kamera ayarlarını yapılandırın.", - "name": "Kamera Adı", - "nameRequired": "Kamera adı gerekli", - "nameLength": "Kamera adı 64 karakterden az olmalıdır.", + "name": "Kamera İsmi", + "nameRequired": "Kamera ismi gereklidir", + "nameLength": "Kamera ismi 64 karakterden az olmalıdır.", "namePlaceholder": "örneğin, ön_kapı veya Arka Bahçe Genel Bakışı", - "enabled": "Etkinleştirilmiş", + "enabled": "Etkin", "ffmpeg": { - "inputs": "Giriş Yayınları", - "path": "Yayın Yolu", - "pathRequired": "Yayın yolu gereklidir", + "inputs": "Giriş Akışları", + "path": "Akış Yolu", + "pathRequired": "Akış yolu gereklidir", "pathPlaceholder": "rtsp://...", "roles": "Roller", "rolesRequired": "En az bir rol gereklidir", "rolesUnique": "Her rol (ses, algılama, kayıt) yalnızca bir akışa atanabilir", - "addInput": "Giriş Yayını Ekle", - "removeInput": "Giriş Yayınını Kaldır", - "inputsRequired": "En az bir giriş yayını gereklidir" + "addInput": "Akış Ekle", + "removeInput": "Akış Kaldır", + "inputsRequired": "En az bir akış gereklidir" }, - "go2rtcStreams": "go2rtc Yayınları", - "streamUrls": "Yayın URL'leri", + "go2rtcStreams": "go2rtc Akışları", + "streamUrls": "Akış URL'leri", "addUrl": "URL ekle", - "addGo2rtcStream": "go2rtc Yayını Ekle", + "addGo2rtcStream": "go2rtc Akışı Ekle", "toast": { "success": "Kamera {{cameraName}} başarıyla kaydedildi" } @@ -1118,11 +1120,11 @@ "title": "Kamera İnceleme Ayarları", "object_descriptions": { "title": "Üretken Yapay Zeka Nesne Açıklamaları", - "desc": "Bu kamera için Yapay Zeka Nesne Tanımlamalarını geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, bu kameradaki izlenen nesneler için Yapay Zeka tarafından oluşturulan tanımlar istenmeyecektir." + "desc": "Bu kamera için yapay zekadan nesne tanımlama taleplerini geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, bu kameradaki izlenen nesneler için yapay zekadan tanımlar istenmeyecektir." }, "review_descriptions": { "title": "Üretken Yapay Zeka İnceleme Açıklamaları", - "desc": "Bu kamera için Yapay Zeka Üretici İnceleme açıklamalarını geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, bu kameradaki inceleme öğeleri için Yapay Zeka tarafından oluşturulan açıklamalar istenmeyecektir." + "desc": "Bu kamera için yapay zekadan incele öğelerini açıklama taleplerini geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, bu kameradaki inceleme öğeleri için yapay zekadan açıklama istenmeyecektir." }, "review": { "title": "İncele", @@ -1134,13 +1136,13 @@ "title": "Sınıflandırmayı İncele", "desc": "Frigate, inceleme öğelerini Uyarılar ve Algılamalar olarak kategorilere ayırır. Varsayılan olarak, tüm kişi ve araba nesneleri Uyarı olarak kabul edilir. İnceleme öğelerinizin kategorilendirmesini, bunlar için gerekli bölgeleri yapılandırarak iyileştirebilirsiniz.", "noDefinedZones": "Bu kamera için herhangi bir bölge tanımlanmamıştır.", - "objectAlertsTips": "{{cameraName}} üzerindeki tüm {{alertsLabels}} nesneleri Uyarılar olarak gösterilecektir.", - "zoneObjectAlertsTips": "{{cameraName}} üzerinde, {{zone}} bölgesinde tespit edilen tüm {{alertsLabels}} nesneleri Uyarılar olarak gösterilecektir.", - "objectDetectionsTips": "{{cameraName}} üzerinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, hangi bölgede olursa olsun Tespitler olarak gösterilecektir.", + "objectAlertsTips": "{{cameraName}} üzerindeki tüm {{alertsLabels}} nesneleri Uyarı olarak gösterilecektir.", + "zoneObjectAlertsTips": "{{cameraName}} üzerinde, {{zone}} bölgesinde tespit edilen tüm {{alertsLabels}} nesneleri Uyarı olarak gösterilecektir.", + "objectDetectionsTips": "{{cameraName}} üzerinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, bölgeden bağımsız olarak Tespit olarak gösterilecektir.", "zoneObjectDetectionsTips": { - "text": "{{cameraName}} üzerindeki {{zone}} bölgesinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, Tespitler olarak gösterilecektir.", - "notSelectDetections": "{{cameraName}} üzerinde {{zone}} bölgesinde tespit edilen ve Uyarı olarak kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, hangi bölgede olurlarsa olsunlar Tespitler olarak gösterilecektir.", - "regardlessOfZoneObjectDetectionsTips": "{{cameraName}} üzerinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, bulundukları bölgeden bağımsız olarak Tespitler (Detections) olarak gösterilecektir." + "text": "{{cameraName}} üzerindeki {{zone}} bölgesinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, Tespit olarak gösterilecektir.", + "notSelectDetections": "{{cameraName}} üzerinde {{zone}} bölgesinde tespit edilen ve Uyarı olarak kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, bölgeden bağımsız olarak Tespitler olarak gösterilecektir.", + "regardlessOfZoneObjectDetectionsTips": "{{cameraName}} üzerinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, bulundukları bölgeden bağımsız olarak Tespit olarak gösterilecektir." }, "unsavedChanges": "{{camera}} için Kaydedilmemiş İnceleme Sınıflandırması ayarları", "selectAlertsZones": "Uyarılar için bölgeleri seçin", @@ -1191,17 +1193,17 @@ }, "deleteRole": { "title": "Rolü Sil", - "desc": "Bu işlem geri alınamaz. Bu işlem, rolü kalıcı olarak silecek ve bu role sahip tüm kullanıcıları 'izleyici' rolüne atayacaktır. Bu rol, izleyiciye tüm kameralara erişim sağlayacaktır.", + "desc": "Bu işlem geri alınamaz. Bu işlem, rolü kalıcı olarak silecek ve bu role sahip tüm kullanıcıları varsayılan 'izleyici' rolüne atayarak kullanıcıların BÜTÜN kameralara erişim sağlamasına neden olacaktır.", "warn": "{{role}} rolünü silmek istediğinizden emin misiniz?", "deleting": "Siliniyor..." }, "form": { "role": { - "title": "Rol Adı", - "placeholder": "Rol adını girin", + "title": "Rol İsmi", + "placeholder": "Rol ismini girin", "desc": "Sadece harf, rakam, nokta ve alt çizgi kullanılabilir.", - "roleIsRequired": "Rol adı gereklidir", - "roleOnlyInclude": "Rol adı yalnızca harf, sayı veya _ içerebilir", + "roleIsRequired": "Rol ismi gereklidir", + "roleOnlyInclude": "Rol ismi yalnızca harf, sayı veya alt çizgi (_) içerebilir", "roleExists": "Bu isimde bir rol zaten mevcut." }, "cameras": { diff --git a/web/public/locales/tr/views/system.json b/web/public/locales/tr/views/system.json index 2cc2ef041a..b2aceb6d7d 100644 --- a/web/public/locales/tr/views/system.json +++ b/web/public/locales/tr/views/system.json @@ -47,7 +47,7 @@ "intelGpuWarning": { "title": "Intel GPU İstatistik Uyarısı", "message": "GPU istatistikleri kullanılamıyor", - "description": "Bu, Intel’in GPU istatistik raporlama araçlarında (intel_gpu_top) bilinen bir hatadır; araç çalışmayı bozarak, donanımsal hızlandırma ve nesne tespiti (i)GPU üzerinde doğru şekilde çalışıyor olsa bile, GPU kullanımını tekrar tekrar %0 olarak döndürür. Bu bir Frigate hatası değildir. Sorunu geçici olarak düzeltmek ve GPU’nun doğru çalıştığını doğrulamak için host sistemini yeniden başlatabilirsiniz. Bu durum performansı etkilemez." + "description": "Bu durum, donanımsal hızlandırma ve nesne tespiti (i)GPU üzerinde sorunsuz çalışıyor olsa bile, Intel’in GPU istatistik raporlama aracındaki (intel_gpu_top) bilinen bir hatadan ötürü GPU kullanımının %0 olarak bildirilmesinden kaynaklanmakta olup, Frigate hatası değildir. Sorunu geçici olarak düzeltmek ve (i)GPU’nun doğru çalıştığını doğrulamak için ana makineyi yeniden başlatabilirsiniz. Bu durum performansı etkilememektedir." } }, "otherProcesses": { @@ -165,7 +165,10 @@ "review_description_events_per_second": "İnceleme Açıklaması", "object_description": "Nesne Açıklaması", "object_description_speed": "Nesne Açıklama Hızı", - "object_description_events_per_second": "Nesne Açıklaması" + "object_description_events_per_second": "Nesne Açıklaması", + "classification": "{{name}} Sınıflandırması", + "classification_speed": "{{name}} Sınıflandırma Hızı", + "classification_events_per_second": "{{name}} Saniyede Sınıflandırma Olayları" }, "infPerSecond": "Saniye Başına Çıkarım", "title": "Zenginleştirmeler", From 8fb413ce7c10e202b6780fe98016020e4967675d Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:16:57 +0100 Subject: [PATCH 40/78] Translated using Weblate (Latvian) Currently translated at 35.1% (26 of 74 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Latvian) Currently translated at 12.9% (17 of 131 strings) Translated using Weblate (Latvian) Currently translated at 9.4% (7 of 74 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (55 of 55 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (25 of 25 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (13 of 13 strings) Translated using Weblate (Latvian) Currently translated at 14.7% (18 of 122 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (6 of 6 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (214 of 214 strings) Translated using Weblate (Latvian) Currently translated at 2.7% (18 of 654 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (41 of 41 strings) Translated using Weblate (Latvian) Currently translated at 7.6% (7 of 92 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (46 of 46 strings) Translated using Weblate (Latvian) Currently translated at 6.5% (33 of 501 strings) Translated using Weblate (Latvian) Currently translated at 14.0% (19 of 135 strings) Translated using Weblate (Latvian) Currently translated at 14.4% (17 of 118 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Latvian) Currently translated at 5.7% (7 of 122 strings) Translated using Weblate (Latvian) Currently translated at 5.1% (7 of 135 strings) Translated using Weblate (Latvian) Currently translated at 28.0% (7 of 25 strings) Translated using Weblate (Latvian) Currently translated at 10.9% (6 of 55 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (46 of 46 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (6 of 6 strings) Translated using Weblate (Latvian) Currently translated at 6.5% (6 of 92 strings) Translated using Weblate (Latvian) Currently translated at 0.9% (6 of 654 strings) Translated using Weblate (Latvian) Currently translated at 8.1% (6 of 74 strings) Translated using Weblate (Latvian) Currently translated at 2.1% (11 of 501 strings) Translated using Weblate (Latvian) Currently translated at 12.2% (6 of 49 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (13 of 13 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Latvian) Currently translated at 17.0% (7 of 41 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Latvian) Currently translated at 11.3% (6 of 53 strings) Translated using Weblate (Latvian) Currently translated at 4.5% (6 of 131 strings) Translated using Weblate (Latvian) Currently translated at 5.9% (7 of 118 strings) Translated using Weblate (Latvian) Currently translated at 100.0% (214 of 214 strings) Translated using Weblate (Latvian) Currently translated at 98.1% (210 of 214 strings) Translated using Weblate (Latvian) Currently translated at 96.7% (207 of 214 strings) Translated using Weblate (Latvian) Currently translated at 93.4% (200 of 214 strings) Translated using Weblate (Latvian) Currently translated at 91.1% (195 of 214 strings) Translated using Weblate (Latvian) Currently translated at 90.6% (194 of 214 strings) Translated using Weblate (Latvian) Currently translated at 89.7% (192 of 214 strings) Translated using Weblate (Latvian) Currently translated at 87.3% (187 of 214 strings) Translated using Weblate (Latvian) Currently translated at 85.5% (183 of 214 strings) Translated using Weblate (Latvian) Currently translated at 84.1% (180 of 214 strings) Translated using Weblate (Latvian) Currently translated at 73.8% (158 of 214 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Gatis Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/lv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/lv/ Translation: Frigate NVR/audio Translation: Frigate NVR/common Translation: Frigate NVR/components-auth Translation: Frigate NVR/components-camera Translation: Frigate NVR/components-dialog Translation: Frigate NVR/components-filter Translation: Frigate NVR/components-icons Translation: Frigate NVR/components-input Translation: Frigate NVR/components-player Translation: Frigate NVR/objects Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-configeditor Translation: Frigate NVR/views-events Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-exports Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-live Translation: Frigate NVR/views-recording Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/lv/audio.json | 36 ++- web/public/locales/lv/common.json | 305 +++++++++++++++++- web/public/locales/lv/components/auth.json | 17 +- web/public/locales/lv/components/camera.json | 87 ++++- web/public/locales/lv/components/dialog.json | 124 ++++++- web/public/locales/lv/components/filter.json | 53 ++- web/public/locales/lv/components/icons.json | 9 +- web/public/locales/lv/components/input.json | 11 +- web/public/locales/lv/components/player.json | 52 ++- web/public/locales/lv/objects.json | 20 +- .../locales/lv/views/classificationModel.json | 37 ++- web/public/locales/lv/views/configEditor.json | 19 +- web/public/locales/lv/views/events.json | 62 +++- web/public/locales/lv/views/explore.json | 50 ++- web/public/locales/lv/views/exports.json | 24 +- web/public/locales/lv/views/faceLibrary.json | 94 +++++- web/public/locales/lv/views/live.json | 14 +- web/public/locales/lv/views/recording.json | 13 +- web/public/locales/lv/views/search.json | 74 ++++- web/public/locales/lv/views/settings.json | 29 +- web/public/locales/lv/views/system.json | 30 +- 21 files changed, 1139 insertions(+), 21 deletions(-) diff --git a/web/public/locales/lv/audio.json b/web/public/locales/lv/audio.json index 0967ef424b..aab12a1b25 100644 --- a/web/public/locales/lv/audio.json +++ b/web/public/locales/lv/audio.json @@ -1 +1,35 @@ -{} +{ + "speech": "Runāšana", + "bicycle": "Velosipēds", + "babbling": "Pļāpāšana", + "car": "Automašīna", + "yell": "Kliedziens", + "motorcycle": "Motocikls", + "bellow": "Rēciens", + "whoop": "Izsauciens", + "bus": "Autobuss", + "whispering": "Čuksti", + "train": "Vilciens", + "insect": "Kukainis", + "mosquito": "Ods", + "fly": "Muša", + "frog": "Varde", + "snake": "Čūska", + "music": "Mūzika", + "musical_instrument": "Mūzikas instruments", + "plucked_string_instrument": "Stīgu instruments", + "guitar": "Ģitāra", + "electric_guitar": "Elektriskā ģitāra", + "bass_guitar": "Basģitāra", + "acoustic_guitar": "Akustiskā ģitāra", + "banjo": "Bandžo", + "piano": "Klavieres", + "electric_piano": "Sintezators", + "organ": "Ērģeles", + "electronic_organ": "Elektriskās ērģeles", + "harpsichord": "Klavesīns", + "laughter": "Smiekli", + "boat": "Laiva", + "snicker": "Ķiķināšana", + "camera": "Kamera" +} diff --git a/web/public/locales/lv/common.json b/web/public/locales/lv/common.json index 0967ef424b..3e8d06126e 100644 --- a/web/public/locales/lv/common.json +++ b/web/public/locales/lv/common.json @@ -1 +1,304 @@ -{} +{ + "time": { + "untilForTime": "Līdz {{time}}", + "today": "Šodien", + "yesterday": "Vakar", + "last7": "Pēdējās 7 dienas", + "last14": "Pēdējās 14 dienas", + "last30": "Pēdējās 30 dienas", + "thisWeek": "Šonedēļ", + "lastWeek": "Pagājušajā nedēļā", + "thisMonth": "Šomēnes", + "lastMonth": "Pagājušajā mēnesī", + "5minutes": "5 minūtes", + "10minutes": "10 minūtes", + "30minutes": "30 minūtes", + "1hour": "1 stunda", + "12hours": "12 stundas", + "24hours": "24 stundas", + "pm": "pm", + "am": "am", + "yr": "{{time}}g", + "year_zero": "{{time}} gadi", + "year_one": "{{time}} gads", + "year_other": "{{time}} gadi", + "mo": "{{time}}mēn", + "month_zero": "{{time}}mēneši", + "month_one": "{{time}}mēnesis", + "month_other": "{{time}}mēneši", + "d": "{{time}}d", + "day_zero": "{{time}} dienas", + "day_one": "{{time}} diena", + "day_other": "{{time}} dienas", + "h": "{{time}}s", + "hour_zero": "{{time}}stundas", + "hour_one": "{{time}}stunda", + "hour_other": "{{time}}stundas", + "m": "{{time}}m", + "minute_zero": "{{time}} minutes", + "minute_one": "{{time}} minute", + "minute_other": "{{time}} minutes", + "s": "{{time}}sek", + "second_zero": "{{time}} sekundes", + "second_one": "{{time}} sekunde", + "second_other": "{{time}} sekundes", + "formattedTimestamp": { + "12hour": "MMM d, h:mm:ss aaa", + "24hour": "MMM d, HH:mm:ss" + }, + "formattedTimestamp2": { + "12hour": "MM/dd h:mm:ssa", + "24hour": "d MMM HH:mm:ss" + }, + "formattedTimestampHourMinute": { + "12hour": "h:mm aaa", + "24hour": "HH:mm" + }, + "formattedTimestampHourMinuteSecond": { + "12hour": "h:mm:ss aaa", + "24hour": "HH:mm:ss" + }, + "formattedTimestampMonthDayHourMinute": { + "12hour": "MMM d, h:mm aaa", + "24hour": "MMM d, HH:mm" + }, + "formattedTimestampMonthDayYear": { + "12hour": "MMM d, yyyy", + "24hour": "MMM d, yyyy" + }, + "formattedTimestampMonthDayYearHourMinute": { + "12hour": "MMM d yyyy, h:mm aaa", + "24hour": "MMM d yyyy, HH:mm" + }, + "formattedTimestampMonthDay": "MMM d", + "formattedTimestampFilename": { + "12hour": "MM-dd-yy-h-mm-ss-a", + "24hour": "MM-dd-yy-HH-mm-ss" + }, + "inProgress": "Izpilda", + "invalidStartTime": "Nederīgs sākuma laiks", + "invalidEndTime": "Nederīgs beigu laiks", + "untilForRestart": "Līdz Frigate pārstartējas.", + "untilRestart": "Līdz pārstartēšanai", + "ago": "{{timeAgo}} pirms", + "justNow": "Nupat" + }, + "unit": { + "speed": { + "mph": "mp/h", + "kph": "km/h" + }, + "length": { + "feet": "Pēda", + "meters": "metri" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/stundā", + "mbph": "MB/stundā", + "gbph": "GB/stundā" + } + }, + "label": { + "back": "Atgriezties", + "hide": "Paslēpt {{item}}", + "show": "Rādīt {{item}}", + "ID": "ID", + "none": "Nav", + "all": "Viss" + }, + "list": { + "two": "{{0}} un {{1}}", + "many": "{{items}}, un {{last}}", + "separatorWithSpace": ", " + }, + "field": { + "optional": "Pēc izvēles", + "internalID": "Iekšējais frigates identifikators, ko izmanto konfigurācijā un datubāzē" + }, + "button": { + "apply": "Apstiprināt", + "reset": "Atiestatīt", + "done": "Gatavs", + "enabled": "Ieslēgts", + "enable": "Ieslēgt", + "disabled": "Izslēgts", + "disable": "Izslēgt", + "save": "Saglabāt", + "saving": "Saglabā…", + "cancel": "Atcelt", + "close": "Aizvērt", + "copy": "Kopēt", + "back": "Atpakaļ", + "history": "Vēsture", + "fullscreen": "Pilnekrāna režīms", + "exitFullscreen": "Iziet no pilnekrāna režīma", + "pictureInPicture": "Attēls attēlā", + "twoWayTalk": "Divvirzienu saruna", + "cameraAudio": "Kameras Audio", + "on": "Iesl", + "off": "Izsl", + "edit": "Labot", + "copyCoordinates": "Kopēt koordinātas", + "delete": "Dzēst", + "yes": "Jā", + "no": "Nē", + "download": "Lejupielādēt", + "info": "Informācija", + "suspended": "Apturēts", + "unsuspended": "Atjaunot", + "play": "Atskaņot", + "unselect": "Noņemt izvēlēto", + "export": "Eksportēt", + "deleteNow": "Izdzēst tagad", + "next": "Nākamais", + "continue": "Turpināt" + }, + "menu": { + "system": "Sistēma", + "systemMetrics": "Sistēmas rādītāji", + "configuration": "Konfigurācija", + "systemLogs": "Sistēmas žurnāli", + "settings": "Iestatījumi", + "configurationEditor": "Konfigurācijas redaktors", + "languages": "Valodas", + "language": { + "en": "English (angļu)", + "es": "Español (spāņu)", + "zhCN": "简体中文 (Vienkāršotā ķīniešu)", + "hi": "हिन्दी (hindi)", + "fr": "Français (Franču)", + "ar": "العربية (Arābu)", + "pt": "Português (Portugāļu)", + "ptBR": "Português brasileiro (Brazīlijas portugāļu)", + "ru": "Русский (Krievu)", + "de": "Deutsch (Vācu)", + "ja": "日本語 (Japāņu)", + "tr": "Türkçe (Turku)", + "it": "Italiano (Itāļu)", + "nl": "Nederlands (Nīderlandiešu)", + "sv": "Svenska (Zviedru)", + "cs": "Čeština (Čehu)", + "nb": "Norsk Bokmål (Norvēģu Bokmål)", + "ko": "한국어 (Korejiešu)", + "vi": "Tiếng Việt (Vjetnamiešu)", + "fa": "فارسی (Persiešu)", + "pl": "Polski (Poļu)", + "uk": "Українська (Ukraiņu)", + "he": "עברית (Ebreju)", + "el": "Ελληνικά (Grieķu)", + "ro": "Română (Rumāņu)", + "hu": "Magyar (Ungāru)", + "fi": "Suomi (Somu)", + "da": "Dansk (Dāņu)", + "sk": "Slovenčina (Slovāku)", + "yue": "粵語 (Kantonas)", + "th": "ไทย (Taju)", + "ca": "Català (Katalāņu)", + "sr": "Српски (Serbu)", + "sl": "Slovenščina (Slovēņu)", + "lt": "Lietuvių (Lietuviešu)", + "bg": "Български (Bulgāru)", + "gl": "Galego (Galisiešu)", + "id": "Bahasa Indonesia (Indonēziešu)", + "ur": "اردو (urdu)", + "withSystem": { + "label": "Izmantojiet sistēmas iestatījumus valodai" + } + }, + "appearance": "Izskats", + "darkMode": { + "label": "Tumšais režīms", + "light": "Gaišs", + "dark": "Tumšs", + "withSystem": { + "label": "Izmantojiet sistēmas iestatījumus gaišajam vai tumšajam režīmam" + } + }, + "withSystem": "Sistēma", + "theme": { + "label": "Tēma", + "blue": "Zila", + "green": "Zaļa", + "nord": "Ziemeļu", + "red": "Sarkana", + "highcontrast": "Augsta kontrasta", + "default": "Noklusējuma" + }, + "help": "Palīdzība", + "documentation": { + "title": "Dokumentācija", + "label": "Frigates dokumentācija" + }, + "restart": "Restartēt Frigate", + "live": { + "title": "Tiešraide", + "allCameras": "Visas kameras", + "cameras": { + "title": "Kameras", + "count_zero": "{{count}}kameras", + "count_one": "{{count}}kamera", + "count_other": "{{count}}kameras" + } + }, + "review": "Pārskats", + "explore": "Meklēt notikumus", + "export": "Eksportēt", + "uiPlayground": "Interfeisa testēšanas vide", + "faceLibrary": "Seju bibliotēka", + "classification": "Atpazīšana", + "user": { + "title": "Lietotājs", + "account": "Konts", + "current": "Pašreizējais lietotājs: {{user}}", + "anonymous": "anonīms", + "logout": "Iziet", + "setPassword": "Izveidot paroli" + } + }, + "toast": { + "copyUrlToClipboard": "Adrese nokopēta.", + "save": { + "title": "Saglabāt", + "error": { + "title": "Neizdevās saglabāt konfigurācijas izmaiņas: {{errorMessage}}", + "noMessage": "Neizdevās saglabāt konfigurācijas izmaiņas" + } + } + }, + "role": { + "title": "Loma", + "admin": "Administrators", + "viewer": "Skatītājs", + "desc": "Administratoriem ir pilna piekļuve visām Frigate funkcijām. Skatītāji var skatīt tikai kameras, skatāmos elementus un arhivētos ierakstus." + }, + "pagination": { + "label": "lappuse", + "previous": { + "title": "Iepriekšējais", + "label": "Pāriet uz iepriekšējo lapu" + }, + "next": { + "title": "Nākamā", + "label": "Dodieties uz nākamo lapu" + }, + "more": "Vairāk lapas" + }, + "accessDenied": { + "documentTitle": "Piekļuve liegta - Frigate", + "title": "Piekļuve liegta", + "desc": "Jums nav atļaujas skatīt šo lapu." + }, + "notFound": { + "documentTitle": "Nav atrasts - Frigate", + "title": "404", + "desc": "Lapa nav atrasta" + }, + "selectItem": "Izvēlēties {{item}}", + "readTheDocumentation": "Laslīt dokumentāciju", + "information": { + "pixels": "{{area}}px" + } +} diff --git a/web/public/locales/lv/components/auth.json b/web/public/locales/lv/components/auth.json index 0967ef424b..b8b99fa54e 100644 --- a/web/public/locales/lv/components/auth.json +++ b/web/public/locales/lv/components/auth.json @@ -1 +1,16 @@ -{} +{ + "form": { + "user": "Lietotājvārds", + "password": "Parole", + "login": "Pieteikties", + "firstTimeLogin": "Vai mēģināt pieteikties pirmo reizi? Jūsu akreditācijas dati ir norādīti Frigate žurnālos.", + "errors": { + "usernameRequired": "Nepieciešams lietotājvārds", + "passwordRequired": "Nepieciešams ievadīt paroli", + "rateLimit": "Pārsniegts mēģinājumu skaits. Lūdzu, mēģiniet vēlreiz vēlāk.", + "loginFailed": "Pieteikšanās neizdevās", + "unknownError": "Nezināma kļūda. Pārbaudiet žurnālu.", + "webUnknownError": "Nezināma kļūda. Pārbaudiet konsoles žurnālus." + } + } +} diff --git a/web/public/locales/lv/components/camera.json b/web/public/locales/lv/components/camera.json index 0967ef424b..d061f554d6 100644 --- a/web/public/locales/lv/components/camera.json +++ b/web/public/locales/lv/components/camera.json @@ -1 +1,86 @@ -{} +{ + "group": { + "label": "Kameru grupas", + "add": "Pievienot kameru grupu", + "edit": "Rediģēt kameru grupu", + "delete": { + "label": "Dzēst kameru grupu", + "confirm": { + "title": "Apstiprināt dzēšanu", + "desc": "Vai esi pārliecināts, ka vēlies izdzēst kameru grupu {{name}}?" + } + }, + "name": { + "label": "Nosaukums", + "placeholder": "Ievadiet vārdu…", + "errorMessage": { + "mustLeastCharacters": "Kameru grupas nosaukumam jāsastāv no vismaz 2 rakstzīmēm.", + "exists": "Kameru grupas nosaukums jau pastāv.", + "nameMustNotPeriod": "Kameru grupas nosaukumā nedrīkst būt punkts.", + "invalid": "Nederīgs kameru grupas nosaukums." + } + }, + "cameras": { + "label": "Kameras", + "desc": "Atlasiet kameras šai grupai." + }, + "icon": "Ikona", + "success": "Kameru grupa ({{name}}) ir saglabāta.", + "camera": { + "birdseye": "Birds-eye", + "setting": { + "label": "Kameras straumēšanas iestatījumi", + "title": "Straumēšanas iestatījumi{{cameraName}}", + "desc": "Mainiet šīs kameru grupas paneļa tiešraides straumes iestatījumus. Šie iestatījumi ir atkarīgi no ierīces/pārlūkprogrammas.", + "audioIsAvailable": "Šai straumei ir pieejams audio", + "audioIsUnavailable": "Šai straumei nav pieejams audio.", + "audio": { + "tips": { + "title": "Šai straumei audio ir jāizvada no kameras un tas ir jākonfigurē go2rtc." + } + }, + "stream": "Straume", + "placeholder": "Izvēlieties straumi", + "streamMethod": { + "label": "Straumēšanas metode", + "placeholder": "Izvēlieties straumēšanas metodi", + "method": { + "noStreaming": { + "label": "Nav straumēšanas", + "desc": "Kameru attēli tiks atjaunināti tikai reizi minūtē, bez tiešraides straumēšanas." + }, + "smartStreaming": { + "label": "Viedā straumēšana (ieteicams)", + "desc": "Viedā straumēšana atjauninās kameras attēlu reizi minūtē, ja netiks konstatēta aktivitāte, lai taupītu joslas platumu un resursus. Kad tiks konstatēta aktivitāte, attēls nemanāmi pārslēdzas uz tiešraides straumēšanu." + }, + "continuousStreaming": { + "label": "Nepārtraukta straumēšana", + "desc": { + "title": "Kameras attēls vienmēr būs tiešraidē, kad tas būs redzams informācijas panelī, pat ja netiks konstatēta nekāda aktivitāte.", + "warning": "Nepārtraukta straumēšana var izraisīt lielu joslas platuma izmantošanu un veiktspējas problēmas. Izmantojiet to piesardzīgi." + } + } + } + }, + "compatibilityMode": { + "label": "Saderības režīms", + "desc": "Iespējojiet šo opciju tikai tad, ja kameras tiešraides straumē tiek rādīti krāsu artefakti un attēla labajā pusē ir diagonāla līnija." + } + } + } + }, + "debug": { + "options": { + "label": "Iestatījumi", + "title": "Iespējas", + "showOptions": "Rādīt Iespējas", + "hideOptions": "Paslēpt Iespējas" + }, + "boundingBox": "Ierobežojošs rāmis", + "timestamp": "Laika zīmogs", + "zones": "Zonas", + "mask": "Maska", + "motion": "Kustība", + "regions": "Reģioni" + } +} diff --git a/web/public/locales/lv/components/dialog.json b/web/public/locales/lv/components/dialog.json index 0967ef424b..3f52bc65dd 100644 --- a/web/public/locales/lv/components/dialog.json +++ b/web/public/locales/lv/components/dialog.json @@ -1 +1,123 @@ -{} +{ + "restart": { + "title": "Vai esi pārliecināts, ka vēlies pārstartēt Frigati?", + "button": "Pārstartēt", + "restarting": { + "title": "Frigate tiek pārstartēta", + "content": "Šī lapa tiks atkārtoti ielādēta pēc {{countdown}} sekundēm.", + "button": "Atjaunot tagad" + } + }, + "explore": { + "plus": { + "submitToPlus": { + "label": "Sūtīt uz Frigate+", + "desc": "Objekti vietās, no kurām vēlaties izvairīties, nav kļūdaini pozitīvi. To iesniegšana kā kļūdaini pozitīvi radīs modelim neskaidrības." + }, + "review": { + "question": { + "label": "Apstipriniet šo tagu pakalpojumam Frigate Plus", + "ask_a": "Vai šis objekts{{label}}?", + "ask_an": "Vai šis objekts ir{{label}}?", + "ask_full": "Vai šis objekts{{untranslatedLabel}} ({{translatedLabel}})?" + }, + "state": { + "submitted": "Iesniegts" + } + } + }, + "video": { + "viewInHistory": "Skatīt vēsturē" + } + }, + "export": { + "time": { + "fromTimeline": "Izvēlieties no laika skalas", + "lastHour_zero": "Pēdējās stundas", + "lastHour_one": "Pēdējās{{count}}stundas", + "lastHour_other": "Pēdējās {{count}} stundas", + "custom": "Pielāgots", + "start": { + "title": "Sākuma laiks", + "label": "Izvēlieties Sākuma laiks" + }, + "end": { + "title": "Beigu laiks", + "label": "Atlasiet Beigu laiks" + } + }, + "name": { + "placeholder": "Ievadiet eksporta nosaukumu" + }, + "select": "Izvēlieties", + "export": "Eksportēt", + "selectOrExport": "Atlasīt vai Eksportēt", + "toast": { + "success": "Eksportēšana veiksmīgi sākta. Skatiet failu eksportēšanas lapā.", + "view": "Skatīt", + "error": { + "failed": "Neizdevās sākt eksportēšanu: {{error}}", + "endTimeMustAfterStartTime": "Beigu laikam ir jābūt pēc sākuma laika", + "noVaildTimeSelected": "Nav izvēlēts derīgs laika diapazons" + } + }, + "fromTimeline": { + "saveExport": "Saglabāt Eksportu", + "previewExport": "Priekšskatīt Eksportu" + } + }, + "streaming": { + "label": "Straume", + "restreaming": { + "disabled": "Šai kamerai nav iespējota atkārtota straumēšana.", + "desc": { + "title": "Konfigurējiet go2rtc, lai šai kamerai varētu piekļūt papildu tiešraides skatīšanās un audio opcijām." + } + }, + "showStats": { + "label": "Rādīt straumes statistiku", + "desc": "Iespējojiet šo opciju, lai straumes statistika tiktu rādīta kā pārklājums kameras attēlam." + }, + "debugView": "Atkļūdošanas režīms" + }, + "search": { + "saveSearch": { + "label": "Saglabāt meklēšanu", + "desc": "Norādiet šīs saglabātās meklēšanas nosaukumu.", + "placeholder": "Ievadiet meklēšanas nosaukumu", + "overwrite": "{{searchName}} jau pastāv. Saglabājot, esošā vērtība tiks pārrakstīta.", + "success": "Meklēšanas ({{searchName}}) ir saglabāts.", + "button": { + "save": { + "label": "Saglabāt šo meklēšanu" + } + } + } + }, + "recording": { + "confirmDelete": { + "title": "Apstipriniet dzēšanu", + "desc": { + "selected": "Vai tiešām vēlaties dzēst visus ierakstītos video, kas saistīti ar šo pārskata vienumu?

Turiet nospiestu taustiņu Shift, lai turpmāk apietu šo dialoglodziņu." + }, + "toast": { + "success": "Ar atlasītajiem pārskata vienumiem saistītais videoieraksts ir veiksmīgi izdzēsts.", + "error": "Neizdevās dzēst: {{error}}" + } + }, + "button": { + "export": "Eksportēt", + "markAsReviewed": "Atzīmēt kā skatītu", + "markAsUnreviewed": "Atzīmēt kā neskatītu", + "deleteNow": "Dzēst tūlīt" + } + }, + "imagePicker": { + "selectImage": "Izsekojamā objekta sīktēla atlasīšana", + "unknownLabel": "Saglabāts sprūda attēls", + "search": { + "placeholder": "Meklēt pēc etiķetes vai apakšetiķetes..." + }, + "noImages": "Šai kamerai nav atrasti sīktēli" + } +} diff --git a/web/public/locales/lv/components/filter.json b/web/public/locales/lv/components/filter.json index 0967ef424b..292946831a 100644 --- a/web/public/locales/lv/components/filter.json +++ b/web/public/locales/lv/components/filter.json @@ -1 +1,52 @@ -{} +{ + "filter": "Filtrs", + "classes": { + "label": "Klases", + "all": { + "title": "Visas klases" + }, + "count_one": "{{count}} klase", + "count_other": "{{count}} klases" + }, + "labels": { + "label": "Atzīmes", + "all": { + "title": "Visas atzīmes", + "short": "Atzīmes" + }, + "count_one": "{{count}} atzīme", + "count_other": "{{count}} Atzīmes" + }, + "zones": { + "label": "Zonas", + "all": { + "title": "Visas zonas", + "short": "Zonas" + } + }, + "dates": { + "selectPreset": "Periods…", + "all": { + "title": "Visi datumi", + "short": "Datumi" + } + }, + "more": "Vairāk filtru", + "reset": { + "label": "Atiestatīt filtrus uz noklusējuma vērtībām" + }, + "timeRange": "Laika diapazons", + "subLabels": { + "label": "Papildus atzīmes", + "all": "Visas papildus atzīmes" + }, + "attributes": { + "label": "Klasifikācijas atribūti", + "all": "Visi atribūti" + }, + "score": "Vērtējums", + "estimatedSpeed": "Paredzamais ātrums ({{unit}})", + "features": { + "label": "Funkcijas" + } +} diff --git a/web/public/locales/lv/components/icons.json b/web/public/locales/lv/components/icons.json index 0967ef424b..180b0b0311 100644 --- a/web/public/locales/lv/components/icons.json +++ b/web/public/locales/lv/components/icons.json @@ -1 +1,8 @@ -{} +{ + "iconPicker": { + "selectIcon": "Izvēlēties ikonu", + "search": { + "placeholder": "Meklēt ikonu…" + } + } +} diff --git a/web/public/locales/lv/components/input.json b/web/public/locales/lv/components/input.json index 0967ef424b..f4af8fe4cc 100644 --- a/web/public/locales/lv/components/input.json +++ b/web/public/locales/lv/components/input.json @@ -1 +1,10 @@ -{} +{ + "button": { + "downloadVideo": { + "label": "Lejuplādēt video", + "toast": { + "success": "Video augšupielāde ir sākusies." + } + } + } +} diff --git a/web/public/locales/lv/components/player.json b/web/public/locales/lv/components/player.json index 0967ef424b..43f8359f92 100644 --- a/web/public/locales/lv/components/player.json +++ b/web/public/locales/lv/components/player.json @@ -1 +1,51 @@ -{} +{ + "noRecordingsFoundForThisTime": "Šim laika brīdim nav atrasti ieraksti", + "noPreviewFound": "Priekšskatījums nav atrasts", + "noPreviewFoundFor": "{{cameraName}} nav atrasts priekšskatījums", + "submitFrigatePlus": { + "title": "Nosūtīt šo kadru uz Frigate+?", + "submit": "Iesniegt" + }, + "livePlayerRequiredIOSVersion": "Šāda veida straumēšanai nepieciešama iOS 17.1 vai jaunāka versija.", + "streamOffline": { + "title": "Bezsaistes straume", + "desc": "No kameras {{cameraName}} detect straumes netika saņemti kadri, pārbaudiet kļūdu žurnālus." + }, + "cameraDisabled": "Kamera ir izslēgta", + "stats": { + "streamType": { + "title": "Straumes veids:", + "short": "Tips" + }, + "bandwidth": { + "title": "Joslas platums:", + "short": "Joslas platums" + }, + "latency": { + "title": "Latentums:", + "value": "{{seconds}} sekundes", + "short": { + "title": "Latentums", + "value": "{{seconds}} sek" + } + }, + "totalFrames": "Kopējais kadru skaits:", + "droppedFrames": { + "title": "Izlaisti kadri:", + "short": { + "title": "Izlaisti", + "value": "{{droppedFrames}} kadri" + } + }, + "decodedFrames": "Dekodētie kadri:", + "droppedFrameRate": "Kadru nomaiņas ātruma kritums:" + }, + "toast": { + "success": { + "submittedFrigatePlus": "Kadrs veiksmīgi iesniegts pakalpojumam Frigate+" + }, + "error": { + "submitFrigatePlusFailed": "Neizdevās iesniegt kadru Frigate+" + } + } +} diff --git a/web/public/locales/lv/objects.json b/web/public/locales/lv/objects.json index 0967ef424b..981d5cb44e 100644 --- a/web/public/locales/lv/objects.json +++ b/web/public/locales/lv/objects.json @@ -1 +1,19 @@ -{} +{ + "person": "Persona", + "bicycle": "Velosipēds", + "car": "Automašīna", + "motorcycle": "Motocikls", + "airplane": "Lidmašīna", + "bus": "Autobuss", + "train": "Vilciens", + "package": "Paciņa", + "bbq_grill": "Grils", + "amazon": "Amazon", + "usps": "USPS", + "ups": "UPS", + "fedex": "FedEx", + "dhl": "DHL", + "postnl": "PostNL", + "dpd": "DPD", + "boat": "Laiva" +} diff --git a/web/public/locales/lv/views/classificationModel.json b/web/public/locales/lv/views/classificationModel.json index 0967ef424b..5d26d5b74f 100644 --- a/web/public/locales/lv/views/classificationModel.json +++ b/web/public/locales/lv/views/classificationModel.json @@ -1 +1,36 @@ -{} +{ + "documentTitle": "Klassifikācijas modeļi", + "details": { + "scoreInfo": "Rezultāts atbilst vidējai klasifikācijas ticamībai no visām objekta detektēšanas reizēm.", + "none": "Nav", + "unknown": "Nezināms" + }, + "description": { + "invalidName": "Nederīgs nosaukums. Nosaukumi drīkst saturēt tikai burtus, ciparus, atstarpes, apostrofus, pasvītras un defises." + }, + "button": { + "deleteClassificationAttempts": "Dzēst klasifikācijas attēlus", + "renameCategory": "Pārdēvēt klasi", + "deleteCategory": "Dzēst klasi", + "deleteImages": "Dzēst attēlus" + }, + "wizard": { + "step3": { + "training": { + "title": "Trenē modeli", + "description": "Tavs modelis tiek trenēts. Aizver šo paziņojumu, un tavs modelis tiks izmantots, tiklīdz trenēšana ir pabeigta." + }, + "retryGenerate": "Atkārtot ģenerēšanu", + "classifying": "Klasificē un trenē...", + "trainingStarted": "Trenēšana veiksmīgi uzsākta", + "errors": { + "generateFailed": "Neizdevās ģenerēt piemērus: {{error}}", + "generationFailed": "Ģenerēšana neizdevās. Mēģini vēlreiz.", + "classifyFailed": "Neizdevās klasificēt attēlus: {{error}}" + } + } + }, + "train": { + "titleShort": "Pēdējās" + } +} diff --git a/web/public/locales/lv/views/configEditor.json b/web/public/locales/lv/views/configEditor.json index 0967ef424b..286da8e9ab 100644 --- a/web/public/locales/lv/views/configEditor.json +++ b/web/public/locales/lv/views/configEditor.json @@ -1 +1,18 @@ -{} +{ + "documentTitle": "Konfigurācijas rediģēšana - Frigate", + "configEditor": "Konfigurācijas redaktors", + "safeConfigEditor": "Konfigurācijas redaktors (drošais režīms)", + "safeModeDescription": "Frigate ir drošajā režīmā konfigurācijas pārbaudes kļūdas dēļ.", + "copyConfig": "Kopēt konfigurāciju", + "saveAndRestart": "Saglabāt un pārstartēt", + "saveOnly": "Tikai saglabāt", + "confirm": "Vai iziet, nesaglabājot?", + "toast": { + "success": { + "copyToClipboard": "Konfigurācija ir kopēta starpliktuvē." + }, + "error": { + "savingError": "Saglabājot konfigurāciju, radās kļūda" + } + } +} diff --git a/web/public/locales/lv/views/events.json b/web/public/locales/lv/views/events.json index 0967ef424b..77d4d34e57 100644 --- a/web/public/locales/lv/views/events.json +++ b/web/public/locales/lv/views/events.json @@ -1 +1,61 @@ -{} +{ + "alerts": "Paziņojumi", + "detections": "Atklājumi", + "motion": { + "label": "Kustība", + "only": "Tikai kustība" + }, + "allCameras": "Visas kameras", + "empty": { + "alert": "Nav paziņojumu, kurus pārskatīt", + "detection": "Nav apskatāmu konstatējumu", + "motion": "Nav atrasti kustības dati" + }, + "timeline": "Laika skala", + "timeline.aria": "Izvēlieties laika skalu", + "zoomIn": "Pietuvināt", + "zoomOut": "Tālināt", + "events": { + "label": "Notikumi", + "aria": "Izvēlieties notikumus", + "noFoundForTimePeriod": "Šajā laika periodā nav atrasts neviens notikums." + }, + "detail": { + "label": "Detaļas", + "noDataFound": "Nav detalizētu datu pārskatīšanai", + "aria": "Pārslēgt detalizēto skatu", + "trackedObject_one": "{{count}} objekts", + "trackedObject_other": "{{count}} objekti", + "noObjectDetailData": "Nav pieejami objekta detalizēti dati.", + "settings": "Detaļas skata iestatījumi", + "alwaysExpandActive": { + "title": "Vienmēr izvērst aktīvs", + "desc": "Vienmēr izvērsiet aktīvā pārskata vienuma objekta informāciju, ja tāda ir pieejama." + } + }, + "objectTrack": { + "trackedPoint": "Izsekots punkts", + "clickToSeek": "Noklikšķiniet, lai pārietu uz šo laiku" + }, + "documentTitle": "Pārskats - Frigate", + "recordings": { + "documentTitle": "Ieraksti - Frigate" + }, + "calendarFilter": { + "last24Hours": "Pēdējās 24 stundas" + }, + "markAsReviewed": "Atzīmēt kā pārskatītu", + "markTheseItemsAsReviewed": "Atzīmēt šos vienumus kā pārskatītus", + "newReviewItems": { + "label": "Skatīt jaunus atsauksmju vienumus", + "button": "Jauni vienumi, kas jāpārskata" + }, + "selected_one": "atlasīts {{count}}", + "selected_other": "atlasīts {{count}}", + "select_all": "Viss", + "camera": "Kamera", + "detected": "atklāts", + "normalActivity": "Normāls", + "needsReview": "Nepieciešama pārskatīšana", + "securityConcern": "Drošības jautājums" +} diff --git a/web/public/locales/lv/views/explore.json b/web/public/locales/lv/views/explore.json index 0967ef424b..63a2d2cbc9 100644 --- a/web/public/locales/lv/views/explore.json +++ b/web/public/locales/lv/views/explore.json @@ -1 +1,49 @@ -{} +{ + "documentTitle": "Notikumu meklēšana - Frigate", + "generativeAI": "Ģeneratīvs AI", + "exploreMore": "Paskatīt vairāk objektu{{label}}", + "details": { + "timestamp": "Laika zīmogs" + }, + "exploreIsUnavailable": { + "title": "Notikumu meklēšana nav pieejama", + "embeddingsReindexing": { + "context": "Meklēšana būs pieejama pēc tam, kad būs pabeigta izsekoto objektu atkārtota indeksēšana.", + "startingUp": "Notiek palaišana…", + "estimatedTime": "Paredzamais atlikušais laiks:" + } + }, + "itemMenu": { + "findSimilar": { + "label": "Atrast līdzīgus", + "aria": "Atrast līdzīgus izsekotos priekšmetus" + }, + "submitToPlus": { + "label": "Iesniegt Frigate+", + "aria": "Iesniegt Frigate Plus" + }, + "viewInHistory": { + "label": "Atrast vēsturē" + }, + "deleteTrackedObject": { + "label": "Dzēst šo izsekoto priekšmetu" + } + }, + "dialog": { + "confirmDelete": { + "title": "Apstiprināt dzēšanu" + } + }, + "searchResult": { + "nextTrackedObject": "Nākamais izsekotais objekts", + "deleteTrackedObject": { + "toast": { + "success": "Izsekotais objekts veiksmīgi izdzēsts.", + "error": "Neizdevās izdzēst izsekoto objektu: {{errorMessage}}" + } + } + }, + "aiAnalysis": { + "title": "MI analīze" + } +} diff --git a/web/public/locales/lv/views/exports.json b/web/public/locales/lv/views/exports.json index 0967ef424b..73209ce9e8 100644 --- a/web/public/locales/lv/views/exports.json +++ b/web/public/locales/lv/views/exports.json @@ -1 +1,23 @@ -{} +{ + "documentTitle": "Eksportēt - Frigate", + "search": "Meklēt", + "noExports": "Eksporti nav atrasti", + "deleteExport": "Dzēst eksportu", + "deleteExport.desc": "Vai esi pārliecināts, ka vēlies izdzēst{{exportName}}?", + "editExport": { + "title": "Pārdēvēt eksportu", + "desc": "Ievadiet jaunu nosaukumu šim eksportam.", + "saveExport": "Saglabāt eksportu" + }, + "tooltip": { + "shareExport": "Kopīgot eksportu", + "downloadVideo": "Lejupielādēt video", + "editName": "Rediģēt nosaukumu", + "deleteExport": "Eksporta dzēšana" + }, + "toast": { + "error": { + "renameExportFailed": "Neizdevās pārdēvēt eksporta failu: {{errorMessage}}" + } + } +} diff --git a/web/public/locales/lv/views/faceLibrary.json b/web/public/locales/lv/views/faceLibrary.json index 0967ef424b..f6e254c22c 100644 --- a/web/public/locales/lv/views/faceLibrary.json +++ b/web/public/locales/lv/views/faceLibrary.json @@ -1 +1,93 @@ -{} +{ + "description": { + "addFace": "Pievienojiet savai seju bibliotēkai jaunu kolekciju, augšupielādējot savu pirmo attēlu.", + "placeholder": "Ievadi kolekcijas nosaukumu", + "invalidName": "Nederīgs nosaukums. Nosaukumi drīkst saturēt tikai burtus, ciparus, atstarpes, apostrofus, pasvītras un defises." + }, + "details": { + "timestamp": "Laika zīmogs", + "unknown": "Nezināms", + "scoreInfo": "Rezultāts ir visu seju rezultāta vidējais, svērts pēc sejas izmēra katrā attēlā." + }, + "documentTitle": "Seju bibliotēka - Frigate", + "uploadFaceImage": { + "title": "Augšupielādējiet sejas attēlu", + "desc": "Augšupielādējiet attēlu, lai skenētu sejas un iekļautu to lapā {{pageToggle}}" + }, + "collections": "Kolekcijas", + "createFaceLibrary": { + "new": "Izveidojiet jaunu seju", + "nextSteps": "Lai izveidotu stabilu pamatu:
  • izmantojiet cilni “Nesenās atpazīšanas”, lai atlasītu un apmācītu attēlus katrai atpazītajai personai.
  • Lai iegūtu labākos rezultātus, koncentrējieties uz tiešiem attēliem; izvairieties no attēlu apmācības, kuros sejas ir attēlotas leņķī.
  • " + }, + "steps": { + "faceName": "Ievadiet sejas nosaukumu", + "uploadFace": "Augšupielādējiet sejas attēlu", + "nextSteps": "Nākamie soļi", + "description": { + "uploadFace": "Augšupielādējiet lietotāja {{name}} attēlu, kurā redzama viņa seja no priekšpuses. Attēls nav jāapgriež, lai redzētu tikai viņa seju." + } + }, + "train": { + "title": "Pēdējās atpazīšanas", + "titleShort": "Pēdējās", + "aria": "Atlasiet pēdējās atpazīšanas", + "empty": "Nav pēdējo sejas atpazīšanas mēģinājumu" + }, + "deleteFaceLibrary": { + "title": "Dzēst Vārdu", + "desc": "Vai tiešām vēlaties dzēst kolekciju {{name}}? Tas neatgriezeniski dzēsīs visas saistītās sejas." + }, + "deleteFaceAttempts": { + "title": "Dzēst sejas", + "desc_zero": "Vai tiešām vēlaties dzēst {{count}} sejas? Šo darbību nevar atsaukt.", + "desc_one": "Vai tiešām vēlaties dzēst {{count}} seju? Šo darbību nevar atsaukt.", + "desc_other": "Vai tiešām vēlaties dzēst {{count}} sejas? Šo darbību nevar atsaukt." + }, + "renameFace": { + "title": "Pārdēvēt seju", + "desc": "Ievadiet jaunu vārdu priekš {{name}}" + }, + "button": { + "deleteFaceAttempts": "Dzēst sejas", + "addFace": "Pievienot seju", + "renameFace": "Pārdēvēt seju", + "deleteFace": "Dzēst seju", + "uploadImage": "Augšupielādēt attēlu", + "reprocessFace": "Atkārtoti apstrādāt seju" + }, + "imageEntry": { + "validation": { + "selectImage": "Lūdzu, atlasiet attēla failu." + }, + "dropActive": "Ievelciet attēlu šeit…", + "dropInstructions": "Velciet un nometiet vai ielīmējiet attēlu šeit vai noklikšķiniet, lai atlasītu", + "maxSize": "Max izmērs: {{size}} MB" + }, + "nofaces": "Nav pieejama neviena seja", + "trainFaceAs": "Atcerieties seju kā:", + "trainFace": "Atcerieties seju", + "toast": { + "success": { + "uploadedImage": "Attēls veiksmīgi augšupielādēts.", + "addFaceLibrary": "{{name}} ir veiksmīgi pievienots seju bibliotēkai!", + "deletedFace_zero": "Veiksmīgi izdzēstas {{count}} sejas.", + "deletedFace_one": "Veiksmīgi izdzēsta {{count}} seja.", + "deletedFace_other": "Veiksmīgi izdzēstas {{count}} sejas.", + "deletedName_zero": "{{count}} sejas ir veiksmīgi izdzēstas.", + "deletedName_one": "{{count}} seja ir veiksmīgi izdzēsta.", + "deletedName_other": "{{count}} sejas ir veiksmīgi izdzēstas.", + "renamedFace": "Seja veiksmīgi pārdēvēta par {{name}}", + "trainedFace": "Seja ir veiksmīgi iegaumēta.", + "updatedFaceScore": "Sejas vērtējums ir veiksmīgi atjaunināts uz {{name}} ({{score}})." + }, + "error": { + "uploadingImageFailed": "Neizdevās augšupielādēt attēlu: {{errorMessage}}", + "addFaceLibraryFailed": "Neizdevās iestatīt sejas vārdu: {{errorMessage}}", + "deleteFaceFailed": "Neizdevās dzēst: {{errorMessage}}", + "deleteNameFailed": "Neizdevās izdzēst vārdu: {{errorMessage}}", + "renameFaceFailed": "Neizdevās pārdēvēt seju: {{errorMessage}}", + "trainFailed": "Neizdevās atcerēties: {{errorMessage}}", + "updateFaceScoreFailed": "Neizdevās atjaunināt sejas vērtējumu: {{errorMessage}}" + } + } +} diff --git a/web/public/locales/lv/views/live.json b/web/public/locales/lv/views/live.json index 0967ef424b..b0f6887c51 100644 --- a/web/public/locales/lv/views/live.json +++ b/web/public/locales/lv/views/live.json @@ -1 +1,13 @@ -{} +{ + "documentTitle": "Tiešraide - Frigate", + "documentTitle.withCamera": "{{camera}} - Tiešraide - Frigate", + "lowBandwidthMode": "Eko režīms", + "twoWayTalk": { + "enable": "Iespējot divvirzienu saziņu", + "disable": "Izslēgt divvirzienu sarunu" + }, + "cameraAudio": { + "enable": "Iespējot kameras audio", + "disable": "Izslēgt kameras audio" + } +} diff --git a/web/public/locales/lv/views/recording.json b/web/public/locales/lv/views/recording.json index 0967ef424b..f2fb166148 100644 --- a/web/public/locales/lv/views/recording.json +++ b/web/public/locales/lv/views/recording.json @@ -1 +1,12 @@ -{} +{ + "export": "Eksportēt", + "filter": "Filtrs", + "calendar": "Kalendārs", + "filters": "Filtri", + "toast": { + "error": { + "noValidTimeSelected": "Atlasīts nederīgs laika diapazons", + "endTimeMustAfterStartTime": "Beigu laikam jābūt pēc sākuma laika" + } + } +} diff --git a/web/public/locales/lv/views/search.json b/web/public/locales/lv/views/search.json index 0967ef424b..b8bb465a94 100644 --- a/web/public/locales/lv/views/search.json +++ b/web/public/locales/lv/views/search.json @@ -1 +1,73 @@ -{} +{ + "search": "Meklēt", + "savedSearches": "Saglabātie meklējumi", + "searchFor": "Meklēt {{inputValue}}", + "button": { + "clear": "Notīrīt meklēšanu", + "save": "Saglabāt meklēto", + "delete": "Izdzēst saglabāto meklējumu", + "filterInformation": "Filtra informācija", + "filterActive": "Filtri aktīvi" + }, + "trackedObjectId": "Izsekojamā objekta ID", + "filter": { + "label": { + "cameras": "Kameras", + "labels": "Etiķetes", + "zones": "Zonas", + "sub_labels": "Papildu etiķetes", + "attributes": "Atribūti", + "search_type": "Meklēšanas veids", + "time_range": "Laika diapazons", + "before": "Pirms", + "after": "Pēc", + "min_score": "Minimālais rezultāts", + "max_score": "Maksimālais rezultāts", + "min_speed": "Minimālais ātrums", + "max_speed": "Maksimālais ātrums", + "recognized_license_plate": "Atpazīta numura zīme", + "has_clip": "Ir klips", + "has_snapshot": "Ir momentuzņēmums" + }, + "searchType": { + "thumbnail": "Sīktēls", + "description": "Apraksts" + }, + "toast": { + "error": { + "beforeDateBeLaterAfter": "'Pirms' datumam jābūt vēlākam par 'pēc' datumu.", + "afterDatebeEarlierBefore": "Datumam 'pēc' ir jābūt agrākam par datumu 'pirms'.", + "minScoreMustBeLessOrEqualMaxScore": "'Min_score' vērtībai ir jābūt mazākai vai vienādai ar 'max_score'.", + "maxScoreMustBeGreaterOrEqualMinScore": "Vērtībai 'max_score' ir jābūt lielākai vai vienādai ar 'min_score'.", + "minSpeedMustBeLessOrEqualMaxSpeed": "'Min_speed' ir jābūt mazākam vai vienādam ar 'max_speed'.", + "maxSpeedMustBeGreaterOrEqualMinSpeed": "Vērtībai 'max_speed' ir jābūt lielākai vai vienādai ar 'min_speed'." + } + }, + "tips": { + "title": "Kā lietot teksta filtrus", + "desc": { + "text": "Filtri palīdz sašaurināt meklēšanas rezultātus. Lūk, kā tos izmantot ievades laukā:", + "step1": "Ierakstiet filtra atslēgas nosaukumu, kam seko kols (piemēram, \"kameras:\").", + "step2": "Atlasiet vērtību no ieteikumiem vai ierakstiet savu.", + "step3": "Izmantojiet vairākus filtrus, pievienojot tos vienu pēc otra ar atstarpi starp tiem.", + "step4": "Datuma filtri (pirms: un pēc:) izmanto formātu {{DateFormat}}.", + "step5": "Laika diapazona filtrs izmanto formātu {{exampleTime}}.", + "step6": "Noņemiet filtrus, noklikšķinot uz 'x' blakus tiem.", + "exampleLabel": "Piemērs:" + } + }, + "header": { + "currentFilterType": "Filtrēt vērtības", + "noFilters": "Filtri", + "activeFilters": "Aktīvie filtri" + } + }, + "similaritySearch": { + "title": "Līdzības meklēšana", + "active": "Aktīva līdzības meklēšana", + "clear": "Notīrīt līdzības meklēšanu" + }, + "placeholder": { + "search": "Meklēt…" + } +} diff --git a/web/public/locales/lv/views/settings.json b/web/public/locales/lv/views/settings.json index 0967ef424b..57c27b436b 100644 --- a/web/public/locales/lv/views/settings.json +++ b/web/public/locales/lv/views/settings.json @@ -1 +1,28 @@ -{} +{ + "documentTitle": { + "default": "Iestatījumi - Frigate", + "authentication": "Autentifikācijas iestatījumi - Frigate", + "cameraManagement": "Pārvaldīt kameras - Frigate", + "cameraReview": "Kameras skata iestatījumi - Frigate", + "enrichments": "Bagātināšanas iestatījumi - Frigate", + "masksAndZones": "Masku un zonu rediģētājs - Frigate", + "motionTuner": "Kustības noteikšana - Frigate" + }, + "cameraWizard": { + "step1": { + "cameraName": "Kameras nosaukums", + "cameraNamePlaceholder": "piem. ieejas_durvis vai Pagalma kopskats", + "username": "Lietotājvārds", + "password": "Parole", + "cameraBrand": "Kameras ražotājs", + "brandInformation": "Ražotāja informācija", + "onvifPortDescription": "Kamerām, kas atbalsta ONVIF, ports parasti ir 80 vai 8080.", + "errors": { + "nameRequired": "Nepieciešams kameras noaukums", + "nameLength": "Kameras nosaukums nedrīkst būt garāks par 64 simboliem", + "invalidCharacters": "Kameras nosaukumā ir neatļauti simboli", + "nameExists": "Kameras nosaukums jau pastāv" + } + } + } +} diff --git a/web/public/locales/lv/views/system.json b/web/public/locales/lv/views/system.json index 0967ef424b..76db6be139 100644 --- a/web/public/locales/lv/views/system.json +++ b/web/public/locales/lv/views/system.json @@ -1 +1,29 @@ -{} +{ + "documentTitle": { + "cameras": "Kameru statistika - Frigate", + "storage": "Uzglabāšanas statistika - Frigate", + "general": "Vispārīgā statistika - Frigate", + "enrichments": "Bagātināšanas statistika - Frigate", + "logs": { + "frigate": "Frigate konfigurācija - Frigate", + "go2rtc": "Logi Go2RTC - Frigate", + "nginx": "Logi Nginx - Frigate" + } + }, + "stats": { + "cameraIsOffline": "{{camera}} ir izslēgta", + "detectIsSlow": "{{detect}} ir lēns ({{speed}} ms)", + "detectIsVerySlow": "{{detect}} ir ļoti lēns ({{speed}} ms)" + }, + "enrichments": { + "infPerSecond": "Inferences sekundē", + "averageInf": "Vidējais inferences ilgums", + "embeddings": { + "face_recognition": "Seju atpazīšana", + "plate_recognition": "Numurzīmju atpazīšana", + "plate_recognition_speed": "Numurzīmju atpazīšanas ātrums", + "object_description": "Objekta apraksts", + "object_description_events_per_second": "Objekta apraksts" + } + } +} From d7e10dffc6b97171f24d77803764b8e4a8ed0662 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:16:59 +0100 Subject: [PATCH 41/78] Translated using Weblate (German) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (German) Currently translated at 99.2% (130 of 131 strings) Translated using Weblate (German) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (German) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (German) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (German) Currently translated at 99.1% (121 of 122 strings) Translated using Weblate (German) Currently translated at 100.0% (654 of 654 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Sebastian Sie Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/de/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/de/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/de/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/de/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/de/ Translation: Frigate NVR/common Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/de/common.json | 8 +++---- web/public/locales/de/components/filter.json | 4 ++++ web/public/locales/de/components/player.json | 2 +- .../locales/de/views/classificationModel.json | 11 +++++---- web/public/locales/de/views/explore.json | 15 ++++++++---- web/public/locales/de/views/faceLibrary.json | 2 +- web/public/locales/de/views/search.json | 3 ++- web/public/locales/de/views/settings.json | 24 ++++++++++--------- web/public/locales/de/views/system.json | 5 +++- 9 files changed, 47 insertions(+), 27 deletions(-) diff --git a/web/public/locales/de/common.json b/web/public/locales/de/common.json index 16af90fdf4..532463f550 100644 --- a/web/public/locales/de/common.json +++ b/web/public/locales/de/common.json @@ -12,7 +12,7 @@ "24hours": "24 Stunden", "month_one": "{{time}} Monat", "month_other": "{{time}} Monate", - "d": "{{time}} Tag", + "d": "{{time}} Tg.", "day_one": "{{time}} Tag", "day_other": "{{time}} Tage", "m": "{{time}} Min", @@ -37,12 +37,12 @@ "30minutes": "30 Minuten", "1hour": "1 Stunde", "lastWeek": "Letzte Woche", - "h": "{{time}} Stunde", + "h": "{{time}} Std.", "ago": "vor {{timeAgo}}", "untilRestart": "Bis zum Neustart", "justNow": "Gerade", "pm": "nachmittags", - "mo": "{{time}}Monat", + "mo": "{{time}} Mon.", "formattedTimestamp": { "12hour": "d. MMM, hh:mm:ss aaa", "24hour": "dd. MMM, hh:mm:ss aaa" @@ -82,7 +82,7 @@ "12hour": "d. MMM yyyy", "24hour": "d. MMM yyyy" }, - "inProgress": "In Bearbeitung", + "inProgress": "Im Gange", "invalidStartTime": "Ungültige Startzeit", "invalidEndTime": "Ungültige Endzeit" }, diff --git a/web/public/locales/de/components/filter.json b/web/public/locales/de/components/filter.json index 193877603a..d593080cd9 100644 --- a/web/public/locales/de/components/filter.json +++ b/web/public/locales/de/components/filter.json @@ -132,5 +132,9 @@ }, "count_one": "{{count}} Klasse", "count_other": "{{count}} Klassen" + }, + "attributes": { + "label": "Klassifizierungsattribute", + "all": "Alle Attribute" } } diff --git a/web/public/locales/de/components/player.json b/web/public/locales/de/components/player.json index a6b251f017..56a1950539 100644 --- a/web/public/locales/de/components/player.json +++ b/web/public/locales/de/components/player.json @@ -24,7 +24,7 @@ "title": "Latenz:", "value": "{{seconds}} Sekunden", "short": { - "title": "Lazenz", + "title": "Latenz", "value": "{{seconds}} s" } }, diff --git a/web/public/locales/de/views/classificationModel.json b/web/public/locales/de/views/classificationModel.json index 2b58dfbea5..217d9df53a 100644 --- a/web/public/locales/de/views/classificationModel.json +++ b/web/public/locales/de/views/classificationModel.json @@ -1,7 +1,9 @@ { "documentTitle": "Klassifizierungsmodelle - Fregatte", "details": { - "scoreInfo": "Die Punktzahl gibt die durchschnittliche Konfidenz aller Erkennungen dieses Objekts wieder." + "scoreInfo": "Die Punktzahl gibt die durchschnittliche Konfidenz aller Erkennungen dieses Objekts wieder.", + "none": "Keiner", + "unknown": "Unbekannt" }, "button": { "deleteClassificationAttempts": "Lösche klassifizierte Bilder", @@ -17,7 +19,7 @@ "trainingInProgress": "Modell wird gerade trainiert", "noNewImages": "Keine weiteren Bilder zum trainieren. Bitte klassifiziere weitere Bilder im Datensatz.", "noChanges": "Keine Veränderungen des Datensatzes seit dem letzten Training.", - "modelNotReady": "Modell ist nicht bereit zum Training" + "modelNotReady": "Modell ist nicht bereit für das Training" }, "toast": { "success": { @@ -130,7 +132,7 @@ "classesTip": "Über Klassen lernen", "classesStateDesc": "Definieren Sie die verschiedenen Zustände, in denen sich Ihr Kamerabereich befinden kann. Beispiel: „offen” und „geschlossen” für ein Garagentor.", "classesObjectDesc": "Definieren Sie die verschiedenen Kategorien, in die erkannte Objekte klassifiziert werden sollen. Beispiel: „Lieferant“, „Bewohner“, „Fremder“ für die Klassifizierung von Personen.", - "classPlaceholder": "Eingabe Klassenbezeichnung...", + "classPlaceholder": "Klassenbezeichnung eingeben...", "errors": { "nameRequired": "Modellname ist erforderlich", "nameLength": "Der Modellname darf maximal 64 Zeichen lang sein", @@ -139,7 +141,8 @@ "classesUnique": "Klassenname muss eindeutig sein", "stateRequiresTwoClasses": "Gebietsmodelle erfordern mindestens zwei Klassen", "objectLabelRequired": "Bitte wähle eine Objektbeschriftung", - "objectTypeRequired": "Bitte wählen Sie einen Klassifizierungstyp aus" + "objectTypeRequired": "Bitte wählen Sie einen Klassifizierungstyp aus", + "noneNotAllowed": "Die Klasse „none“ ist nicht zulässig" } }, "step2": { diff --git a/web/public/locales/de/views/explore.json b/web/public/locales/de/views/explore.json index 1068bfd7f2..87da740081 100644 --- a/web/public/locales/de/views/explore.json +++ b/web/public/locales/de/views/explore.json @@ -18,13 +18,15 @@ "updatedSublabel": "Unterkategorie erfolgreich aktualisiert.", "updatedLPR": "Nummernschild erfolgreich aktualisiert.", "regenerate": "Eine neue Beschreibung wurde von {{provider}} angefordert. Je nach Geschwindigkeit des Anbieters kann es einige Zeit dauern, bis die neue Beschreibung generiert ist.", - "audioTranscription": "Die Audio-Transkription wurde erfolgreich angefordert. Je nach Geschwindigkeit Ihres Frigate-Servers kann die Transkription einige Zeit in Anspruch nehmen." + "audioTranscription": "Die Audio-Transkription wurde erfolgreich angefordert. Je nach Geschwindigkeit Ihres Frigate-Servers kann die Transkription einige Zeit in Anspruch nehmen.", + "updatedAttributes": "Attribute erfolgreich aktualisiert." }, "error": { "regenerate": "Der Aufruf von {{provider}} für eine neue Beschreibung ist fehlgeschlagen: {{errorMessage}}", "updatedSublabelFailed": "Untekategorie konnte nicht aktualisiert werden: {{errorMessage}}", "updatedLPRFailed": "Aktualisierung des Kennzeichens fehlgeschlagen: {{errorMessage}}", - "audioTranscription": "Die Anforderung der Audio Transkription ist fehlgeschlagen: {{errorMessage}}" + "audioTranscription": "Die Anforderung der Audio Transkription ist fehlgeschlagen: {{errorMessage}}", + "updatedAttributesFailed": "Attribute konnten nicht aktualisiert werden: {{errorMessage}}" } } }, @@ -72,7 +74,12 @@ }, "score": { "label": "Ergebnis" - } + }, + "editAttributes": { + "title": "Attribute bearbeiten", + "desc": "Wählen Sie Klassifizierungsattribute für dieses {{label}} aus" + }, + "attributes": "Klassifizierungsattribute" }, "documentTitle": "Erkunde - Frigate", "generativeAI": "Generative KI", @@ -254,7 +261,7 @@ "faceOrLicense_plate": "{{attribute}} erkannt für {{label}}", "other": "{{label}} erkannt als {{attribute}}" }, - "gone": "{{label}} hat verlassen", + "gone": "{{label}} hat sich entfernt", "heard": "{{label}} wurde gehört", "external": "{{label}} erkannt", "header": { diff --git a/web/public/locales/de/views/faceLibrary.json b/web/public/locales/de/views/faceLibrary.json index bdbd448254..e69114b50f 100644 --- a/web/public/locales/de/views/faceLibrary.json +++ b/web/public/locales/de/views/faceLibrary.json @@ -44,7 +44,7 @@ "deleteFace": "Lösche Gesicht" }, "train": { - "title": "Kürzliche Erkennungen", + "title": "Neueste Erkennungen", "aria": "Wähle aktuelle Erkennungen", "empty": "Es gibt keine aktuellen Versuche zur Gesichtserkennung", "titleShort": "frisch" diff --git a/web/public/locales/de/views/search.json b/web/public/locales/de/views/search.json index 5729716d8a..0b6424f420 100644 --- a/web/public/locales/de/views/search.json +++ b/web/public/locales/de/views/search.json @@ -25,7 +25,8 @@ "max_speed": "Maximalgeschwindigkeit", "time_range": "Zeitraum", "labels": "Labels", - "sub_labels": "Unterlabels" + "sub_labels": "Unterlabels", + "attributes": "Attribute" }, "toast": { "error": { diff --git a/web/public/locales/de/views/settings.json b/web/public/locales/de/views/settings.json index b43173fcf5..f577ae774a 100644 --- a/web/public/locales/de/views/settings.json +++ b/web/public/locales/de/views/settings.json @@ -5,7 +5,7 @@ "camera": "Kameraeinstellungen - Frigate", "masksAndZones": "Masken- und Zoneneditor – Frigate", "object": "Debug - Frigate", - "general": "UI-Einstellungen – Frigate", + "general": "UI-Einstellungen - Frigate", "frigatePlus": "Frigate+ Einstellungen – Frigate", "classification": "Klassifizierungseinstellungen – Frigate", "motionTuner": "Bewegungserkennungs-Optimierer – Frigate", @@ -490,7 +490,7 @@ }, "users": { "addUser": "Benutzer hinzufügen", - "updatePassword": "Passwort aktualisieren", + "updatePassword": "Passwort zurücksetzen", "toast": { "success": { "deleteUser": "Benutzer {{user}} wurde erfolgreich gelöscht", @@ -514,7 +514,7 @@ "changeRole": "Benutzerrolle ändern", "deleteUser": "Benutzer löschen", "noUsers": "Keine Benutzer gefunden.", - "password": "Passwort", + "password": "Passwort zurücksetzen", "username": "Benutzername", "actions": "Aktionen", "role": "Rolle" @@ -599,7 +599,8 @@ "currentPasswordRequired": "Aktuelles Passwort wird benötigt", "incorrectCurrentPassword": "Aktuelles Passwort ist falsch", "passwordVerificationFailed": "Passwort konnte nicht überprüft werden", - "multiDeviceWarning": "Alle anderen Geräte, auf denen Sie angemeldet sind, müssen sich innerhalb von {{refresh_time}} erneut anmelden. Sie können auch alle Benutzer dazu zwingen, sich sofort erneut zu authentifizieren, indem Sie Ihr JWT-Geheimnis rotieren." + "multiDeviceWarning": "Alle anderen Geräte, auf denen Sie angemeldet sind, müssen sich innerhalb von {{refresh_time}} erneut anmelden.", + "multiDeviceAdmin": "Sie können auch alle Benutzer dazu zwingen, sich sofort erneut zu authentifizieren, indem Sie Ihr JWT-Geheimnis ändern." } } }, @@ -704,8 +705,8 @@ }, "enrichments": { "birdClassification": { - "title": "Vogel Klassifizierung", - "desc": "Die Vogelklassifizierung identifiziert bekannte Vögel mithilfe eines quantisierten Tensorflow-Modells. Wenn ein bekannter Vogel erkannt wird, wird sein allgemeiner Name als sub_label hinzugefügt. Diese Informationen sind in der Benutzeroberfläche, in Filtern und in Benachrichtigungen enthalten." + "title": "Vogelerkennung", + "desc": "Die Vogelerkennung identifiziert Vögelarten mithilfe eines quantisierten Tensorflowmodells. Wenn eine Vogelart erkannt wird, wird ihr Name als sub_label hinzugefügt. Diese Informationen sind in der Benutzeroberfläche, in Filtern und in Benachrichtigungen enthalten." }, "title": "Anreicherungseinstellungen", "unsavedChanges": "Ungesicherte geänderte Verbesserungseinstellungen", @@ -729,7 +730,7 @@ "desc": "Die Größe des für die Einbettung der semantischen Suche verwendeten Modells.", "large": { "title": "groß", - "desc": "Bei der Verwendung von groß wird das gesamte Jina-Modell verwendet und automatisch auf der GPU ausgeführt, falls zutreffend." + "desc": "Bei der Verwendung von groß wird das gesamte Jina-Modell verwendet und automatisch auf der GPU ausgeführt, falls möglich." } }, "title": "Semantische Suche", @@ -738,7 +739,7 @@ }, "faceRecognition": { "title": "Gesichtserkennung", - "desc": "Die Gesichtserkennung ermöglicht es, Personen Namen zuzuweisen, und wenn ihr Gesicht erkannt wird, ordnet Frigate den Namen der Person als Untertitel zu. Diese Informationen sind in der Benutzeroberfläche, den Filtern und in den Benachrichtigungen enthalten.", + "desc": "Die Gesichtserkennung ermöglicht es, Personen Namen zuzuweisen. Wenn ein Gesicht erkannt wird, ordnet Frigate den Namen der Person als Untertitel zu. Diese Informationen sind in der Benutzeroberfläche, den Filtern und in den Benachrichtigungen enthalten.", "readTheDocumentation": "Lies die Dokumentation", "modelSize": { "label": "Modellgröße", @@ -1095,7 +1096,7 @@ } }, "step3": { - "description": "Konfigurieren Sie Stream-Rollen und fügen Sie zusätzliche Streams für Ihre Kamera hinzu", + "description": "Konfigurieren Sie Stream-Rollen und fügen Sie zusätzliche Streams für Ihre Kamera hinzu.", "validationTitle": "Stream Validierung", "connectAllStreams": "Verbinde alle Streams", "reconnectionSuccess": "Wiederverbindung erfolgreich.", @@ -1208,7 +1209,8 @@ "audioCodecRequired": "Ein Audiostream ist erforderlich, um die Audioerkennung zu unterstützen.", "restreamingWarning": "Die Reduzierung der Verbindungen zur Kamera für den Aufzeichnungsstream kann zu einer geringfügigen Erhöhung der CPU-Auslastung führen.", "brands": { - "reolink-rtsp": "Reolink RTSP wird nicht empfohlen. Aktivieren Sie HTTP in den Firmware-Einstellungen der Kamera und starten Sie den Assistenten neu." + "reolink-rtsp": "Reolink RTSP wird nicht empfohlen. Aktivieren Sie HTTP in den Firmware-Einstellungen der Kamera und starten Sie den Assistenten neu.", + "reolink-http": "Für Reolink-HTTP-Streams sollten sie FFmpeg verwenden, um eine bessere Kompatibilität zu gewährleisten. Aktivieren Sie für diesen Stream die Option „Stream-Kompatibilitätsmodus verwenden“." }, "dahua": { "substreamWarning": "Substream 1 ist auf eine niedrige Auflösung festgelegt. Viele Kameras von Dahua / Amcrest / EmpireTech unterstützen zusätzliche Substreams, die in den Kameraeinstellungen aktiviert werden müssen. Es wird empfohlen, diese Streams zu überprüfen und zu nutzen, sofern sie verfügbar sind." @@ -1224,7 +1226,7 @@ "addCamera": "Neue Kamera hinzufügen", "editCamera": "Kamera bearbeiten:", "selectCamera": "Wähle eine Kamera", - "backToSettings": "Zurück zu Kamera-Einstellungen", + "backToSettings": "Zurück zu Kameraeinstellungen", "streams": { "title": "Kameras aktivieren / deaktivieren", "desc": "Deaktiviere eine Kamera vorübergehend, bis Frigate neu gestartet wird. Deaktivierung einer Kamera stoppt die Verarbeitung der Streams dieser Kamera durch Frigate vollständig. Erkennung, Aufzeichnung und Debugging sind dann nicht mehr verfügbar.
    Hinweis: Dies deaktiviert nicht die go2rtc restreams." diff --git a/web/public/locales/de/views/system.json b/web/public/locales/de/views/system.json index bd948a0376..a7bb342aed 100644 --- a/web/public/locales/de/views/system.json +++ b/web/public/locales/de/views/system.json @@ -178,7 +178,10 @@ "review_description_events_per_second": "Bewertungsbeschreibung", "object_description": "Objekt Beschreibung", "object_description_speed": "Objektbeschreibung Geschwindigkeit", - "object_description_events_per_second": "Objektbeschreibung" + "object_description_events_per_second": "Objektbeschreibung", + "classification": "{{name}} Klassifizierung", + "classification_speed": "{{name}} Klassifizierungsgeschwindigkeit", + "classification_events_per_second": "{{name}} Klassifizierungsereignisse pro Sekunde" }, "title": "Optimierungen", "infPerSecond": "Rückschlüsse pro Sekunde", From 50a5e40410660cb68c900066d4ac7a7f08834aef Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:01 +0100 Subject: [PATCH 42/78] Translated using Weblate (Danish) Currently translated at 36.0% (9 of 25 strings) Translated using Weblate (Danish) Currently translated at 7.3% (9 of 122 strings) Co-authored-by: Hosted Weblate Co-authored-by: Sean Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/da/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/da/ Translation: Frigate NVR/components-player Translation: Frigate NVR/views-classificationmodel --- web/public/locales/da/components/player.json | 2 +- web/public/locales/da/views/classificationModel.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/public/locales/da/components/player.json b/web/public/locales/da/components/player.json index 8a89b29ab4..099369052c 100644 --- a/web/public/locales/da/components/player.json +++ b/web/public/locales/da/components/player.json @@ -1,5 +1,5 @@ { - "noRecordingsFoundForThisTime": "Ingen optagelser fundet i det angivet tidsrum", + "noRecordingsFoundForThisTime": "Ingen optagelser fundet i det angivne tidsrum", "noPreviewFound": "Ingen forhåndsvisning fundet", "cameraDisabled": "Kamera er deaktiveret", "noPreviewFoundFor": "Ingen forhåndsvisning fundet for {{cameraName}}", diff --git a/web/public/locales/da/views/classificationModel.json b/web/public/locales/da/views/classificationModel.json index b30e65d5c1..a3aa81f28e 100644 --- a/web/public/locales/da/views/classificationModel.json +++ b/web/public/locales/da/views/classificationModel.json @@ -1,7 +1,8 @@ { "documentTitle": "Klassifikationsmodeller", "details": { - "scoreInfo": "Scoren repræsenterer den gennemsnitlige klassifikationssikkerhed på tværs af alle registreringer af dette objekt." + "scoreInfo": "Scoren repræsenterer den gennemsnitlige klassifikationssikkerhed på tværs af alle registreringer af dette objekt.", + "unknown": "Ukendt" }, "description": { "invalidName": "Ugyldigt navn. Navne må kun indeholde bogstaver, tal, mellemrum, apostroffer, understregninger og bindestreger." From 1be7c561d71aa9dea6f82fdbb2c0d3dadb0c6447 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:02 +0100 Subject: [PATCH 43/78] Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/el/common.json | 62 +++++++++++++++++++- web/public/locales/el/components/dialog.json | 5 ++ web/public/locales/el/views/explore.json | 8 ++- web/public/locales/el/views/exports.json | 5 ++ web/public/locales/el/views/faceLibrary.json | 5 +- web/public/locales/el/views/settings.json | 6 +- 6 files changed, 83 insertions(+), 8 deletions(-) diff --git a/web/public/locales/el/common.json b/web/public/locales/el/common.json index 5cc5277b71..3bcd3316f1 100644 --- a/web/public/locales/el/common.json +++ b/web/public/locales/el/common.json @@ -68,7 +68,14 @@ "formattedTimestampFilename": { "12hour": "dd-MM-yy-h-mm-ss-a", "24hour": "dd-MM-yy-HH-mm-ss" - } + }, + "d": "{{time}}η", + "h": "{{time}}ω", + "m": "{{time}}λ", + "s": "{{time}}δ", + "inProgress": "Σε εξέλιξη", + "invalidStartTime": "Μη έγκυρη ώρα έναρξης", + "invalidEndTime": "Μη έγκυρη ώρα λήξης" }, "menu": { "live": { @@ -100,7 +107,8 @@ "yes": "Ναι", "no": "Όχι", "download": "Κατέβασμα", - "info": "Πληροφορίες" + "info": "Πληροφορίες", + "history": "Ιστορία" }, "unit": { "speed": { @@ -120,6 +128,54 @@ } }, "label": { - "back": "Επιστροφή" + "back": "Επιστροφή", + "hide": "Απόκρυψη {{item}}", + "show": "Εμφάνιση {{item}}", + "ID": "ID", + "none": "Κανένα", + "all": "Όλα" + }, + "toast": { + "save": { + "title": "Αποθήκευση", + "error": { + "title": "Αποτυχία αποθήκευσης αλλαγών διαμόρφωσης: {{errorMessage}}", + "noMessage": "Αποτυχία αποθήκευσης αλλαγών διαμόρφωσης" + } + } + }, + "role": { + "admin": "Διαχειριστής", + "desc": "Οι διαχειριστές έχουν πλήρη πρόσβαση σε όλες τις λειτουργίες του περιβάλλοντος χρήστη Frigate. Οι θεατές έχουν περιορισμένη πρόσβαση στην προβολή καμερών, στην αναθεώρηση στοιχείων και σε ιστορικό υλικό στο περιβάλλον χρήστη.", + "viewer": "Θεατής" + }, + "pagination": { + "previous": { + "title": "Προηγούμενο", + "label": "Μετάβαση στην προηγούμενη σελίδα" + }, + "next": { + "title": "Επόμενο", + "label": "Μετάβαση στην επόμενη σελίδα" + }, + "more": "Περισσότερες σελίδες" + }, + "accessDenied": { + "documentTitle": "Πρόσβαση απορρίφθηκε - Frigate", + "title": "Πρόσβαση απορρίφθηκε", + "desc": "Δεν έχετε άδεια να δείτε αυτή τη σελίδα." + }, + "notFound": { + "documentTitle": "Δεν βρέθηκε - Frigate", + "title": "404", + "desc": "Η σελίδα δεν βρέθηκε" + }, + "list": { + "two": "{{0}} και {{1}}", + "many": "{{items}} και {{last}}", + "separatorWithSpace": ", " + }, + "field": { + "internalID": "Το εσωτερικό ID που χρησιμοποίησε η Fregate στη διαμόρφωση και τη βάση δεδομένων" } } diff --git a/web/public/locales/el/components/dialog.json b/web/public/locales/el/components/dialog.json index c4826881aa..40c3f05457 100644 --- a/web/public/locales/el/components/dialog.json +++ b/web/public/locales/el/components/dialog.json @@ -46,5 +46,10 @@ "toast": { "success": "Επιτυχής έναρξη εξαγωγής. Δείτε το αρχείο στον φάκελο /exports." } + }, + "search": { + "saveSearch": { + "label": "Αποθήκευση αναζήτησης" + } } } diff --git a/web/public/locales/el/views/explore.json b/web/public/locales/el/views/explore.json index 12390dcb94..a33fb17bf2 100644 --- a/web/public/locales/el/views/explore.json +++ b/web/public/locales/el/views/explore.json @@ -42,5 +42,11 @@ "noImageFound": "Δεν βρέθηκε εικόνα για αυτό το χρονικό σημείο." }, "trackedObjectsCount_one": "{{count}} παρακολουθούμενο αντικείμενο ", - "trackedObjectsCount_other": "{{count}} παρακολουθούμενα αντικείμενα " + "trackedObjectsCount_other": "{{count}} παρακολουθούμενα αντικείμενα ", + "itemMenu": { + "downloadVideo": { + "label": "Λήψη βίντεο", + "aria": "Λήψη βίντεο" + } + } } diff --git a/web/public/locales/el/views/exports.json b/web/public/locales/el/views/exports.json index 8aff542388..f6526eea01 100644 --- a/web/public/locales/el/views/exports.json +++ b/web/public/locales/el/views/exports.json @@ -13,5 +13,10 @@ "error": { "renameExportFailed": "Αποτυχία μετονομασίας εξαγωγής:{{errorMessage}}" } + }, + "tooltip": { + "shareExport": "Κοινή χρήση εξαγωγής", + "downloadVideo": "Λήψη βίντεο", + "deleteExport": "Διαγραφή εξαγωγής" } } diff --git a/web/public/locales/el/views/faceLibrary.json b/web/public/locales/el/views/faceLibrary.json index f41e89c968..7bb548e073 100644 --- a/web/public/locales/el/views/faceLibrary.json +++ b/web/public/locales/el/views/faceLibrary.json @@ -1,6 +1,6 @@ { "description": { - "addFace": "Οδηγός για την προσθήκη μιας νέας συλλογής στη Βιβλιοθήκη Προσώπων.", + "addFace": "Προσθέστε μια νέα συλλογή στη Βιβλιοθήκη Προσώπων ανεβάζοντας την πρώτη σας εικόνα.", "placeholder": "Εισαγάγετε ένα όνομα για αυτήν τη συλλογή", "invalidName": "Μη έγκυρο όνομα. Τα ονόματα μπορούν να περιλαμβάνουν γράμματα, αριθμούς, κενό διάστημα, απόστροφο, παύλα, κάτω παύλα." }, @@ -27,7 +27,8 @@ }, "documentTitle": "Βιβλιοθήκη προσώπων - Frigate", "uploadFaceImage": { - "title": "Μεταφόρτωση Εικόνας Προσώπου" + "title": "Μεταφόρτωση Εικόνας Προσώπου", + "desc": "Ανεβάστε μια εικόνα για να σαρώσετε πρόσωπα και να τα συμπεριλάβετε στο {{pageToggle}}" }, "steps": { "nextSteps": "Επόμενα βήματα", diff --git a/web/public/locales/el/views/settings.json b/web/public/locales/el/views/settings.json index 909bc57e6a..75884e0d2f 100644 --- a/web/public/locales/el/views/settings.json +++ b/web/public/locales/el/views/settings.json @@ -7,9 +7,11 @@ "masksAndZones": "Ρυθμίσεις Μασκών και Ζωνών - Frigate", "motionTuner": "Ρύθμιση Κίνησης - Frigate", "object": "Επίλυση σφαλμάτων - Frigate", - "general": "Γενικές ρυθμίσεις - Frigate", + "general": "Ρυθμίσεις UI - Frigate", "frigatePlus": "Ρυθμίσεις Frigate+ - Frigate", - "notifications": "Ρυθμίσεις Ειδοποιήσεων" + "notifications": "Ρυθμίσεις Ειδοποιήσεων", + "cameraManagement": "Διαχείριση καμερών - Frigate", + "cameraReview": "Ρυθμίσεις αξιολόγησης κάμερας - Frigate" }, "masksAndZones": { "zones": { From 4ae3c9786565399bf6485924ee6ead5d0eed5884 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:04 +0100 Subject: [PATCH 44/78] Translated using Weblate (Estonian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 61.9% (57 of 92 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (46 of 46 strings) Translated using Weblate (Estonian) Currently translated at 13.3% (87 of 654 strings) Translated using Weblate (Estonian) Currently translated at 16.9% (22 of 130 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (214 of 214 strings) Translated using Weblate (Estonian) Currently translated at 59.7% (55 of 92 strings) Translated using Weblate (Estonian) Currently translated at 7.5% (9 of 120 strings) Translated using Weblate (Estonian) Currently translated at 24.5% (13 of 53 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (55 of 55 strings) Translated using Weblate (Estonian) Currently translated at 13.3% (87 of 654 strings) Translated using Weblate (Estonian) Currently translated at 16.6% (8 of 48 strings) Translated using Weblate (Estonian) Currently translated at 55.5% (40 of 72 strings) Co-authored-by: Hosted Weblate Co-authored-by: Priit Jõerüüt Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/et/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/et/ Translation: Frigate NVR/common Translation: Frigate NVR/components-camera Translation: Frigate NVR/components-dialog Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-live Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings --- web/public/locales/et/audio.json | 33 ++- web/public/locales/et/common.json | 191 +++++++++++++++++- web/public/locales/et/components/camera.json | 39 +++- web/public/locales/et/components/dialog.json | 118 ++++++++++- web/public/locales/et/components/filter.json | 37 +++- web/public/locales/et/components/player.json | 7 +- web/public/locales/et/objects.json | 70 ++++++- .../locales/et/views/classificationModel.json | 28 +++ web/public/locales/et/views/configEditor.json | 19 +- web/public/locales/et/views/events.json | 34 +++- web/public/locales/et/views/explore.json | 37 ++++ web/public/locales/et/views/faceLibrary.json | 29 +++ web/public/locales/et/views/live.json | 111 +++++++++- web/public/locales/et/views/search.json | 14 +- web/public/locales/et/views/settings.json | 60 +++++- web/public/locales/et/views/system.json | 9 +- 16 files changed, 811 insertions(+), 25 deletions(-) diff --git a/web/public/locales/et/audio.json b/web/public/locales/et/audio.json index 8248b37f57..8797c78672 100644 --- a/web/public/locales/et/audio.json +++ b/web/public/locales/et/audio.json @@ -24,5 +24,36 @@ "clapping": "Käteplagin", "finger_snapping": "Sõrmede naksutamine", "hands": "Käed", - "camera": "Kaamera" + "camera": "Kaamera", + "speech": "Kõne", + "babbling": "Lobisemine", + "yell": "Karjumine", + "bellow": "Röökimine", + "whoop": "Kisamine", + "whispering": "Sosistamine", + "laughter": "Naermine", + "snicker": "Itsitamine", + "sigh": "Ohkamine", + "crying": "Nutmine", + "singing": "Laulmine", + "choir": "Koorilaulmine", + "yodeling": "Joodeldamine", + "chant": "Skandeerimine", + "mantra": "Mantra lugemine", + "child_singing": "Lastelaul", + "whistling": "Vilistamine", + "gasp": "Hingeldamine", + "pant": "Ähkimine", + "door": "Uks", + "mouse": "Hiir", + "keyboard": "Klahvistik", + "sink": "Kraanikauss", + "blender": "Kannmikser", + "clock": "Kell", + "scissors": "Käärid", + "hair_dryer": "Föön", + "toothbrush": "Hambahari", + "vehicle": "Sõiduk", + "bark": "Puukoor", + "goat": "Kits" } diff --git a/web/public/locales/et/common.json b/web/public/locales/et/common.json index 3a6fcbfd5e..c092658e02 100644 --- a/web/public/locales/et/common.json +++ b/web/public/locales/et/common.json @@ -39,21 +39,144 @@ "minute_other": "{{time}} minutit", "s": "{{time}} sek", "second_one": "{{time}} sekund", - "second_other": "{{time}} sekundit" + "second_other": "{{time}} sekundit", + "formattedTimestampHourMinute": { + "24hour": "HH:mm", + "12hour": "hh:mm aaa" + }, + "formattedTimestampHourMinuteSecond": { + "24hour": "HH:mm:ss", + "12hour": "hh:mm:ss aaa" + }, + "formattedTimestampFilename": { + "12hour": "yy-MM-dd-hh-mm-ss-a", + "24hour": "yy-MM-dd-HH-mm-ss" + }, + "formattedTimestamp": { + "12hour": "MMM d, hh:mm:ss aaa", + "24hour": "MMM d, HH:mm:ss" + }, + "formattedTimestamp2": { + "12hour": "dd.MM hh:mm:ssa", + "24hour": "d MMM HH:mm:ss" + }, + "formattedTimestampMonthDayHourMinute": { + "12hour": "MMM d, hh:mm aaa", + "24hour": "MMM d, HH:mm" + }, + "formattedTimestampMonthDayYear": { + "12hour": "MMM d, yyyy", + "24hour": "MMM d, yyyy" + }, + "inProgress": "Töös", + "invalidStartTime": "Vigane algusaeg", + "invalidEndTime": "Vigane lõpuaeg", + "formattedTimestampMonthDayYearHourMinute": { + "12hour": "dd. MMM yyyy, hh:mm aaa", + "24hour": "dd. MMM yyyy, HH:mm" + }, + "formattedTimestampMonthDay": "dd. MMM" }, "menu": { "user": { - "setPassword": "Lisa salasõna" + "setPassword": "Lisa salasõna", + "logout": "Logi välja", + "title": "Kasutaja", + "account": "Kasutajakonto", + "current": "Praegune kasutaja: {{user}}", + "anonymous": "anonüümne" }, "live": { - "allCameras": "Kõik kaamerad" + "allCameras": "Kõik kaamerad", + "title": "Otseülekanne", + "cameras": { + "title": "Kaamerad", + "count_one": "{{count}} kaamera", + "count_other": "{{count}} kaamerat" + } }, "settings": "Seadistused", "language": { "withSystem": { "label": "Kasuta keele jaoks süsteemi seadistusi" + }, + "en": "English (inglise keel)", + "es": "Español (hispaania keel)", + "zhCN": "简体中文 (hiina keel lihtsustatud hieroglüüfidega)", + "hi": "हिन्दी (hindi keel)", + "fr": "Français (prantsuse keel)", + "ar": "العربية (araabia keel)", + "pt": "Português (portugali keel)", + "ptBR": "Português brasileiro (Brasiilia portugali keel)", + "ru": "Русский (vene keel)", + "de": "Deutsch (saksa keel)", + "ja": "日本語 (jaapani keel)", + "tr": "Türkçe (türgi keel)", + "it": "Italiano (itaalia keel)", + "nl": "Nederlands (hollandi keel)", + "sv": "Svenska (rootsi keel)", + "cs": "Čeština (tšehhi keel)", + "nb": "Norsk Bokmål (norra bokmål)", + "ko": "한국어 (korea keel)", + "vi": "Tiếng Việt (vietnami keel)", + "fa": "فارسی (pärsia keel)", + "pl": "Polski (poola keel)", + "uk": "Українська (ukraina keel)", + "he": "עברית (heebrea keel)", + "el": "Ελληνικά (kreeka keel)", + "ro": "Română (rumeenia keel)", + "hu": "Magyar (ungari keel)", + "fi": "Suomi (soome keel)", + "da": "Dansk (taani keel)", + "sk": "Slovenčina (slovaki keel)", + "yue": "粵語 (kantoni keel)", + "th": "ไทย (tai keel)", + "ca": "Català (katalaani keel)", + "sr": "Српски (serbia keel)", + "sl": "Slovenščina (sloveeni keel)", + "lt": "Lietuvių (leedu keel)", + "bg": "Български (bulgaaria keel)", + "gl": "Galego (galeegi keel)", + "id": "Bahasa Indonesia (indoneesia keel)", + "ur": "اردو (urdu keel)" + }, + "system": "Süsteem", + "systemMetrics": "Süsteemi meetrika", + "configuration": "Seadistused", + "systemLogs": "Süsteemi logid", + "configurationEditor": "Seadistuste haldur", + "languages": "Keeled", + "appearance": "Välimus", + "darkMode": { + "label": "Tume kujundus", + "light": "Hele kujundus", + "dark": "Tume kujundus", + "withSystem": { + "label": "Kasuta süsteemi seadistusi heleda või tumeda kujunduse jaoks" } - } + }, + "withSystem": "Süsteem", + "theme": { + "label": "Kujundus", + "blue": "Sinine", + "green": "Roheline", + "nord": "Põhjala", + "red": "Punane", + "highcontrast": "Väga kontrastne", + "default": "Vaikimisi kujundus" + }, + "help": "Abiteave", + "documentation": { + "title": "Dokumentatsioon", + "label": "Frigate'i dokumentatsioon" + }, + "restart": "Käivita Frigate uuesti", + "review": "Ülevaatamine", + "explore": "Uuri", + "export": "Ekspordi", + "uiPlayground": "Leht kasutajaliidese katsetamiseks", + "faceLibrary": "Näoteek", + "classification": "Klassifikatsioon" }, "unit": { "speed": { @@ -67,6 +190,10 @@ "kbph": "kB/t", "mbph": "MB/t", "gbph": "GB/t" + }, + "length": { + "feet": "jalga", + "meters": "meetrit" } }, "button": { @@ -97,7 +224,15 @@ "yes": "Jah", "no": "Ei", "download": "Laadi alla", - "info": "Teave" + "info": "Teave", + "suspended": "Peata", + "unsuspended": "Lõpeta peatamine", + "play": "Esita", + "unselect": "Eemalda valik", + "export": "Ekspordi", + "deleteNow": "Kustuta kohe", + "next": "Järgmine", + "continue": "Jätka" }, "label": { "back": "Mine tagasi", @@ -113,6 +248,50 @@ "separatorWithSpace": ", " }, "field": { - "optional": "Valikuline" + "optional": "Valikuline", + "internalID": "Seadistustes ja andmebaasis kasutatav Frigate'i sisemine tunnus" + }, + "toast": { + "copyUrlToClipboard": "Võrguaadress on kopeeritud lõikelauale.", + "save": { + "title": "Salvesta", + "error": { + "title": "Seadistuste muudatuste salvestamine ei õnnestunud: {{errorMessage}}", + "noMessage": "Seadistuste muudatuste salvestamine ei õnnestunud" + } + } + }, + "role": { + "title": "Roll", + "admin": "Peakasutaja", + "viewer": "Vaataja", + "desc": "Peakasutajatel on Frigate'i kasutajaliideses kõik õigused. Vaatajad võivad vaid kaamerate pilti vaadata, objekte ülevaadata ning otsida arhiivist vanu videoid." + }, + "pagination": { + "label": "lehenummerdus", + "previous": { + "title": "Eelmine", + "label": "Mine eelmisele lehele" + }, + "next": { + "title": "Järgmine", + "label": "Mine järgmisele lehele" + }, + "more": "Järgnevad lehed" + }, + "accessDenied": { + "documentTitle": "Ligipääs on keelatud - Frigate", + "title": "Ligipääs on keelatud", + "desc": "Sul pole õigusi selle lehe vaatamiseks." + }, + "notFound": { + "documentTitle": "Lehte ei leidu - Frigate", + "title": "404", + "desc": "Veebilehte ei leidu" + }, + "selectItem": "Vali {{item}}", + "readTheDocumentation": "Loe dokumentatsiooni ja juhendit", + "information": { + "pixels": "{{area}} px" } } diff --git a/web/public/locales/et/components/camera.json b/web/public/locales/et/components/camera.json index a62fe5aa92..e5df620ec1 100644 --- a/web/public/locales/et/components/camera.json +++ b/web/public/locales/et/components/camera.json @@ -9,9 +9,39 @@ "placeholder": "Vali videovoog", "streamMethod": { "label": "Voogedastuse meetod", - "placeholder": "Vali voogedastuse meetod" + "placeholder": "Vali voogedastuse meetod", + "method": { + "noStreaming": { + "label": "Voogedastust pole", + "desc": "Kaamerapildid uuenevad kord minutis ja voogedastust pole." + }, + "smartStreaming": { + "label": "Nutikas voogedastus (soovituslik)", + "desc": "Nutika voogedastuse puhul ilma igasuguse tuvastatava tegevuseta kaamerapildid uuenevad kord minutis ja voogedastust pole. Sellega säästad ribalaiud ja kuid ressursse. Tegevuse tuvastamisel käivitub tavapärane voogedastus." + }, + "continuousStreaming": { + "label": "Pidev voogedastus", + "desc": { + "title": "Kaamera voogedastus toimub töölauavaates pidevalt, seda ka siis, kui seal pole mingit tegevust tuvastatud.", + "warning": "Pidev voogedastus võib põhjustada suurt andmeedastuse mahutu ja tekitada jõudlusprobleeme. Kasuta seda võimalust ettevaatlikult." + } + } + } + }, + "audioIsAvailable": "Selle voogedastuse puhul on saadaval ka heliriba", + "audioIsUnavailable": "Selle voogedastuse puhul pole heliriba saadaval", + "compatibilityMode": { + "label": "Ühilduvusrežiim", + "desc": "Kasuta seda võimalust vaid olukorras, kus kaamera voogedastuses paistab visuaalseid vigu ja pidi paremas ääres on diagonaalne joon." + }, + "desc": "Muuda selle kaamergrupi voogedastuse valikuid töölauavaates.Need seadistused on seadme- ja veebibrauserikohased.", + "audio": { + "tips": { + "title": "See kaamera peab oskama heli jäädvustada ja edastada ja go2rtc kontekstis seadistatud selle voogedastuse jaoks." + } } - } + }, + "birdseye": "Vaade linnulennult" }, "add": "Lisa kaameragrupp", "edit": "Muuda kaameragruppi", @@ -48,6 +78,9 @@ }, "boundingBox": "Piirdekast", "timestamp": "Ajatempel", - "zones": "Tsoonid" + "zones": "Tsoonid", + "mask": "Mask", + "motion": "Liikumine", + "regions": "Alad" } } diff --git a/web/public/locales/et/components/dialog.json b/web/public/locales/et/components/dialog.json index 9e03b622d5..946142d8a9 100644 --- a/web/public/locales/et/components/dialog.json +++ b/web/public/locales/et/components/dialog.json @@ -1,6 +1,122 @@ { "restart": { "title": "Kas oled kindel, et soovid Frigate'i uuesti käivitada?", - "button": "Käivita uuesti" + "button": "Käivita uuesti", + "restarting": { + "title": "Frigate käivitub uuesti", + "content": "See leht laaditakse uuesti {{countdown}} sekundi pärast.", + "button": "Laadi uuesti kohe" + } + }, + "search": { + "saveSearch": { + "label": "Salvesta otsing", + "desc": "Sisesta nimi salvestatud otsingu jaoks.", + "placeholder": "Sisesta nimi oma otsingu jaoks", + "overwrite": "„{{searchName}}“ on juba olemas. Salvestamisel kirjutad olemasoleva väärtuse üle.", + "success": "„{{searchName}}“ otsing on salvestatud.", + "button": { + "save": { + "label": "Salvesta see otsing" + } + } + } + }, + "explore": { + "video": { + "viewInHistory": "Vaata ajaloos" + }, + "plus": { + "review": { + "state": { + "submitted": "Saadetud" + }, + "question": { + "ask_a": "Kas see objekt on {{label}}?", + "ask_an": "Kas see objekt on {{label}}?", + "ask_full": "Kas see objekt on {{untranslatedLabel}} ({{translatedLabel}})?", + "label": "Kinnita see silt Frigate+ teenuse jaoks" + } + }, + "submitToPlus": { + "label": "Saada teenusesse Frigate+", + "desc": "Objektid asukohtades, mida sa tahad vältida, pole valepositiivsed. Kui sa neid sellistena saadad teenusele, siis see ainult ajab tehisaru mudeli sassi." + } + } + }, + "export": { + "time": { + "fromTimeline": "Vali ajajoonelt", + "lastHour_one": "Viimase tunni jooksul", + "lastHour_other": "Viimase {{count}} tunni jooksul", + "custom": "Sinu valitud ajavahemik", + "start": { + "title": "Algusaeg", + "label": "Vali algusaeg" + }, + "end": { + "title": "Lõpuaeg", + "label": "Vali lõpuaeg" + } + }, + "name": { + "placeholder": "Sisesta ekspordifaili nimi" + }, + "select": "Vali", + "export": "Ekspordi", + "selectOrExport": "Vali või ekspordi", + "toast": { + "success": "Eksportimise käivitamine õnnestus. Faili leiad eksportimise lehelt.", + "view": "Vaata", + "error": { + "failed": "Eksportimise käivitamine ei õnnestunud: {{error}}", + "endTimeMustAfterStartTime": "Ajavahemiku lõpp peab olema peale algust", + "noVaildTimeSelected": "Ühtegi kehtivat ajavahemikku pole valitud" + } + }, + "fromTimeline": { + "saveExport": "Salvesta eksporditud sisu", + "previewExport": "Eksporditud sisu eelvaade" + } + }, + "streaming": { + "label": "Voogedastus", + "restreaming": { + "disabled": "Voogedastuse kordus pole selle kaamera puhul kasutatav.", + "desc": { + "title": "Kui tahad selle kaameraga kasutada täiendavaid otseeetri ja helivõimalusi, siis seadista go2rtc." + } + }, + "debugView": "Veaotsinguvaade", + "showStats": { + "label": "Näita voogedastuse statistikat", + "desc": "Lülita see eelistus sisse, kui soovid kaamerapildi ülekattena näha voogedastuse statistikat." + } + }, + "recording": { + "button": { + "export": "Ekspordi", + "markAsReviewed": "Märgi ülevaadatuks", + "markAsUnreviewed": "Märgi mitteülevaadatuks", + "deleteNow": "Kustuta kohe" + }, + "confirmDelete": { + "title": "Kinnita kustutamine", + "desc": { + "selected": "Kas sa oled kindel et soovid selle kõik ülevaadatava objektiga seotud kirjed kustutada?

    Vajuta alla Shift klahv ja saad sellest vaatest tulevikus mööda minna." + }, + "toast": { + "success": "Selle ülevaadatava objektiga seotud videosisu on kustutatud.", + "error": "Kustutamine ei õnnestunud: {{error}}" + } + } + }, + "imagePicker": { + "selectImage": "Vali jälgitava objekti pisipilt", + "unknownLabel": "Päästikpilt on salvestatud", + "search": { + "placeholder": "Otsi sildi või alamsildi alusel..." + }, + "noImages": "Selle kaamera kohta ei leidu pisipilte" } } diff --git a/web/public/locales/et/components/filter.json b/web/public/locales/et/components/filter.json index 9782f8de8f..0df74f6d03 100644 --- a/web/public/locales/et/components/filter.json +++ b/web/public/locales/et/components/filter.json @@ -16,16 +16,21 @@ "all": { "title": "Kõik sildid", "short": "Sildid" - } + }, + "label": "Sildid", + "count_one": "{{count}} silt", + "count_other": "{{count}} silti" }, "subLabels": { - "all": "Kõik alamsildid" + "all": "Kõik alamsildid", + "label": "Alamsildid" }, "dates": { "all": { "title": "Kõik kuupäevad", "short": "Kuupäevad" - } + }, + "selectPreset": "Vali eelseadistus…" }, "explore": { "settings": { @@ -53,5 +58,31 @@ }, "disableLogStreaming": "Keela logi voogedastus", "allLogs": "Kõik logid" + }, + "classes": { + "label": "Klassid", + "all": { + "title": "Kõik klassid" + }, + "count_one": "{{count}} klass", + "count_other": "{{count}} klassi" + }, + "zones": { + "label": "Tsoonid", + "all": { + "title": "Kõik tsoonid", + "short": "Tsoonid" + } + }, + "more": "Täiendavad filtrid", + "timeRange": "Ajavahemik", + "reset": { + "label": "Lähtesta filtrid vaikimisi väärtusteks" + }, + "score": "Punktiskoor", + "estimatedSpeed": "Hinnanguline kiirus: ({{unit}})", + "features": { + "label": "Omadused", + "hasSnapshot": "Leidub hetkvõte" } } diff --git a/web/public/locales/et/components/player.json b/web/public/locales/et/components/player.json index c70da87ed8..2906ab6420 100644 --- a/web/public/locales/et/components/player.json +++ b/web/public/locales/et/components/player.json @@ -1,3 +1,8 @@ { - "noRecordingsFoundForThisTime": "Hetkel ei leidu ühtego salvestust" + "noRecordingsFoundForThisTime": "Hetkel ei leidu ühtego salvestust", + "noPreviewFound": "Eelvaadet ei leidu", + "noPreviewFoundFor": "{{cameraName}} kaamera eelvaadet ei leidu", + "submitFrigatePlus": { + "submit": "Saada" + } } diff --git a/web/public/locales/et/objects.json b/web/public/locales/et/objects.json index efe92e788b..19830deafa 100644 --- a/web/public/locales/et/objects.json +++ b/web/public/locales/et/objects.json @@ -48,5 +48,73 @@ "cup": "Kruus", "fork": "Kahvel", "knife": "Nuga", - "spoon": "Lusikas" + "spoon": "Lusikas", + "bowl": "Kauss", + "banana": "Banaan", + "apple": "Õun", + "sandwich": "Võileib", + "orange": "Apelsin", + "broccoli": "Spargelkapsas", + "carrot": "Porgand", + "hot_dog": "Viinerisai", + "pizza": "Pitsa", + "donut": "Sõõrik", + "cake": "Kook", + "chair": "Tool", + "couch": "Kušett", + "potted_plant": "Potilill", + "bed": "Voodi", + "mirror": "Peegel", + "dining_table": "Söögilaud", + "window": "Aken", + "desk": "Kirjutuslaud", + "toilet": "Tualett", + "door": "Uks", + "tv": "Teler", + "laptop": "Sülearvuti", + "mouse": "Hiir", + "remote": "Kaugjuhtimispult", + "keyboard": "Klahvistik", + "cell_phone": "Mobiiltelefon", + "microwave": "Mikrolaineahi", + "oven": "Ahi", + "toaster": "Röster", + "sink": "Kraanikauss", + "refrigerator": "Külmkapp", + "blender": "Kannmikser", + "book": "Raamat", + "clock": "Kell", + "vase": "Vaas", + "scissors": "Käärid", + "teddy_bear": "Mängukaru", + "hair_dryer": "Föön", + "toothbrush": "Hambahari", + "hair_brush": "Juuksehari", + "vehicle": "Sõiduk", + "squirrel": "Orav", + "deer": "Hirv", + "bark": "Puukoor", + "fox": "Rebane", + "goat": "Kits", + "rabbit": "Jänes", + "raccoon": "Pesukaru", + "robot_lawnmower": "Robotmuruniiduk", + "waste_bin": "Prügikast", + "on_demand": "Nõudmisel", + "face": "Nägu", + "license_plate": "Sõiduki numbrimärk", + "package": "Pakett", + "bbq_grill": "Väligrill", + "amazon": "Amazoni sõiduk", + "usps": "USPS-i sõiduk", + "ups": "UPS-i sõiduk", + "fedex": "FedExi sõiduk", + "dhl": "DHL-i sõiduk", + "an_post": "An Posti sõiduk", + "purolator": "Purolatori sõiduk", + "postnl": "PostNL-i sõiduk", + "nzpost": "NZPost-i sõiduk", + "postnord": "PostNordi sõiduk", + "gls": "GLS-i sõiduk", + "dpd": "DPD sõiduk" } diff --git a/web/public/locales/et/views/classificationModel.json b/web/public/locales/et/views/classificationModel.json index 3d714264a9..2b393998fe 100644 --- a/web/public/locales/et/views/classificationModel.json +++ b/web/public/locales/et/views/classificationModel.json @@ -4,5 +4,33 @@ "deletedModel_one": "{{count}} mudeli kustutamine õnnestus", "deletedModel_other": "{{count}} mudeli kustutamine õnnestus" } + }, + "documentTitle": "Klassifitseerimise mudelid - Frigate", + "details": { + "scoreInfo": "Skoor näitab selle objekti kõigi tuvastuste keskmist klassifitseerimise usaldusväärsust." + }, + "button": { + "deleteClassificationAttempts": "Kustuta klassifitseerimispildid" + }, + "description": { + "invalidName": "Vigane nimi. Nimed võivad sisaldada ainult tähti, numbreid, tühikuid, ülakomasid, alakriipse ja sidekriipse." + }, + "deleteModel": { + "desc_one": "Kas oled kindel, et soovid kustutada {{count}} mudeli? Järgnevaga kustuvad jäädavalt kõik seotud andmed, sealhulgas pildid ja koolitusandmed. Seda tegevust ei saa tagasi pöörata.", + "desc_other": "Kas oled kindel, et soovid kustutada {{count}} mudelit? Järgnevaga kustuvad jäädavalt kõik seotud andmed, sealhulgas pildid ja koolitusandmed. Seda tegevust ei saa tagasi pöörata." + }, + "deleteDatasetImages": { + "desc_one": "Kas oled kindel, et soovid kustutada {{count}} pildi {{dataset}} andmekogust? Seda tegevust ei saa tagasi pöörata ja hiljem on vaja mudelit uuesti koolitada.", + "desc_other": "Kas oled kindel, et soovid kustutada {{count}} pilti {{dataset}} andmekogust? Seda tegevust ei saa tagasi pöörata ja hiljem on vaja mudelit uuesti koolitada." + }, + "deleteTrainImages": { + "desc_one": "Kas oled kindel, et soovid kustutada {{count}} pildi? Seda tegevust ei saa tagasi pöörata.", + "desc_other": "Kas oled kindel, et soovid kustutada {{count}} pilti? Seda tegevust ei saa tagasi pöörata." + }, + "wizard": { + "step3": { + "allImagesRequired_one": "Palun klassifitseeri kõik pildid. Jäänud on veel {{count}} pilt.", + "allImagesRequired_other": "Palun klassifitseeri kõik pildid. Jäänud on veel {{count}} pilti." + } } } diff --git a/web/public/locales/et/views/configEditor.json b/web/public/locales/et/views/configEditor.json index 0967ef424b..56371cd04d 100644 --- a/web/public/locales/et/views/configEditor.json +++ b/web/public/locales/et/views/configEditor.json @@ -1 +1,18 @@ -{} +{ + "toast": { + "error": { + "savingError": "Viga seadistuse salvestamisel" + }, + "success": { + "copyToClipboard": "Seadistused on kopeeritud lõikelauale." + } + }, + "documentTitle": "Seadistuste haldus - Frigate", + "safeConfigEditor": "Seadistuste haldus (ohutusrežiim)", + "configEditor": "Seadistuste haldus", + "safeModeDescription": "Seadistuste vea tõttu on Frigate hetkel ohutusrežiimis.", + "copyConfig": "Kopeeri seadistused", + "saveAndRestart": "Salvesta ja käivita uuesti", + "saveOnly": "Vaid salvesta", + "confirm": "Kas väljud ilma salvestamata?" +} diff --git a/web/public/locales/et/views/events.json b/web/public/locales/et/views/events.json index a9a849b49d..ef02cf0807 100644 --- a/web/public/locales/et/views/events.json +++ b/web/public/locales/et/views/events.json @@ -2,7 +2,17 @@ "alerts": "Häired", "allCameras": "Kõik kaamerad", "detail": { - "settings": "Üksikasjaliku vaate seadistused" + "settings": "Üksikasjaliku vaate seadistused", + "label": "Üksikasjad", + "noDataFound": "Ülevaatamiseks pole üksikasjalikke andmeid", + "aria": "Lülita üksikasjalik vaade sisse/välja", + "trackedObject_one": "{{count}} objekt", + "trackedObject_other": "{{count}} objekti", + "noObjectDetailData": "Objekti üksikasjalikke andmeid pole saadaval.", + "alwaysExpandActive": { + "title": "Alati laienda aktiivse kirje andmeid", + "desc": "Kui vähegi saadaval, siis alati laienda aktiivse ülevaatamisel kirje andmeid." + } }, "detections": "Tuvastamise tulemused", "motion": { @@ -26,6 +36,26 @@ "zoomOut": "Suumi välja", "events": { "label": "Sündmused", - "aria": "Vali sündmused" + "aria": "Vali sündmused", + "noFoundForTimePeriod": "Selle ajavahemiku kohta ei leidu sündmusi." + }, + "selected_one": "{{count}} valitud", + "selected_other": "{{count}} valitud", + "markAsReviewed": "Märgi ülevaadatuks", + "markTheseItemsAsReviewed": "Märgi need kirjed ülevaadatuks", + "newReviewItems": { + "label": "Vaata uusi ülevaatamiseks mõeldud kirjeid", + "button": "Uued ülevaatamiseks mõeldud kirjed" + }, + "documentTitle": "Ülevaatamine - Frigate", + "recordings": { + "documentTitle": "Salvestised - Frigate" + }, + "calendarFilter": { + "last24Hours": "Viimased 24 tundi" + }, + "objectTrack": { + "clickToSeek": "Klõpsa siia ajapunkti kerimiseks", + "trackedPoint": "Jälgitav punkt" } } diff --git a/web/public/locales/et/views/explore.json b/web/public/locales/et/views/explore.json index d31e353684..e383340568 100644 --- a/web/public/locales/et/views/explore.json +++ b/web/public/locales/et/views/explore.json @@ -6,6 +6,14 @@ "itemMenu": { "findSimilar": { "aria": "Otsi sarnaseid jälgitavaid objekte" + }, + "downloadSnapshot": { + "label": "Laadi hetkvõte alla", + "aria": "Laadi hetkvõte alla" + }, + "downloadCleanSnapshot": { + "label": "Laadi puhas hetkvõte alla", + "aria": "Laadi puhas hetkvõte alla" } }, "trackingDetails": { @@ -15,5 +23,34 @@ "desc": "Kui objekt on sisenenud tsooni, siis alati näida tsooni märgistust." } } + }, + "documentTitle": "Avasta - Frigate", + "generativeAI": "Generatiivne tehisaru", + "exploreMore": "Avasta rohkem {{label}}-tüüpi objekte", + "exploreIsUnavailable": { + "embeddingsReindexing": { + "step": { + "thumbnailsEmbedded": "Pisipildid on lõimitud: ", + "descriptionsEmbedded": "Kirjeldused on lõimitud: ", + "trackedObjectsProcessed": "Jälgitud objektid on töödeldud: " + } + } + }, + "type": { + "details": "üksikasjad", + "thumbnail": "pisipilt", + "snapshot": "hetkvõte" + }, + "details": { + "item": { + "tips": { + "mismatch_one": "Tuvastasin {{count}} võõra objekti ja need on lisatud ülevaatamiseks. Need objektid kas ei ole piisavad häire või tuvastamise jaoks, aga ka võivad juba olla eemaldatud või kustutatud.", + "mismatch_other": "Tuvastasin {{count}} võõrast objekti ja need on lisatud ülevaatamiseks. Need objektid kas ei ole piisavad häire või tuvastamise jaoks, aga ka võivad juba olla eemaldatud või kustutatud." + } + }, + "snapshotScore": { + "label": "Hetkvõttete punktiskoor" + }, + "regenerateFromSnapshot": "Loo uuesti hetkvõttest" } } diff --git a/web/public/locales/et/views/faceLibrary.json b/web/public/locales/et/views/faceLibrary.json index 68d072c7ee..adebe77888 100644 --- a/web/public/locales/et/views/faceLibrary.json +++ b/web/public/locales/et/views/faceLibrary.json @@ -1,5 +1,34 @@ { "button": { "uploadImage": "Laadi pilt üles" + }, + "collections": "Kogumikud", + "description": { + "placeholder": "Sisesta nimi selle kogumiku jaoks", + "invalidName": "Vigane nimi. Nimed võivad sisaldada ainult tähti, numbreid, tühikuid, ülakomasid, alakriipse ja sidekriipse.", + "addFace": "Laadides üles oma esimese pildi saad lisada uue kogumiku Näoteeki." + }, + "documentTitle": "Näoteek - Frigate", + "createFaceLibrary": { + "new": "Lisa uus nägu" + }, + "deleteFaceLibrary": { + "title": "Kustuta nimi" + }, + "toast": { + "error": { + "addFaceLibraryFailed": "Näo sidumine nimega ei õnnestunud: {{errorMessage}}" + }, + "success": { + "addFaceLibrary": "Lisamine Näoteeki õnnestus: {{name}}!", + "deletedFace_one": "{{count}} näo kustutamine õnnestus.", + "deletedFace_other": "{{count}} näo kustutamine õnnestus.", + "deletedName_one": "{{count}} näo kustutamine õnnestus.", + "deletedName_other": "{{count}} näo kustutamine õnnestus." + } + }, + "deleteFaceAttempts": { + "desc_one": "Kas oled kindel, et soovid kustutada {{count}} näo? Seda tegevust ei saa tagasi pöörata.", + "desc_other": "Kas oled kindel, et soovid kustutada {{count}} nägu? Seda tegevust ei saa tagasi pöörata." } } diff --git a/web/public/locales/et/views/live.json b/web/public/locales/et/views/live.json index b98e33517e..87a5696790 100644 --- a/web/public/locales/et/views/live.json +++ b/web/public/locales/et/views/live.json @@ -9,6 +9,115 @@ "cameraEnabled": "Kaamera on kasutusel", "objectDetection": "Objektide tuvastamine", "audioDetection": "Heli tuvastus", - "transcription": "Heli üleskirjutus" + "transcription": "Heli üleskirjutus", + "snapshots": "Hetkvõtted" + }, + "documentTitle": "Otseülekanne - Frigate", + "documentTitle.withCamera": "{{camera}} - Otseülekanne - Frigate", + "lowBandwidthMode": "Väikese ribalaiusega režiim", + "twoWayTalk": { + "enable": "Lülita kahepoolne kõneside sisse", + "disable": "Lülita kahepoolne kõneside välja" + }, + "cameraAudio": { + "enable": "Lülita kaamera heli sisse", + "disable": "Lülita kaamera heli välja" + }, + "ptz": { + "move": { + "clickMove": { + "label": "Kaamerapildi joondamiseks keskele klõpsa kaadris", + "enable": "Kasuta klõpsamisega teisaldamist", + "disable": "Ära kasuta klõpsamisega teisaldamist" + }, + "left": { + "label": "Pööra liigutatavat kaamerat vasakule" + }, + "up": { + "label": "Pööra liigutatavat kaamerat üles" + }, + "down": { + "label": "Pööra liigutatavat kaamerat alla" + }, + "right": { + "label": "Pööra liigutatavat kaamerat paremale" + } + }, + "zoom": { + "in": { + "label": "Suumi liigutatavat kaamerat sisse" + }, + "out": { + "label": "Suumi liigutatavat kaamerat välja" + } + }, + "focus": { + "in": { + "label": "Fookusta liigutatavat kaamerat sisse" + }, + "out": { + "label": "Fookusta liigutatavat kaamerat välja" + } + }, + "presets": "Liigutatava kaamera eelseadistused", + "frame": { + "center": { + "label": "Klõpsa kaadrit liigutatava kaamera pildi sättimiseks keskele" + } + } + }, + "camera": { + "enable": "Lülita kaamera sisse", + "disable": "Lülita kaamera välja" + }, + "detect": { + "enable": "Lülita tuvastamine sisse", + "disable": "Lülita tuvastamine välja" + }, + "recording": { + "enable": "Lülita salvestamine sisse", + "disable": "Lülita salvestamine välja" + }, + "snapshots": { + "enable": "Lülita hetkvõtted sisse", + "disable": "Lülita hetkvõtted välja" + }, + "streamStats": { + "enable": "Näita voogedastuse statistikat", + "disable": "Peida voogedastuse statistika" + }, + "stream": { + "twoWayTalk": { + "available": "Kahepoolne kõneside on selle voogedastuse puhul saadaval", + "unavailable": "Kahepoolne kõneside pole selle voogedastuse puhul saadaval", + "tips": "Sinu seadme peab seda funktsionaalsust toetama ja WebRTC peab olema kahepoolse kõneside jaoks seadistatud." + } + }, + "notifications": "Teavitused", + "audio": "Heli", + "snapshot": { + "takeSnapshot": "Laadi hetkvõte alla", + "noVideoSource": "Hetkvõtte tegemiseks pole saadaval ühtegi videoallikat.", + "captureFailed": "Hetkvõtte jäädvustamine ei õnnestunud.", + "downloadStarted": "Hetkvõtte allalaadimine algas." + }, + "audioDetect": { + "enable": "Lülita helituvastus sisse", + "disable": "Lülita helituvastus välja" + }, + "transcription": { + "enable": "Lülita reaalajas heli üleskirjutus sisse", + "disable": "Lülita reaalajas heli üleskirjutus välja" + }, + "autotracking": { + "enable": "Lülita automaatne jälgimine sisse", + "disable": "Lülita automaatne jälgimine välja" + }, + "manualRecording": { + "title": "Nõudmisel", + "playInBackground": { + "label": "Esita taustal", + "desc": "Kasuta seda valikut, kui tahad voogedastuse jätkumist ka siis, kui pildivaade on peidetud." + } } } diff --git a/web/public/locales/et/views/search.json b/web/public/locales/et/views/search.json index 0a99c5460e..d691c563ee 100644 --- a/web/public/locales/et/views/search.json +++ b/web/public/locales/et/views/search.json @@ -2,5 +2,17 @@ "placeholder": { "search": "Otsi…" }, - "search": "Otsi" + "search": "Otsi", + "savedSearches": "Salvestatud otsingud", + "searchFor": "Otsi: {{inputValue}}", + "button": { + "clear": "Tühjenda otsing", + "save": "Salvesta otsing", + "delete": "Kustuta salvestatud otsing" + }, + "filter": { + "label": { + "has_snapshot": "Leidub hetkvõte" + } + } } diff --git a/web/public/locales/et/views/settings.json b/web/public/locales/et/views/settings.json index 71d987d583..c3398cf7bb 100644 --- a/web/public/locales/et/views/settings.json +++ b/web/public/locales/et/views/settings.json @@ -8,10 +8,18 @@ }, "step3": { "streamUrlPlaceholder": "rtsp://kasutajanimi:salasõna@host:port/asukoht" + }, + "steps": { + "probeOrSnapshot": "Võta proov või tee hetkvõte" + }, + "step2": { + "testing": { + "fetchingSnapshot": "Laadin kaamera hetkvõtet alla..." + } } }, "users": { - "updatePassword": "Muuda salasõna", + "updatePassword": "Lähtesta salasõna", "toast": { "success": { "updatePassword": "Salasõna muutmine õnnestus." @@ -21,7 +29,7 @@ } }, "table": { - "password": "Salasõna" + "password": "Lähtesta salasõna" }, "dialog": { "form": { @@ -90,12 +98,21 @@ "cameraReview": "Kaamerate kordusvaatuste seadistused - Frigate", "general": "Kasutajaliidese seadistused - Frigate", "frigatePlus": "Frigate+ seadistused - Frigate", - "notifications": "Teavituste seadistused - Frigate" + "notifications": "Teavituste seadistused - Frigate", + "cameraManagement": "Kaamerate haldus - Frigate", + "masksAndZones": "Maskide ja tsoonide haldus - Frigate", + "object": "Silumine ja veaotsing - Frigate" }, "general": { "title": "Kasutajaliidese seadistused", "cameraGroupStreaming": { "clearAll": "Kustuta kõik voogedastuse seadistused" + }, + "liveDashboard": { + "title": "Töölaud reaalajas", + "automaticLiveView": { + "label": "Automaatne otseülekande vaade" + } } }, "cameraManagement": { @@ -120,6 +137,14 @@ "unsavedChanges": "Frigate+ seadistuste muudatused on salvestamata", "toast": { "success": "Frigate+ seadistuste muudatused on salvestatud. Muudatuste kasutuselevõtmiseks käivita Frigate uuesti." + }, + "snapshotConfig": { + "title": "Hetkvõtte seadistused", + "table": { + "snapshots": "Hetkvõtted", + "cleanCopySnapshots": "clean_copy Hetkvõtted", + "camera": "Kaamera" + } } }, "masksAndZones": { @@ -135,5 +160,34 @@ "point_one": "{{count}} punkt", "point_other": "{{count}} punkti" } + }, + "roles": { + "toast": { + "success": { + "userRolesUpdated_one": "{{count}} selle rolliga kasutaja on nüüd määratud Vaatajaks, kellel on ligipääs kõikidele kaameratele.", + "userRolesUpdated_other": "{{count}} selle rolliga kasutajat on nüüd määratud Vaatajaks, kellel on ligipääs kõikidele kaameratele." + } + } + }, + "menu": { + "ui": "Kasutajaliides", + "cameraManagement": "Haldus", + "masksAndZones": "Maskid ja tsoonid", + "triggers": "Päästikud", + "debug": "Silumine ja veaotsing", + "users": "Kasutajad", + "roles": "Rollid", + "notifications": "Teavitused", + "frigateplus": "Frigate+" + }, + "dialog": { + "unsavedChanges": { + "title": "Sul on salvestamata muudatusi.", + "desc": "Kas soovid muudatused enne jätkamist salvestada?" + } + }, + "cameraSetting": { + "camera": "Kaamera", + "noCamera": "Kaamerat pole" } } diff --git a/web/public/locales/et/views/system.json b/web/public/locales/et/views/system.json index 2a258b6e6f..deb38f24b5 100644 --- a/web/public/locales/et/views/system.json +++ b/web/public/locales/et/views/system.json @@ -1,5 +1,12 @@ { "documentTitle": { - "general": "Üldine statistika - Frigate" + "general": "Üldine statistika - Frigate", + "cameras": "Kaamerate statistika - Frigate", + "storage": "Andmeruumi statistika - Frigate" + }, + "logs": { + "download": { + "label": "Laadi logid alla" + } } } From 32429688ff459d1ee3324e57193ece9492bedf84 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:06 +0100 Subject: [PATCH 45/78] Translated using Weblate (Russian) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Russian) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Russian) Currently translated at 100.0% (654 of 654 strings) Translated using Weblate (Russian) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Russian) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Russian) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Russian) Currently translated at 99.1% (121 of 122 strings) Translated using Weblate (Russian) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Russian) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Russian) Currently translated at 100.0% (53 of 53 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Artem Vladimirov Co-authored-by: Gatis Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ru/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ru/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ru/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ru/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ru/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ru/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ru/ Translation: Frigate NVR/common Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/ru/common.json | 7 +- web/public/locales/ru/components/dialog.json | 3 +- web/public/locales/ru/components/filter.json | 4 + .../locales/ru/views/classificationModel.json | 36 +++++-- web/public/locales/ru/views/events.json | 10 +- web/public/locales/ru/views/explore.json | 25 +++-- web/public/locales/ru/views/faceLibrary.json | 3 +- web/public/locales/ru/views/live.json | 16 +++- web/public/locales/ru/views/search.json | 3 +- web/public/locales/ru/views/settings.json | 94 ++++++++++++++----- web/public/locales/ru/views/system.json | 21 ++++- 11 files changed, 168 insertions(+), 54 deletions(-) diff --git a/web/public/locales/ru/common.json b/web/public/locales/ru/common.json index 341cad2360..0de87d293e 100644 --- a/web/public/locales/ru/common.json +++ b/web/public/locales/ru/common.json @@ -88,7 +88,9 @@ "12hour": "d MMM, yyyy", "24hour": "d MMM, yyyy" }, - "inProgress": "В процессе" + "inProgress": "В процессе", + "invalidStartTime": "Некорректное время начала", + "invalidEndTime": "Некорректное время окончания" }, "selectItem": "Выбрать {{item}}", "button": { @@ -126,7 +128,8 @@ "unselect": "Снять выбор", "export": "Экспортировать", "deleteNow": "Удалить сейчас", - "next": "Следующий" + "next": "Следующий", + "continue": "Продолжить" }, "label": { "back": "Вернуться", diff --git a/web/public/locales/ru/components/dialog.json b/web/public/locales/ru/components/dialog.json index a1dc88e86e..b935670c2e 100644 --- a/web/public/locales/ru/components/dialog.json +++ b/web/public/locales/ru/components/dialog.json @@ -70,7 +70,8 @@ "failed": "Не удалось запустить экспорт: {{error}}", "noVaildTimeSelected": "Не выбран допустимый временной диапазон", "endTimeMustAfterStartTime": "Время окончания должно быть после времени начала" - } + }, + "view": "Просмотр" }, "fromTimeline": { "saveExport": "Сохранить экспорт", diff --git a/web/public/locales/ru/components/filter.json b/web/public/locales/ru/components/filter.json index 0b75d2347d..095ea91ba4 100644 --- a/web/public/locales/ru/components/filter.json +++ b/web/public/locales/ru/components/filter.json @@ -133,5 +133,9 @@ }, "count_one": "{{count}} класс", "count_other": "{{count}} классы" + }, + "attributes": { + "label": "Атрибуты классификации", + "all": "Все атрибуты" } } diff --git a/web/public/locales/ru/views/classificationModel.json b/web/public/locales/ru/views/classificationModel.json index 1017128fe7..b5b7e22228 100644 --- a/web/public/locales/ru/views/classificationModel.json +++ b/web/public/locales/ru/views/classificationModel.json @@ -1,7 +1,9 @@ { - "documentTitle": "Модели классификации", + "documentTitle": "Классификация моделей - Frigate", "details": { - "scoreInfo": "Оценка представляет собой среднюю степень достоверности классификации по всем обнаружениям данного объекта." + "scoreInfo": "Оценка представляет собой среднюю степень достоверности классификации по всем обнаружениям данного объекта.", + "none": "Нет", + "unknown": "Неизвестно" }, "button": { "deleteClassificationAttempts": "Удалить изображения классификации", @@ -31,7 +33,7 @@ "deleteCategoryFailed": "Не удалось удалить класс: {{errorMessage}}", "deleteModelFailed": "Не удалось удалить модель: {{errorMessage}}", "categorizeFailed": "Не удалось классифицировать изображение: {{errorMessage}}", - "trainingFailed": "Не удалось начать обучение модели: {{errorMessage}}", + "trainingFailed": "Ошибка обучения модели. Проверьте логи Frigate для получения подробной информации.", "updateModelFailed": "Не удалось обновить модель: {{errorMessage}}", "renameCategoryFailed": "Не удалось переименовать класс: {{errorMessage}}", "trainingFailedToStart": "Не удалось начать обучение модели: {{errorMessage}}" @@ -39,7 +41,9 @@ }, "deleteCategory": { "title": "Удалить класс", - "desc": "Вы уверены, что хотите удалить класс {{name}}? Это приведёт к безвозвратному удалению всех связанных с ним изображений и потребует повторного обучения модели." + "desc": "Вы уверены, что хотите удалить класс {{name}}? Это приведёт к безвозвратному удалению всех связанных с ним изображений и потребует повторного обучения модели.", + "minClassesTitle": "Не удалось удалить класс", + "minClassesDesc": "Модель классификации должна содержать как минимум 2 класса. Добавьте ещё один класс перед удалением этого." }, "deleteModel": { "title": "Удалить модель классификации", @@ -75,7 +79,7 @@ }, "train": { "title": "Недавние классификации", - "titleShort": "Недавние", + "titleShort": "Недавнее", "aria": "Выбрать недавние классификации" }, "categories": "Классы", @@ -135,7 +139,8 @@ "classesUnique": "Имена классов должны быть уникальными", "stateRequiresTwoClasses": "Модели состояний требуют не менее 2 классов", "objectLabelRequired": "Пожалуйста, выберите метку объекта", - "objectTypeRequired": "Пожалуйста, выберите тип классификации" + "objectTypeRequired": "Пожалуйста, выберите тип классификации", + "noneNotAllowed": "Класс 'нет' не допускается" } }, "step2": { @@ -167,7 +172,22 @@ "generationFailed": "Генерация не удалась. Пожалуйста, попробуйте снова.", "classifyFailed": "Не удалось классифицировать изображения: {{error}}" }, - "generateSuccess": "Примеры изображений успешно сгенерированы" + "generateSuccess": "Примеры изображений успешно сгенерированы", + "allImagesRequired_one": "Пожалуйста, классифицируйте все изображения. Осталось {{count}} изображение.", + "allImagesRequired_few": "Пожалуйста, классифицируйте все изображения. Осталось {{count}} изображения.", + "allImagesRequired_many": "Пожалуйста, классифицируйте все изображения. Осталось {{count}} изображений.", + "modelCreated": "Модель успешно создана. Используйте раздел \"Последние классификации\", чтобы добавить изображения для отсутствующих состояний, а затем обучите модель.", + "missingStatesWarning": { + "title": "Примеры отсутствующих состояний", + "description": "Рекомендуется выбрать примеры для всех состояний для достижения наилучших результатов. Вы можете продолжить, не выбрав все состояния, но модель не будет обучена, пока для всех состояний не появятся изображения. После продолжения используйте раздел «Последние классификации», чтобы классифицировать изображения для отсутствующих состояний, а затем обучите модель." + } } - } + }, + "tooltip": { + "trainingInProgress": "Модель в данный момент обучается", + "noNewImages": "Нет новых изображений для обучения. Сначала классифицируйте больше изображений в наборе данных.", + "noChanges": "В наборе данных не было изменений с момента последнего обучения.", + "modelNotReady": "Модель не готова к обучению" + }, + "none": "Нет" } diff --git a/web/public/locales/ru/views/events.json b/web/public/locales/ru/views/events.json index c54e542624..16fe307ca8 100644 --- a/web/public/locales/ru/views/events.json +++ b/web/public/locales/ru/views/events.json @@ -41,8 +41,8 @@ "detail": { "noDataFound": "Нет данных для просмотра", "aria": "Переключить подробный режим просмотра", - "trackedObject_one": "объект", - "trackedObject_other": "объекты", + "trackedObject_one": "{{count}} объект", + "trackedObject_other": "{{count}} объекта", "noObjectDetailData": "Данные о деталях объекта недоступны.", "label": "Деталь", "settings": "Настройки подробного просмотра", @@ -56,5 +56,9 @@ "clickToSeek": "Перейти к этому моменту" }, "zoomIn": "Увеличить", - "zoomOut": "Отдалить" + "zoomOut": "Отдалить", + "select_all": "Всё", + "normalActivity": "Нормальный", + "needsReview": "Требуется ревью", + "securityConcern": "Вопрос безопасности" } diff --git a/web/public/locales/ru/views/explore.json b/web/public/locales/ru/views/explore.json index 18c211a95e..3247544f50 100644 --- a/web/public/locales/ru/views/explore.json +++ b/web/public/locales/ru/views/explore.json @@ -49,13 +49,15 @@ "updatedSublabel": "Успешно обновлена дополнительная метка.", "updatedLPR": "Номерной знак успешно обновлён.", "regenerate": "Новое описание запрошено у {{provider}}. В зависимости от скорости работы вашего провайдера, генерация нового описания может занять некоторое время.", - "audioTranscription": "Запрос на транскрипцию звука успешно выполнен." + "audioTranscription": "Запрос на расшифровку аудио успешно отправлен. В зависимости от скорости вашего сервера Frigate, расшифровка может занять некоторое время.", + "updatedAttributes": "Атрибуты успешно обновлены." }, "error": { "updatedSublabelFailed": "Не удалось обновить дополнительную метку: {{errorMessage}}", "updatedLPRFailed": "Не удалось обновить номерной знак: {{errorMessage}}", "regenerate": "Не удалось запросить новое описание у {{provider}}: {{errorMessage}}", - "audioTranscription": "Не удалось запросить транскрипцию аудио: {{errorMessage}}" + "audioTranscription": "Не удалось запросить транскрипцию аудио: {{errorMessage}}", + "updatedAttributesFailed": "Не удалось обновить атрибуты: {{errorMessage}}" } } }, @@ -103,7 +105,12 @@ }, "score": { "label": "Оценка" - } + }, + "editAttributes": { + "title": "Редактировать атрибуты", + "desc": "Выберите атрибуты классификации для этого {{label}}" + }, + "attributes": "Атрибуты классификации" }, "trackedObjectDetails": "Детали объекта", "type": { @@ -111,7 +118,8 @@ "snapshot": "снимок", "video": "видео", "object_lifecycle": "жизненный цикл объекта", - "thumbnail": "миниатюра" + "thumbnail": "миниатюра", + "tracking_details": "подробности отслеживания" }, "objectLifecycle": { "title": "Жизненный цикл объекта", @@ -207,6 +215,10 @@ }, "hideObjectDetails": { "label": "Скрыть путь объекта" + }, + "downloadCleanSnapshot": { + "label": "Скачать чистый снимок", + "aria": "Скачать чистый снимок" } }, "dialog": { @@ -262,7 +274,8 @@ "header": { "zones": "Зоны", "ratio": "Соотношение", - "area": "Область" + "area": "Область", + "score": "Оценка" } }, "annotationSettings": { @@ -277,7 +290,7 @@ "millisecondsToOffset": "Смещение аннотаций детекции в миллисекундах. По умолчанию: 0", "tips": "Уменьшите значение, если воспроизведение видео опережает рамки и точки пути, и увеличьте значение, если воспроизведение видео отстаёт от них. Это значение может быть отрицательным.", "toast": { - "success": "Смещение аннотаций для {{camera}} сохранено в конфигурационном файле. Перезапустите Frigate, чтобы применить изменения." + "success": "Смещение аннотаций для {{camera}} сохранено в конфигурационном файле." } } }, diff --git a/web/public/locales/ru/views/faceLibrary.json b/web/public/locales/ru/views/faceLibrary.json index ee8d702fb5..90aa901d18 100644 --- a/web/public/locales/ru/views/faceLibrary.json +++ b/web/public/locales/ru/views/faceLibrary.json @@ -30,7 +30,8 @@ "train": { "aria": "Выберите последние распознавания", "title": "Последние распознавания", - "empty": "Нет недавних попыток распознавания лиц" + "empty": "Нет недавних попыток распознавания лиц", + "titleShort": "Недавнее" }, "toast": { "success": { diff --git a/web/public/locales/ru/views/live.json b/web/public/locales/ru/views/live.json index 8a189bf766..9cda2d3c93 100644 --- a/web/public/locales/ru/views/live.json +++ b/web/public/locales/ru/views/live.json @@ -87,7 +87,7 @@ }, "manualRecording": { "title": "По требованию", - "tips": "Создать ручное событие на основе настроек хранения записей этой камеры.", + "tips": "Скачать моментальный снимок или создать ручное событие, исходя из настроек хранения записей для этой камеры.", "playInBackground": { "label": "Воспроизведение в фоне", "desc": "Включите эту опцию, чтобы продолжать трансляцию при скрытом плеере." @@ -134,7 +134,7 @@ "tips": "Включите эту опцию, чтобы продолжать трансляцию при скрытом плеере." }, "debug": { - "picker": "В режиме отладки выбор потока камеры недоступен. Вид отладчика всегда использует поток настроенный для режима обнаружения." + "picker": "Выбор потока недоступен в режиме отладки. В отладочном представлении всегда используется поток, назначенный на роль обнаружения." } }, "cameraSettings": { @@ -172,12 +172,18 @@ "disable": "Выключить транскрипцию звука" }, "snapshot": { - "noVideoSource": "Нет видеоисточника для снимка", - "captureFailed": "Не удалось сделать снимок." + "noVideoSource": "Нет видеоисточника для снимка.", + "captureFailed": "Не удалось сделать снимок.", + "takeSnapshot": "Скачать моментальный снимок", + "downloadStarted": "Загрузка снимка началась." }, "noCameras": { "title": "Камеры не настроены", "description": "Начните с подключения камеры к Frigate.", - "buttonText": "Добавить камеру" + "buttonText": "Добавить камеру", + "restricted": { + "title": "Нет доступных камер", + "description": "У вас нет разрешения на просмотр камер в этой группе." + } } } diff --git a/web/public/locales/ru/views/search.json b/web/public/locales/ru/views/search.json index 0c7f8477f8..cf90fb1527 100644 --- a/web/public/locales/ru/views/search.json +++ b/web/public/locales/ru/views/search.json @@ -26,7 +26,8 @@ "max_speed": "Макс. скорость", "has_clip": "Есть клип", "has_snapshot": "Есть снимок", - "labels": "Метки" + "labels": "Метки", + "attributes": "Атрибуты" }, "searchType": { "thumbnail": "Миниатюра", diff --git a/web/public/locales/ru/views/settings.json b/web/public/locales/ru/views/settings.json index 7044dc7e89..504c51178a 100644 --- a/web/public/locales/ru/views/settings.json +++ b/web/public/locales/ru/views/settings.json @@ -167,7 +167,12 @@ "setPassword": "Установить пароль", "desc": "Создайте надежный пароль для защиты аккаунта.", "cannotBeEmpty": "Пароль не может быть пустым", - "doNotMatch": "Пароли не совпадают" + "doNotMatch": "Пароли не совпадают", + "currentPasswordRequired": "Текущий пароль обязателен", + "incorrectCurrentPassword": "Текущий пароль указан неверно", + "passwordVerificationFailed": "Не удалось проверить пароль", + "multiDeviceWarning": "Все остальные устройства, на которых вы вошли в систему, потребуют повторного входа в течение {{refresh_time}}.", + "multiDeviceAdmin": "Вы также можете принудительно заставить всех пользователей повторно пройти аутентификацию немедленно, обновив свой JWT-секрет." }, "deleteUser": { "warn": "Вы уверены, что хотите удалить пользователя {{username}}?", @@ -182,7 +187,8 @@ "viewer": "Наблюдатель", "viewerDesc": "Доступны только панель мониторинга, обзор событий, поиск и экспорт данных.", "admin": "Администратор", - "adminDesc": "Полный доступ ко всем функциям." + "adminDesc": "Полный доступ ко всем функциям.", + "customDesc": "Роль с настраиваемыми правами доступа к определённым камерам." }, "select": "Выбрать роль" }, @@ -207,7 +213,16 @@ "veryStrong": "Очень сложный" }, "match": "Пароли совпадают", - "notMatch": "Пароли не совпадают" + "notMatch": "Пароли не совпадают", + "show": "Показать пароль", + "hide": "Скрыть пароль", + "requirements": { + "title": "Требования к паролю:", + "length": "Не менее 8 символов", + "uppercase": "Как минимум одна заглавная буква", + "digit": "Как минимум одна цифра", + "special": "Хотя бы один специальный символ (!@#$%^&*(),.?\":{}|<>)" + } }, "newPassword": { "title": "Новый пароль", @@ -217,7 +232,11 @@ "placeholder": "Введите новый пароль" }, "usernameIsRequired": "Необходимо ввести имя пользователя", - "passwordIsRequired": "Требуется пароль" + "passwordIsRequired": "Требуется пароль", + "currentPassword": { + "title": "Текущий пароль", + "placeholder": "Введите ваш текущий пароль" + } }, "createUser": { "title": "Создать нового пользователя", @@ -244,7 +263,7 @@ "table": { "username": "Имя пользователя", "actions": "Действия", - "password": "Пароль", + "password": "Сбросить пароль", "noUsers": "Пользователей не найдено.", "changeRole": "Изменить роль пользователя", "role": "Роль", @@ -254,7 +273,7 @@ "title": "Управление пользователями", "desc": "Управление учетными записями пользователей Frigate." }, - "updatePassword": "Обновить пароль", + "updatePassword": "Сбросить пароль", "addUser": "Добавить пользователя" }, "notification": { @@ -414,7 +433,7 @@ "name": { "title": "Название", "inputPlaceHolder": "Введите название…", - "tips": "Имя должно содержать не менее 2 символов, включать хотя бы одну букву и не совпадать с названием камеры или другой зоны." + "tips": "Имя должно содержать не менее 2 символов, включать хотя бы одну букву и не должно совпадать с названием камеры или другой зоны на этой камере." }, "inertia": { "title": "Инерция", @@ -436,7 +455,7 @@ "desc": "Задаёт минимальную скорость объектов для учёта в этой зоне." }, "toast": { - "success": "Зона ({{zoneName}}) сохранена. Перезапустите Frigate для применения изменений." + "success": "Зона ({{zoneName}}) сохранена." } }, "motionMasks": { @@ -463,8 +482,8 @@ "documentTitle": "Редактирование маски движения - Frigate", "toast": { "success": { - "title": "{{polygonName}} сохранена. Перезапустите Frigate для применения изменений.", - "noName": "Маска движения сохранена. Перезапустите Frigate для применения изменений." + "title": "{{polygonName}} сохранена.", + "noName": "Маска движения сохранена." } } }, @@ -551,8 +570,8 @@ }, "toast": { "success": { - "title": "{{polygonName}} сохранена. Перезапустите Frigate для применения изменений.", - "noName": "Маска объектов сохранена. Перезапустите Frigate для применения изменений." + "title": "{{polygonName}} сохранена.", + "noName": "Маска объектов сохранена." } } }, @@ -753,7 +772,7 @@ "triggers": { "documentTitle": "Триггеры", "management": { - "title": "Управление триггерами", + "title": "Триггеры", "desc": "Управление триггерами для камеры {{camera}}. Используйте тип миниатюры для срабатывания по миниатюрам, похожим на выбранный отслеживаемый объект, и тип описания для срабатывания по описаниям, похожим на указанный вами текст." }, "addTrigger": "Добавить Триггер", @@ -774,7 +793,9 @@ }, "actions": { "alert": "Отметить как предупреждение", - "notification": "Отправить оповещение" + "notification": "Отправить оповещение", + "sub_label": "Добавить подметку", + "attribute": "Добавить атрибут" }, "dialog": { "createTrigger": { @@ -792,25 +813,28 @@ "form": { "name": { "title": "Имя", - "placeholder": "Введите имя триггера", + "placeholder": "Назовите этот триггер", "error": { - "minLength": "Имя должно быть длиной не менее 2 символов.", - "invalidCharacters": "Имя может содержать только буквы, цифры, символы подчеркивания и дефисы.", + "minLength": "Поле должно содержать не менее 2 символов.", + "invalidCharacters": "Поле может содержать только буквы, цифры, символы подчеркивания и дефисы.", "alreadyExists": "Триггер с таким именем уже существует для этой камеры." - } + }, + "description": "Введите уникальное имя или описание для идентификации этого триггера" }, "enabled": { "description": "Включить или отключить этот триггер" }, "type": { "title": "Тип", - "placeholder": "Выберите тип триггера" + "placeholder": "Выберите тип триггера", + "description": "Срабатывать при обнаружении похожего описания отслеживаемого объекта", + "thumbnail": "Срабатывать при обнаружении похожей миниатюры отслеживаемого объекта" }, "content": { "title": "Содержимое", - "imagePlaceholder": "Выберите изображение", + "imagePlaceholder": "Выберите миниатюру", "textPlaceholder": "Введите текстовое содержимое", - "imageDesc": "Выберите изображение, чтобы активировать это действие при обнаружении похожего изображения.", + "imageDesc": "Отображаются только 100 последних миниатюр. Если вы не можете найти нужную миниатюру, просмотрите предыдущие объекты в разделе \"Обзор\" и настройте триггер оттуда через меню.", "textDesc": "Введите текст, чтобы активировать это действие при обнаружении похожего описания отслеживаемого объекта.", "error": { "required": "Требуется содержимое." @@ -821,11 +845,12 @@ "error": { "min": "Порог должен быть не менее 0", "max": "Порог должен быть не более 1" - } + }, + "desc": "Установите порог схожести для этого триггера. Более высокое значение требует более точного совпадения для срабатывания триггера." }, "actions": { "title": "Действия", - "desc": "По умолчанию Frigate отправляет MQTT-сообщение для всех триггеров. Выберите дополнительное действие, которое будет выполняться при срабатывании этого триггера.", + "desc": "По умолчанию Frigate отправляет MQTT-сообщение для всех триггеров. Подметки добавляют имя триггера к метке объекта. Атрибуты — это доступные для поиска метаданные, хранящиеся отдельно в метаданных отслеживаемого объекта.", "error": { "min": "Необходимо выбрать хотя бы одно действие." } @@ -852,6 +877,23 @@ "semanticSearch": { "title": "Семантический поиск выключен", "desc": "Для использования триггеров необходимо включить семантический поиск." + }, + "wizard": { + "title": "Создать триггер", + "step1": { + "description": "Настройте основные параметры вашего триггера." + }, + "step2": { + "description": "Настройте содержимое, которое будет активировать это действие." + }, + "step3": { + "description": "Настройте порог и действия для этого триггера." + }, + "steps": { + "nameAndType": "Имя и тип", + "configureData": "Настроить данные", + "thresholdAndActions": "Порог и действия" + } } }, "cameraWizard": { @@ -878,7 +920,7 @@ "testFailed": "Тест потока не удался: {{error}}" }, "step1": { - "description": "Введите данные камеры и проверьте подключение.", + "description": "Введите параметры вашей камеры и выберите: автоматическое определение или ручной выбор производителя.", "cameraName": "Имя камеры", "cameraNamePlaceholder": "Например, front_door или Обзор заднего двора", "host": "Хост/IP-адрес", @@ -1134,6 +1176,10 @@ "required": "Необходимо выбрать хотя бы одну камеру." } } + }, + "management": { + "title": "Управление ролями наблюдателя", + "desc": "Управление пользовательскими ролями наблюдателя и их правами доступа к камерам для этого экземпляра Frigate." } }, "cameraManagement": { diff --git a/web/public/locales/ru/views/system.json b/web/public/locales/ru/views/system.json index ad2b914ac3..f3f7a3d95c 100644 --- a/web/public/locales/ru/views/system.json +++ b/web/public/locales/ru/views/system.json @@ -76,7 +76,12 @@ } }, "npuMemory": "Память NPU", - "npuUsage": "Использование NPU" + "npuUsage": "Использование NPU", + "intelGpuWarning": { + "title": "Предупреждение: статистика Intel GPU", + "message": "Статистика GPU недоступна", + "description": "Это известная ошибка в инструментах отчетности статистики Intel GPU (intel_gpu_top), из-за которой они ломаются и постоянно возвращают уровень использования GPU 0%, даже в случаях, когда аппаратное ускорение и обнаружение объектов корректно работают на (i)GPU. Это не ошибка Frigate. Вы можете перезапустить хост-систему, чтобы временно устранить проблему и убедиться, что GPU работает правильно. На производительность это не влияет." + } }, "otherProcesses": { "title": "Другие процессы", @@ -180,7 +185,17 @@ "yolov9_plate_detection": "Обнаружение номеров YOLOv9", "face_recognition": "Распознавание лиц", "plate_recognition": "Распознавание номеров", - "image_embedding": "Векторизация изображений" - } + "image_embedding": "Векторизация изображений", + "review_description": "Описание проверки", + "review_description_speed": "Скорость просмотра описания", + "review_description_events_per_second": "Описание проверки", + "object_description": "Описание объекта", + "object_description_speed": "Скорость описания объекта", + "object_description_events_per_second": "Описание объекта", + "classification": "{{name}} Классификация", + "classification_speed": "{{name}}Классификация скорости", + "classification_events_per_second": "{{name}} событий классификации в секунду" + }, + "averageInf": "Среднее время обработки" } } From f94aa0ff2c5233d9e9f95322ac2a0061248daeb2 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:08 +0100 Subject: [PATCH 46/78] Translated using Weblate (Romanian) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (654 of 654 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (214 of 214 strings) Co-authored-by: Hosted Weblate Co-authored-by: Liviu Roman Co-authored-by: lukasig Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ro/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ro/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ro/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ro/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ro/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ro/ Translation: Frigate NVR/common Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/ro/common.json | 4 ++-- web/public/locales/ro/components/filter.json | 4 ++++ .../locales/ro/views/classificationModel.json | 7 +++++-- web/public/locales/ro/views/explore.json | 13 ++++++++++--- web/public/locales/ro/views/search.json | 3 ++- web/public/locales/ro/views/settings.json | 10 ++++++---- web/public/locales/ro/views/system.json | 5 ++++- 7 files changed, 33 insertions(+), 13 deletions(-) diff --git a/web/public/locales/ro/common.json b/web/public/locales/ro/common.json index 3707fc392b..a70e1c06f5 100644 --- a/web/public/locales/ro/common.json +++ b/web/public/locales/ro/common.json @@ -1,8 +1,8 @@ { "time": { "untilForTime": "Până la {{time}}", - "untilForRestart": "Pana la repornirea Frigate.", - "untilRestart": "Pana la repornire", + "untilForRestart": "Până la repornirea Frigate.", + "untilRestart": "Până la repornire", "ago": "{{timeAgo}} în urmă", "justNow": "Acum", "today": "Astăzi", diff --git a/web/public/locales/ro/components/filter.json b/web/public/locales/ro/components/filter.json index 9130c01f20..74a65aa627 100644 --- a/web/public/locales/ro/components/filter.json +++ b/web/public/locales/ro/components/filter.json @@ -132,5 +132,9 @@ }, "count_one": "{{count}} Clasă", "count_other": "{{count}} Clase" + }, + "attributes": { + "label": "Atribute de clasificare", + "all": "Toate atributele" } } diff --git a/web/public/locales/ro/views/classificationModel.json b/web/public/locales/ro/views/classificationModel.json index 47b2c139e5..1ecc6018e4 100644 --- a/web/public/locales/ro/views/classificationModel.json +++ b/web/public/locales/ro/views/classificationModel.json @@ -116,7 +116,8 @@ "classesUnique": "Numele claselor trebuie să fie unice", "stateRequiresTwoClasses": "Modelele de stare necesită cel puțin 2 clase", "objectLabelRequired": "Vă rugăm să selectați o etichetă de obiect", - "objectTypeRequired": "Vă rugăm să selectați un tip de clasificare" + "objectTypeRequired": "Vă rugăm să selectați un tip de clasificare", + "noneNotAllowed": "Clasa 'niciuna' nu este permisă" }, "states": "Stări" }, @@ -172,7 +173,9 @@ "states": "Stări" }, "details": { - "scoreInfo": "Scorul reprezintă încrederea medie a clasificării pentru toate detecțiile acestui obiect." + "scoreInfo": "Scorul reprezintă încrederea medie a clasificării pentru toate detecțiile acestui obiect.", + "none": "Niciuna", + "unknown": "Necunoscut" }, "edit": { "title": "Editează modelul de clasificare", diff --git a/web/public/locales/ro/views/explore.json b/web/public/locales/ro/views/explore.json index 08eb56561b..fa1b228ad8 100644 --- a/web/public/locales/ro/views/explore.json +++ b/web/public/locales/ro/views/explore.json @@ -106,13 +106,15 @@ "regenerate": "O nouă descriere a fost solicitată de la {{provider}}. În funcție de viteza furnizorului tău, regenerarea noii descrieri poate dura ceva timp.", "updatedSublabel": "Subeticheta a fost actualizată cu succes.", "updatedLPR": "Plăcuța de înmatriculare a fost actualizată cu succes.", - "audioTranscription": "Transcrierea audio a fost solicitată cu succes. În funcție de viteza serverului dumneavoastră Frigate, transcrierea poate dura ceva timp până la finalizare." + "audioTranscription": "Transcrierea audio a fost solicitată cu succes. În funcție de viteza serverului dumneavoastră Frigate, transcrierea poate dura ceva timp până la finalizare.", + "updatedAttributes": "Atributele au fost actualizate cu succes." }, "error": { "updatedSublabelFailed": "Nu s-a putut actualiza sub-etichetarea: {{errorMessage}}", "updatedLPRFailed": "Plăcuța de înmatriculare nu a putut fi actualizată: {{errorMessage}}", "regenerate": "Eroare la apelarea {{provider}} pentru o nouă descriere: {{errorMessage}}", - "audioTranscription": "Solicitarea transcrierii audio a eșuat: {{errorMessage}}" + "audioTranscription": "Solicitarea transcrierii audio a eșuat: {{errorMessage}}", + "updatedAttributesFailed": "Actualizarea atributelor a eșuat: {{errorMessage}}" } } }, @@ -160,7 +162,12 @@ "regenerateFromThumbnails": "Regenerează din miniaturi", "score": { "label": "Scor" - } + }, + "editAttributes": { + "title": "Editează atribute", + "desc": "Selectează atributele de clasificare pentru acest {{label}}" + }, + "attributes": "Atribute de clasificare" }, "exploreMore": "Explorează mai multe obiecte cu {{label}}", "trackedObjectDetails": "Detalii despre obiectul urmărit", diff --git a/web/public/locales/ro/views/search.json b/web/public/locales/ro/views/search.json index 94d035a5e7..5c5f391e85 100644 --- a/web/public/locales/ro/views/search.json +++ b/web/public/locales/ro/views/search.json @@ -26,7 +26,8 @@ "max_speed": "Viteza maximă", "recognized_license_plate": "Număr de înmatriculare recunoscut", "has_clip": "Are videoclip", - "has_snapshot": "Are snapshot" + "has_snapshot": "Are snapshot", + "attributes": "Atribute" }, "tips": { "desc": { diff --git a/web/public/locales/ro/views/settings.json b/web/public/locales/ro/views/settings.json index 81b7326c6e..a85f852cdd 100644 --- a/web/public/locales/ro/views/settings.json +++ b/web/public/locales/ro/views/settings.json @@ -548,7 +548,8 @@ "currentPasswordRequired": "Parola curentă este obligatorie", "incorrectCurrentPassword": "Parola curentă incorectă", "passwordVerificationFailed": "Nu s-a putut verifica parola", - "multiDeviceWarning": "Oricare alte dispozitive unde ești autentificat vor fi nevoite să se relogheze în termen de {{refresh_time}}. De asemenea, poți forța toți utilizatorii să se re-autentifice imediat prin rotirea secretului JWT." + "multiDeviceWarning": "Orice alte dispozitive pe care ești autentificat vor trebui să se autentifice din nou în termen de {{refresh_time}}.", + "multiDeviceAdmin": "De asemenea, poți forța toți utilizatorii să se reautentifice imediat prin rotirea secretului tău JWT." } }, "addUser": "Adaugă utilizator", @@ -570,7 +571,7 @@ "deleteUserFailed": "Ștergerea utilizatorului a eșuat: {{errorMessage}}" } }, - "updatePassword": "Actualizează parola", + "updatePassword": "Resetează parola", "title": "Utilizatori", "table": { "username": "Nume utilizator", @@ -579,7 +580,7 @@ "noUsers": "Nu a fost găsit niciun utilizator.", "changeRole": "Schimbă rolul utilizatorului", "deleteUser": "Șterge utilizatorul", - "password": "Parolă" + "password": "Resetează parola" } }, "notification": { @@ -1157,7 +1158,8 @@ "audioCodecRequired": "Este necesar un stream audio pentru a suporta detecția audio.", "restreamingWarning": "Reducerea conexiunilor la cameră pentru stream-ul de înregistrare poate crește ușor utilizarea procesorului (CPU).", "brands": { - "reolink-rtsp": "RTSP Reolink nu este recomandat. Activați HTTP în setările de firmware ale camerei și reporniți asistentul." + "reolink-rtsp": "RTSP Reolink nu este recomandat. Activați HTTP în setările de firmware ale camerei și reporniți asistentul.", + "reolink-http": "Stream-urile HTTP Reolink ar trebui să folosească FFmpeg pentru o compatibilitate mai bună. Activează 'Use stream compatibility mode' pentru acest stream." }, "dahua": { "substreamWarning": "Substream-ul 1 este blocat la o rezoluție scăzută. Multe camere Dahua / Amcrest / EmpireTech suportă stream-uri secundare suplimentare care trebuie activate în setările camerei. Se recomandă să verificați și să utilizați aceste stream-uri dacă sunt disponibile." diff --git a/web/public/locales/ro/views/system.json b/web/public/locales/ro/views/system.json index 2584d85812..e64990bbad 100644 --- a/web/public/locales/ro/views/system.json +++ b/web/public/locales/ro/views/system.json @@ -134,7 +134,10 @@ "review_description_events_per_second": "Descriere Revizuire", "object_description": "Descriere Obiect", "object_description_speed": "Viteză Descriere Obiect", - "object_description_events_per_second": "Descriere Obiect" + "object_description_events_per_second": "Descriere Obiect", + "classification": "{{name}} Clasificare", + "classification_speed": "{{name}} Viteză de clasificare", + "classification_events_per_second": "{{name}} Evenimente de clasificare pe secundă" }, "infPerSecond": "Inferențe pe secundă", "averageInf": "Timp Mediu de Inferență" From 2522a10afbcbcb52adb62baa111c2d86f5e10dd9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:10 +0100 Subject: [PATCH 47/78] Translated using Weblate (Ukrainian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (121 of 121 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (654 of 654 strings) Co-authored-by: Hosted Weblate Co-authored-by: Максим Горпиніч Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/uk/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/uk/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/uk/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/uk/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/uk/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/uk/ Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/uk/components/filter.json | 4 ++++ .../locales/uk/views/classificationModel.json | 7 +++++-- web/public/locales/uk/views/explore.json | 13 ++++++++++--- web/public/locales/uk/views/search.json | 3 ++- web/public/locales/uk/views/settings.json | 4 ++-- web/public/locales/uk/views/system.json | 5 ++++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/web/public/locales/uk/components/filter.json b/web/public/locales/uk/components/filter.json index 5a29434afc..a99867c0c3 100644 --- a/web/public/locales/uk/components/filter.json +++ b/web/public/locales/uk/components/filter.json @@ -132,5 +132,9 @@ }, "count_one": "Клас {{count}}", "count_other": "{{count}} Класи" + }, + "attributes": { + "label": "Атрибути класифікації", + "all": "Усі атрибути" } } diff --git a/web/public/locales/uk/views/classificationModel.json b/web/public/locales/uk/views/classificationModel.json index b88ec48f7f..a96997bc74 100644 --- a/web/public/locales/uk/views/classificationModel.json +++ b/web/public/locales/uk/views/classificationModel.json @@ -116,7 +116,8 @@ "classesUnique": "Назви класів мають бути унікальними", "stateRequiresTwoClasses": "Моделі станів вимагають щонайменше 2 класів", "objectLabelRequired": "Будь ласка, виберіть мітку об'єкта", - "objectTypeRequired": "Будь ласка, виберіть тип класифікації" + "objectTypeRequired": "Будь ласка, виберіть тип класифікації", + "noneNotAllowed": "Клас «none» не дозволено" }, "states": "Штати" }, @@ -172,7 +173,9 @@ "states": "Стани" }, "details": { - "scoreInfo": "Оцінка представляє середню достовірність класифікації для всіх виявлень цього об'єкта." + "scoreInfo": "Оцінка представляє середню достовірність класифікації для всіх виявлень цього об'єкта.", + "none": "Жоден", + "unknown": "Невідомо" }, "edit": { "title": "Редагувати модель класифікації", diff --git a/web/public/locales/uk/views/explore.json b/web/public/locales/uk/views/explore.json index db7715f51f..d97e09ab69 100644 --- a/web/public/locales/uk/views/explore.json +++ b/web/public/locales/uk/views/explore.json @@ -104,13 +104,15 @@ "updatedLPR": "Номерний знак успішно оновлено.", "updatedSublabel": "Підмітку успішно оновлено.", "regenerate": "Новий опис було запрошено від {{provider}}. Залежно від швидкості вашого провайдера, його перегенерація може зайняти деякий час.", - "audioTranscription": "Запит на аудіотранскрипцію успішно надіслано. Залежно від швидкості вашого сервера Frigate, транскрипція може тривати деякий час." + "audioTranscription": "Запит на аудіотранскрипцію успішно надіслано. Залежно від швидкості вашого сервера Frigate, транскрипція може тривати деякий час.", + "updatedAttributes": "Атрибути успішно оновлено." }, "error": { "regenerate": "Не вдалося звернутися до {{provider}} для отримання нового опису: {{errorMessage}}", "updatedSublabelFailed": "Не вдалося оновити підмітку: {{errorMessage}}", "updatedLPRFailed": "Не вдалося оновити номерний знак: {{errorMessage}}", - "audioTranscription": "Не вдалося надіслати запит на транскрипцію аудіо: {{errorMessage}}" + "audioTranscription": "Не вдалося надіслати запит на транскрипцію аудіо: {{errorMessage}}", + "updatedAttributesFailed": "Не вдалося оновити атрибути: {{errorMessage}}" } }, "button": { @@ -165,7 +167,12 @@ "regenerateFromSnapshot": "Відновити зі знімка", "score": { "label": "Оцінка" - } + }, + "editAttributes": { + "title": "Редагувати атрибути", + "desc": "Виберіть атрибути класифікації для цього {{label}}" + }, + "attributes": "Атрибути класифікації" }, "dialog": { "confirmDelete": { diff --git a/web/public/locales/uk/views/search.json b/web/public/locales/uk/views/search.json index 0d8657e3dc..052b4c4579 100644 --- a/web/public/locales/uk/views/search.json +++ b/web/public/locales/uk/views/search.json @@ -34,7 +34,8 @@ "max_speed": "Максимальна швидкість", "recognized_license_plate": "Розпізнаний номерний знак", "has_clip": "Має клiп", - "has_snapshot": "Має знiмок" + "has_snapshot": "Має знiмок", + "attributes": "Атрибути" }, "searchType": { "thumbnail": "Мініатюра", diff --git a/web/public/locales/uk/views/settings.json b/web/public/locales/uk/views/settings.json index 811444be36..661196ec1e 100644 --- a/web/public/locales/uk/views/settings.json +++ b/web/public/locales/uk/views/settings.json @@ -596,7 +596,7 @@ "desc": "Керувати обліковими записами користувачів цього екземпляра Frigate." }, "addUser": "Додати користувача", - "updatePassword": "Оновити пароль", + "updatePassword": "Скинути пароль", "toast": { "success": { "deleteUser": "Користувач {{user}} успішно видалений", @@ -612,7 +612,7 @@ } }, "table": { - "password": "Пароль", + "password": "Скинути пароль", "deleteUser": "Видалити користувача", "username": "Ім'я користувача", "actions": "Дії", diff --git a/web/public/locales/uk/views/system.json b/web/public/locales/uk/views/system.json index d76a729a77..0e2a585118 100644 --- a/web/public/locales/uk/views/system.json +++ b/web/public/locales/uk/views/system.json @@ -63,7 +63,10 @@ "review_description_events_per_second": "Опис огляду", "object_description": "Опис об'єкта", "object_description_speed": "Опис об'єкта Швидкість", - "object_description_events_per_second": "Опис об'єкта" + "object_description_events_per_second": "Опис об'єкта", + "classification": "Класифікація {{name}}", + "classification_speed": "Швидкість класифікації {{name}}", + "classification_events_per_second": "{{name}} Подій класифікації за секунду" }, "title": "Збагаченням", "infPerSecond": "Висновки за секунду", From 29bcb7f47a531cdfa5d0a9191dbf674d32703782 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:11 +0100 Subject: [PATCH 48/78] Translated using Weblate (Japanese) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (92 of 92 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (654 of 654 strings) Translated using Weblate (Japanese) Currently translated at 92.4% (49 of 53 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (41 of 41 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (13 of 13 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (501 of 501 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Japanese) Currently translated at 7.3% (9 of 122 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (214 of 214 strings) Translated using Weblate (Japanese) Currently translated at 92.4% (49 of 53 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (55 of 55 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Japanese) Currently translated at 4.9% (6 of 122 strings) Co-authored-by: Hosted Weblate Co-authored-by: yhi264 Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ja/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ja/ Translation: Frigate NVR/audio Translation: Frigate NVR/common Translation: Frigate NVR/components-auth Translation: Frigate NVR/components-dialog Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-events Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-exports Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-live Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/ja/audio.json | 76 +++++- web/public/locales/ja/common.json | 27 +- web/public/locales/ja/components/auth.json | 3 +- web/public/locales/ja/components/dialog.json | 8 +- web/public/locales/ja/components/filter.json | 4 + .../locales/ja/views/classificationModel.json | 174 ++++++++++++- web/public/locales/ja/views/events.json | 25 +- web/public/locales/ja/views/explore.json | 86 +++++- web/public/locales/ja/views/exports.json | 6 + web/public/locales/ja/views/faceLibrary.json | 13 +- web/public/locales/ja/views/live.json | 6 +- web/public/locales/ja/views/search.json | 3 +- web/public/locales/ja/views/settings.json | 244 +++++++++++++++--- web/public/locales/ja/views/system.json | 21 +- 14 files changed, 633 insertions(+), 63 deletions(-) diff --git a/web/public/locales/ja/audio.json b/web/public/locales/ja/audio.json index c546c09b26..e049b882ef 100644 --- a/web/public/locales/ja/audio.json +++ b/web/public/locales/ja/audio.json @@ -425,5 +425,79 @@ "television": "テレビ", "radio": "ラジオ", "field_recording": "フィールド録音", - "scream": "悲鳴" + "scream": "悲鳴", + "sodeling": "ソデリング", + "chird": "チャープ", + "change_ringing": "着信音の変更", + "shofar": "ショファー", + "liquid": "液体", + "splash": "水しぶき", + "slosh": "水が揺れる音", + "squish": "ぐちゃっという音", + "drip": "滴る音", + "pour": "注ぐ", + "trickle": "ちょろちょろ流れる音", + "gush": "勢いよく噴き出す", + "fill": "満たす", + "spray": "噴霧", + "pump": "ポンプ", + "stir": "かき混ぜる", + "boiling": "沸騰", + "sonar": "ソナー", + "arrow": "矢", + "whoosh": "ヒューという音", + "thump": "ドンという音", + "thunk": "鈍い衝撃音", + "electronic_tuner": "電子チューナー", + "effects_unit": "エフェクター", + "chorus_effect": "コーラス効果", + "basketball_bounce": "バスケットボールのバウンド", + "bang": "バンという音", + "slap": "平手打ち", + "whack": "強打", + "smash": "粉砕", + "breaking": "破壊", + "bouncing": "跳ねる音", + "whip": "ムチの音", + "flap": "はためく音", + "scratch": "引っかく音", + "scrape": "こする音", + "rub": "こする", + "roll": "転がる", + "crushing": "押しつぶす", + "crumpling": "くしゃくしゃにする音", + "tearing": "引き裂く音", + "beep": "ビープ音", + "ping": "ピン音", + "ding": "ディン音", + "clang": "金属音", + "squeal": "きしむ音", + "creak": "きしみ", + "rustle": "かさかさ音", + "whir": "ブーンという音", + "clatter": "ガタガタ音", + "sizzle": "ジュージュー音", + "clicking": "クリック音", + "clickety_clack": "カチカチ音", + "rumble": "ゴロゴロ音", + "plop": "ポチャン", + "hum": "ハム音", + "zing": "ジーン音", + "boing": "ボイン音", + "crunch": "バリバリ音", + "sine_wave": "正弦波", + "harmonic": "倍音", + "chirp_tone": "チャープ音", + "pulse": "パルス", + "inside": "内側", + "outside": "外側", + "reverberation": "残響", + "echo": "エコー", + "noise": "ノイズ", + "mains_hum": "電源ハム", + "distortion": "歪み", + "sidetone": "サイドトーン", + "cacophony": "不協和音", + "throbbing": "脈動", + "vibration": "振動" } diff --git a/web/public/locales/ja/common.json b/web/public/locales/ja/common.json index ba84f3e2fa..7cef62aa5c 100644 --- a/web/public/locales/ja/common.json +++ b/web/public/locales/ja/common.json @@ -66,7 +66,10 @@ "formattedTimestampFilename": { "12hour": "MM-dd-yy-h-mm-ss-a", "24hour": "MM-dd-yy-HH-mm-ss" - } + }, + "inProgress": "処理中", + "invalidStartTime": "開始時刻が無効です", + "invalidEndTime": "終了時刻が無効です" }, "readTheDocumentation": "ドキュメントを見る", "unit": { @@ -88,7 +91,12 @@ } }, "label": { - "back": "戻る" + "back": "戻る", + "hide": "{{item}} を非表示", + "show": "{{item}} を表示", + "ID": "ID", + "none": "なし", + "all": "すべて" }, "button": { "apply": "適用", @@ -125,7 +133,8 @@ "unselect": "選択解除", "export": "書き出し", "deleteNow": "今すぐ削除", - "next": "次へ" + "next": "次へ", + "continue": "続行" }, "menu": { "system": "システム", @@ -224,7 +233,8 @@ "withSystem": { "label": "システム設定に従う" } - } + }, + "classification": "分類" }, "toast": { "copyUrlToClipboard": "URLをクリップボードにコピーしました。", @@ -267,5 +277,14 @@ "selectItem": "{{item}} を選択", "information": { "pixels": "{{area}}ピクセル" + }, + "list": { + "two": "{{0}} と {{1}}", + "many": "{{items}}と {{last}}", + "separatorWithSpace": ", " + }, + "field": { + "optional": "任意", + "internalID": "Frigate が設定で使用する内部 ID です" } } diff --git a/web/public/locales/ja/components/auth.json b/web/public/locales/ja/components/auth.json index b9ff983253..d767e3282c 100644 --- a/web/public/locales/ja/components/auth.json +++ b/web/public/locales/ja/components/auth.json @@ -10,6 +10,7 @@ "loginFailed": "ログインに失敗しました", "unknownError": "不明なエラー。ログを確認してください。", "webUnknownError": "不明なエラー。コンソールログを確認してください。" - } + }, + "firstTimeLogin": "初めてログインしますか?認証情報は Frigate のログに表示されています。" } } diff --git a/web/public/locales/ja/components/dialog.json b/web/public/locales/ja/components/dialog.json index 2c5f5e0d44..6294745483 100644 --- a/web/public/locales/ja/components/dialog.json +++ b/web/public/locales/ja/components/dialog.json @@ -51,12 +51,13 @@ "export": "書き出し", "selectOrExport": "選択または書き出し", "toast": { - "success": "書き出しを開始しました。/exports フォルダでファイルを確認できます。", + "success": "書き出しを開始しました。出力ページでファイルを確認できます。", "error": { "failed": "書き出しの開始に失敗しました: {{error}}", "endTimeMustAfterStartTime": "終了時間は開始時間より後である必要があります", "noVaildTimeSelected": "有効な時間範囲が選択されていません" - } + }, + "view": "表示" }, "fromTimeline": { "saveExport": "書き出しを保存", @@ -114,6 +115,7 @@ "search": { "placeholder": "ラベルまたはサブラベルで検索…" }, - "noImages": "このカメラのサムネイルは見つかりません" + "noImages": "このカメラのサムネイルは見つかりません", + "unknownLabel": "保存済みトリガー画像" } } diff --git a/web/public/locales/ja/components/filter.json b/web/public/locales/ja/components/filter.json index 66a52a29ec..bbcc3149d0 100644 --- a/web/public/locales/ja/components/filter.json +++ b/web/public/locales/ja/components/filter.json @@ -132,5 +132,9 @@ "selectPlatesFromList": "リストから1件以上選択してください。", "selectAll": "すべて選択", "clearAll": "すべてクリア" + }, + "attributes": { + "label": "分類属性", + "all": "すべての属性" } } diff --git a/web/public/locales/ja/views/classificationModel.json b/web/public/locales/ja/views/classificationModel.json index 54710f96c5..e16f1fce5b 100644 --- a/web/public/locales/ja/views/classificationModel.json +++ b/web/public/locales/ja/views/classificationModel.json @@ -1,14 +1,182 @@ { - "documentTitle": "分類モデル", + "documentTitle": "分類モデル - Frigate", "button": { - "deleteImages": "画像を削除" + "deleteImages": "画像を削除", + "deleteClassificationAttempts": "分類画像を削除", + "renameCategory": "クラス名を変更", + "deleteCategory": "クラスを削除", + "trainModel": "モデルを学習", + "addClassification": "分類を追加", + "deleteModels": "モデルを削除", + "editModel": "モデルを編集" }, "toast": { "success": { "deletedImage": "削除された画像", "categorizedImage": "画像の分類に成功しました", "trainedModel": "モデルを正常に学習させました。", - "trainingModel": "モデルのトレーニングを正常に開始しました。" + "trainingModel": "モデルのトレーニングを正常に開始しました。", + "deletedCategory": "クラスを削除しました", + "deletedModel_other": "{{count}} 件のモデルを削除しました", + "updatedModel": "モデル設定を更新しました", + "renamedCategory": "クラス名を {{name}} に変更しました" + }, + "error": { + "deleteImageFailed": "削除に失敗しました: {{errorMessage}}", + "deleteCategoryFailed": "クラスの削除に失敗しました: {{errorMessage}}", + "deleteModelFailed": "モデルの削除に失敗しました: {{errorMessage}}", + "categorizeFailed": "画像の分類に失敗しました: {{errorMessage}}", + "trainingFailed": "モデルの学習に失敗しました。Frigate のログを確認してください。", + "trainingFailedToStart": "モデルの学習を開始できませんでした: {{errorMessage}}", + "updateModelFailed": "モデルの更新に失敗しました: {{errorMessage}}", + "renameCategoryFailed": "クラス名の変更に失敗しました: {{errorMessage}}" + } + }, + "train": { + "titleShort": "Classifications,最近の分類結果を選択,,False,train.aria,,", + "title": "最近の分類結果", + "aria": "最近の分類結果を選択" + }, + "wizard": { + "step1": { + "typeObject": "Classification", + "typeState": "Classification", + "description": "状態モデルは固定カメラ領域の状態変化(例:ドアの開閉)を監視し、オブジェクトモデルは検出されたオブジェクトに分類(例:既知の動物や配達員など)を追加します。", + "name": "名前", + "namePlaceholder": "モデル名を入力...", + "type": "タイプ", + "objectLabel": "オブジェクトラベル", + "objectLabelPlaceholder": "オブジェクトタイプを選択...", + "classificationType": "分類タイプ", + "classificationTypeTip": "分類タイプについて", + "classificationTypeDesc": "サブラベルはオブジェクトのラベルに追加のテキストを追加します(例:「人: UPS」)。属性は、オブジェクトのメタデータとは別に保存される、検索可能なメタデータです。", + "classificationSubLabel": "サブラベル", + "classificationAttribute": "属性", + "classes": "クラス", + "states": "状態", + "classesTip": "クラスについて", + "classesStateDesc": "カメラ領域の状態を定義します。例: ガレージドアの「開」「閉」。", + "classesObjectDesc": "検出されたオブジェクトを分類するための、異なるカテゴリを定義します。例:人物の分類として「delivery_person」「resident」「stranger」など。", + "classPlaceholder": "クラス名を入力...", + "errors": { + "nameRequired": "モデル名は必須です", + "nameLength": "モデル名は 64 文字以内で入力してください", + "nameOnlyNumbers": "モデル名を数字のみにはできません", + "classRequired": "少なくとも 1 つのクラスが必要です", + "classesUnique": "クラス名は一意である必要があります", + "noneNotAllowed": "「none」というクラス名は使用できません", + "stateRequiresTwoClasses": "状態モデルには少なくとも 2 つのクラスが必要です", + "objectLabelRequired": "オブジェクトラベルを選択してください", + "objectTypeRequired": "分類タイプを選択してください" + } + }, + "title": "新しい分類を作成", + "steps": { + "nameAndDefine": "名前と定義", + "stateArea": "状態エリア", + "chooseExamples": "例を選択" + }, + "step2": { + "description": "カメラを選択し、それぞれの監視エリアを定義します。モデルはこれらのエリアの状態を分類します。", + "cameras": "カメラ", + "selectCamera": "カメラを選択", + "noCameras": "+ をクリックしてカメラを追加", + "selectCameraPrompt": "リストからカメラを選択して監視エリアを定義します" + }, + "step3": { + "selectImagesPrompt": "{{className}} の画像をすべて選択", + "selectImagesDescription": "画像をクリックして選択します。このクラスの作業が完了したら「続行」をクリックしてください。", + "allImagesRequired_other": "すべての画像を分類してください。残り {{count}} 枚です。", + "generating": { + "title": "サンプル画像を生成中", + "description": "Frigate が録画から代表的な画像を抽出しています。しばらくお待ちください..." + }, + "training": { + "title": "モデルを学習中", + "description": "モデルはバックグラウンドで学習されています。このダイアログを閉じると、学習完了後すぐにモデルが有効になります。" + }, + "retryGenerate": "再生成", + "noImages": "サンプル画像が生成されませんでした", + "classifying": "分類・学習中...", + "trainingStarted": "学習を開始しました", + "modelCreated": "モデルを作成しました。不足している状態の画像を「最近の分類」から追加し、モデルを学習してください。", + "errors": { + "noCameras": "カメラが設定されていません", + "noObjectLabel": "オブジェクトラベルが選択されていません", + "generateFailed": "例の生成に失敗しました: {{error}}", + "generationFailed": "生成に失敗しました。もう一度お試しください。", + "classifyFailed": "画像の分類に失敗しました: {{error}}" + }, + "generateSuccess": "サンプル画像を生成しました", + "missingStatesWarning": { + "title": "状態の例が不足しています", + "description": "最良の結果を得るため、すべての状態の例を選択することを推奨します。すべてを選択しなくても続行できますが、全状態に画像が揃うまでモデルは学習されません。続行後、「最近の分類」から不足分を分類し、学習を行ってください。" + } + } + }, + "details": { + "scoreInfo": "このスコアは、このオブジェクトに対するすべての検出結果の分類信頼度の平均を表します。", + "none": "なし", + "unknown": "不明" + }, + "tooltip": { + "trainingInProgress": "モデルは現在学習中です", + "noNewImages": "学習に使用できる新しい画像がありません。先にデータセット内の画像を分類してください。", + "noChanges": "前回の学習以降、データセットに変更はありません。", + "modelNotReady": "モデルはまだ学習可能な状態ではありません" + }, + "deleteCategory": { + "title": "クラスを削除", + "desc": "クラス {{name}} を削除してもよろしいですか?関連するすべての画像が完全に削除され、モデルの再学習が必要になります。", + "minClassesTitle": "クラスを削除できません", + "minClassesDesc": "分類モデルには少なくとも 2 つのクラスが必要です。別のクラスを追加してから削除してください。" + }, + "deleteModel": { + "title": "分類モデルを削除", + "single": "{{name}} を削除してもよろしいですか?画像や学習データを含むすべての関連データが完全に削除され、この操作は元に戻せません。", + "desc_other": "{{count}} 件のモデルを削除してもよろしいですか?関連するすべてのデータが完全に削除され、この操作は元に戻せません。" + }, + "edit": { + "title": "分類モデルを編集", + "descriptionState": "この状態分類モデルのクラスを編集します。変更を反映するにはモデルの再学習が必要です。", + "descriptionObject": "このオブジェクト分類モデルのオブジェクトタイプおよび分類タイプを編集します。", + "stateClassesInfo": "注意: 状態クラスを変更すると、更新後のクラスでモデルを再学習する必要があります。" + }, + "deleteDatasetImages": { + "title": "データセット画像を削除", + "desc_other": "{{dataset}} から {{count}} 枚の画像を削除してもよろしいですか?この操作は元に戻せず、モデルの再学習が必要になります。" + }, + "deleteTrainImages": { + "title": "学習用画像を削除", + "desc_other": "{{count}} 枚の画像を削除してもよろしいですか?この操作は元に戻すことができません。" + }, + "renameCategory": { + "title": "クラス名を変更", + "desc": "{{name}} の新しい名前を入力してください。変更を有効にするにはモデルの再学習が必要です。" + }, + "description": { + "invalidName": "無効な名前です。使用できるのは、英数字、空白、アポストロフィ、アンダースコア、ハイフンのみです。" + }, + "categories": "クラス", + "createCategory": { + "new": "新しいクラスを作成" + }, + "categorizeImageAs": "画像を次として分類:", + "categorizeImage": "画像を分類", + "menu": { + "objects": "オブジェクト", + "states": "状態" + }, + "noModels": { + "object": { + "title": "オブジェクト分類モデルがありません", + "description": "検出されたオブジェクトを分類するためのカスタムモデルを作成します。", + "buttonText": "オブジェクトモデルを作成" + }, + "state": { + "title": "状態分類モデルがありません", + "description": "特定のカメラ領域の状態変化を監視・分類するためのカスタムモデルを作成します。", + "buttonText": "状態モデルを作成" } } } diff --git a/web/public/locales/ja/views/events.json b/web/public/locales/ja/views/events.json index b19ad95537..b1f85a0661 100644 --- a/web/public/locales/ja/views/events.json +++ b/web/public/locales/ja/views/events.json @@ -36,5 +36,28 @@ "selected_other": "{{count}} 件選択", "detected": "検出", "suspiciousActivity": "不審なアクティビティ", - "threateningActivity": "脅威となるアクティビティ" + "threateningActivity": "脅威となるアクティビティ", + "zoomIn": "ズームイン", + "zoomOut": "ズームアウト", + "detail": { + "label": "詳細", + "noDataFound": "確認する詳細データはありません", + "aria": "詳細表示を切り替え", + "trackedObject_one": "{{count}} 件のオブジェクト", + "trackedObject_other": "{{count}} 件のオブジェクト", + "noObjectDetailData": "オブジェクトの詳細データがありません。", + "settings": "詳細表示設定", + "alwaysExpandActive": { + "title": "アクティブ項目を常に展開", + "desc": "利用可能な場合、アクティブなレビュー項目のオブジェクト詳細を常に展開する。" + } + }, + "objectTrack": { + "trackedPoint": "追跡ポイント", + "clickToSeek": "クリックしてこの時点に移動" + }, + "select_all": "すべて", + "normalActivity": "通常", + "needsReview": "要確認", + "securityConcern": "セキュリティ上の懸念" } diff --git a/web/public/locales/ja/views/explore.json b/web/public/locales/ja/views/explore.json index 3e782f926a..ccee1d47aa 100644 --- a/web/public/locales/ja/views/explore.json +++ b/web/public/locales/ja/views/explore.json @@ -19,13 +19,15 @@ "regenerate": "{{provider}} に新しい説明をリクエストしました。プロバイダの速度により再生成に時間がかかる場合があります。", "updatedSublabel": "サブラベルを更新しました。", "updatedLPR": "ナンバープレートを更新しました。", - "audioTranscription": "音声文字起こしをリクエストしました。" + "audioTranscription": "音声文字起こしのリクエストは正常に送信されました。Frigate サーバーの処理速度によっては、文字起こしの完了までにしばらく時間がかかる場合があります。", + "updatedAttributes": "属性が正常に更新されました。" }, "error": { "regenerate": "{{provider}} への新しい説明の呼び出しに失敗しました: {{errorMessage}}", "updatedSublabelFailed": "サブラベルの更新に失敗しました: {{errorMessage}}", "updatedLPRFailed": "ナンバープレートの更新に失敗しました: {{errorMessage}}", - "audioTranscription": "音声文字起こしのリクエストに失敗しました: {{errorMessage}}" + "audioTranscription": "音声文字起こしのリクエストに失敗しました: {{errorMessage}}", + "updatedAttributesFailed": "属性の更新に失敗しました: {{errorMessage}}" } } }, @@ -73,7 +75,12 @@ "tips": { "descriptionSaved": "説明を保存しました", "saveDescriptionFailed": "説明の更新に失敗しました: {{errorMessage}}" - } + }, + "editAttributes": { + "title": "属性を編集", + "desc": "この {{label}} の分類属性を選択してください" + }, + "attributes": "分類属性" }, "exploreMore": "{{label}} のオブジェクトをさらに探索", "exploreIsUnavailable": { @@ -108,7 +115,9 @@ "details": "詳細", "snapshot": "スナップショット", "video": "動画", - "object_lifecycle": "オブジェクトのライフサイクル" + "object_lifecycle": "オブジェクトのライフサイクル", + "thumbnail": "サムネイル", + "tracking_details": "追跡詳細" }, "objectLifecycle": { "title": "オブジェクトのライフサイクル", @@ -193,12 +202,26 @@ }, "deleteTrackedObject": { "label": "この追跡オブジェクトを削除" + }, + "downloadCleanSnapshot": { + "label": "クリーンなスナップショットをダウンロード", + "aria": "クリーンなスナップショットをダウンロード" + }, + "viewTrackingDetails": { + "label": "追跡詳細を表示", + "aria": "追跡詳細を表示" + }, + "showObjectDetails": { + "label": "オブジェクトの移動経路を表示" + }, + "hideObjectDetails": { + "label": "オブジェクトの移動経路を非表示" } }, "dialog": { "confirmDelete": { "title": "削除の確認", - "desc": "この追跡オブジェクトを削除すると、スナップショット、保存された埋め込み、および関連するライフサイクル項目が削除されます。履歴ビューの録画映像は削除されません

    続行してもよろしいですか?" + "desc": "この追跡オブジェクトを削除すると、スナップショット、保存された埋め込み、および関連する追跡詳細項目が削除されます。履歴ビューの録画映像は削除されません

    続行してもよろしいですか?" } }, "noTrackedObjects": "追跡オブジェクトは見つかりませんでした", @@ -211,12 +234,63 @@ "success": "追跡オブジェクトを削除しました。", "error": "追跡オブジェクトの削除に失敗しました: {{errorMessage}}" } - } + }, + "previousTrackedObject": "前の追跡オブジェクト", + "nextTrackedObject": "次の追跡オブジェクト" }, "aiAnalysis": { "title": "AI 解析" }, "concerns": { "label": "懸念" + }, + "trackingDetails": { + "title": "追跡詳細", + "noImageFound": "このタイムスタンプに対応する画像が見つかりません。", + "createObjectMask": "オブジェクトマスクを作成", + "adjustAnnotationSettings": "注釈設定を調整", + "scrollViewTips": "クリックして、このオブジェクトのライフサイクルにおける重要な瞬間を表示します。", + "autoTrackingTips": "自動追跡カメラでは、バウンディングボックスの位置が不正確になる場合があります。", + "count": "{{second}} 件中 {{first}} 件目", + "trackedPoint": "追跡ポイント", + "lifecycleItemDesc": { + "visible": "{{label}} が検出されました", + "entered_zone": "{{label}} が {{zones}} に入りました", + "active": "{{label}} がアクティブになりました", + "stationary": "{{label}} が静止状態になりました", + "attribute": { + "faceOrLicense_plate": "{{label}} に {{attribute}} が検出されました", + "other": "{{label}} は {{attribute}} と認識されました" + }, + "gone": "{{label}} が離脱しました", + "heard": "{{label}} の音が検出されました", + "external": "{{label}} が検出されました", + "header": { + "zones": "ゾーン", + "ratio": "比率", + "area": "面積", + "score": "スコア" + } + }, + "annotationSettings": { + "title": "注釈設定", + "showAllZones": { + "title": "すべてのゾーンを表示", + "desc": "オブジェクトがゾーンに入ったフレームでは常にゾーンを表示します。" + }, + "offset": { + "label": "注釈オフセット", + "millisecondsToOffset": "検出アノテーションをオフセットするミリ秒数です。デフォルト: 0", + "toast": { + "success": "{{camera}} のアノテーションオフセットが設定ファイルに保存されました。" + }, + "desc": "このデータはカメラの detect ストリーム から取得されていますが、表示される画像自体は record ストリーム のものです。そのため、2 つのストリームが完全に同期している可能性は低く、バウンディングボックスと実際の映像が正確に一致しない場合があります。この設定を使用すると、注釈(アノテーション)を 時間的に前後へオフセット することができ、録画映像との位置合わせをより正確に行えます。", + "tips": "映像の再生がバウンディングボックスや軌跡ポイントより先行している場合は値を小さくし、遅れている場合は値を大きくしてください。この値は負の値も指定できます。" + } + }, + "carousel": { + "previous": "前のスライド", + "next": "次のスライド" + } } } diff --git a/web/public/locales/ja/views/exports.json b/web/public/locales/ja/views/exports.json index b5107f475f..3e8ce14d46 100644 --- a/web/public/locales/ja/views/exports.json +++ b/web/public/locales/ja/views/exports.json @@ -13,5 +13,11 @@ "error": { "renameExportFailed": "書き出し名の変更に失敗しました: {{errorMessage}}" } + }, + "tooltip": { + "shareExport": "エクスポートを共有", + "downloadVideo": "動画をダウンロード", + "editName": "名前を編集", + "deleteExport": "エクスポートを削除" } } diff --git a/web/public/locales/ja/views/faceLibrary.json b/web/public/locales/ja/views/faceLibrary.json index f82b4e764d..5b9392cafa 100644 --- a/web/public/locales/ja/views/faceLibrary.json +++ b/web/public/locales/ja/views/faceLibrary.json @@ -2,7 +2,7 @@ "description": { "placeholder": "このコレクションの名前を入力", "addFace": "最初の画像をアップロードして、フェイスライブラリに新しいコレクションを追加してください。", - "invalidName": "無効な名前です。名前に使用できるのは英数字、スペース、アポストロフィ、アンダースコア、ハイフンのみです。" + "invalidName": "無効な名前です。使用できるのは、英数字、空白、アポストロフィ、アンダースコア、ハイフンのみです。" }, "details": { "person": "人物", @@ -23,7 +23,7 @@ "title": "コレクションを作成", "desc": "新しいコレクションを作成", "new": "新しい顔を作成", - "nextSteps": "強固な基盤を作るために:
  • [学習]タブで各人物に対して画像を選択し学習させてください。
  • 最良の結果のため、正面を向いた画像に集中し、斜めからの顔画像は学習に使わないでください。
  • " + "nextSteps": "強固な基盤を作るために:
  • [過去の学習]タブで各人物に対して画像を選択し学習させてください。
  • 最良の結果のため、正面を向いた画像に集中し、斜めからの顔画像は学習に使わないでください。
  • " }, "selectItem": "{{item}} を選択", "steps": { @@ -35,9 +35,10 @@ } }, "train": { - "title": "学習", - "aria": "学習を選択", - "empty": "最近の顔認識の試行はありません" + "title": "過去の学習", + "aria": "過去の学習を選択", + "empty": "最近の顔認識の試行はありません", + "titleShort": "Classifications,最近の分類結果を選択,,False,train.aria,," }, "selectFace": "顔を選択", "deleteFaceLibrary": { @@ -80,7 +81,7 @@ "deletedName_other": "{{count}} 件の顔を削除しました。", "renamedFace": "顔の名前を {{name}} に変更しました", "trainedFace": "顔の学習が完了しました。", - "updatedFaceScore": "顔のスコアを更新しました。" + "updatedFaceScore": "顔のスコアを {{name}} ({{score}})に更新しました。" }, "error": { "uploadingImageFailed": "画像のアップロードに失敗しました: {{errorMessage}}", diff --git a/web/public/locales/ja/views/live.json b/web/public/locales/ja/views/live.json index cfcd5739d6..f88901ab85 100644 --- a/web/public/locales/ja/views/live.json +++ b/web/public/locales/ja/views/live.json @@ -172,7 +172,11 @@ "noCameras": { "title": "カメラが設定されていません", "buttonText": "カメラを追加", - "description": "開始するには、カメラを接続してください。" + "description": "開始するには、Frigateにカメラを接続してください。", + "restricted": { + "title": "利用可能なカメラがありません", + "description": "このグループ内のカメラを表示する権限がありません。" + } }, "snapshot": { "takeSnapshot": "即時スナップショットをダウンロード", diff --git a/web/public/locales/ja/views/search.json b/web/public/locales/ja/views/search.json index d5be5ed30b..540606c834 100644 --- a/web/public/locales/ja/views/search.json +++ b/web/public/locales/ja/views/search.json @@ -26,7 +26,8 @@ "max_speed": "最大速度", "recognized_license_plate": "認識されたナンバープレート", "has_clip": "クリップあり", - "has_snapshot": "スナップショットあり" + "has_snapshot": "スナップショットあり", + "attributes": "属性" }, "searchType": { "thumbnail": "サムネイル", diff --git a/web/public/locales/ja/views/settings.json b/web/public/locales/ja/views/settings.json index 000aac8980..5c36b19195 100644 --- a/web/public/locales/ja/views/settings.json +++ b/web/public/locales/ja/views/settings.json @@ -7,7 +7,7 @@ "masksAndZones": "マスク/ゾーンエディタ - Frigate", "motionTuner": "モーションチューナー - Frigate", "object": "デバッグ - Frigate", - "general": "一般設定 - Frigate", + "general": "UI設定 - Frigate", "frigatePlus": "Frigate+ 設定 - Frigate", "notifications": "通知設定 - Frigate", "cameraManagement": "カメラ設定 - Frigate", @@ -39,7 +39,7 @@ "noCamera": "カメラなし" }, "general": { - "title": "一般設定", + "title": "UI設定", "liveDashboard": { "title": "ライブダッシュボード", "automaticLiveView": { @@ -49,6 +49,14 @@ "playAlertVideos": { "label": "アラート動画を再生", "desc": "既定では、ライブダッシュボードの最近のアラートは小さなループ動画として再生されます。無効にすると、最近のアラートはこのデバイス/ブラウザでは静止画像のみ表示されます。" + }, + "displayCameraNames": { + "label": "常にカメラ名を表示", + "desc": "マルチカメラのライブビュー ダッシュボードで、カメラ名を常にチップ表示します。" + }, + "liveFallbackTimeout": { + "label": "ライブプレイヤーのフォールバック タイムアウト", + "desc": "カメラの高画質ライブストリームが利用できない場合、指定した秒数後に低帯域モードへ切り替えます。デフォルト:3 秒" } }, "storedLayouts": { @@ -239,7 +247,8 @@ "mustNotBeSameWithCamera": "ゾーン名はカメラ名と同一にできません。", "alreadyExists": "この名前のゾーンはこのカメラに既に存在します。", "mustNotContainPeriod": "ゾーン名にピリオドは使用できません。", - "hasIllegalCharacter": "ゾーン名に不正な文字が含まれています。" + "hasIllegalCharacter": "ゾーン名に不正な文字が含まれています。", + "mustHaveAtLeastOneLetter": "ゾーン名には少なくとも 1 文字が必要です。" } }, "distance": { @@ -296,7 +305,7 @@ "name": { "title": "名称", "inputPlaceHolder": "名前を入力…", - "tips": "名前は2文字以上、かつカメラ名や他のゾーン名と重複しない必要があります。" + "tips": "名前は2文字以上で、少なくとも1文字のアルファベットを含み、このカメラ上の他のゾーン名やカメラ名と同一であってはなりません。" }, "inertia": { "title": "慣性", @@ -330,7 +339,7 @@ } }, "toast": { - "success": "ゾーン({{zoneName}})を保存しました。変更を適用するには Frigate を再起動してください。" + "success": "ゾーン({{zoneName}})を保存しました。" } }, "motionMasks": { @@ -353,8 +362,8 @@ }, "toast": { "success": { - "title": "{{polygonName}} を保存しました。変更を適用するには Frigate を再起動してください。", - "noName": "モーションマスクを保存しました。変更を適用するには Frigate を再起動してください。" + "title": "{{polygonName}} を保存しました。", + "noName": "モーションマスクを保存しました。" } } }, @@ -377,8 +386,8 @@ }, "toast": { "success": { - "title": "{{polygonName}} を保存しました。変更を適用するには Frigate を再起動してください。", - "noName": "オブジェクトマスクを保存しました。変更を適用するには Frigate を再起動してください。" + "title": "{{polygonName}} を保存しました。", + "noName": "オブジェクトマスクを保存しました。" } } } @@ -472,7 +481,7 @@ "desc": "この Frigate インスタンスのユーザーアカウントを管理します。" }, "addUser": "ユーザーを追加", - "updatePassword": "パスワードを更新", + "updatePassword": "パスワードをリセット", "toast": { "success": { "createUser": "ユーザー {{user}} を作成しました", @@ -493,7 +502,7 @@ "role": "ロール", "noUsers": "ユーザーが見つかりません。", "changeRole": "ユーザーロールを変更", - "password": "パスワード", + "password": "パスワードをリセット", "deleteUser": "ユーザーを削除" }, "dialog": { @@ -518,7 +527,16 @@ "veryStrong": "非常に強い" }, "match": "パスワードが一致しています", - "notMatch": "パスワードが一致しません" + "notMatch": "パスワードが一致しません", + "show": "パスワードを表示", + "hide": "パスワードを非表示", + "requirements": { + "title": "パスワード要件:", + "length": "8 文字以上", + "uppercase": "大文字を 1 文字以上含める", + "digit": "数字を 1 文字以上含める", + "special": "少なくとも 1 つの特殊文字(!@#$%^&*(),.?”:{}|<>)が必要です" + } }, "newPassword": { "title": "新しいパスワード", @@ -528,7 +546,11 @@ } }, "usernameIsRequired": "ユーザー名は必須です", - "passwordIsRequired": "パスワードは必須です" + "passwordIsRequired": "パスワードは必須です", + "currentPassword": { + "title": "現在のパスワード", + "placeholder": "現在のパスワードを入力" + } }, "createUser": { "title": "新規ユーザーを作成", @@ -546,7 +568,12 @@ "doNotMatch": "パスワードが一致しません", "updatePassword": "{{username}} のパスワードを更新", "setPassword": "パスワードを設定", - "desc": "強力なパスワードを作成して、このアカウントを保護してください。" + "desc": "強力なパスワードを作成して、このアカウントを保護してください。", + "currentPasswordRequired": "現在のパスワードは必須です", + "incorrectCurrentPassword": "現在のパスワードが正しくありません", + "passwordVerificationFailed": "パスワードの確認に失敗しました", + "multiDeviceWarning": "他のログイン中のデバイスは {{refresh_time}} 以内に再ログインが必要になります。", + "multiDeviceAdmin": "JWT シークレットをローテーションすることで、すべてのユーザーに即時再認証を強制することもできます。" }, "changeRole": { "title": "ユーザーロールを変更", @@ -722,7 +749,7 @@ "triggers": { "documentTitle": "トリガー", "management": { - "title": "トリガー管理", + "title": "トリガー", "desc": "{{camera}} のトリガーを管理します。サムネイルタイプでは、選択した追跡オブジェクトに類似するサムネイルでトリガーし、説明タイプでは、指定したテキストに類似する説明でトリガーします。" }, "addTrigger": "トリガーを追加", @@ -743,7 +770,9 @@ }, "actions": { "alert": "アラートとしてマーク", - "notification": "通知を送信" + "notification": "通知を送信", + "sub_label": "サブラベルを追加", + "attribute": "属性を追加" }, "dialog": { "createTrigger": { @@ -763,23 +792,26 @@ "title": "名称", "placeholder": "トリガー名を入力", "error": { - "minLength": "名称は2文字以上である必要があります。", - "invalidCharacters": "名称に使用できるのは英数字、アンダースコア、ハイフンのみです。", + "minLength": "この項目は2文字以上で入力してください。", + "invalidCharacters": "このフィールドに使用できるのは英数字、アンダースコア、ハイフンのみです。", "alreadyExists": "このカメラには同名のトリガーが既に存在します。" - } + }, + "description": "このトリガーを識別するための一意の名前または説明を入力してください" }, "enabled": { "description": "このトリガーを有効/無効にする" }, "type": { "title": "タイプ", - "placeholder": "トリガータイプを選択" + "placeholder": "トリガータイプを選択", + "description": "類似した追跡オブジェクトの説明が検出されたときにトリガー", + "thumbnail": "類似した追跡オブジェクトのサムネイルが検出されたときにトリガー" }, "content": { "title": "コンテンツ", - "imagePlaceholder": "画像を選択", + "imagePlaceholder": "サムネイルを選択", "textPlaceholder": "テキストを入力", - "imageDesc": "類似画像が検出されたときにこのアクションをトリガーするための画像を選択します。", + "imageDesc": "最新のサムネイル100件のみが表示されます。目的のサムネイルが見つからない場合は、探索で過去のオブジェクトを確認し、そこのメニューからトリガーを設定してください。", "textDesc": "類似する追跡オブジェクトの説明が検出されたときにこのアクションをトリガーするためのテキストを入力します。", "error": { "required": "コンテンツは必須です。" @@ -790,11 +822,12 @@ "error": { "min": "しきい値は 0 以上である必要があります", "max": "しきい値は 1 以下である必要があります" - } + }, + "desc": "このトリガーの類似度しきい値を設定します。値が高いほど、より近い一致が必要になります。" }, "actions": { "title": "アクション", - "desc": "既定では、すべてのトリガーに対して MQTT メッセージが送信されます。必要に応じて、トリガー時に実行する追加アクションを選択してください。", + "desc": "デフォルトでは、Frigate はすべてのトリガーに対して MQTT メッセージを送信します。サブラベルは、トリガー名をオブジェクトのラベルに追加します。属性(Attributes)は、追跡オブジェクトのメタデータとは別に保存される検索可能なメタデータです。", "error": { "min": "少なくとも1つのアクションを選択してください。" } @@ -821,12 +854,29 @@ "semanticSearch": { "desc": "トリガーを使用するにはセマンティック検索を有効にする必要があります。", "title": "セマンティック検索が無効です" + }, + "wizard": { + "title": "トリガーを作成", + "step1": { + "description": "トリガーの基本設定を構成します。" + }, + "step2": { + "description": "このアクションをトリガーする内容を設定します。" + }, + "step3": { + "description": "このトリガーのしきい値とアクションを設定します。" + }, + "steps": { + "nameAndType": "名前と種類", + "configureData": "データを設定", + "thresholdAndActions": "しきい値とアクション" + } } }, "cameraWizard": { "step3": { "saveAndApply": "新しいカメラを保存", - "description": "保存前の最終検証と解析。保存する前に各ストリームを接続してください。", + "description": "カメラのストリームの役割を設定し、必要に応じてストリームを追加します。", "validationTitle": "ストリーム検証", "connectAllStreams": "すべてのストリームを接続", "reconnectionSuccess": "再接続に成功しました。", @@ -862,6 +912,43 @@ "dahua": { "substreamWarning": "サブストリーム1は低解像度に固定されています。多くの Dahua/Amcrest/EmpireTech 製カメラでは、追加のサブストリームが利用可能であり、カメラ本体の設定で有効化する必要があります。使用できる場合は、それらのストリームを確認して活用することを推奨します。" } + }, + "streamsTitle": "カメラ ストリーム", + "addStream": "ストリームを追加", + "addAnotherStream": "別のストリームを追加", + "streamUrl": "ストリーム URL", + "streamUrlPlaceholder": "rtsp://ユーザー名:パスワード@ホスト:ポート/パス", + "selectStream": "ストリームを選択", + "searchCandidates": "候補を検索…", + "noStreamFound": "ストリームが見つかりません", + "url": "URL", + "resolution": "解像度", + "selectResolution": "解像度を選択", + "quality": "品質", + "selectQuality": "品質を選択", + "roleLabels": { + "detect": "オブジェクト検出", + "record": "録画", + "audio": "音声" + }, + "testStream": "接続をテスト", + "testSuccess": "ストリーム テスト成功!", + "testFailed": "ストリーム テスト失敗", + "testFailedTitle": "テスト失敗", + "connected": "接続済み", + "notConnected": "未接続", + "featuresTitle": "機能", + "go2rtc": "カメラへの接続数を削減", + "detectRoleWarning": "続行するには、少なくとも 1 つのストリームに「検出」ロールが必要です。", + "rolesPopover": { + "title": "ストリーム ロール", + "detect": "オブジェクト検出用のメイン フィードです。", + "record": "設定に基づいて映像フィードのセグメントを保存します。", + "audio": "音声ベース検出用のフィードです。" + }, + "featuresPopover": { + "title": "ストリーム機能", + "description": "go2rtc の再配信を使用してカメラへの接続数を削減します。" } }, "title": "カメラを追加", @@ -869,7 +956,8 @@ "steps": { "nameAndConnection": "名称と接続", "streamConfiguration": "ストリーム設定", - "validationAndTesting": "検証とテスト" + "validationAndTesting": "検証とテスト", + "probeOrSnapshot": "プローブまたはスナップショット" }, "save": { "success": "新しいカメラ {{cameraName}} を保存しました。", @@ -886,7 +974,7 @@ "testFailed": "ストリームテストに失敗しました: {{error}}" }, "step1": { - "description": "カメラの詳細を入力し、接続テストを実行します。", + "description": "カメラの詳細を入力し、カメラを自動検出するか、メーカーを手動で選択してください。", "cameraName": "カメラ名", "cameraNamePlaceholder": "例: front_door または Back Yard Overview", "host": "ホスト/IP アドレス", @@ -917,14 +1005,24 @@ "nameExists": "このカメラ名は既に存在します", "brands": { "reolink-rtsp": "Reolink の RTSP は推奨されません。カメラ設定で http を有効にし、カメラウィザードを再起動することを推奨します。" - } + }, + "customUrlRtspRequired": "カスタム URL は「rtsp://」で始まる必要があります。非 RTSP カメラ ストリームの場合は手動構成が必要です。" }, "docs": { "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" - } + }, + "connectionSettings": "接続設定", + "detectionMethod": "ストリーム検出方法", + "onvifPort": "ONVIF ポート", + "probeMode": "カメラをプローブ", + "manualMode": "手動選択", + "useDigestAuth": "ダイジェスト認証を使用", + "useDigestAuthDescription": "ONVIF に HTTP ダイジェスト認証を使用します。一部のカメラでは、通常の管理者ユーザーではなく専用の ONVIF ユーザー名/パスワードが必要な場合があります。", + "detectionMethodDescription": "(対応している場合)ONVIF を使用してカメラを自動設定し、カメラのストリーム URL を検出するか、カメラのブランドを手動で選択して事前定義された URL を使用します。カスタム RTSP URL を入力する場合は、手動設定を選択し、「その他」を選んでください。", + "onvifPortDescription": "ONVIF に対応しているカメラの場合、通常は 80 または 8080 です。" }, "step2": { - "description": "ストリームのロールを設定し、必要に応じて追加ストリームを登録します。", + "description": "選択した検出方法に応じて、カメラから利用可能なストリームを自動検出するか、手動で設定してください。", "streamsTitle": "カメラストリーム", "addStream": "ストリームを追加", "addAnotherStream": "ストリームをさらに追加", @@ -943,8 +1041,8 @@ "audio": "音声" }, "testStream": "接続テスト", - "testSuccess": "ストリームテストに成功しました!", - "testFailed": "ストリームテストに失敗しました", + "testSuccess": "接続テストに成功しました!", + "testFailed": "接続テストに失敗しました。入力を確認し、もう一度実行してください。", "testFailedTitle": "テスト失敗", "connected": "接続済み", "notConnected": "未接続", @@ -960,7 +1058,87 @@ "featuresPopover": { "title": "ストリーム機能", "description": "go2rtc のリストリーミングを使用してカメラへの接続数を削減します。" - } + }, + "streamDetails": "ストリームの詳細", + "probing": "カメラをプローブ中…", + "retry": "再試行", + "testing": { + "probingMetadata": "カメラのメタデータを取得中…", + "fetchingSnapshot": "カメラのスナップショットを取得中…" + }, + "probeFailed": "カメラのプローブに失敗しました: {{error}}", + "probingDevice": "デバイスをプローブ中…", + "probeSuccessful": "プローブ成功", + "probeError": "プローブ エラー", + "probeNoSuccess": "プローブ失敗", + "deviceInfo": "デバイス情報", + "manufacturer": "メーカー", + "model": "モデル", + "firmware": "ファームウェア", + "profiles": "プロファイル", + "ptzSupport": "PTZ 対応", + "autotrackingSupport": "自動追跡対応", + "presets": "プリセット", + "rtspCandidates": "RTSP 候補", + "rtspCandidatesDescription": "カメラのプローブから以下の RTSP URL が見つかりました。接続をテストしてストリームのメタデータを確認してください。", + "candidateStreamTitle": "候補 {{number}}", + "useCandidate": "使用", + "uriCopy": "コピー", + "uriCopied": "URI をクリップボードにコピーしました", + "testConnection": "接続をテスト", + "toggleUriView": "クリックして URI の全表示を切り替え", + "errors": { + "hostRequired": "ホスト/IP アドレスは必須です" + }, + "noRtspCandidates": "カメラから RTSP URL を取得できませんでした。認証情報が正しくないか、カメラが ONVIF に対応していない、または RTSP URL を取得する方法がサポートされていない可能性があります。RTSP URL を手動で入力してください。" + }, + "step4": { + "description": "新しいカメラを保存する前の最終検証と分析です。保存前に各ストリームを接続してください。", + "validationTitle": "ストリーム検証", + "connectAllStreams": "すべてのストリームを接続", + "reconnectionSuccess": "再接続に成功しました。", + "reconnectionPartial": "一部のストリームで再接続に失敗しました。", + "streamUnavailable": "ストリームのプレビューを表示できません", + "reload": "再読み込み", + "connecting": "接続中…", + "streamTitle": "ストリーム {{number}}", + "valid": "有効", + "failed": "失敗", + "notTested": "未テスト", + "connectStream": "接続", + "connectingStream": "接続中", + "disconnectStream": "切断", + "estimatedBandwidth": "推定帯域幅", + "roles": "ロール", + "ffmpegModule": "ストリーム互換モードを使用", + "none": "なし", + "error": "エラー", + "streamValidated": "ストリーム {{number}} の検証に成功しました", + "streamValidationFailed": "ストリーム {{number}} の検証に失敗しました", + "saveAndApply": "新しいカメラを保存", + "saveError": "無効な設定です。設定を確認してください。", + "issues": { + "title": "ストリーム検証", + "videoCodecGood": "ビデオ コーデックは {{codec}} です。", + "audioCodecGood": "オーディオ コーデックは {{codec}} です。", + "resolutionHigh": "解像度 {{resolution}} はリソース使用量が増加する可能性があります。", + "resolutionLow": "解像度 {{resolution}} は小さなオブジェクトを確実に検出するには低すぎる可能性があります。", + "audioCodecRecordError": "録画で音声をサポートするには AAC オーディオ コーデックが必要です。", + "audioCodecRequired": "音声検出をサポートするには音声ストリームが必要です。", + "restreamingWarning": "録画用ストリームでカメラへの接続数を削減すると、CPU 使用率がわずかに増加する場合があります。", + "brands": { + "reolink-rtsp": "Reolink の RTSP は推奨されません。カメラのファームウェア設定で HTTP を有効にし、ウィザードを再起動してください。", + "reolink-http": "Reolink の HTTP ストリームは互換性向上のため FFmpeg を使用してください。このストリームで「ストリーム互換モードを使用」を有効にしてください。" + }, + "dahua": { + "substreamWarning": "サブストリーム 1 は低解像度に固定されています。多くの Dahua / Amcrest / EmpireTech カメラは追加のサブストリームをサポートしており、カメラ設定で有効化する必要があります。利用可能であればそれらのストリームを使用することを推奨します。" + }, + "hikvision": { + "substreamWarning": "サブストリーム 1 は低解像度に固定されています。多くの Hikvision カメラは追加のサブストリームをサポートしており、カメラ設定で有効化する必要があります。利用可能であればそれらのストリームを使用することを推奨します。" + }, + "noAudioWarning": "このストリームでは音声が検出されていません。録画には音声が含まれません。" + }, + "ffmpegModuleDescription": "何度か試してもストリームが読み込まれない場合は、このオプションを有効にしてください。有効にすると、Frigate は go2rtc と併用して ffmpeg モジュールを使用します。一部のカメラストリームでは、互換性が向上する場合があります。" } }, "cameraManagement": { diff --git a/web/public/locales/ja/views/system.json b/web/public/locales/ja/views/system.json index da57fa7c30..222b65b3c5 100644 --- a/web/public/locales/ja/views/system.json +++ b/web/public/locales/ja/views/system.json @@ -76,7 +76,12 @@ } }, "npuUsage": "NPU 使用率", - "npuMemory": "NPU メモリ" + "npuMemory": "NPU メモリ", + "intelGpuWarning": { + "title": "Intel GPU 統計情報の警告", + "message": "GPU の統計情報を取得できません", + "description": "これは Intel の GPU 統計取得ツール(intel_gpu_top)における既知の不具合です。ハードウェアアクセラレーションやオブジェクト検出が (i)GPU 上で正しく動作している場合でも、GPU 使用率が 0% と繰り返し表示されることがあります。これは Frigate の不具合ではありません。ホストを再起動することで一時的に解消し、GPU が正常に動作していることを確認できます。本問題はパフォーマンスには影響しません。" + } }, "otherProcesses": { "title": "その他のプロセス", @@ -180,7 +185,17 @@ "plate_recognition_speed": "ナンバープレート認識速度", "text_embedding_speed": "テキスト埋め込み速度", "yolov9_plate_detection_speed": "YOLOv9 ナンバープレート検出速度", - "yolov9_plate_detection": "YOLOv9 ナンバープレート検出" - } + "yolov9_plate_detection": "YOLOv9 ナンバープレート検出", + "review_description": "レビュー説明", + "review_description_speed": "レビュー説明の処理速度", + "review_description_events_per_second": "レビュー説明", + "object_description": "オブジェクト説明", + "object_description_speed": "オブジェクト説明の処理速度", + "object_description_events_per_second": "オブジェクト説明", + "classification": "{{name}} の分類", + "classification_speed": "{{name}} 分類の処理速度", + "classification_events_per_second": "{{name}} 分類の毎秒イベント数" + }, + "averageInf": "平均推論時間" } } From 525cc5b663805d2102e72ee5c1bf7711ca9aa73b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:13 +0100 Subject: [PATCH 49/78] Translated using Weblate (Catalan) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Catalan) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Catalan) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Catalan) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Catalan) Currently translated at 100.0% (121 of 121 strings) Translated using Weblate (Catalan) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Catalan) Currently translated at 100.0% (654 of 654 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com> Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ca/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ca/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ca/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ca/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ca/ Translation: Frigate NVR/common Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/ca/components/filter.json | 4 ++++ .../locales/ca/views/classificationModel.json | 9 ++++++--- web/public/locales/ca/views/explore.json | 13 ++++++++++--- web/public/locales/ca/views/search.json | 3 ++- web/public/locales/ca/views/settings.json | 8 ++++---- web/public/locales/ca/views/system.json | 5 ++++- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/web/public/locales/ca/components/filter.json b/web/public/locales/ca/components/filter.json index e178972947..5a0a7b0834 100644 --- a/web/public/locales/ca/components/filter.json +++ b/web/public/locales/ca/components/filter.json @@ -132,5 +132,9 @@ }, "count_one": "{{count}} Classe", "count_other": "{{count}} Classes" + }, + "attributes": { + "label": "Atributs de classificació", + "all": "Tots els atributs" } } diff --git a/web/public/locales/ca/views/classificationModel.json b/web/public/locales/ca/views/classificationModel.json index 568a38dbb6..7a9a7571d1 100644 --- a/web/public/locales/ca/views/classificationModel.json +++ b/web/public/locales/ca/views/classificationModel.json @@ -54,7 +54,7 @@ }, "renameCategory": { "title": "Reanomena la classe", - "desc": "Introduïu un nom nou per {{name}}. Se us requerirà que torneu a entrenar el model per al canvi de nom a afectar." + "desc": "Introduïu un nom nou per {{name}}. Se us requerirà que torneu a entrenar el model per al canvi de nom afectar." }, "description": { "invalidName": "Nom no vàlid. Els noms només poden incloure lletres, números, espais, apòstrofs, guions baixos i guions." @@ -116,7 +116,8 @@ "classesUnique": "Els noms de classe han de ser únics", "stateRequiresTwoClasses": "Els models d'estat requereixen almenys 2 classes", "objectLabelRequired": "Seleccioneu una etiqueta d'objecte", - "objectTypeRequired": "Seleccioneu un tipus de classificació" + "objectTypeRequired": "Seleccioneu un tipus de classificació", + "noneNotAllowed": "La classe 'none' no està permesa" }, "states": "Estats" }, @@ -172,7 +173,9 @@ "states": "Estats" }, "details": { - "scoreInfo": "La puntuació representa la confiança mitjana de la classificació en totes les deteccions d'aquest objecte." + "scoreInfo": "La puntuació representa la confiança mitjana de la classificació en totes les deteccions d'aquest objecte.", + "none": "Cap", + "unknown": "Desconegut" }, "edit": { "title": "Edita el model de classificació", diff --git a/web/public/locales/ca/views/explore.json b/web/public/locales/ca/views/explore.json index dec29735ad..0461ac7c7b 100644 --- a/web/public/locales/ca/views/explore.json +++ b/web/public/locales/ca/views/explore.json @@ -100,13 +100,15 @@ "updatedSublabel": "Subetiqueta actualitzada amb èxit.", "updatedLPR": "Matrícula actualitzada amb èxit.", "regenerate": "El {{provider}} ha sol·licitat una nova descripció. En funció de la velocitat del vostre proveïdor, la nova descripció pot trigar un temps a regenerar-se.", - "audioTranscription": "S'ha sol·licitat correctament la transcripció d'àudio. Depenent de la velocitat del vostre servidor Frigate, la transcripció pot trigar una estona a completar-se." + "audioTranscription": "S'ha sol·licitat correctament la transcripció d'àudio. Depenent de la velocitat del vostre servidor Frigate, la transcripció pot trigar una estona a completar-se.", + "updatedAttributes": "Els atributs s'han actualitzat correctament." }, "error": { "regenerate": "No s'ha pogut contactar amb {{provider}} per obtenir una nova descripció: {{errorMessage}}", "updatedSublabelFailed": "No s'ha pogut actualitzar la subetiqueta: {{errorMessage}}", "updatedLPRFailed": "No s'ha pogut actualitzar la matrícula: {{errorMessage}}", - "audioTranscription": "Error en demanar la transcripció d'audio {{errorMessage}}" + "audioTranscription": "Error en demanar la transcripció d'audio {{errorMessage}}", + "updatedAttributesFailed": "No s'han pogut actualitzar els atributs: {{errorMessage}}" } }, "title": "Revisar detalls de l'element", @@ -162,7 +164,12 @@ }, "score": { "label": "Puntuació" - } + }, + "editAttributes": { + "title": "Edita els atributs", + "desc": "Seleccioneu els atributs de classificació per a aquesta {{label}}" + }, + "attributes": "Atributs de classificació" }, "searchResult": { "tooltip": "S'ha identificat {{type}} amb una confiança del {{confidence}}%", diff --git a/web/public/locales/ca/views/search.json b/web/public/locales/ca/views/search.json index dec4537280..71f333180d 100644 --- a/web/public/locales/ca/views/search.json +++ b/web/public/locales/ca/views/search.json @@ -15,7 +15,8 @@ "max_speed": "Velocitat màxima", "recognized_license_plate": "Matrícula reconeguda", "has_clip": "Té Clip", - "has_snapshot": "Té instantània" + "has_snapshot": "Té instantània", + "attributes": "Atributs" }, "searchType": { "thumbnail": "Miniatura", diff --git a/web/public/locales/ca/views/settings.json b/web/public/locales/ca/views/settings.json index 1fcd97460e..88b75bca60 100644 --- a/web/public/locales/ca/views/settings.json +++ b/web/public/locales/ca/views/settings.json @@ -484,7 +484,7 @@ "users": { "table": { "username": "Usuari", - "password": "Contrasenya", + "password": "Restableix la contrasenya", "deleteUser": "Suprimir usuari", "noUsers": "No s'han trobat usuaris.", "changeRole": "Canviar la funció d’usuari", @@ -595,7 +595,7 @@ "title": "Gestió d'usuaris", "desc": "Gestioneu els comptes d'usuari d'aquesta instància de Frigate." }, - "updatePassword": "Actualitzar contrasenya" + "updatePassword": "Restableix la contrasenya" }, "frigatePlus": { "snapshotConfig": { @@ -696,7 +696,7 @@ "title": "Classificació d'ocells", "desc": "La classificació d’ocells identifica ocells coneguts mitjançant un model TensorFlow quantitzat. Quan es reconeix un ocell conegut, el seu nom comú s’afegeix com a subetiqueta. Aquesta informació es mostra a la interfície d’usuari, als filtres i també a les notificacions." }, - "title": "Parmàmetres complementaris", + "title": "Configuració dels enriquiments", "toast": { "error": "No s'han pogut guardar els canvis de configuració: {{errorMessage}}", "success": "Els paràmetres complementaris s'han desat. Reinicia Frigate per aplicar els canvis." @@ -805,7 +805,7 @@ "documentTitle": "Disparadors", "management": { "title": "Activadors", - "desc": "Gestionar els disparadors de {{camera}}. Usa les tipus de miniatures per disparar miniatures similars a l'objecte a seguir seleccionat, i el tipus de descripció per disparar en cas de descripcions similars a l'especificada." + "desc": "Gestionar els disparadors de {{camera}}. Usa els tipus de miniatures per disparar miniatures similars a l'objecte a seguir seleccionat, i el tipus de descripció per disparar en cas de descripcions similars a l'especificada." }, "addTrigger": "Afegir disaprador", "semanticSearch": { diff --git a/web/public/locales/ca/views/system.json b/web/public/locales/ca/views/system.json index f610e6a2af..662ca75529 100644 --- a/web/public/locales/ca/views/system.json +++ b/web/public/locales/ca/views/system.json @@ -190,7 +190,10 @@ "review_description_events_per_second": "Descripció de la revisió", "object_description": "Descripció de l'objecte", "object_description_speed": "Velocitat de la descripció de l'objecte", - "object_description_events_per_second": "Descripció de l'objecte" + "object_description_events_per_second": "Descripció de l'objecte", + "classification": "{{name}} Classificació", + "classification_speed": "Velocitat de classificació de {{name}}", + "classification_events_per_second": "{{name}} Esdeveniments de classificació per segon" }, "infPerSecond": "Inferències per segon", "averageInf": "Temps mitjà d'inferència" From 59780203a38c866043dbd8d4e97372e03d40d81a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:15 +0100 Subject: [PATCH 50/78] Translated using Weblate (Czech) Currently translated at 61.0% (399 of 654 strings) Translated using Weblate (Czech) Currently translated at 96.2% (51 of 53 strings) Translated using Weblate (Czech) Currently translated at 96.2% (51 of 53 strings) Translated using Weblate (Czech) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Czech) Currently translated at 75.6% (31 of 41 strings) Translated using Weblate (Czech) Currently translated at 23.7% (29 of 122 strings) Translated using Weblate (Czech) Currently translated at 23.7% (29 of 122 strings) Co-authored-by: Hosted Weblate Co-authored-by: Vitek Co-authored-by: lukascissa Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/cs/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/cs/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/cs/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/cs/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/cs/ Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-events Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings --- .../locales/cs/views/classificationModel.json | 51 ++++++++++++------- web/public/locales/cs/views/events.json | 3 +- web/public/locales/cs/views/faceLibrary.json | 5 +- web/public/locales/cs/views/search.json | 3 +- web/public/locales/cs/views/settings.json | 2 +- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/web/public/locales/cs/views/classificationModel.json b/web/public/locales/cs/views/classificationModel.json index 25d14c7230..f3713a2555 100644 --- a/web/public/locales/cs/views/classificationModel.json +++ b/web/public/locales/cs/views/classificationModel.json @@ -1,34 +1,47 @@ { - "documentTitle": "Klasifikační modely", + "documentTitle": "Klasifikační modely - Frigate", "button": { "deleteClassificationAttempts": "Odstrániť Klasifikačné obrazy", - "renameCategory": "Premenovať triedu", - "deleteCategory": "Zmazať triedu", - "deleteImages": "Zmazať obrázok", - "trainModel": "Trenovací model", - "addClassification": "Pridať klasifikáciu", - "deleteModels": "Zmazať modeli", - "editModel": "Upraviť model" + "renameCategory": "Přejmenovat třídu", + "deleteCategory": "Smazat třídu", + "deleteImages": "Smazat obrázek", + "trainModel": "Trénovat model", + "addClassification": "Přidat klasifikaci", + "deleteModels": "Smazat modely", + "editModel": "Upravit model" }, "details": { - "scoreInfo": "Skóre predstavuje priemernú istotu klasifikácie naprieč detekciami tohoto objektu." + "scoreInfo": "Skóre predstavuje priemernú istotu klasifikácie naprieč detekciami tohoto objektu.", + "none": "Nic", + "unknown": "Neznámý" }, "tooltip": { - "trainingInProgress": "Model se práve trénuje", - "noNewImages": "Žiadne nové obrázky na trénovanie. Najskôr klasifikujte viac obrazkov v datasete.", - "noChanges": "Od posledného treningu nedošlo k žiadnym zmenám v datasete.", - "modelNotReady": "Model nieje pripravený na trénovanie." + "trainingInProgress": "Model se právě trénuje", + "noNewImages": "Žádné obrázky pro trénování. Nejdříve klasifikujte obrázky pro dataset.", + "noChanges": "Od posledního trénování nedošlo k žádné změně.", + "modelNotReady": "Model není připravený na trénování." }, "toast": { "success": { - "deletedImage": "Zmazať obrazky", - "deletedModel_one": "Úspešne odstranený {{count}} model", - "deletedModel_few": "Úspešne odstranené {{count}} modely", - "deletedModel_other": "Úspěšne ostranených {{count}} modelov", - "deletedCategory": "Zmazať triedu", + "deletedImage": "Smazat obrázky", + "deletedModel_one": "Úspěšně odstraněný {{count}} model", + "deletedModel_few": "Úspěšně odstraněné {{count}} modely", + "deletedModel_other": "Úspěšně odstraněných {{count}} modelů", + "deletedCategory": "Smazat třídu", "categorizedImage": "Obrázek úspěšně klasifikován", "trainedModel": "Úspěšně vytrénovaný model.", - "trainingModel": "Trénování modelu bylo úspěšně zahájeno." + "trainingModel": "Trénování modelu bylo úspěšně zahájeno.", + "updatedModel": "Konfigurace modelu úspěšně aktualizována.", + "renamedCategory": "Třída úspěšně přejmenována na {{name}}" + }, + "error": { + "deleteImageFailed": "Chyba při mazání: {{errorMessage}}", + "deleteCategoryFailed": "Chyba při mazání třídy: {{errorMessage}}", + "deleteModelFailed": "Chyba při mazání modelu: {{errorMessage}}", + "categorizeFailed": "Chyba při mazání obrázku: {{errorMessage}}" } + }, + "train": { + "titleShort": "Nedávný" } } diff --git a/web/public/locales/cs/views/events.json b/web/public/locales/cs/views/events.json index 6757c21915..d05bd7cdc9 100644 --- a/web/public/locales/cs/views/events.json +++ b/web/public/locales/cs/views/events.json @@ -43,6 +43,7 @@ "label": "Detail", "noDataFound": "Žádná detailní data k prohlédnutí", "aria": "Přepnout detailní zobrazení", - "trackedObject_other": "{{count}} objektů" + "trackedObject_other": "{{count}} objektů", + "trackedObject_one": "{{count}} objektů" } } diff --git a/web/public/locales/cs/views/faceLibrary.json b/web/public/locales/cs/views/faceLibrary.json index 73b4c562af..cf4b1faeaa 100644 --- a/web/public/locales/cs/views/faceLibrary.json +++ b/web/public/locales/cs/views/faceLibrary.json @@ -38,7 +38,8 @@ "train": { "title": "Nedávná rozpoznání", "empty": "Nejsou zde žádné předchozí pokusy o rozpoznání obličeje", - "aria": "Vybrat trénink" + "aria": "Vybrat poslední rozpoznávání", + "titleShort": "Nedávný" }, "description": { "addFace": "Přidejte novou kolekci do Knihovny obličejů nahráním prvního obrázku.", @@ -76,7 +77,7 @@ "deletedName_one": "{{count}} obličej byl úspěšně odstraněn.", "deletedName_few": "{{count}} tváře byly úspěšně odstraněny.", "deletedName_other": "{{count}} tváře byly úspěšně odstraněny.", - "updatedFaceScore": "Úspěšně aktualizováno skóre obličeje.", + "updatedFaceScore": "Úspěšně aktualizováno skóre obličeje na {{name}} ({{score}}).", "addFaceLibrary": "{{name}} byl(a) úspěšně přidán(a) do Knihovny obličejů!" }, "error": { diff --git a/web/public/locales/cs/views/search.json b/web/public/locales/cs/views/search.json index e828a67164..2d699792cc 100644 --- a/web/public/locales/cs/views/search.json +++ b/web/public/locales/cs/views/search.json @@ -26,7 +26,8 @@ "min_score": "Minimální Skóre", "recognized_license_plate": "Rozpoznaná SPZ", "has_clip": "Má Klip", - "has_snapshot": "Má Snímek" + "has_snapshot": "Má Snímek", + "attributes": "Atributy" }, "tips": { "desc": { diff --git a/web/public/locales/cs/views/settings.json b/web/public/locales/cs/views/settings.json index 1a73650284..3875b82695 100644 --- a/web/public/locales/cs/views/settings.json +++ b/web/public/locales/cs/views/settings.json @@ -8,7 +8,7 @@ "masksAndZones": "Editor masky a zón - Frigate", "motionTuner": "Ladění detekce pohybu - Frigate", "object": "Ladění - Frigate", - "general": "Nastavení rozhraní- Frigate", + "general": "Nastavení rozhraní - Frigate", "frigatePlus": "Frigate+ nastavení - Frigate", "enrichments": "Nastavení obohacení - Frigate", "cameraManagement": "Správa kamer - Frigate", From a1094615e1d53dae674cd4f4ed7a3f91ee651ef3 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:17 +0100 Subject: [PATCH 51/78] Translated using Weblate (Croatian) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Croatian) Currently translated at 9.8% (21 of 214 strings) Translated using Weblate (Croatian) Currently translated at 17.3% (16 of 92 strings) Translated using Weblate (Croatian) Currently translated at 8.1% (10 of 122 strings) Translated using Weblate (Croatian) Currently translated at 18.9% (14 of 74 strings) Translated using Weblate (Croatian) Currently translated at 7.4% (10 of 135 strings) Translated using Weblate (Croatian) Currently translated at 9.9% (13 of 131 strings) Translated using Weblate (Croatian) Currently translated at 4.7% (24 of 501 strings) Translated using Weblate (Croatian) Currently translated at 16.1% (19 of 118 strings) Translated using Weblate (Croatian) Currently translated at 39.0% (16 of 41 strings) Translated using Weblate (Croatian) Currently translated at 80.0% (8 of 10 strings) Translated using Weblate (Croatian) Currently translated at 34.6% (17 of 49 strings) Translated using Weblate (Croatian) Currently translated at 28.0% (7 of 25 strings) Translated using Weblate (Croatian) Currently translated at 92.3% (12 of 13 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (6 of 6 strings) Translated using Weblate (Croatian) Currently translated at 2.1% (14 of 654 strings) Translated using Weblate (Croatian) Currently translated at 30.9% (17 of 55 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Zoran Ivancevic Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/hr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/hr/ Translation: Frigate NVR/audio Translation: Frigate NVR/common Translation: Frigate NVR/components-auth Translation: Frigate NVR/components-dialog Translation: Frigate NVR/components-filter Translation: Frigate NVR/components-input Translation: Frigate NVR/components-player Translation: Frigate NVR/objects Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-events Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-exports Translation: Frigate NVR/views-live Translation: Frigate NVR/views-recording Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/hr/audio.json | 25 ++++++++++- web/public/locales/hr/common.json | 22 ++++++++- web/public/locales/hr/components/auth.json | 11 ++++- web/public/locales/hr/components/dialog.json | 44 +++++++++++++++++- web/public/locales/hr/components/filter.json | 26 ++++++++++- web/public/locales/hr/components/icons.json | 5 ++- web/public/locales/hr/components/input.json | 5 ++- web/public/locales/hr/components/player.json | 18 +++++++- web/public/locales/hr/objects.json | 20 ++++++++- .../locales/hr/views/classificationModel.json | 23 +++++++++- web/public/locales/hr/views/configEditor.json | 11 ++++- web/public/locales/hr/views/events.json | 25 ++++++++++- web/public/locales/hr/views/explore.json | 23 +++++++++- web/public/locales/hr/views/exports.json | 16 ++++++- web/public/locales/hr/views/faceLibrary.json | 15 ++++++- web/public/locales/hr/views/live.json | 45 ++++++++++++++++++- web/public/locales/hr/views/recording.json | 10 ++++- web/public/locales/hr/views/search.json | 24 +++++++++- web/public/locales/hr/views/settings.json | 17 ++++++- web/public/locales/hr/views/system.json | 22 ++++++++- 20 files changed, 387 insertions(+), 20 deletions(-) diff --git a/web/public/locales/hr/audio.json b/web/public/locales/hr/audio.json index 5b518113a1..d0b8ecaaaf 100644 --- a/web/public/locales/hr/audio.json +++ b/web/public/locales/hr/audio.json @@ -1,3 +1,26 @@ { - "speech": "Govor" + "speech": "Govor", + "babbling": "Brbljanje", + "bicycle": "Bicikl", + "yell": "Vikanje", + "car": "Automobil", + "bellow": "Ispod", + "motorcycle": "Motocikl", + "whispering": "Šaptanje", + "bus": "Autobus", + "laughter": "Smijeh", + "train": "Vlak", + "snicker": "Tenisica", + "boat": "Čamac", + "crying": "Plakanje", + "singing": "Pjevanje", + "choir": "Zbor", + "yodeling": "Jodlanje", + "mantra": "Mantra", + "bird": "Ptica", + "child_singing": "Dijete pjeva", + "cat": "Mačka", + "dog": "Pas", + "horse": "Konj", + "sheep": "Ovca" } diff --git a/web/public/locales/hr/common.json b/web/public/locales/hr/common.json index af7c7980ce..1e60764bda 100644 --- a/web/public/locales/hr/common.json +++ b/web/public/locales/hr/common.json @@ -1,5 +1,25 @@ { "time": { - "untilForTime": "Do {{time}}" + "untilForTime": "Do {{time}}", + "untilForRestart": "Dok se Frigate ponovno pokrene.", + "untilRestart": "Do ponovnog pokretanja", + "justNow": "Upravo", + "today": "Danas", + "yesterday": "Jučer", + "last7": "Zadnjih 7 dana", + "last14": "Zadnjih 14 dana", + "last30": "Zadnjih 30 dana", + "thisWeek": "Ovaj tjedan", + "lastWeek": "Prošli tjedan", + "thisMonth": "Ovaj mjesec", + "lastMonth": "Prošli mjesec", + "5minutes": "5 minuta", + "10minutes": "10 minuta", + "30minutes": "30 minuta", + "1hour": "1 sat", + "12hours": "12 sati", + "24hours": "24 sata", + "pm": "pm", + "am": "am" } } diff --git a/web/public/locales/hr/components/auth.json b/web/public/locales/hr/components/auth.json index f3b92ea455..110ad2c644 100644 --- a/web/public/locales/hr/components/auth.json +++ b/web/public/locales/hr/components/auth.json @@ -1,5 +1,14 @@ { "form": { - "user": "Korisničko ime" + "user": "Korisničko ime", + "password": "Lozinka", + "login": "Prijava", + "errors": { + "usernameRequired": "Korisničko ime je obavezno", + "passwordRequired": "Lozinka je obavezna", + "loginFailed": "Prijava nije uspjela", + "unknownError": "Nepoznata greška. Provjeri dnevnik.", + "webUnknownError": "Nepoznata greška. Provjerite logove u konzoli." + } } } diff --git a/web/public/locales/hr/components/dialog.json b/web/public/locales/hr/components/dialog.json index 660031e5e8..2f360e1d6a 100644 --- a/web/public/locales/hr/components/dialog.json +++ b/web/public/locales/hr/components/dialog.json @@ -1,5 +1,47 @@ { "restart": { - "title": "Jeste li sigurni da želite ponovno pokrenuti Frigate?" + "title": "Jeste li sigurni da želite ponovno pokrenuti Frigate?", + "button": "Ponovno pokreni", + "restarting": { + "title": "Frigate se ponovno pokreće", + "content": "Ova stranica će se osvježiti za {{countdown}} sekundi.", + "button": "Forsiraj ponovno pokretanje odmah" + } + }, + "explore": { + "plus": { + "submitToPlus": { + "label": "Pošalji u Frigate+" + }, + "review": { + "question": { + "label": "Potvrdi oznaku za Frigate Plus", + "ask_a": "Da li je ovaj objekt {{label}}?", + "ask_an": "Da li je ovaj objekt {{label}}?", + "ask_full": "Da li je ovaj objekt {{untranslatedLabel}} ({{translatedLabel}})?" + }, + "state": { + "submitted": "Poslano" + } + } + }, + "video": { + "viewInHistory": "Pogledaj u povijesti" + } + }, + "export": { + "time": { + "lastHour_one": "Zadnji sat", + "lastHour_few": "Zadnja {{count}} sata", + "lastHour_other": "Zadnjih {{count}} sati", + "start": { + "title": "Vrijeme početka", + "label": "Odaberi vrijeme početka" + }, + "end": { + "title": "Vrijeme kraja", + "label": "Odaberi vrijeme kraja" + } + } } } diff --git a/web/public/locales/hr/components/filter.json b/web/public/locales/hr/components/filter.json index 37845aa999..d501197435 100644 --- a/web/public/locales/hr/components/filter.json +++ b/web/public/locales/hr/components/filter.json @@ -2,5 +2,29 @@ "filter": "Filter", "classes": { "label": "Klase" - } + }, + "labels": { + "label": "Oznake", + "all": { + "title": "Sve oznake", + "short": "Oznake" + }, + "count_one": "{{count}} oznake", + "count_other": "{{count}} oznake" + }, + "zones": { + "label": "Zone", + "all": { + "title": "Sve zone", + "short": "Zone" + } + }, + "dates": { + "selectPreset": "Odaberi predložak…", + "all": { + "title": "Svi datumi", + "short": "Datumi" + } + }, + "more": "Više filtera" } diff --git a/web/public/locales/hr/components/icons.json b/web/public/locales/hr/components/icons.json index b973f10727..f6c0fa5d54 100644 --- a/web/public/locales/hr/components/icons.json +++ b/web/public/locales/hr/components/icons.json @@ -1,5 +1,8 @@ { "iconPicker": { - "selectIcon": "Odaberite ikonu" + "selectIcon": "Odaberite ikonu", + "search": { + "placeholder": "Traži ikonu…" + } } } diff --git a/web/public/locales/hr/components/input.json b/web/public/locales/hr/components/input.json index ffeca81c5c..0df384beab 100644 --- a/web/public/locales/hr/components/input.json +++ b/web/public/locales/hr/components/input.json @@ -1,7 +1,10 @@ { "button": { "downloadVideo": { - "label": "Preuzmi video" + "label": "Preuzmi video", + "toast": { + "success": "Preuzimanje vašeg videa za recenziju je počelo." + } } } } diff --git a/web/public/locales/hr/components/player.json b/web/public/locales/hr/components/player.json index 752b358dcd..f322719041 100644 --- a/web/public/locales/hr/components/player.json +++ b/web/public/locales/hr/components/player.json @@ -1,3 +1,19 @@ { - "noRecordingsFoundForThisTime": "Nisu pronađene snimke za ovo vrijeme" + "noRecordingsFoundForThisTime": "Nisu pronađene snimke za ovo vrijeme", + "submitFrigatePlus": { + "title": "Pošalji ovaj kadar u Frigate+?", + "submit": "Pošalji" + }, + "cameraDisabled": "Kamera je onemogućena", + "stats": { + "streamType": { + "short": "Vrsta" + }, + "latency": { + "value": "{{seconds}} sekundi", + "short": { + "value": "{{seconds}} sekundi" + } + } + } } diff --git a/web/public/locales/hr/objects.json b/web/public/locales/hr/objects.json index afc1338078..1a9a743921 100644 --- a/web/public/locales/hr/objects.json +++ b/web/public/locales/hr/objects.json @@ -1,3 +1,21 @@ { - "person": "Osoba" + "person": "Osoba", + "bicycle": "Bicikl", + "car": "Automobil", + "motorcycle": "Motocikl", + "airplane": "Zrakoplov", + "bus": "Autobus", + "train": "Vlak", + "boat": "Čamac", + "traffic_light": "Semafor", + "fire_hydrant": "Hidrant", + "street_sign": "Prometni znak", + "stop_sign": "Znak stop", + "bench": "Klupa", + "bird": "Ptica", + "cat": "Mačka", + "dog": "Pas", + "horse": "Konj", + "sheep": "Ovca", + "cow": "Krava" } diff --git a/web/public/locales/hr/views/classificationModel.json b/web/public/locales/hr/views/classificationModel.json index 0967ef424b..e3070af27b 100644 --- a/web/public/locales/hr/views/classificationModel.json +++ b/web/public/locales/hr/views/classificationModel.json @@ -1 +1,22 @@ -{} +{ + "documentTitle": "Klasifikacijski modeli - Frigate", + "button": { + "deleteImages": "Obriši slike", + "trainModel": "Treniraj model", + "addClassification": "Dodaj klasifikaciju", + "deleteModels": "Obriši modele", + "editModel": "Uredi model" + }, + "tooltip": { + "trainingInProgress": "Model se trenutno trenira", + "modelNotReady": "Model nije spreman za treniranje" + }, + "details": { + "unknown": "Nepoznato" + }, + "toast": { + "success": { + "deletedImage": "Obrisane slike" + } + } +} diff --git a/web/public/locales/hr/views/configEditor.json b/web/public/locales/hr/views/configEditor.json index 6443eaa831..fe4011ecc1 100644 --- a/web/public/locales/hr/views/configEditor.json +++ b/web/public/locales/hr/views/configEditor.json @@ -1,3 +1,12 @@ { - "documentTitle": "Uređivač konfiguracije - Frigate" + "documentTitle": "Uređivač konfiguracije - Frigate", + "copyConfig": "Kopiraj konfiguraciju", + "saveAndRestart": "Spremi i pokreni ponovno", + "saveOnly": "Samo spremi", + "confirm": "Izađi bez spremanja?", + "toast": { + "error": { + "savingError": "Greška pri spremanju konfiguracije" + } + } } diff --git a/web/public/locales/hr/views/events.json b/web/public/locales/hr/views/events.json index b38704712e..47c1530f25 100644 --- a/web/public/locales/hr/views/events.json +++ b/web/public/locales/hr/views/events.json @@ -1,3 +1,26 @@ { - "alerts": "Upozorenja" + "alerts": "Upozorenja", + "detections": "Detekcije", + "motion": { + "label": "Pokret", + "only": "Samo pokret" + }, + "allCameras": "Sve kamere", + "empty": { + "alert": "Nema uzbuna za pregledati", + "detection": "Nema detekcija za pregled", + "motion": "Nema podataka o pokretu" + }, + "timeline": "Vremenska linija", + "timeline.aria": "Odaberi vremensku liniju", + "zoomOut": "Udalji", + "events": { + "label": "Događaji", + "aria": "Odaberi događaje", + "noFoundForTimePeriod": "Nema pronađenih događaja za ovaj vremenski period." + }, + "zoomIn": "Približi", + "detail": { + "label": "Detalji" + } } diff --git a/web/public/locales/hr/views/explore.json b/web/public/locales/hr/views/explore.json index c4f84e742f..114e4f8935 100644 --- a/web/public/locales/hr/views/explore.json +++ b/web/public/locales/hr/views/explore.json @@ -1,3 +1,24 @@ { - "documentTitle": "Istražite - Frigate" + "documentTitle": "Istražite - Frigate", + "generativeAI": "Generativni AI", + "exploreIsUnavailable": { + "title": "Istraživanje je nedostupno", + "embeddingsReindexing": { + "startingUp": "Pokretanje…", + "finishingShortly": "Završava uskoro", + "estimatedTime": "Procjenjeno preostalo vrijeme:" + }, + "downloadingModels": { + "setup": { + "textModel": "Tekstualni model" + } + } + }, + "details": { + "timestamp": "Vremenska oznaka" + }, + "trackedObjectDetails": "Detalji praćenog objekta", + "type": { + "details": "detalji" + } } diff --git a/web/public/locales/hr/views/exports.json b/web/public/locales/hr/views/exports.json index 529e7c42e0..6a4956e699 100644 --- a/web/public/locales/hr/views/exports.json +++ b/web/public/locales/hr/views/exports.json @@ -1,4 +1,18 @@ { "documentTitle": "Izvoz - Frigate", - "search": "Pretraga" + "search": "Pretraga", + "deleteExport.desc": "Da li si siguran da želiš obrisati {{exportName}}?", + "editExport": { + "saveExport": "Spremi izvoz", + "title": "Preimenuj izvoz", + "desc": "Unesite novo ime ovog izvoza." + }, + "tooltip": { + "shareExport": "Podijeli izvoz", + "downloadVideo": "Preuzmi video", + "editName": "Uredi ime", + "deleteExport": "Obriši izvoz" + }, + "noExports": "Izvozi nisu pronađeni", + "deleteExport": "Obriši izvoz" } diff --git a/web/public/locales/hr/views/faceLibrary.json b/web/public/locales/hr/views/faceLibrary.json index 7f5754c9a8..b500f4b90c 100644 --- a/web/public/locales/hr/views/faceLibrary.json +++ b/web/public/locales/hr/views/faceLibrary.json @@ -1,6 +1,7 @@ { "description": { - "addFace": "Vodič za dodavanje nove kolekcije u Biblioteku lica." + "addFace": "Dodaj novu kolekcije u Biblioteku lica učitavanjem prve slike.", + "placeholder": "Unesi ime za ovu kolekciju" }, "steps": { "faceName": "Unesi Ime Lica", @@ -24,5 +25,17 @@ "desc_one": "Jeste li sigurni da želite izbrisati {{count}} lice? Ova se radnja ne može poništiti.", "desc_few": "Jeste li sigurni da želite izbrisati {{count}} lica? Ova se radnja ne može poništiti.", "desc_other": "Jeste li sigurni da želite izbrisati {{count}} lica? Ova se radnja ne može poništiti." + }, + "details": { + "timestamp": "Vremenska oznaka", + "unknown": "Nepoznato" + }, + "documentTitle": "Biblioteka lica - Frigate", + "uploadFaceImage": { + "title": "Učitaj sliku lica" + }, + "collections": "Kolekcije", + "createFaceLibrary": { + "new": "Stvori novo lice" } } diff --git a/web/public/locales/hr/views/live.json b/web/public/locales/hr/views/live.json index 93f59972a7..edf847efc6 100644 --- a/web/public/locales/hr/views/live.json +++ b/web/public/locales/hr/views/live.json @@ -1,3 +1,46 @@ { - "documentTitle": "Uživo - Frigate" + "documentTitle": "Uživo - Frigate", + "documentTitle.withCamera": "{{camera}} - Uživo - Frigate", + "twoWayTalk": { + "enable": "Omogući dvosmjerni razgovor", + "disable": "Onemogući dvosmjerni razgovor" + }, + "cameraAudio": { + "enable": "Omogući zvuk kamere", + "disable": "Onemogući zvuk kamere" + }, + "ptz": { + "move": { + "clickMove": { + "label": "Klikni unutar kadra da centriraš kameru", + "enable": "Omogući pomicanje klikom", + "disable": "Onemogući pomicanje klikom" + }, + "left": { + "label": "Pomakni PTZ kameru u lijevo" + }, + "up": { + "label": "Pomakni PTZ kameru gore" + }, + "down": { + "label": "Pomakni PTZ kameru dolje" + }, + "right": { + "label": "Pomakni PTZ kameru u desno" + } + }, + "zoom": { + "in": { + "label": "Približi PTZ kameru" + }, + "out": { + "label": "Udalji PTZ kameru" + } + }, + "focus": { + "in": { + "label": "Izoštri fokus PTZ kamere" + } + } + } } diff --git a/web/public/locales/hr/views/recording.json b/web/public/locales/hr/views/recording.json index 110cf71ebc..8470b3e3d4 100644 --- a/web/public/locales/hr/views/recording.json +++ b/web/public/locales/hr/views/recording.json @@ -1,4 +1,12 @@ { "filter": "Filter", - "export": "Izvoz" + "export": "Izvoz", + "calendar": "Kalendar", + "filters": "Filteri", + "toast": { + "error": { + "endTimeMustAfterStartTime": "Vrijeme završetka mora biti nakon vremena početka", + "noValidTimeSelected": "Nije izabran ispravan vremenski raspon" + } + } } diff --git a/web/public/locales/hr/views/search.json b/web/public/locales/hr/views/search.json index 370cb28b91..c31ff77c89 100644 --- a/web/public/locales/hr/views/search.json +++ b/web/public/locales/hr/views/search.json @@ -1,3 +1,25 @@ { - "search": "Pretraga" + "search": "Pretraga", + "savedSearches": "Spremljene pretrage", + "searchFor": "Pretraži {{inputValue}}", + "button": { + "clear": "Izbriši pretragu", + "save": "Spremi pretragu", + "delete": "Obriši spremljene pretrage", + "filterInformation": "Filtriraj informacije", + "filterActive": "Filteri aktivni" + }, + "filter": { + "label": { + "cameras": "Kamere", + "labels": "Oznake", + "zones": "Zone", + "search_type": "Pretraži vrstu", + "time_range": "Vremenski raspon", + "attributes": "Atributi", + "before": "Prije", + "after": "Poslije", + "min_score": "Min ocjena" + } + } } diff --git a/web/public/locales/hr/views/settings.json b/web/public/locales/hr/views/settings.json index c2153a6099..500361bf33 100644 --- a/web/public/locales/hr/views/settings.json +++ b/web/public/locales/hr/views/settings.json @@ -1,5 +1,20 @@ { "documentTitle": { - "default": "Postavke - Frigate" + "default": "Postavke - Frigate", + "authentication": "Postavke autentikacije - Frigate", + "cameraManagement": "Upravljaj kamerama - Frigate", + "masksAndZones": "Uređivač maski i zona - Frigate", + "general": "Postavke sučelja - Frigate", + "frigatePlus": "Frigate+ postavke - Frigate", + "notifications": "Postavke notifikacija - Frigate", + "enrichments": "Postavke obogaćivanja - Frigate" + }, + "menu": { + "ui": "Sučelje", + "cameraReview": "Pregled", + "enrichments": "Obogaćenja", + "masksAndZones": "Maske / Zone", + "triggers": "Okidači", + "users": "Korisnici" } } diff --git a/web/public/locales/hr/views/system.json b/web/public/locales/hr/views/system.json index 076c823a0a..417209030a 100644 --- a/web/public/locales/hr/views/system.json +++ b/web/public/locales/hr/views/system.json @@ -1,5 +1,25 @@ { "documentTitle": { - "cameras": "Statistika kamera - Frigate" + "cameras": "Statistika kamera - Frigate", + "general": "Generalne statistike - Frigate", + "logs": { + "go2rtc": "Go2RTC dnevnik - Frigate", + "nginx": "Nginx dnevnik - Frigate", + "frigate": "Frigate logovi - Frigate" + }, + "storage": "Statistika pohrane - Frigate", + "enrichments": "Statistika obogaćivanja - Frigate" + }, + "title": "Sustav", + "logs": { + "download": { + "label": "Preuzmi dnevnik" + }, + "type": { + "label": "Vrsta", + "timestamp": "Vremenska oznaka", + "tag": "Oznaka", + "message": "Poruka" + } } } From aa9dbbb48d51db020156af7ab20fd8c0374ff668 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:19 +0100 Subject: [PATCH 52/78] Translated using Weblate (Hebrew) Currently translated at 100.0% (501 of 501 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (92 of 92 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (46 of 46 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (654 of 654 strings) Translated using Weblate (Hebrew) Currently translated at 94.3% (617 of 654 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (214 of 214 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (41 of 41 strings) Translated using Weblate (Hebrew) Currently translated at 94.3% (617 of 654 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (118 of 118 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (55 of 55 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Hebrew) Currently translated at 96.2% (51 of 53 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Hebrew) Currently translated at 97.8% (90 of 92 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (501 of 501 strings) Translated using Weblate (Hebrew) Currently translated at 99.2% (134 of 135 strings) Translated using Weblate (Hebrew) Currently translated at 90.2% (83 of 92 strings) Translated using Weblate (Hebrew) Currently translated at 91.1% (195 of 214 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (46 of 46 strings) Translated using Weblate (Hebrew) Currently translated at 95.1% (39 of 41 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (13 of 13 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (10 of 10 strings) Translated using Weblate (Hebrew) Currently translated at 45.0% (55 of 122 strings) Translated using Weblate (Hebrew) Currently translated at 48.6% (318 of 654 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Hebrew) Currently translated at 98.1% (54 of 55 strings) Translated using Weblate (Hebrew) Currently translated at 82.9% (112 of 135 strings) Translated using Weblate (Hebrew) Currently translated at 90.0% (118 of 131 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Hebrew) Currently translated at 88.6% (47 of 53 strings) Co-authored-by: Hosted Weblate Co-authored-by: Ronen Atsil Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/he/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/he/ Translation: Frigate NVR/audio Translation: Frigate NVR/common Translation: Frigate NVR/components-auth Translation: Frigate NVR/components-camera Translation: Frigate NVR/components-dialog Translation: Frigate NVR/components-filter Translation: Frigate NVR/objects Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-events Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-exports Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-live Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/he/audio.json | 31 +- web/public/locales/he/common.json | 40 +- web/public/locales/he/components/auth.json | 3 +- web/public/locales/he/components/camera.json | 3 +- web/public/locales/he/components/dialog.json | 14 +- web/public/locales/he/components/filter.json | 6 +- web/public/locales/he/objects.json | 2 +- .../locales/he/views/classificationModel.json | 193 ++++++- web/public/locales/he/views/events.json | 20 +- web/public/locales/he/views/explore.json | 107 +++- web/public/locales/he/views/exports.json | 6 + web/public/locales/he/views/faceLibrary.json | 17 +- web/public/locales/he/views/live.json | 29 +- web/public/locales/he/views/search.json | 5 +- web/public/locales/he/views/settings.json | 524 +++++++++++++++++- web/public/locales/he/views/system.json | 28 +- 16 files changed, 968 insertions(+), 60 deletions(-) diff --git a/web/public/locales/he/audio.json b/web/public/locales/he/audio.json index 841dfa83bf..711a8d3384 100644 --- a/web/public/locales/he/audio.json +++ b/web/public/locales/he/audio.json @@ -94,7 +94,7 @@ "electronic_organ": "אורגן חשמלי", "hammond_organ": "עוגב המונד", "synthesizer": "סינתיסייזר", - "sampler": "דגם", + "sampler": "דוגם", "harpsichord": "צֶ'מבָּלוֹ", "percussion": "הַקָשָׁה", "boat": "סירה", @@ -102,7 +102,7 @@ "motorcycle": "אופנוע", "bus": "אוטובוס", "bicycle": "אופניים", - "train": "למד פנים", + "train": "אימון", "skateboard": "סקייטבורד", "camera": "מצלמה", "howl": "יללה", @@ -474,5 +474,30 @@ "bouncing": "הַקפָּצָה", "whip": "שׁוֹט", "flap": "מַדָף", - "scratch": "לְגַרֵד" + "scratch": "לְגַרֵד", + "scrape": "סריקה", + "rub": "שפשוף", + "roll": "גלגול", + "crushing": "מעיכה", + "crumpling": "קימוט", + "tearing": "קריעה", + "beep": "ביפ", + "ping": "פינג", + "ding": "דינג", + "clang": "צלצול מתכתי", + "squeal": "חריקה", + "creak": "חריקה", + "rustle": "רשרוש", + "whir": "זמזום", + "clatter": "רעש נקישות", + "chird": "Chird", + "sizzle": "צליל חריכה", + "clicking": "נקישות", + "clickety_clack": "נקישות רצופות", + "rumble": "רעם נמוך", + "plop": "פלופ", + "hum": "המהום", + "zing": "זמזום חד", + "boing": "בּוֹאִינְג (צליל קפיצי / אלסטי)", + "crunch": "חריקה / פיצוח" } diff --git a/web/public/locales/he/common.json b/web/public/locales/he/common.json index 813d44e51c..0496941bb0 100644 --- a/web/public/locales/he/common.json +++ b/web/public/locales/he/common.json @@ -78,7 +78,10 @@ "12hour": "MMM d, yyyy" }, "30minutes": "30 דקות", - "thisMonth": "החודש" + "thisMonth": "החודש", + "inProgress": "בתהליך", + "invalidStartTime": "זמן התחלה לא תקין", + "invalidEndTime": "זמן סיום לא תקין" }, "unit": { "speed": { @@ -88,10 +91,23 @@ "length": { "feet": "רגל", "meters": "מטרים" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/hour", + "mbph": "MB/hour", + "gbph": "GB/hour" } }, "label": { - "back": "אחורה" + "back": "אחורה", + "hide": "הסתר {{item}}", + "show": "הצג {{item}}", + "ID": "ID", + "none": "ללא", + "all": "הכל" }, "button": { "apply": "החל", @@ -128,7 +144,8 @@ "on": "פעיל", "download": "הורדה", "info": "מידע", - "next": "הבא" + "next": "הבא", + "continue": "המשך" }, "menu": { "system": "מערכת", @@ -229,7 +246,8 @@ "current": "משתמש מחובר: {{user}}", "setPassword": "קביעת סיסמה", "title": "משתמש" - } + }, + "classification": "סיווג" }, "toast": { "copyUrlToClipboard": "כתובת האתר המועתקת.", @@ -270,5 +288,17 @@ "desc": "דף לא נמצא" }, "selectItem": "בחירה:{{item}}", - "readTheDocumentation": "קרא את התיעוד" + "readTheDocumentation": "קרא את התיעוד", + "list": { + "two": "{{0}} ו־{{1}}", + "many": "{{items}}, ו־{{last}}", + "separatorWithSpace": ", " + }, + "field": { + "optional": "אופציונלי", + "internalID": "המזהה הפנימי ש־Frigate משתמש בו בהגדרות ובמסד הנתונים" + }, + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/he/components/auth.json b/web/public/locales/he/components/auth.json index 17b28cba1f..0f6caf3cf4 100644 --- a/web/public/locales/he/components/auth.json +++ b/web/public/locales/he/components/auth.json @@ -10,6 +10,7 @@ "webUnknownError": "שגיאה לא ידועה, בדוק את הלוגים.", "rateLimit": "חרגת מהמגבלת בקשות. נסה שוב מאוחר יותר.", "loginFailed": "ההתחברות נכשלה" - } + }, + "firstTimeLogin": "מתחבר בפעם הראשונה? פרטי ההתחברות מודפסים בלוגים של פריגייט." } } diff --git a/web/public/locales/he/components/camera.json b/web/public/locales/he/components/camera.json index f9de9a6c14..184b192bd6 100644 --- a/web/public/locales/he/components/camera.json +++ b/web/public/locales/he/components/camera.json @@ -41,7 +41,8 @@ "label": "מצב תאימות", "desc": "הפעל אפשרות זו רק אם השידור החי של המצלמה שלך מציג עיוותים בצבע ויש לו קו אלכסוני בצד ימין של התמונה." } - } + }, + "birdseye": "מבט על" }, "edit": "ערכית קבוצת מצלמות", "delete": { diff --git a/web/public/locales/he/components/dialog.json b/web/public/locales/he/components/dialog.json index 472d3d541e..85353b9938 100644 --- a/web/public/locales/he/components/dialog.json +++ b/web/public/locales/he/components/dialog.json @@ -15,7 +15,8 @@ "failed": "נכשל בהתחלת הייצוא: {{error}}", "noVaildTimeSelected": "לא נבחר טווח זמן תקף" }, - "success": "הייצוא הוחל בהצלחה. הצג את הקובץ בתיקייה /ייצוא." + "success": "הייצוא התחיל בהצלחה. ניתן לצפות בקובץ בעמוד הייצוא.", + "view": "תצוגה" }, "time": { "end": { @@ -108,7 +109,16 @@ "button": { "export": "ייצוא", "markAsReviewed": "סמן כסוקר", - "deleteNow": "מחיקה כעת" + "deleteNow": "מחיקה כעת", + "markAsUnreviewed": "סימון כלא נבדק" } + }, + "imagePicker": { + "selectImage": "בחר תמונה ממוזערת של אובייקט במעקב", + "unknownLabel": "תמונת הטריגר נשמרה", + "search": { + "placeholder": "חיפוש לפי תווית או תווית משנה…" + }, + "noImages": "לא נמצאו תמונות ממוזערות עבור מצלמה זו" } } diff --git a/web/public/locales/he/components/filter.json b/web/public/locales/he/components/filter.json index 17f7914cfc..b11aba954d 100644 --- a/web/public/locales/he/components/filter.json +++ b/web/public/locales/he/components/filter.json @@ -5,7 +5,7 @@ "tips": "עליך תחילה לסנן לפי אובייקטים במעקב שיש להם תמונת מצב.

    לא ניתן לשלוח ל-Frigate+ אובייקטים במעקב ללא תמונת מצב.", "label": "העלאה ל- +Frigate" }, - "label": "מאפיינים", + "label": "תכונות", "hasVideoClip": "קיים סרטון", "hasSnapshot": "קיימת לכידת תמונה" }, @@ -132,5 +132,9 @@ }, "count_one": "{{count}} מחלקה", "count_other": "{{count}} מחלקות" + }, + "attributes": { + "label": "מאפייני סיווג", + "all": "כל המאפיינים" } } diff --git a/web/public/locales/he/objects.json b/web/public/locales/he/objects.json index 68f648da0b..65f00b1b76 100644 --- a/web/public/locales/he/objects.json +++ b/web/public/locales/he/objects.json @@ -5,7 +5,7 @@ "motorcycle": "אופנוע", "airplane": "מטוס", "bus": "אוטובוס", - "train": "למד פנים", + "train": "אימון", "boat": "סירה", "traffic_light": "רמזור", "fire_hydrant": "ברז כיבוי אש", diff --git a/web/public/locales/he/views/classificationModel.json b/web/public/locales/he/views/classificationModel.json index 0967ef424b..0e965eb741 100644 --- a/web/public/locales/he/views/classificationModel.json +++ b/web/public/locales/he/views/classificationModel.json @@ -1 +1,192 @@ -{} +{ + "documentTitle": "מודלי סיווג - Frigate", + "details": { + "scoreInfo": "הציון מייצג את ממוצע רמת הביטחון של הסיווג, על פני כל הזיהויים של האובייקט הזה.", + "none": "ללא ערך", + "unknown": "לא ידוע" + }, + "button": { + "deleteClassificationAttempts": "מחיקת אוסף התמונות", + "renameCategory": "שינוי שם קטגוריה", + "deleteCategory": "מחיקת קטגוריה", + "deleteImages": "מחיקת תמונות", + "trainModel": "אימון מודל", + "addClassification": "הוספת סיווג", + "deleteModels": "מחיקת מודלים", + "editModel": "עריכת מודל" + }, + "tooltip": { + "trainingInProgress": "המודל נמצא כרגע בתהליך אימון", + "noNewImages": "אין תמונות חדשות לאימון. קודם סווג עוד תמונות במערך הנתונים (Dataset).", + "noChanges": "לא בוצעו שינויים במערך הנתונים מאז האימון האחרון.", + "modelNotReady": "המודל עדיין לא מוכן לאימון" + }, + "toast": { + "success": { + "deletedCategory": "הקטגוריה נמחקה", + "deletedImage": "התמונות נמחקו", + "deletedModel_one": "נמחק בהצלחה {{count}} מודל", + "deletedModel_two": "נמחקו בהצלחה {{count}} מודלים", + "deletedModel_other": "", + "categorizedImage": "התמונה סווגה בהצלחה", + "trainedModel": "המודל אומן בהצלחה.", + "trainingModel": "אימון המודל התחיל בהצלחה.", + "updatedModel": "תצורת המודל עודכנה בהצלחה.", + "renamedCategory": "שם הקטגוריה שונה בהצלחה ל־{{name}}" + }, + "error": { + "deleteImageFailed": "המחיקה נכשלה: {{errorMessage}}", + "deleteCategoryFailed": "מחיקת הקטגוריה נכשלה: {{errorMessage}}", + "deleteModelFailed": "מחיקת המודל נכשלה: {{errorMessage}}", + "categorizeFailed": "סיווג התמונה נכשל: {{errorMessage}}", + "trainingFailed": "אימון המודל נכשל. בדוק בלוגים של Frigate לפרטים.", + "trainingFailedToStart": "הפעלת אימון המודל נכשלה: {{errorMessage}}", + "updateModelFailed": "עדכון המודל נכשל: {{errorMessage}}", + "renameCategoryFailed": "שינוי שם הקטגוריה נכשל: {{errorMessage}}" + } + }, + "train": { + "titleShort": "לאחרונה", + "title": "סיווגים אחרונים", + "aria": "בחר סיווגים אחרונים" + }, + "deleteCategory": { + "title": "מחיקת קטגוריה", + "desc": "האם אתה בטוח שברצונך למחוק את הקטגוריה {{name}}? פעולה זו תמחק לצמיתות את כל התמונות המשויכות, ותדרוש אימון מחדש של המודל.", + "minClassesTitle": "לא ניתן למחוק את הקטגוריה", + "minClassesDesc": "מודל סיווג חייב לכלול לפחות 2 קטגוריות. הוסף קטגוריה נוספת לפני שתמחק את הקטגוריה הזו." + }, + "deleteModel": { + "title": "מחיקת מודל סיווג", + "single": "האם אתה בטוח שברצונך למחוק את {{name}}? פעולה זו תמחק לצמיתות את כל הנתונים המשויכים, כולל תמונות ונתוני אימון. לא ניתן לבטל פעולה זו.", + "desc_one": "האם אתה בטוח שברצונך למחוק מודל אחד ({{count}})? פעולה זו תמחק לצמיתות את כל הנתונים המשויכים, כולל תמונות ונתוני אימון. לא ניתן לבטל פעולה זו.", + "desc_two": "האם אתה בטוח שברצונך למחוק {{count}} מודלים? פעולה זו תמחק לצמיתות את כל הנתונים המשויכים, כולל תמונות ונתוני אימון. לא ניתן לבטל פעולה זו.", + "desc_other": "" + }, + "edit": { + "title": "עריכת מודל סיווג", + "descriptionState": "ערוך את הקטגוריות של מודל הסיווג הזה. כל שינוי ידרוש אימון מחדש של המודל.", + "descriptionObject": "ערוך את סוג האובייקט ואת סוג הסיווג עבור מודל סיווג האובייקטים הזה.", + "stateClassesInfo": "הערה: שינוי קטגוריות המצבים מחייב אימון מחדש של המודל עם הקטגוריות המעודכנות." + }, + "deleteDatasetImages": { + "title": "מחיקת תמונות מערך הנתונים", + "desc_one": "האם אתה בטוח שברצונך למחוק {{count}} תמונה מתוך {{dataset}}? לא ניתן לבטל פעולה זו, והיא תדרוש אימון מחדש של המודל.", + "desc_two": "האם אתה בטוח שברצונך למחוק {{count}} תמונות מתוך {{dataset}}? לא ניתן לבטל פעולה זו, והיא תדרוש אימון מחדש של המודל.", + "desc_other": "" + }, + "deleteTrainImages": { + "title": "מחיקת תמונות אימון", + "desc_one": "האם אתה בטוח שברצונך למחוק {{count}} תמונה? לא ניתן לבטל פעולה זו.", + "desc_two": "האם אתה בטוח שברצונך למחוק {{count}} תמונות? לא ניתן לבטל פעולה זו.", + "desc_other": "" + }, + "renameCategory": { + "title": "שינוי שם קטגוריה", + "desc": "הזן שם חדש עבור {{name}}. יהיה עליך לאמן מחדש את המודל כדי שהשינוי בשם ייכנס לתוקף." + }, + "description": { + "invalidName": "שם לא תקין. שמות יכולים לכלול רק אותיות, מספרים, רווחים, גרש (’), קו תחתון (_) ומקף (-)." + }, + "categories": "קטגוריות", + "createCategory": { + "new": "יצירת קטגוריה חדשה" + }, + "wizard": { + "step3": { + "errors": { + "noObjectLabel": "לא נבחרה תווית אובייקט", + "generateFailed": "יצירת דוגמאות נכשלה: {{error}}", + "generationFailed": "היצירה נכשלה. נסה שוב.", + "classifyFailed": "סיווג התמונות נכשל: {{error}}", + "noCameras": "לא הוגדרו מצלמות" + }, + "generateSuccess": "תמונות לדוגמה נוצרו בהצלחה", + "missingStatesWarning": { + "title": "חסרות דוגמאות מצב", + "description": "מומלץ לבחור דוגמאות לכל המצבים כדי לקבל את התוצאות הטובות ביותר. אפשר להמשיך גם בלי לבחור את כל המצבים, אבל המודל לא יאומן עד שלכל המצבים יהיו תמונות.\nאחרי שתמשיך, השתמש בתצוגת סיווגים אחרונים כדי לסווג תמונות למצבים החסרים, ואז בצע אימון מודל." + }, + "training": { + "title": "אימון מודל", + "description": "המודל שלך נמצא כעת בתהליך אימון ברקע. אפשר לסגור את החלון הזה, והמודל יתחיל לפעול מיד לאחר סיום האימון." + }, + "classifying": "מסווג ומאמן...", + "trainingStarted": "האימון התחיל בהצלחה", + "modelCreated": "המודל נוצר בהצלחה. השתמש בתצוגת סיווגים אחרונים כדי להוסיף תמונות למצבים חסרים, ולאחר מכן אמן את המודל.", + "selectImagesPrompt": "בחר את כל התמונות עם: {{className}}", + "selectImagesDescription": "לחץ על תמונות כדי לבחור אותן. לחץ על המשך כשתסיים עם מחלקה זו.", + "allImagesRequired_one": "אנא סווג את כל התמונות. נותרה {{count}} תמונה.", + "allImagesRequired_two": "אנא סווג את כל התמונות. נותרו {{count}} תמונות.", + "allImagesRequired_other": "", + "generating": { + "title": "יוצר תמונות לדוגמה", + "description": "Frigate שואב תמונות מייצגות מההקלטות שלך. פעולה זו עשויה להימשך מספר רגעים..." + }, + "retryGenerate": "נסה ליצור מחדש", + "noImages": "לא נוצרו תמונות לדוגמה" + }, + "title": "צור סיווג חדש", + "steps": { + "nameAndDefine": "תן שם והגדר", + "stateArea": "אזור מצב", + "chooseExamples": "בחר דוגמאות" + }, + "step1": { + "description": "מודלי מצבים מנטרים אזורים קבועים במצלמה ומזהים בהם שינויי מצב (למשל: דלת פתוחה/סגורה). מודלי אובייקטים מוסיפים סיווגים לאובייקטים שזוהו (למשל: בעלי חיים מוכרים, שליחים, וכד׳).", + "name": "שם", + "namePlaceholder": "הזן שם למודל...", + "type": "סוג", + "typeState": "מצב", + "typeObject": "אובייקט", + "objectLabel": "תווית אובייקט", + "objectLabelPlaceholder": "בחר סוג אובייקט...", + "classificationType": "סוג סיווג", + "classificationTypeTip": "למד על סוגי הסיווגים", + "classificationTypeDesc": "תוויות משנה (Sub Labels) מוסיפות טקסט נוסף לתווית האובייקט (למשל: 'Person: UPS'). מאפיינים (Attributes) הם מטא־נתונים שניתנים לחיפוש, הנשמרים בנפרד בתוך מטא־הנתונים של האובייקט.", + "classificationSubLabel": "תווית משנה", + "classificationAttribute": "מאפיינים", + "classes": "מחלקות", + "states": "מצבים", + "classesTip": "למד על מחלקות", + "classesStateDesc": "הגדר את המצבים השונים שבהם אזור המצלמה יכול להיות. לדוגמה: 'open' ו־'closed' עבור דלת מוסך.", + "classesObjectDesc": "הגדר את הקטגוריות השונות לסיווג אובייקטים שזוהו. לדוגמה:\n'delivery_person', 'resident', 'stranger' עבור סיווג אנשים.", + "classPlaceholder": "הזן שם מחלקה...", + "errors": { + "nameRequired": "שם מודל הוא שדה חובה", + "nameLength": "שם המודל חייב להיות באורך של עד 64 תווים", + "nameOnlyNumbers": "שם המודל אינו יכול להכיל מספרים בלבד", + "classRequired": "נדרשת לפחות מחלקה אחת", + "classesUnique": "שמות המחלקות חייבים להיות ייחודיים", + "noneNotAllowed": "המחלקה 'none' אינה מותרת", + "stateRequiresTwoClasses": "מודלי מצבים דורשים לפחות שתי מחלקות", + "objectLabelRequired": "אנא בחר תווית אובייקט", + "objectTypeRequired": "אנא בחר סוג סיווג" + } + }, + "step2": { + "description": "בחר מצלמות והגדר את האזור לניטור עבור כל מצלמה. המודל יסווג את מצב האזורים הללו.", + "cameras": "מצלמות", + "selectCamera": "בחר מצלמה", + "noCameras": "לחץ על ‎+‎ כדי להוסיף מצלמות", + "selectCameraPrompt": "בחר מצלמה מהרשימה כדי להגדיר את אזור הניטור שלה" + } + }, + "categorizeImageAs": "סווג תמונה כ־:", + "categorizeImage": "סווג תמונה", + "menu": { + "objects": "אובייקטים", + "states": "מצבים" + }, + "noModels": { + "object": { + "title": "אין מודלים לסיווג אובייקטים", + "description": "צור מודל מותאם אישית לסיווג אובייקטים שזוהו.", + "buttonText": "צור מודל אובייקט" + }, + "state": { + "title": "אין מודלים לסיווג מצבים", + "description": "צור מודל מותאם אישית לניטור ולסיווג שינויים במצב באזורים מסוימים במצלמה.", + "buttonText": "צור מודל מצב" + } + } +} diff --git a/web/public/locales/he/views/events.json b/web/public/locales/he/views/events.json index fbccbeeb2e..6abcccd11b 100644 --- a/web/public/locales/he/views/events.json +++ b/web/public/locales/he/views/events.json @@ -38,12 +38,24 @@ "detail": { "noDataFound": "אין נתונים מפורטים לבדיקה", "aria": "הפעלה/כיבוי תצוגת פרטים", - "trackedObject_one": "אובייקט במעקב", - "trackedObject_other": "אובייקטים במעקב", - "noObjectDetailData": "אין נתוני אובייקט זמינים." + "trackedObject_one": "אובייקט {{count}}", + "trackedObject_other": "{{count}} אובייקטים", + "noObjectDetailData": "אין נתוני אובייקט זמינים.", + "label": "פרטים", + "settings": "הגדרות תצוגת פרטים", + "alwaysExpandActive": { + "title": "תמיד להרחיב את הפעיל", + "desc": "כאשר אפשר, תמיד להציג בהרחבה את פרטי האובייקט של פריט הבדיקה הפעיל." + } }, "objectTrack": { "trackedPoint": "נקודה במעקב", "clickToSeek": "לחץ כדי לחפש את הזמן הזה" - } + }, + "zoomIn": "הגדל (זום פנימה)", + "zoomOut": "הקטן (זום החוצה)", + "select_all": "הכל", + "normalActivity": "רגיל", + "needsReview": "טעון בדיקה", + "securityConcern": "חשש אבטחה" } diff --git a/web/public/locales/he/views/explore.json b/web/public/locales/he/views/explore.json index 0646e50892..07be1ceecb 100644 --- a/web/public/locales/he/views/explore.json +++ b/web/public/locales/he/views/explore.json @@ -27,6 +27,28 @@ }, "deleteTrackedObject": { "label": "מחק את אובייקט המעקב הזה" + }, + "audioTranscription": { + "aria": "בקשת תמלול אודיו", + "label": "תמלל" + }, + "showObjectDetails": { + "label": "הצגת מסלול האובייקט" + }, + "hideObjectDetails": { + "label": "הסתרת מסלול האובייקט" + }, + "downloadCleanSnapshot": { + "label": "הורד תמונה נקיה", + "aria": "הורד תמונה נקיה" + }, + "viewTrackingDetails": { + "label": "הצג פרטי מעקב", + "aria": "הצג את פרטי המעקב" + }, + "addTrigger": { + "label": "הוסף טריגר", + "aria": "הוסף טריגר לאובייקט במעקב זה" } }, "generativeAI": "Generative - AI", @@ -64,7 +86,9 @@ "details": "פרטים", "snapshot": "לכידת תמונה", "video": "וידיאו", - "object_lifecycle": "שלבי זיהוי של האובייקט" + "object_lifecycle": "שלבי זיהוי של האובייקט", + "thumbnail": "תמונה ממוזערת", + "tracking_details": "פרטי מעקב" }, "objectLifecycle": { "title": "שלבי זיהוי של האובייקט", @@ -132,12 +156,16 @@ "success": { "updatedSublabel": "תווית המשנה עודכנה בהצלחה.", "updatedLPR": "לוחית הרישוי עודכנה בהצלחה.", - "regenerate": "תיאור חדש התבקש מ-{{provider}}. בהתאם למהירות הספק שלך, ייתכן שייקח זמן מה ליצירת התיאור החדש." + "regenerate": "תיאור חדש התבקש מ-{{provider}}. בהתאם למהירות הספק שלך, ייתכן שייקח זמן מה ליצירת התיאור החדש.", + "updatedAttributes": "המאפיינים עודכנו בהצלחה.", + "audioTranscription": "בקשת תמלול האודיו נשלחה בהצלחה. בהתאם למהירות שרת ה־Frigate שלך, התמלול עשוי להימשך זמן מה עד להשלמתו." }, "error": { "regenerate": "ההתקשרות ל-{{provider}} לקבלת תיאור חדש נכשלה: {{errorMessage}}", "updatedSublabelFailed": "עדכון תווית המשנה נכשל: {{errorMessage}}", - "updatedLPRFailed": "עדכון לוחית הרישוי נכשל: {{errorMessage}}" + "updatedLPRFailed": "עדכון לוחית הרישוי נכשל: {{errorMessage}}", + "updatedAttributesFailed": "נכשל בעדכון המאפיינים: {{errorMessage}}", + "audioTranscription": "נכשל בשליחת בקשה לתמלול אודיו: {{errorMessage}}" } }, "title": "סקירת הפריט", @@ -184,12 +212,20 @@ "descriptionSaved": "התיאור נשמר בהצלחה", "saveDescriptionFailed": "עדכון התיאור נכשל: {{errorMessage}}" }, - "zones": "אזורים" + "zones": "אזורים", + "editAttributes": { + "title": "ערוך מאפיינים", + "desc": "בחר מאפייני סיווג עבור {{label}} זה" + }, + "score": { + "label": "ציון" + }, + "attributes": "מאפייני סיווג" }, "dialog": { "confirmDelete": { "title": "אישור מחיקה", - "desc": "מחיקת אובייקט זה במעקב מסירה את תמונת המצב, כל ההטמעות שנשמרו וכל ערכי שלבי האובייקט המשויכים. קטעי וידאו מוקלטים של אובייקט זה במעקב בתצוגת היסטוריה לא יימחקו.

    האם אתה בטוח שברצונך להמשיך?" + "desc": "מחיקת אובייקט זה במעקב תסיר את הצילום, כל ה־embeddings השמורים וכל רשומות פרטי המעקב המשויכות. קטעי וידאו מוקלטים של אובייקט זה בתצוגת היסטוריה לא יימחקו.

    האם אתה בטוח שברצונך להמשיך?" } }, "searchResult": { @@ -199,11 +235,68 @@ "error": "מחיקת האובייקט במעקב נכשלה: {{errorMessage}}", "success": "האובייקט המעקב נמחק בהצלחה." } - } + }, + "previousTrackedObject": "האובייקט הקודם במעקב", + "nextTrackedObject": "האובייקט הבא במעקב" }, "noTrackedObjects": "לא נמצאו אובייקטים במעקב", "fetchingTrackedObjectsFailed": "שגיאה באחזור אובייקטים במעקב: {{errorMessage}}", "trackedObjectsCount_one": "אובייקט במעקב ({{count}}) ", "trackedObjectsCount_two": "אובייקטים במעקב ({{count}}) ", - "trackedObjectsCount_other": "אובייקטים במעקב ({{count}}) " + "trackedObjectsCount_other": "אובייקטים במעקב ({{count}}) ", + "trackingDetails": { + "title": "פרטי מעקב", + "noImageFound": "לא נמצאה תמונה עבור חותמת הזמן הזו.", + "createObjectMask": "יצירת מסכת אובייקט", + "adjustAnnotationSettings": "התאמת הגדרות הסימון", + "scrollViewTips": "לחץ כדי לראות את הרגעים החשובים לאורך כל זמן המעקב אחרי האובייקט הזה.", + "autoTrackingTips": "מיקומי תיבות התחימה (Bounding Boxes) לא יהיו מדויקים עבור מצלמות עם מעקב אוטומטי (Autotracking).", + "count": "{{first}} מתוך {{second}}", + "trackedPoint": "נקודת מעקב", + "lifecycleItemDesc": { + "visible": "זוהה {{label}}", + "entered_zone": "{{label}} נכנס ל־{{zones}}", + "active": "{{label}} הפך לפעיל", + "stationary": "{{label}} הפך לנייח", + "attribute": { + "faceOrLicense_plate": "זוהה {{attribute}} עבור {{label}}", + "other": "{{label}} זוהה כ־{{attribute}}" + }, + "gone": "{{label}} יצא", + "heard": "{{label}} נשמע", + "external": "זוהה {{label}}", + "header": { + "zones": "אזורים", + "ratio": "יחס", + "area": "אזור", + "score": "ציון" + } + }, + "annotationSettings": { + "title": "הגדרות סימון", + "showAllZones": { + "title": "הצגת כל האזורים", + "desc": "תמיד להציג אזורים בפריימים שבהם אובייקטים נכנסו לאזור." + }, + "offset": { + "label": "היסט סימון", + "desc": "הנתונים האלה מגיעים מזרם ה־Detect של המצלמה, אבל מוצגים כשכבה מעל תמונות מזרם ה־Record. סביר ששני הזרמים לא מסונכרנים בצורה מושלמת. לכן, מסגרת הזיהוי (Bounding Box) והווידאו לא תמיד יסתדרו בדיוק אחד על השני.\nאפשר להשתמש בהגדרה הזו כדי להזיז את הסימונים קדימה או אחורה בזמן (היסט), וכך ליישר אותם טוב יותר עם ההקלטה.", + "millisecondsToOffset": "מספר המילישניות להיסט סימוני ה־Detect. ברירת מחדל: 0", + "tips": "הקטן את הערך אם הווידאו מקדים את המסגרות ונקודות המסלול, והגדל את הערך אם הווידאו מאחוריהם. הערך יכול להיות גם שלילי.", + "toast": { + "success": "היסט הסימון עבור {{camera}} נשמר בקובץ התצורה." + } + } + }, + "carousel": { + "previous": "שקופית קודמת", + "next": "שקופית הבאה" + } + }, + "aiAnalysis": { + "title": "ניתוח AI" + }, + "concerns": { + "label": "סיכונים" + } } diff --git a/web/public/locales/he/views/exports.json b/web/public/locales/he/views/exports.json index 93e26a7b89..2bd8c7e003 100644 --- a/web/public/locales/he/views/exports.json +++ b/web/public/locales/he/views/exports.json @@ -13,5 +13,11 @@ "title": "שנה שם ייצוא", "desc": "הכנס שם חדש עבור הייצוא הזה.", "saveExport": "שמירת ייצוא" + }, + "tooltip": { + "shareExport": "שתף ייצוא", + "downloadVideo": "הורדת סרטון", + "editName": "עריכת שם", + "deleteExport": "מחיקת ייצוא" } } diff --git a/web/public/locales/he/views/faceLibrary.json b/web/public/locales/he/views/faceLibrary.json index 96b2481003..0918c847d5 100644 --- a/web/public/locales/he/views/faceLibrary.json +++ b/web/public/locales/he/views/faceLibrary.json @@ -1,11 +1,11 @@ { "description": { - "addFace": "עיין בהוספת אוסף חדש לספריית הפנים.", + "addFace": "הוסף אוסף חדש לספריית הפנים באמצעות העלאת התמונה הראשונה שלך.", "placeholder": "הזנת שם לאוסף זה", - "invalidName": "שם לא חוקי. שמות יכולים לכלול רק אותיות, מספרים, רווחים, גרשים, קווים תחתונים ומקפים." + "invalidName": "שם לא תקין. שמות יכולים לכלול רק אותיות, מספרים, רווחים, גרש (’), קו תחתון (_) ומקף (-)." }, "createFaceLibrary": { - "nextSteps": "כדי לבנות בסיס חזק:
  • השתמשו בכרטיסייה 'אימון' כדי לבחור ולאמן תמונות עבור כל אדם שזוהה.
  • התמקדו בתמונות ישירות לקבלת התוצאות הטובות ביותר; הימנעו מאימון תמונות שלוכדות פנים בזווית.
  • ", + "nextSteps": "כדי לבנות בסיס חזק:
  • השתמש בלשונית זיהויים אחרונים כדי לבחור ולאמן על תמונות עבור כל אדם שזוהה.
  • כדי לקבל תוצאות מיטביות, התמקד בתמונות פנים מלפנים; הימנע מתמונות אימון שבהן הפנים מצולמות בזווית.
  • ", "title": "יצירת אוסף", "desc": "יצירת אוסף חדש", "new": "יצירת פנים חדשות" @@ -22,7 +22,7 @@ "addFaceLibrary": "{{name}} נוסף בהצלחה לספריית הפנים!", "renamedFace": "שם הפנים שונה בהצלחה ל-{{name}}", "trainedFace": "פנים אומנו בהצלחה.", - "updatedFaceScore": "ציון הפנים עודכן בהצלחה." + "updatedFaceScore": "ציון הפנים עבור {{name}} עודכן בהצלחה ({{score}})." }, "error": { "deleteFaceFailed": "המחיקה נכשלה: {{errorMessage}}", @@ -58,9 +58,10 @@ } }, "train": { - "title": "רכבת", - "aria": "בחירת אימון", - "empty": "אין ניסיונות זיהוי פנים אחרונים" + "title": "זיהויים אחרונים", + "aria": "בחירת זיהויים אחרונים", + "empty": "אין ניסיונות זיהוי פנים אחרונים", + "titleShort": "לאחרונה" }, "selectItem": "בחירה:{{item}}", "selectFace": "בחירת פנים", @@ -91,7 +92,7 @@ "selectImage": "בחירת קובץ תמונה." }, "dropActive": "שחרר/י את התמונה כאן…", - "dropInstructions": "גרור ושחרר תמונה כאן, או לחץ כדי לבחור", + "dropInstructions": "גרור ושחרר או הדבק תמונה כאן, או לחץ כדי לבחור", "maxSize": "גודל מקסימאלי: {{size}}MB" }, "nofaces": "אין פנים זמינים", diff --git a/web/public/locales/he/views/live.json b/web/public/locales/he/views/live.json index 7b7c535690..5426392b9a 100644 --- a/web/public/locales/he/views/live.json +++ b/web/public/locales/he/views/live.json @@ -1,7 +1,7 @@ { "manualRecording": { - "title": "הקלטה לפי דרישה", - "tips": "התחלת אירוע הקלטה ידני המבוסס על הגדרות שמירת ההקלטה של מצלמה זו.", + "title": "לפי דרישה", + "tips": "הורד צילום מיידי או התחל אירוע ידני בהתאם להגדרות שמירת ההקלטות של מצלמה זו.", "playInBackground": { "label": "ניגון ברקע", "desc": "הפעל אפשרות זו כדי להמשיך להזרים גם כאשר הנגן מוסתר." @@ -128,6 +128,9 @@ }, "available": "קול זמין עבור שידור זה", "unavailable": "קול אינו זמין עבור שידור זה" + }, + "debug": { + "picker": "בחירת זרם אינה זמינה במצב Debug. תצוגת Debug תמיד משתמשת בזרם שמוגדר עם הייעוד detect." } }, "cameraSettings": { @@ -137,7 +140,8 @@ "recording": "הקלטה", "snapshots": "לכידת תמונה", "audioDetection": "זיהוי קול", - "autotracking": "מעקב אוטומטי" + "autotracking": "מעקב אוטומטי", + "transcription": "תמלול אודיו" }, "streamingSettings": "הגדרות שידור", "notifications": "התראות", @@ -162,5 +166,24 @@ "label": "עריכת קבוצת מצלמות" }, "exitEdit": "יציאה מעריכה" + }, + "snapshot": { + "takeSnapshot": "הורדת תמונת מצב מיידית", + "noVideoSource": "אין מקור וידאו זמין לצילום תמונת מצב.", + "captureFailed": "צילום תמונת מצב נכשל.", + "downloadStarted": "התחילה הורדת תמונת המצב." + }, + "transcription": { + "enable": "הפעלת תמלול אודיו חי", + "disable": "השבתת תמלול אודיו חי" + }, + "noCameras": { + "title": "לא הוגדרו מצלמות", + "description": "התחל על-ידי חיבור מצלמה ל-Frigate.", + "buttonText": "הוסף מצלמה", + "restricted": { + "title": "אין מצלמות זמינות", + "description": "אין לך הרשאה לצפות במצלמות כלשהן בקבוצה זו." + } } } diff --git a/web/public/locales/he/views/search.json b/web/public/locales/he/views/search.json index 0865e46542..1991713752 100644 --- a/web/public/locales/he/views/search.json +++ b/web/public/locales/he/views/search.json @@ -4,7 +4,7 @@ "searchFor": "חפש את{{inputValue}}", "button": { "clear": "ניקוי חיפוש", - "save": "שמירת החיפוש", + "save": "שמור חיפוש", "delete": "מחיקת חיפוש שמור", "filterInformation": "סינון מידע", "filterActive": "מסננים פעילים" @@ -26,7 +26,8 @@ "min_speed": "מהירות מינמאלית", "recognized_license_plate": "לוחית רישוי מוכרת", "has_clip": "קיים סרטון קליפ", - "has_snapshot": "לכידת תמונה קיימת" + "has_snapshot": "לכידת תמונה קיימת", + "attributes": "מאפיינים" }, "searchType": { "thumbnail": "תמונה ממוזערת", diff --git a/web/public/locales/he/views/settings.json b/web/public/locales/he/views/settings.json index e0737aa6e6..cb24111c08 100644 --- a/web/public/locales/he/views/settings.json +++ b/web/public/locales/he/views/settings.json @@ -46,7 +46,8 @@ "mustBeAtLeastTwoCharacters": "שם האזור חייב להיות באורך של לפחות 2 תווים.", "mustNotBeSameWithCamera": "שם האזור לא חייב להיות זהה לשם המצלמה.", "mustNotContainPeriod": "שם האזור אינו יכול להכיל נקודות.", - "hasIllegalCharacter": "שם האזור מכיל תווים לא חוקיים." + "hasIllegalCharacter": "שם האזור מכיל תווים לא חוקיים.", + "mustHaveAtLeastOneLetter": "שם האזור חייב לכלול לפחות אות אחת." } }, "distance": { @@ -111,7 +112,7 @@ "name": { "title": "שם", "inputPlaceHolder": "הזן שם…", - "tips": "השם חייב להיות באורך של לפחות 2 תווים ואינו יכול להיות שם של מצלמה או אזור אחר." + "tips": "השם חייב להכיל לפחות 2 תווים, לכלול לפחות אות אחת, ואסור שיהיה זהה לשם של מצלמה או של אזור אחר במצלמה זו." }, "point_one": "נקודה {{count}}", "point_two": "נקודות {{count}}", @@ -125,7 +126,7 @@ "desc": "קובע את משך הזמן המינימלי בשניות שהאובייקט חייב להיות באזור כדי שיופעל. ברירת מחדל: 0" }, "objects": { - "title": "אובייקט", + "title": "אובייקטים", "desc": "רשימת אובייקטים החלים על אזור זה." }, "speedEstimation": { @@ -148,7 +149,7 @@ } }, "toast": { - "success": "האזור ({{zoneName}}) נשמר. הפעל מחדש את Frigate כדי להחיל את השינויים." + "success": "האזור ({{zoneName}}) נשמר בהצלחה." }, "allObjects": "כל האובייקטים" }, @@ -176,8 +177,8 @@ }, "toast": { "success": { - "title": "{{polygonName}} נשמר. הפעל מחדש את Frigate כדי להחיל את השינויים.", - "noName": "מיסוך התנועה נשמר. הפעל מחדש את Frigate כדי להחיל את השינויים." + "title": "{{polygonName}} נשמר בהצלחה.", + "noName": "מסכת תנועה נשמרה בהצלחה." } } }, @@ -202,8 +203,8 @@ }, "toast": { "success": { - "title": "{{polygonName}} נשמר. הפעל מחדש את Frigate כדי להחיל את השינויים.", - "noName": "מיסוך האובייקט נשמר. הפעל מחדש את Frigate כדי להחיל את השינויים." + "title": "{{polygonName}} נשמר בהצלחה.", + "noName": "מיסוך האובייקט נשמר." } } } @@ -225,6 +226,14 @@ "playAlertVideos": { "label": "ניגון סרטוני התראות", "desc": "כברירת מחדל, התראות אחרונות בדשבורד שידור חי מופעלות כסרטונים קצרים בלולאה. השבת אפשרות זו כדי להציג רק תמונה סטטית של התראות אחרונות במכשיר/דפדפן זה." + }, + "displayCameraNames": { + "label": "תמיד להציג שם מצלמה", + "desc": "תמיד להציג את שמות המצלמות בצ’יפ בתצוגת הלייב מרובת המצלמות בדשבורד." + }, + "liveFallbackTimeout": { + "label": "זמן המתנה למעבר לנגן חלופי בשידור חי", + "desc": "כאשר זרם השידור חי באיכות גבוהה של מצלמה אינו זמין, המערכת תעבור למצב רוחב־פס נמוך לאחר מספר השניות הזה. ברירת מחדל: 3." } }, "cameraGroupStreaming": { @@ -232,7 +241,7 @@ "title": "הגדרות הזרמת קבוצת מצלמות", "clearAll": "נקה את כל הגדרות השידור" }, - "title": "הגדרות כלליות", + "title": "הגדרות UI", "storedLayouts": { "title": "פריסות תצוגה שמורות", "desc": "ניתן לגרור/לשנות את גודל הפריסה של המצלמות בקבוצת מצלמות. המיקומים נשמרים באחסון המקומי של הדפדפן שלך.", @@ -268,7 +277,7 @@ "notifications": "הגדרת התראות - Frigate", "authentication": "הגדרות אימות - Frigate", "default": "הגדרות - Frigate", - "general": "הגדרות כלליות - Frigate", + "general": "הגדרות ממשק (UI) - Frigate", "cameraManagement": "ניהול מצלמות - Frigate", "cameraReview": "הגדרות סקירת מצלמה - Frigate" }, @@ -427,7 +436,20 @@ "area": "אזור", "tips": "הפעל אפשרות זו כדי לצייר מלבן על תמונת המצלמה כדי להציג את השטח והיחס שלה. ניתן להשתמש בערכים אלה כדי להגדיר פרמטרים של מסנן צורת אובייקט בתצורה שלך." }, - "desc": "תצוגת ניפוי שגיאות מציגה תצוגה בזמן אמת של אובייקטים במעקב והסטטיסטיקות שלהם. רשימת האובייקטים מציגה סיכום בהשהיית זמן של האובייקטים שזוהו." + "desc": "תצוגת ניפוי שגיאות מציגה תצוגה בזמן אמת של אובייקטים במעקב והסטטיסטיקות שלהם. רשימת האובייקטים מציגה סיכום בהשהיית זמן של האובייקטים שזוהו.", + "openCameraWebUI": "פתח את ממשק ה־Web של {{camera}}", + "audio": { + "title": "אודיו", + "noAudioDetections": "אין זיהויי אודיו", + "score": "ציון", + "currentRMS": "RMS נוכחי", + "currentdbFS": "dbFS נוכחי" + }, + "paths": { + "title": "נתיבים", + "desc": "הצג נקודות משמעותיות במסלול התנועה של האובייקט במעקב", + "tips": "

    נתיבים


    קווים ועיגולים יציינו נקודות משמעותיות שבהן האובייקט במעקב נע במהלך מחזור חייו.

    " + } }, "users": { "title": "משתמשים", @@ -436,7 +458,7 @@ "desc": "נהל את חשבונות המשתמשים של מופע Frigate זה." }, "addUser": "הוספת משתמש", - "updatePassword": "עדכון סיסמה", + "updatePassword": "איפוס סיסמה", "toast": { "success": { "createUser": "המשתמש {{user}} נוצר בהצלחה", @@ -456,7 +478,7 @@ "role": "הרשאות", "noUsers": "לא נמצאו משתמשים.", "changeRole": "שינוי הרשאות משתמש", - "password": "סיסמה", + "password": "איפוס סיסמה", "deleteUser": "מחיקת משתמש", "username": "שם משתמש" }, @@ -482,7 +504,16 @@ "veryStrong": "מאוד חזק" }, "match": "סיסמאות תואמות", - "notMatch": "הסיסמאות אינן תואמות." + "notMatch": "הסיסמאות אינן תואמות.", + "show": "הצג סיסמה", + "hide": "הסתר סיסמה", + "requirements": { + "title": "דרישות סיסמה:", + "length": "לפחות 8 תווים", + "uppercase": "לפחות אות גדולה אחת", + "digit": "לפחות ספרה אחת", + "special": "לפחות תו מיוחד אחד (!@#$%^&*(),.?\":{}|<>)" + } }, "newPassword": { "title": "סיסמה חדשה", @@ -492,7 +523,11 @@ } }, "usernameIsRequired": "נדרש שם משתמש", - "passwordIsRequired": "נדרשת סיסמה" + "passwordIsRequired": "נדרשת סיסמה", + "currentPassword": { + "title": "סיסמה נוכחית", + "placeholder": "הזן את הסיסמה הנוכחית שלך" + } }, "createUser": { "title": "יצירת משתמש חדש", @@ -510,7 +545,12 @@ "doNotMatch": "הסיסמאות אינן תואמות", "updatePassword": "עדכון סיסמה עבור {{username}}", "setPassword": "קבע סיסמה", - "desc": "צור סיסמה חזקה כדי לאבטח חשבון זה." + "desc": "צור סיסמה חזקה כדי לאבטח חשבון זה.", + "currentPasswordRequired": "נדרשת הסיסמה הנוכחית", + "incorrectCurrentPassword": "הסיסמה הנוכחית שגויה", + "passwordVerificationFailed": "נכשל באימות הסיסמה", + "multiDeviceWarning": "כל מכשיר אחר שבו אתה מחובר יידרש להתחבר מחדש בתוך {{refresh_time}}.", + "multiDeviceAdmin": "ניתן גם לאלץ את כל המשתמשים להתחבר מחדש באופן מיידי על־ידי החלפת מפתח ה־JWT שלך." }, "changeRole": { "title": "שינוי הרשאות משתמש", @@ -521,7 +561,8 @@ "admin": "מנהל", "adminDesc": "גישה מלאה לכל התכונות.", "viewer": "צופה", - "viewerDesc": "מוגבל לדשבורד שידור חי, סקירה, גילוי וייצוא בלבד." + "viewerDesc": "מוגבל לדשבורד שידור חי, סקירה, גילוי וייצוא בלבד.", + "customDesc": "תפקיד מותאם אישית עם גישה למצלמות מסוימות." } } } @@ -624,5 +665,454 @@ "success": "הגדרות Frigate+ נשמרו. הפעל מחדש את Frigate כדי להחיל את השינויים.", "error": "שמירת שינויי התצורה נכשלה: {{errorMessage}}" } + }, + "cameraWizard": { + "step1": { + "brandInformation": "פרטי יצרן", + "brandUrlFormat": "למצלמות עם פורמט כתובת RTSP כמו: {{exampleUrl}}", + "connectionSettings": "הגדרות חיבור", + "detectionMethod": "שיטת זיהוי זרם", + "onvifPort": "פורט ONVIF", + "probeMode": "בדיקת מצלמה", + "manualMode": "בחירה ידנית", + "detectionMethodDescription": "בדוק את המצלמה באמצעות ONVIF (אם נתמך) כדי למצוא את כתובות הזרמים שלה, או בחר ידנית את יצרן המצלמה כדי להשתמש בכתובות מוגדרות מראש.\nכדי להזין כתובת RTSP מותאמת אישית, בחר בשיטה ידנית ואז בחר \"אחר\".", + "onvifPortDescription": "במצלמות שתומכות ב-ONVIF, זה בדרך כלל 80 או 8080.", + "useDigestAuth": "שימוש באימות Digest", + "useDigestAuthDescription": "השתמש באימות HTTP Digest עבור ONVIF. בחלק מהמצלמות נדרש שם משתמש/סיסמה ייעודיים ל-ONVIF, ולא משתמש ה-Admin הרגיל.", + "errors": { + "brandOrCustomUrlRequired": "בחר יצרן מצלמה והזן Host/IP, או בחר “אחר” והזן כתובת מותאמת אישית", + "nameRequired": "שם המצלמה הוא שדה חובה", + "nameLength": "שם המצלמה חייב להיות באורך של עד 64 תווים", + "invalidCharacters": "שם המצלמה מכיל תווים לא חוקיים", + "nameExists": "שם המצלמה כבר קיים", + "customUrlRtspRequired": "כתובות מותאמות אישית חייבות להתחיל ב־\"rtsp://\". עבור זרמי מצלמה שאינם RTSP נדרשת הגדרה ידנית." + }, + "description": "הזן את פרטי המצלמה ובחר אם לבצע בדיקה למצלמה או לבחור ידנית את היצרן.", + "cameraName": "שם מצלמה", + "cameraNamePlaceholder": "לדוגמה: front_door או סקירת החצר האחורית", + "host": "HOST / כתובת IP", + "port": "פורט", + "username": "שם משתמש", + "usernamePlaceholder": "אופציונלי", + "password": "סיסמה", + "passwordPlaceholder": "אופציונלי", + "selectTransport": "בחר פרוטוקול תעבורה", + "cameraBrand": "יצרן מצלמה", + "selectBrand": "בחר יצרן מצלמה עבור תבנית כתובת ה-URL", + "customUrl": "כתובת (URL) זרם מותאמת אישית", + "customUrlPlaceholder": "rtsp://username:password@host:port/path" + }, + "step2": { + "description": "בדוק את המצלמה כדי לאתר זרמים זמינים, או הגדר ידנית את ההגדרות לפי שיטת הזיהוי שבחרת.", + "testSuccess": "בדיקת החיבור הצליחה!", + "testFailed": "בדיקת החיבור נכשלה. בדוק את הנתונים שהזנת ונסה שוב.", + "testFailedTitle": "הבדיקה נכשלה", + "streamDetails": "פרטי זרם", + "probing": "בודק מצלמה...", + "retry": "נסה שוב", + "testing": { + "probingMetadata": "בודק את נתוני המטא של המצלמה…", + "fetchingSnapshot": "שולף תמונת מצב מהמצלמה…" + }, + "probeFailed": "בדיקת המצלמה נכשלה: {{error}}", + "probingDevice": "בודק את ההתקן…", + "probeSuccessful": "הבדיקה הצליחה", + "probeError": "בדיקה נכשלה", + "probeNoSuccess": "הבדיקה לא הצליחה", + "deviceInfo": "מידע על ההתקן", + "manufacturer": "יצרן", + "model": "דגם", + "firmware": "קושחה", + "profiles": "פרופילים", + "ptzSupport": "תמיכה ב-PTZ", + "autotrackingSupport": "תמיכה ב-Autotracking", + "presets": "פריסטים", + "rtspCandidates": "כתובות RTSP מוצעות", + "rtspCandidatesDescription": "כתובות ה־RTSP הבאות נמצאו בבדיקת המצלמה. בצע בדיקת חיבור כדי לצפות בנתוני הזרם (Metadata).", + "noRtspCandidates": "לא נמצאו כתובות RTSP מהמצלמה. ייתכן שפרטי ההתחברות שגויים, או שהמצלמה לא תומכת ב-ONVIF, או שהשיטה שבה השתמשנו לשליפת כתובות RTSP אינה נתמכת. חזור אחורה והזן את כתובת ה-RTSP ידנית.", + "candidateStreamTitle": "אפשרות {{number}}", + "useCandidate": "השתמש", + "uriCopy": "העתק", + "uriCopied": "הכתובת (URI) הועתקה ללוח", + "testConnection": "בדיקת חיבור", + "toggleUriView": "לחץ כדי להציג/להסתיר את הכתובת המלאה", + "connected": "מחובר", + "notConnected": "לא מחובר", + "errors": { + "hostRequired": "כתובת Host/IP היא שדה חובה" + } + }, + "step3": { + "description": "הגדר תפקידי זרם (Roles) והוסף זרמים נוספים למצלמה שלך.", + "streamsTitle": "זרמי מצלמה", + "addStream": "הוסף זרם", + "addAnotherStream": "הוסף זרם נוסף", + "streamTitle": "זרם {{number}}", + "streamUrl": "כתובת הזרם (URL)", + "selectStream": "בחר זרם", + "searchCandidates": "חיפוש אפשרויות…", + "noStreamFound": "לא נמצא זרם", + "url": "URL", + "resolution": "רזולוציה", + "selectResolution": "בחר רזולוציה", + "quality": "איכות", + "selectQuality": "בחר איכות", + "roles": "תפקידים", + "roleLabels": { + "detect": "זיהוי אובייקטים", + "record": "הקלטה", + "audio": "קול (Audio)" + }, + "testStream": "בדיקת חיבור", + "testSuccess": "בדיקת הזרם הצליחה!", + "testFailed": "בדיקת הזרם נכשלה", + "testFailedTitle": "הבדיקה נכשלה", + "connected": "מחובר", + "notConnected": "לא מחובר", + "featuresTitle": "תכונות", + "go2rtc": "הפחתת חיבורים למצלמה", + "detectRoleWarning": "כדי להמשיך, לפחות זרם אחד חייב להיות עם ייעוד \"detect\".", + "rolesPopover": { + "title": "ייעודי הזרם", + "detect": "הזרם הראשי לזיהוי אובייקטים.", + "record": "שומר קטעים מזרם הווידאו לפי הגדרות התצורה.", + "audio": "זרם לזיהוי מבוסס אודיו." + }, + "featuresPopover": { + "title": "תכונות הזרם", + "description": "השתמש ב־go2rtc לריסטרים (Restream) כדי להפחית את מספר החיבורים למצלמה שלך." + }, + "streamUrlPlaceholder": "rtsp://username:password@host:port/path" + }, + "step4": { + "description": "אימות וניתוח סופיים לפני שמירת המצלמה החדשה. התחבר לכל זרם לפני השמירה.", + "validationTitle": "אימות הזרם", + "connectAllStreams": "התחברות לכל הזרמים", + "reconnectionSuccess": "חיבור מחדש הצליח.", + "reconnectionPartial": "חלק מהזרמים לא הצליחו להתחבר מחדש.", + "streamUnavailable": "תצוגה מקדימה של הזרם אינה זמינה", + "reload": "טעינה מחדש", + "connecting": "מתחבר...", + "streamTitle": "זרם {{number}}", + "valid": "תקין", + "failed": "נכשל", + "notTested": "לא נבדק", + "connectStream": "התחבר", + "connectingStream": "מתחבר", + "disconnectStream": "נתק", + "estimatedBandwidth": "רוחב־פס משוער", + "roles": "ייעודים", + "ffmpegModule": "שימוש במצב תאימות לזרם", + "ffmpegModuleDescription": "אם הזרם לא נטען אחרי כמה ניסיונות, נסה להפעיל את זה. כשהאפשרות פעילה, Frigate ישתמש במודול ffmpeg יחד עם go2rtc. זה עשוי לשפר תאימות עם זרמים של חלק מהמצלמות.", + "none": "ללא", + "error": "שגיאה", + "streamValidated": "הזרם {{number}} אומת בהצלחה", + "streamValidationFailed": "אימות הזרם {{number}} נכשל", + "saveAndApply": "שמירת מצלמה חדשה", + "saveError": "תצורה לא תקינה. בדוק את ההגדרות שלך.", + "issues": { + "title": "אימות הזרם", + "videoCodecGood": "קידוד הווידאו הוא {{codec}}.", + "audioCodecGood": "קידוד האודיו הוא {{codec}}.", + "resolutionHigh": "רזולוציה של {{resolution}} עשויה לגרום לשימוש מוגבר במשאבים.", + "resolutionLow": "רזולוציה של {{resolution}} עשויה להיות נמוכה מדי לזיהוי אמין של אובייקטים קטנים.", + "noAudioWarning": "לא זוהה אודיו בזרם הזה, ולכן ההקלטות יהיו ללא שמע.", + "audioCodecRecordError": "כדי לכלול אודיו בהקלטות נדרש קידוד שמע AAC.", + "audioCodecRequired": "כדי לאפשר זיהוי אודיו נדרש זרם שמע.", + "restreamingWarning": "הפחתת מספר החיבורים למצלמה עבור זרם ההקלטה (record) עשויה להעלות מעט את השימוש ב־CPU.", + "brands": { + "reolink-rtsp": "RTSP של Reolink לא מומלץ. הפעל HTTP בהגדרות הקושחה של המצלמה, ואז הפעל מחדש את אשף ההגדרה.", + "reolink-http": "בזרמי HTTP של Reolink מומלץ להשתמש ב־FFmpeg לתאימות טובה יותר. הפעל עבור הזרם הזה את האפשרות “שימוש במצב תאימות לזרם”." + }, + "dahua": { + "substreamWarning": "זרם משנה 1 נעול לרזולוציה נמוכה. מצלמות רבות של Dahua / Amcrest / EmpireTech תומכות בזרמי משנה נוספים שצריך להפעיל בהגדרות המצלמה מומלץ לבדוק אם קיימים זרמי משנה כאלה ולהשתמש בהם במידה וזמינים." + }, + "hikvision": { + "substreamWarning": "זרם משנה 1 נעול לרזולוציה נמוכה. מצלמות רבות של Hikvision תומכות בזרמי משנה נוספים שצריך להפעיל בהגדרות המצלמה. מומלץ לבדוק אם קיימים זרמי משנה כאלה ולהשתמש בהם, אם הם זמינים." + } + } + }, + "title": "הוסף מצלמה", + "description": "בצע את השלבים הבאים כדי להוסיף מצלמה חדשה להתקנת ה־Frigate שלך.", + "steps": { + "nameAndConnection": "שם וחיבור", + "probeOrSnapshot": "בדיקה (Probe) או צילום תמונה (Snapshot)", + "streamConfiguration": "הגדרות זרם", + "validationAndTesting": "אימות ובדיקה" + }, + "save": { + "success": "המצלמה החדשה {{cameraName}} נשמרה בהצלחה.", + "failure": "שגיאה בשמירת {{cameraName}}." + }, + "testResultLabels": { + "resolution": "רזולוציה", + "video": "וידיאו", + "audio": "אודיו", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "אנא ספק כתובת URL תקינה לזרם", + "testFailed": "בדיקת הזרם נכשלה: {{error}}" + } + }, + "cameraManagement": { + "title": "ניהול מצלמות", + "addCamera": "הוספת מצלמה חדשה", + "editCamera": "עריכת מצלמה:", + "selectCamera": "בחירת מצלמה", + "backToSettings": "חזרה להגדרות מצלמה", + "streams": { + "title": "הפעלה / השבתה של מצלמות", + "desc": "השבת מצלמה זמנית עד ש־Frigate יופעל מחדש. השבתת מצלמה עוצרת לחלוטין את העיבוד של Frigate עבור זרמי המצלמה הזו. זיהוי, הקלטה וניפוי שגיאות לא יהיו זמינים.
    \nהערה: פעולה זו לא משביתה את ה־restreams של go2rtc." + }, + "cameraConfig": { + "add": "הוספת מצלמה", + "edit": "עריכת מצלמה", + "description": "נהל את הגדרות המצלמה, כולל קלטי הזרמים והייעודים שלהם.", + "name": "שם מצלמה", + "nameRequired": "שם המצלמה הוא שדה חובה", + "nameLength": "שם המצלמה חייב להיות קצר מ־64 תווים.", + "namePlaceholder": "לדוגמה: front_door או תצוגת סקירה של החצר האחורית", + "enabled": "מופעל", + "ffmpeg": { + "inputs": "זרמי קלט", + "path": "נתיב זרם", + "pathRequired": "נתיב זרם הוא שדה חובה", + "roles": "ייעודים", + "rolesRequired": "נדרש לפחות ייעוד אחד", + "rolesUnique": "כל ייעוד (audio, detect, record) ניתן להקצות לזרם אחד בלבד", + "addInput": "הוסף זרם קלט", + "removeInput": "הסר זרם קלט", + "inputsRequired": "נדרש לפחות זרם קלט אחד", + "pathPlaceholder": "rtsp://..." + }, + "go2rtcStreams": "זרמי go2rtc", + "streamUrls": "כתובות URL של הזרמים", + "addUrl": "הוסף URL", + "addGo2rtcStream": "הוסף זרם go2rtc", + "toast": { + "success": "המצלמה {{cameraName}} נשמרה בהצלחה" + } + } + }, + "cameraReview": { + "title": "הגדרות סקירת מצלמה", + "object_descriptions": { + "title": "Generative AI תיאורי אובייקטים", + "desc": "הפעל/השבת זמנית תיאורי אובייקטים של Generative AI עבור מצלמה זו. כאשר האפשרות מושבתת, לא יתבקשו תיאורים שנוצרו ע״י AI עבור אובייקטים במעקב במצלמה זו." + }, + "review_descriptions": { + "title": "תיאורי סקירה של Generative AI", + "desc": "הפעל/השבת זמנית תיאורי סקירה של Generative AI עבור מצלמה זו. כאשר האפשרות מושבתת, לא יתבקשו תיאורים שנוצרו ע״י AI עבור פריטי סקירה במצלמה זו." + }, + "review": { + "title": "סקירה", + "desc": "הפעל/השבת זמנית התראות וזיהויים עבור מצלמה זו עד ש-Frigate יופעל מחדש. כאשר האפשרות מושבתת, לא ייווצרו פריטי סקירה חדשים. ", + "alerts": "התראות. ", + "detections": "זיהויים. " + }, + "reviewClassification": { + "title": "סיווג סקירה", + "desc": "Frigate מסווג פריטי סקירה ל־התראות ול־זיהויים. כברירת מחדל, כל אובייקט מסוג person ו־car נחשב ל־התראה. ניתן לדייק את הסיווג של פריטי הסקירה שלך באמצעות הגדרת אזורים נדרשים עבורם.", + "noDefinedZones": "לא הוגדרו אזורים למצלמה זו.", + "objectAlertsTips": "כל האובייקטים מסוג {{alertsLabels}} ב־{{cameraName}} יוצגו כהתראות.", + "zoneObjectAlertsTips": "כל האובייקטים מסוג {{alertsLabels}} שזוהו בתוך {{zone}} ב־{{cameraName}} יוצגו כהתראות.", + "objectDetectionsTips": "כל האובייקטים מסוג {{detectionsLabels}} שלא סווגו ב־{{cameraName}} יוצגו כזיהויים, ללא קשר לאיזה אזור הם נמצאים בו.", + "zoneObjectDetectionsTips": { + "text": "כל האובייקטים מסוג {{detectionsLabels}} שלא סווגו בתוך {{zone}} ב־{{cameraName}} יוצגו כזיהויים.", + "notSelectDetections": "כל האובייקטים מסוג {{detectionsLabels}} שזוהו בתוך {{zone}} ב־{{cameraName}} ושאינם מסווגים כהתראות יוצגו כזיהויים, ללא קשר לאיזה אזור הם נמצאים בו.", + "regardlessOfZoneObjectDetectionsTips": "כל האובייקטים מסוג {{detectionsLabels}} שלא סווגו ב־{{cameraName}} יוצגו כזיהויים, ללא קשר לאיזה אזור הם נמצאים בו." + }, + "unsavedChanges": "הגדרות סיווג סקירה שלא נשמרו עבור {{camera}}", + "selectAlertsZones": "בחר אזורים עבור התראות", + "selectDetectionsZones": "בחר אזורים עבור זיהויים", + "limitDetections": "הגבל זיהויים לאזורים מסוימים", + "toast": { + "success": "הגדרות סיווג הסקירה נשמרו. הפעל מחדש את Frigate כדי להחיל את השינויים." + } + } + }, + "roles": { + "management": { + "title": "ניהול תפקיד צופה", + "desc": "נהל תפקידי צופה מותאמים אישית ואת הרשאות הגישה שלהם למצלמות עבור מופע Frigate זה." + }, + "addRole": "הוסף תפקיד", + "table": { + "role": "תפקיד", + "cameras": "מצלמות", + "actions": "פעולות", + "noRoles": "לא נמצאו תפקידים מותאמים אישית.", + "editCameras": "ערוך מצלמות", + "deleteRole": "מחק תפקיד" + }, + "toast": { + "success": { + "createRole": "התפקיד {{role}} נוצר בהצלחה", + "updateCameras": "המצלמות עודכנו עבור התפקיד {{role}}", + "deleteRole": "התפקיד {{role}} נמחק בהצלחה", + "userRolesUpdated_one": "המשתמש {{count}} שהוקצה לתפקיד זה עודכן ל־צופה (viewer), שלו יש גישה לכל המצלמות.", + "userRolesUpdated_two": "{{count}} משתמשים שהוקצו לתפקיד זה עודכנו ל־צופה (viewer), שלו יש גישה לכל המצלמות.", + "userRolesUpdated_other": "" + }, + "error": { + "createRoleFailed": "נכשל ביצירת התפקיד: {{errorMessage}}", + "updateCamerasFailed": "נכשל בעדכון המצלמות: {{errorMessage}}", + "deleteRoleFailed": "נכשל במחיקת התפקיד: {{errorMessage}}", + "userUpdateFailed": "נכשל בעדכון תפקידי המשתמשים: {{errorMessage}}" + } + }, + "dialog": { + "createRole": { + "title": "צור תפקיד חדש", + "desc": "הוסף תפקיד חדש והגדר הרשאות גישה למצלמות." + }, + "editCameras": { + "title": "ערוך מצלמות לתפקיד", + "desc": "עדכן את גישת המצלמות עבור התפקיד {{role}}." + }, + "deleteRole": { + "title": "מחק תפקיד", + "desc": "לא ניתן לבטל פעולה זו. הפעולה תמחק לצמיתות את התפקיד ותעביר כל משתמש שהוקצה לתפקיד זה לתפקיד צופה (viewer), המעניק גישה לכל המצלמות.", + "warn": "האם אתה בטוח שברצונך למחוק את {{role}}?", + "deleting": "מוחק..." + }, + "form": { + "role": { + "title": "שם תפקיד", + "placeholder": "הזן שם תפקיד", + "desc": "מותר להשתמש רק באותיות, מספרים, נקודות וקווים תחתונים.", + "roleIsRequired": "שם תפקיד הוא שדה חובה", + "roleOnlyInclude": "שם התפקיד יכול לכלול רק אותיות, מספרים, נקודות או קווים תחתונים", + "roleExists": "כבר קיים תפקיד בשם זה." + }, + "cameras": { + "title": "מצלמות", + "desc": "בחר את המצלמות שלתפקיד זה יש גישה אליהן. נדרשת לפחות מצלמה אחת.", + "required": "חובה לבחור לפחות מצלמה אחת." + } + } + } + }, + "triggers": { + "documentTitle": "טריגרים", + "semanticSearch": { + "title": "חיפוש סמנטי מושבת", + "desc": "כדי להשתמש בטריגרים, יש להפעיל חיפוש סמנטי." + }, + "management": { + "title": "טריגרים", + "desc": "נהל טריגרים עבור {{camera}}. השתמש בסוג תמונה ממוזערת (Thumbnail) כדי להפעיל טריגרים על תמונות ממוזערות דומות לאובייקט שבחרת למעקב, ובסוג תיאור (Description) כדי להפעיל טריגרים על תיאורים דומים לטקסט שתגדיר." + }, + "addTrigger": "הוסף טריגר", + "table": { + "name": "שם", + "type": "סוג", + "content": "תוכן", + "threshold": "סף", + "actions": "פעולות", + "noTriggers": "לא הוגדרו טריגרים למצלמה זו.", + "edit": "עריכה", + "deleteTrigger": "מחק טריגר", + "lastTriggered": "הפעלה אחרונה" + }, + "type": { + "thumbnail": "תמונה ממוזערת", + "description": "תיאור" + }, + "actions": { + "notification": "שלח התראה", + "sub_label": "הוסף תווית משנה", + "attribute": "הוסף מאפיינים" + }, + "dialog": { + "createTrigger": { + "title": "צור טריגר", + "desc": "צור טריגר עבור המצלמה {{camera}}" + }, + "editTrigger": { + "title": "ערוך טריגר", + "desc": "ערוך את ההגדרות עבור הטריגר במצלמה {{camera}}" + }, + "deleteTrigger": { + "title": "מחק טריגר", + "desc": "האם אתה בטוח שברצונך למחוק את הטריגר {{triggerName}}? פעולה זו אינה ניתנת לביטול." + }, + "form": { + "name": { + "title": "שם", + "placeholder": "תן שם לטריגר", + "description": "הזן שם או תיאור ייחודיים לזיהוי הטריגר הזה", + "error": { + "minLength": "השדה חייב להכיל לפחות 2 תווים.", + "invalidCharacters": "השדה יכול להכיל רק אותיות, מספרים, קווים תחתונים (_) ומקפים (-).", + "alreadyExists": "כבר קיים טריגר בשם זה עבור מצלמה זו." + } + }, + "enabled": { + "description": "הפעל או השבת טריגר זה" + }, + "type": { + "title": "סוג", + "placeholder": "בחר סוג טריגר", + "description": "הפעל טריגר כאשר מזוהה תיאור דומה של אובייקט במעקב", + "thumbnail": "הפעל טריגר כאשר מזוהה תמונה ממוזערת דומה של אובייקט במעקב" + }, + "content": { + "title": "תוכן", + "imagePlaceholder": "בחר תמונה ממוזערת", + "textPlaceholder": "הזן תוכן טקסט", + "imageDesc": "מוצגות רק 100 התמונות הממוזערות האחרונות. אם אינך מוצא את התמונה הממוזערת הרצויה, אנא סקור אובייקטים מוקדמים יותר ב־Explore והגדר משם טריגר דרך התפריט.", + "textDesc": "הזן טקסט להפעלת פעולה זו כאשר מזוהה תיאור דומה של אובייקט במעקב.", + "error": { + "required": "נדרש תוכן." + } + }, + "threshold": { + "title": "סף", + "desc": "הגדר את סף הדמיון עבור טריגר זה. סף גבוה יותר מחייב התאמה קרובה יותר כדי להפעיל את הטריגר.", + "error": { + "min": "הסף חייב להיות לפחות 0", + "max": "הסף חייב להיות לכל היותר 1" + } + }, + "actions": { + "title": "פעולות", + "desc": "כברירת מחדל, Frigate שולח הודעת MQTT עבור כל הטריגרים. תוויות משנה (Sub Labels) מוסיפות את שם הטריגר לתווית האובייקט. מאפיינים (Attributes) הם מטא־נתונים הניתנים לחיפוש, הנשמרים בנפרד במטא־הנתונים של האובייקט במעקב.", + "error": { + "min": "חובה לבחור לפחות פעולה אחת." + } + } + } + }, + "wizard": { + "title": "צור טריגר", + "step1": { + "description": "הגדר את ההגדרות הבסיסיות של הטריגר שלך." + }, + "step2": { + "description": "הגדר את התוכן שיפעיל פעולה זו." + }, + "step3": { + "description": "הגדר את הסף והפעולות עבור טריגר זה." + }, + "steps": { + "nameAndType": "שם וסוג", + "configureData": "הגדר נתונים", + "thresholdAndActions": "סף ופעולות" + } + }, + "toast": { + "success": { + "createTrigger": "הטריגר {{name}} נוצר בהצלחה.", + "updateTrigger": "הטריגר {{name}} עודכן בהצלחה.", + "deleteTrigger": "הטריגר {{name}} נמחק בהצלחה." + }, + "error": { + "createTriggerFailed": "נכשל ביצירת הטריגר: {{errorMessage}}", + "updateTriggerFailed": "נכשל בעדכון הטריגר: {{errorMessage}}", + "deleteTriggerFailed": "נכשל במחיקת הטריגר: {{errorMessage}}" + } + } } } diff --git a/web/public/locales/he/views/system.json b/web/public/locales/he/views/system.json index d30f9437e4..90480a554a 100644 --- a/web/public/locales/he/views/system.json +++ b/web/public/locales/he/views/system.json @@ -7,7 +7,8 @@ "reindexingEmbeddings": "אינדקס מחדש של ההטמעות ({{processed}}% הושלם)", "cameraIsOffline": "{{camera}} לא זמינה", "detectIsSlow": "{{detect}} איטי ({{speed}} אלפיות שנייה)", - "detectIsVerySlow": "{{detect}} איטי מאוד ({{speed}} אלפיות שנייה)" + "detectIsVerySlow": "{{detect}} איטי מאוד ({{speed}} אלפיות שנייה)", + "shmTooLow": "יש להגדיל את הקצאת ‎/dev/shm‏ ({{total}} MB) לפחות ל־{{min}} MB." }, "documentTitle": { "cameras": "מצב מצלמות - Frigate", @@ -86,7 +87,12 @@ } }, "npuUsage": "שימוש ב-NPU", - "npuMemory": "NPU זיכרון" + "npuMemory": "NPU זיכרון", + "intelGpuWarning": { + "title": "אזהרת סטטיסטיקות GPU של Intel", + "message": "נתוני ה־GPU אינם זמינים", + "description": "זהו באג ידוע בכלי הדיווח של Intel לסטטיסטיקות GPU ‏(intel_gpu_top): לפעמים הוא “נשבר” ומתחיל להחזיר שוב ושוב שימוש GPU של 0%, גם במקרים שבהם ההאצה החומרתית וזיהוי האובייקטים כן עובדים תקין על ה־(i)GPU.\nזה לא באג של Frigate. אפשר לאתחל את ה־Host כדי לתקן את זה זמנית, וככה גם לוודא שה־GPU באמת עובד כמו שצריך.\nהתקלה הזו לא משפיעה על הביצועים." + } }, "otherProcesses": { "title": "תהליכים אחרים", @@ -108,8 +114,18 @@ "plate_recognition_speed": "מהירות זיהוי לוחית", "text_embedding_speed": "מהירות הטמעת טקסט", "yolov9_plate_detection_speed": "מהירות זיהוי לוחיות YOLOv9", - "yolov9_plate_detection": "זיהוי לוחיות YOLOv9" - } + "yolov9_plate_detection": "זיהוי לוחיות YOLOv9", + "review_description": "תיאור סקירה", + "review_description_speed": "מהירות תיאור הסקירה", + "review_description_events_per_second": "תיאור סקירה", + "object_description": "תיאור אובייקט", + "object_description_speed": "מהירות תיאור האובייקט", + "object_description_events_per_second": "תיאור אובייקט", + "classification": "סיווג {{name}}", + "classification_speed": "מהירות סיווג {{name}}", + "classification_events_per_second": "אירועי סיווג לשנייה עבור {{name}}" + }, + "averageInf": "זמן הסקה ממוצע" }, "storage": { "cameraStorage": { @@ -130,6 +146,10 @@ "title": "הקלטות", "earliestRecording": "ההקלטה המוקדמת ביותר הזמינה:", "tips": "ערך זה מייצג את סך האחסון בו משתמשים ההקלטות במסד הנתונים של Frigate. Frigate אינו עוקב אחר ניצול האחסון עבור כל הקבצים בדיסק שלך." + }, + "shm": { + "title": "הקצאת SHM (זיכרון משותף)", + "warning": "גודל ה־SHM הנוכחי של {{total}}MB קטן מדי. הגדל אותו לפחות ל־{{min_shm}}MB." } }, "cameras": { From bd2382dc45d733604d7ed667c972699c958c88e4 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:21 +0100 Subject: [PATCH 53/78] Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Languages add-on Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/ml/audio.json | 1 + web/public/locales/ml/common.json | 1 + web/public/locales/ml/components/auth.json | 1 + web/public/locales/ml/components/camera.json | 1 + web/public/locales/ml/components/dialog.json | 1 + web/public/locales/ml/components/filter.json | 1 + web/public/locales/ml/components/icons.json | 1 + web/public/locales/ml/components/input.json | 1 + web/public/locales/ml/components/player.json | 1 + web/public/locales/ml/objects.json | 1 + web/public/locales/ml/views/classificationModel.json | 1 + web/public/locales/ml/views/configEditor.json | 1 + web/public/locales/ml/views/events.json | 1 + web/public/locales/ml/views/explore.json | 1 + web/public/locales/ml/views/exports.json | 1 + web/public/locales/ml/views/faceLibrary.json | 1 + web/public/locales/ml/views/live.json | 1 + web/public/locales/ml/views/recording.json | 1 + web/public/locales/ml/views/search.json | 1 + web/public/locales/ml/views/settings.json | 1 + web/public/locales/ml/views/system.json | 1 + 21 files changed, 21 insertions(+) create mode 100644 web/public/locales/ml/audio.json create mode 100644 web/public/locales/ml/common.json create mode 100644 web/public/locales/ml/components/auth.json create mode 100644 web/public/locales/ml/components/camera.json create mode 100644 web/public/locales/ml/components/dialog.json create mode 100644 web/public/locales/ml/components/filter.json create mode 100644 web/public/locales/ml/components/icons.json create mode 100644 web/public/locales/ml/components/input.json create mode 100644 web/public/locales/ml/components/player.json create mode 100644 web/public/locales/ml/objects.json create mode 100644 web/public/locales/ml/views/classificationModel.json create mode 100644 web/public/locales/ml/views/configEditor.json create mode 100644 web/public/locales/ml/views/events.json create mode 100644 web/public/locales/ml/views/explore.json create mode 100644 web/public/locales/ml/views/exports.json create mode 100644 web/public/locales/ml/views/faceLibrary.json create mode 100644 web/public/locales/ml/views/live.json create mode 100644 web/public/locales/ml/views/recording.json create mode 100644 web/public/locales/ml/views/search.json create mode 100644 web/public/locales/ml/views/settings.json create mode 100644 web/public/locales/ml/views/system.json diff --git a/web/public/locales/ml/audio.json b/web/public/locales/ml/audio.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/audio.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/common.json b/web/public/locales/ml/common.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/common.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/auth.json b/web/public/locales/ml/components/auth.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/auth.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/camera.json b/web/public/locales/ml/components/camera.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/camera.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/dialog.json b/web/public/locales/ml/components/dialog.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/dialog.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/filter.json b/web/public/locales/ml/components/filter.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/filter.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/icons.json b/web/public/locales/ml/components/icons.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/icons.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/input.json b/web/public/locales/ml/components/input.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/input.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/components/player.json b/web/public/locales/ml/components/player.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/components/player.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/objects.json b/web/public/locales/ml/objects.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/objects.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/classificationModel.json b/web/public/locales/ml/views/classificationModel.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/classificationModel.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/configEditor.json b/web/public/locales/ml/views/configEditor.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/configEditor.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/events.json b/web/public/locales/ml/views/events.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/events.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/explore.json b/web/public/locales/ml/views/explore.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/explore.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/exports.json b/web/public/locales/ml/views/exports.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/exports.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/faceLibrary.json b/web/public/locales/ml/views/faceLibrary.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/faceLibrary.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/live.json b/web/public/locales/ml/views/live.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/live.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/recording.json b/web/public/locales/ml/views/recording.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/recording.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/search.json b/web/public/locales/ml/views/search.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/search.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/settings.json b/web/public/locales/ml/views/settings.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/settings.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/ml/views/system.json b/web/public/locales/ml/views/system.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/web/public/locales/ml/views/system.json @@ -0,0 +1 @@ +{} From d2aa2a0558db875b88e2e67cbabd8204250bcac5 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:23 +0100 Subject: [PATCH 54/78] Translated using Weblate (Polish) Currently translated at 98.6% (73 of 74 strings) Translated using Weblate (Polish) Currently translated at 69.6% (85 of 122 strings) Translated using Weblate (Polish) Currently translated at 93.1% (122 of 131 strings) Translated using Weblate (Polish) Currently translated at 83.7% (113 of 135 strings) Translated using Weblate (Polish) Currently translated at 58.1% (71 of 122 strings) Co-authored-by: Artur Co-authored-by: Hosted Weblate Co-authored-by: piesu Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/pl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/pl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pl/ Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-system --- web/public/locales/pl/components/filter.json | 3 ++ .../locales/pl/views/classificationModel.json | 33 ++++++++++++++++++- web/public/locales/pl/views/explore.json | 6 ++-- web/public/locales/pl/views/system.json | 5 ++- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/web/public/locales/pl/components/filter.json b/web/public/locales/pl/components/filter.json index b604c98c27..f718061a58 100644 --- a/web/public/locales/pl/components/filter.json +++ b/web/public/locales/pl/components/filter.json @@ -132,5 +132,8 @@ }, "count_one": "{{count}} Klasa", "count_other": "{{count}} Klas(y)" + }, + "attributes": { + "all": "Wszystkie atrybuty" } } diff --git a/web/public/locales/pl/views/classificationModel.json b/web/public/locales/pl/views/classificationModel.json index 0b63b917d2..d2fa9815a5 100644 --- a/web/public/locales/pl/views/classificationModel.json +++ b/web/public/locales/pl/views/classificationModel.json @@ -11,7 +11,9 @@ "editModel": "Edytuj model" }, "details": { - "scoreInfo": "Wynik przedstawia średnią pewność klasyfikacji wszystkich wykryć danego obiektu." + "scoreInfo": "Wynik przedstawia średnią pewność klasyfikacji wszystkich wykryć danego obiektu.", + "none": "Brak", + "unknown": "Nieznany" }, "toast": { "success": { @@ -114,6 +116,35 @@ "steps": { "nameAndDefine": "Nazwij i zdefiniuj", "stateArea": "Obszar stanu" + }, + "step1": { + "name": "Nazwa", + "type": "Typ", + "classes": "Klasy", + "errors": { + "noneNotAllowed": "Klasa „żadne” jest niedozwolona.", + "stateRequiresTwoClasses": "Modele stanowe wymagają co najmniej dwie klasy.", + "objectLabelRequired": "Proszę wybrać etykietę obiektu", + "objectTypeRequired": "Proszę wybrać typ klasyfikacji" + } + }, + "step2": { + "description": "Wybierz kamery i określ obszar monitorowania dla każdej z nich. Model sklasyfikuje stan tych obszarów.", + "cameras": "Kamery", + "selectCamera": "Wybierz kamerę", + "noCameras": "Kliknij +, aby dodać kamery", + "selectCameraPrompt": "Wybierz kamerę z listy, aby zdefiniować jej obszar monitorowania." + }, + "step3": { + "selectImagesPrompt": "Zaznacz wszystkie obrazy z: {{className}}", + "selectImagesDescription": "Kliknij na zdjęcia, aby je wybrać. Po zakończeniu zajęć kliknij „Kontynuuj”.", + "allImagesRequired_one": "Proszę sklasyfikować wszystkie obrazy. Pozostał {{count}} obraz.", + "allImagesRequired_few": "Proszę sklasyfikować wszystkie obrazy. Pozostały {{count}} obrazy.", + "allImagesRequired_many": "Proszę sklasyfikować wszystkie obrazy. Pozostało {{count}} obrazów.", + "generating": { + "title": "Generowanie przykładowych obrazów" + }, + "trainingStarted": "Szkolenie rozpoczęło się pomyślnie" } } } diff --git a/web/public/locales/pl/views/explore.json b/web/public/locales/pl/views/explore.json index 5132a2a637..cceb1a1622 100644 --- a/web/public/locales/pl/views/explore.json +++ b/web/public/locales/pl/views/explore.json @@ -21,13 +21,15 @@ "regenerate": "Zażądano nowego opisu od {{provider}}. W zależności od szybkości twojego dostawcy, wygenerowanie nowego opisu może zająć trochę czasu.", "updatedSublabel": "Pomyślnie zaktualizowano podetykietę.", "updatedLPR": "Pomyślnie zaktualizowano tablicę rejestracyjną.", - "audioTranscription": "Wysłano prośbę o audio transkrypcję." + "audioTranscription": "Wysłano prośbę o audio transkrypcję. W zależności od szybkości serwera Frigate, transkrypcja może potrwać trochę czasu.", + "updatedAttributes": "Atrybuty zostały pomyślnie zaktualizowane." }, "error": { "regenerate": "Nie udało się wezwać {{provider}} dla nowego opisu: {{errorMessage}}", "updatedSublabelFailed": "Nie udało się zaktualizować podetykiety: {{errorMessage}}", "updatedLPRFailed": "Nie udało się zaktualizować tablicy rejestracyjnej: {{errorMessage}}", - "audioTranscription": "Nie udało się włączyć audio transkrypcji: {{errorMessage}}" + "audioTranscription": "Nie udało się włączyć audio transkrypcji: {{errorMessage}}", + "updatedAttributesFailed": "Nie udało się zaktualizować atrybutów: {{errorMessage}}" } } }, diff --git a/web/public/locales/pl/views/system.json b/web/public/locales/pl/views/system.json index ba82ea97d9..d6fe5ea89e 100644 --- a/web/public/locales/pl/views/system.json +++ b/web/public/locales/pl/views/system.json @@ -184,7 +184,10 @@ "yolov9_plate_detection_speed": "Prędkość detekcji rejestracji samochodowych YOLOv9", "yolov9_plate_detection": "Detekcja rejestracji samochodowych YOLOv9", "text_embedding": "Osadzenie tekstu", - "face_recognition": "Rozpoznawanie twarzy" + "face_recognition": "Rozpoznawanie twarzy", + "classification_events_per_second": "{{name}} Klasyfikacja zdarzeń na sekundę", + "classification_speed": "{{name}} Szybkość klasyfikacji", + "classification": "{{name}} Klasyfikacja" } } } From bfc2859c8eed56517cf3e15c0888e62bc9992815 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:24 +0100 Subject: [PATCH 55/78] Translated using Weblate (Italian) Currently translated at 100.0% (654 of 654 strings) Translated using Weblate (Italian) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Italian) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Italian) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Italian) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Italian) Currently translated at 100.0% (49 of 49 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Gringo Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/it/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/it/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/it/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/it/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/it/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/it/ Translation: Frigate NVR/common Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/it/components/filter.json | 4 ++ .../locales/it/views/classificationModel.json | 12 ++++-- web/public/locales/it/views/events.json | 3 +- web/public/locales/it/views/explore.json | 19 ++++++-- web/public/locales/it/views/faceLibrary.json | 3 +- web/public/locales/it/views/search.json | 3 +- web/public/locales/it/views/settings.json | 43 +++++++++++++------ web/public/locales/it/views/system.json | 5 ++- 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/web/public/locales/it/components/filter.json b/web/public/locales/it/components/filter.json index 22fc520931..ac5eaebada 100644 --- a/web/public/locales/it/components/filter.json +++ b/web/public/locales/it/components/filter.json @@ -132,5 +132,9 @@ }, "count_one": "{{count}} Classe", "count_other": "{{count}} Classi" + }, + "attributes": { + "label": "Attributi di classificazione", + "all": "Tutti gli attributi" } } diff --git a/web/public/locales/it/views/classificationModel.json b/web/public/locales/it/views/classificationModel.json index 84fb1299f1..a35a391723 100644 --- a/web/public/locales/it/views/classificationModel.json +++ b/web/public/locales/it/views/classificationModel.json @@ -116,7 +116,8 @@ "classesUnique": "I nomi delle classi devono essere univoci", "stateRequiresTwoClasses": "I modelli di stato richiedono almeno 2 classi", "objectLabelRequired": "Seleziona un'etichetta per l'oggetto", - "objectTypeRequired": "Seleziona un tipo di classificazione" + "objectTypeRequired": "Seleziona un tipo di classificazione", + "noneNotAllowed": "La classe 'nessuno' non è consentita" }, "states": "Stati" }, @@ -156,7 +157,7 @@ "modelCreated": "Modello creato correttamente. Utilizza la vista Classificazioni recenti per aggiungere immagini per gli stati mancanti, quindi addestrare il modello.", "missingStatesWarning": { "title": "Esempi di stati mancanti", - "description": "Non hai selezionato esempi per tutti gli stati. Il modello non verrà addestrato finché tutti gli stati non avranno immagini. Dopo aver continuato, utilizza la vista Classificazioni recenti per classificare le immagini per gli stati mancanti, quindi addestra il modello." + "description": "Per ottenere risultati ottimali, si consiglia di selezionare esempi per tutti gli stati. È possibile continuare senza selezionare tutti gli stati, ma il modello non verrà addestrato finché tutti gli stati non avranno immagini. Dopo aver continuato, utilizza la vista Classificazioni recenti per classificare le immagini per gli stati mancanti, quindi addestra il modello." } } }, @@ -172,7 +173,9 @@ "states": "Stati" }, "details": { - "scoreInfo": "Il punteggio rappresenta la confidenza media della classificazione in tutti i rilevamenti di questo oggetto." + "scoreInfo": "Il punteggio rappresenta la confidenza media della classificazione in tutti i rilevamenti di questo oggetto.", + "none": "Nessuno", + "unknown": "Sconosciuto" }, "edit": { "title": "Modifica modello di classificazione", @@ -185,5 +188,6 @@ "modelNotReady": "Il modello non è pronto per l'addestramento", "noNewImages": "Nessuna nuova immagine da addestrare. Classifica prima più immagini nel database.", "noChanges": "Nessuna modifica al database dall'ultimo addestramento." - } + }, + "none": "Nessuno" } diff --git a/web/public/locales/it/views/events.json b/web/public/locales/it/views/events.json index d5e861ccfb..623fb1deec 100644 --- a/web/public/locales/it/views/events.json +++ b/web/public/locales/it/views/events.json @@ -59,5 +59,6 @@ "zoomOut": "Rimpicciolisci", "normalActivity": "Normale", "needsReview": "Necessita revisione", - "securityConcern": "Rischio per la sicurezza" + "securityConcern": "Rischio per la sicurezza", + "select_all": "Tutti" } diff --git a/web/public/locales/it/views/explore.json b/web/public/locales/it/views/explore.json index 9277ee993a..2ec1f4614e 100644 --- a/web/public/locales/it/views/explore.json +++ b/web/public/locales/it/views/explore.json @@ -53,13 +53,15 @@ "regenerate": "È stata richiesta una nuova descrizione a {{provider}}. A seconda della velocità del tuo provider, la rigenerazione della nuova descrizione potrebbe richiedere del tempo.", "updatedSublabel": "Sottoetichetta aggiornata correttamente.", "updatedLPR": "Targa aggiornata con successo.", - "audioTranscription": "Trascrizione audio richiesta con successo. A seconda della velocità del server Frigate, la trascrizione potrebbe richiedere del tempo." + "audioTranscription": "Trascrizione audio richiesta con successo. A seconda della velocità del server Frigate, la trascrizione potrebbe richiedere del tempo.", + "updatedAttributes": "Attributi aggiornati correttamente." }, "error": { "regenerate": "Impossibile chiamare {{provider}} per una nuova descrizione: {{errorMessage}}", "updatedSublabelFailed": "Impossibile aggiornare la sottoetichetta: {{errorMessage}}", "updatedLPRFailed": "Impossibile aggiornare la targa: {{errorMessage}}", - "audioTranscription": "Impossibile richiedere la trascrizione audio: {{errorMessage}}" + "audioTranscription": "Impossibile richiedere la trascrizione audio: {{errorMessage}}", + "updatedAttributesFailed": "Impossibile aggiornare gli attributi: {{errorMessage}}" } } }, @@ -103,7 +105,12 @@ }, "score": { "label": "Punteggio" - } + }, + "editAttributes": { + "title": "Modifica attributi", + "desc": "Seleziona gli attributi di classificazione per questa {{label}}" + }, + "attributes": "Attributi di classificazione" }, "objectLifecycle": { "annotationSettings": { @@ -207,6 +214,10 @@ "viewTrackingDetails": { "label": "Visualizza i dettagli di tracciamento", "aria": "Mostra i dettagli di tracciamento" + }, + "downloadCleanSnapshot": { + "label": "Scarica istantanea pulita", + "aria": "Scarica istantanea pulita" } }, "dialog": { @@ -279,7 +290,7 @@ "millisecondsToOffset": "Millisecondi per compensare il rilevamento delle annotazioni. Predefinito: 0", "tips": "Ridurre il valore se la riproduzione video è in anticipo rispetto ai riquadri e ai punti del percorso, e aumentarlo se la riproduzione video è in ritardo rispetto ad essi. Questo valore può essere negativo.", "toast": { - "success": "La differenza dell'annotazione per {{camera}} è stato salvato nel file di configurazione. Riavvia Frigate per applicare le modifiche." + "success": "La differenza dell'annotazione per {{camera}} è stato salvato nel file di configurazione." } } }, diff --git a/web/public/locales/it/views/faceLibrary.json b/web/public/locales/it/views/faceLibrary.json index ad47e7bc02..b40e7fbcf0 100644 --- a/web/public/locales/it/views/faceLibrary.json +++ b/web/public/locales/it/views/faceLibrary.json @@ -18,7 +18,8 @@ "train": { "title": "Riconoscimenti recenti", "aria": "Seleziona i riconoscimenti recenti", - "empty": "Non ci sono recenti tentativi di riconoscimento facciale" + "empty": "Non ci sono recenti tentativi di riconoscimento facciale", + "titleShort": "Recente" }, "button": { "addFace": "Aggiungi volto", diff --git a/web/public/locales/it/views/search.json b/web/public/locales/it/views/search.json index 873ef007c1..97f8000c1c 100644 --- a/web/public/locales/it/views/search.json +++ b/web/public/locales/it/views/search.json @@ -25,7 +25,8 @@ "after": "Dopo", "max_speed": "Velocità massima", "recognized_license_plate": "Targa riconosciuta", - "sub_labels": "Sottoetichette" + "sub_labels": "Sottoetichette", + "attributes": "Attributi" }, "tips": { "desc": { diff --git a/web/public/locales/it/views/settings.json b/web/public/locales/it/views/settings.json index afaf31078f..9cdcea5fb3 100644 --- a/web/public/locales/it/views/settings.json +++ b/web/public/locales/it/views/settings.json @@ -143,8 +143,8 @@ "add": "Nuova maschera di movimento", "toast": { "success": { - "title": "{{polygonName}} è stato salvato. Riavvia Frigate per applicare le modifiche.", - "noName": "La maschera di movimento è stata salvata. Riavvia Frigate per applicare le modifiche." + "title": "{{polygonName}} è stato salvato.", + "noName": "La maschera di movimento è stata salvata." } } }, @@ -239,7 +239,7 @@ "name": { "inputPlaceHolder": "Inserisci un nome…", "title": "Nome", - "tips": "Il nome deve essere composto da almeno 2 caratteri, contenere almeno una lettera e non deve essere il nome di una telecamera o di un'altra zona." + "tips": "Il nome deve essere composto da almeno 2 caratteri, contenere almeno una lettera e non deve essere il nome di una telecamera o di un'altra zona di questa telecamera." }, "clickDrawPolygon": "Fai clic per disegnare un poligono sull'immagine.", "point_one": "{{count}} punto", @@ -261,7 +261,7 @@ }, "allObjects": "Tutti gli oggetti", "toast": { - "success": "La zona ({{zoneName}}) è stata salvata. Riavvia Frigate per applicare le modifiche." + "success": "La zona ({{zoneName}}) è stata salvata." } }, "objectMasks": { @@ -283,8 +283,8 @@ }, "toast": { "success": { - "noName": "La maschera oggetto è stata salvata. Riavvia Frigate per applicare le modifiche.", - "title": "{{polygonName}} è stato salvato. Riavvia Frigate per applicare le modifiche." + "noName": "La maschera oggetto è stata salvata.", + "title": "{{polygonName}} è stato salvato." } }, "label": "Maschere di oggetti", @@ -427,7 +427,16 @@ "title": "Password", "placeholder": "Inserisci la password", "match": "Le password corrispondono", - "notMatch": "Le password non corrispondono" + "notMatch": "Le password non corrispondono", + "show": "Mostra password", + "hide": "Nascondi password", + "requirements": { + "title": "Requisiti password:", + "length": "Almeno 8 caratteri", + "uppercase": "Almeno una lettera maiuscola", + "digit": "Almeno una cifra", + "special": "Almeno un carattere speciale (!@#$%^&*(),.?\":{}|<>)" + } }, "newPassword": { "title": "Nuova password", @@ -437,7 +446,11 @@ } }, "usernameIsRequired": "Il nome utente è obbligatorio", - "passwordIsRequired": "La password è obbligatoria" + "passwordIsRequired": "La password è obbligatoria", + "currentPassword": { + "title": "Password attuale", + "placeholder": "Inserisci la password attuale" + } }, "createUser": { "desc": "Aggiungi un nuovo account utente e specifica un ruolo per l'accesso alle aree dell'interfaccia utente di Frigate.", @@ -450,11 +463,16 @@ "setPassword": "Imposta password", "desc": "Crea una password complessa per proteggere questo account.", "cannotBeEmpty": "La password non può essere vuota", - "doNotMatch": "Le password non corrispondono" + "doNotMatch": "Le password non corrispondono", + "currentPasswordRequired": "È richiesta la password attuale", + "incorrectCurrentPassword": "La password attuale è errata", + "passwordVerificationFailed": "Impossibile verificare la password", + "multiDeviceWarning": "Sarà necessario effettuare nuovamente l'accesso su qualsiasi altro dispositivo entro {{refresh_time}}.", + "multiDeviceAdmin": "Puoi anche forzare tutti gli utenti a riautenticarsi immediatamente ruotando il tuo segreto JWT." } }, "table": { - "password": "Password", + "password": "Reimposta password", "username": "Nome utente", "actions": "Azioni", "role": "Ruolo", @@ -482,7 +500,7 @@ "desc": "Gestisci gli account utente di questa istanza Frigate." }, "addUser": "Aggiungi utente", - "updatePassword": "Aggiorna password" + "updatePassword": "Reimposta password" }, "general": { "liveDashboard": { @@ -1240,7 +1258,8 @@ "audioCodecRequired": "Per supportare il rilevamento audio è necessario un flusso audio.", "restreamingWarning": "Riducendo le connessioni alla telecamera per il flusso di registrazione l'utilizzo della CPU potrebbe aumentare leggermente.", "brands": { - "reolink-rtsp": "Reolink RTSP non è consigliato. Abilita HTTP nelle impostazioni del firmware della telecamera e riavvia la procedura guidata." + "reolink-rtsp": "Reolink RTSP non è consigliato. Abilita HTTP nelle impostazioni del firmware della telecamera e riavvia la procedura guidata.", + "reolink-http": "I flussi HTTP di Reolink dovrebbero utilizzare FFmpeg per una migliore compatibilità. Abilita \"Usa modalità compatibilità flusso\" per questo flusso." }, "dahua": { "substreamWarning": "Il sottoflusso 1 è bloccato a bassa risoluzione. Molte telecamere Dahua/Amcrest/EmpireTech supportano sottoflussi aggiuntivi che devono essere abilitati nelle impostazioni della telecamera. Si consiglia di controllare e utilizzare tali flussi, se disponibili." diff --git a/web/public/locales/it/views/system.json b/web/public/locales/it/views/system.json index b031828e1c..c4f4b96772 100644 --- a/web/public/locales/it/views/system.json +++ b/web/public/locales/it/views/system.json @@ -105,7 +105,10 @@ "review_description_events_per_second": "Descrizione della revisione", "object_description": "Descrizione dell'oggetto", "object_description_speed": "Velocità della descrizione dell'oggetto", - "object_description_events_per_second": "Descrizione dell'oggetto" + "object_description_events_per_second": "Descrizione dell'oggetto", + "classification": "Classificazione {{name}}", + "classification_speed": "Velocità di classificazione {{name}}", + "classification_events_per_second": "Eventi di classificazione {{name}} al secondo" }, "title": "Miglioramenti", "infPerSecond": "Inferenze al secondo", From 5d960aa282f016e05885edbc6295d069698ae769 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:25 +0100 Subject: [PATCH 56/78] Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/id/common.json | 3 +- web/public/locales/id/components/dialog.json | 4 ++- web/public/locales/id/components/filter.json | 8 +++++ web/public/locales/id/components/player.json | 3 +- web/public/locales/id/objects.json | 3 +- .../locales/id/views/classificationModel.json | 17 ++++++++- web/public/locales/id/views/explore.json | 3 +- web/public/locales/id/views/live.json | 3 +- web/public/locales/id/views/search.json | 7 +++- web/public/locales/id/views/settings.json | 36 +++++++++++++++++-- web/public/locales/id/views/system.json | 7 +++- 11 files changed, 82 insertions(+), 12 deletions(-) diff --git a/web/public/locales/id/common.json b/web/public/locales/id/common.json index 9072354dce..34ededad10 100644 --- a/web/public/locales/id/common.json +++ b/web/public/locales/id/common.json @@ -9,7 +9,8 @@ "untilForTime": "Hingga {{time}}", "last7": "7 hari terakhir", "last14": "14 hari terakhir", - "last30": "30 hari terakhir" + "last30": "30 hari terakhir", + "thisWeek": "Minggu Ini" }, "readTheDocumentation": "Baca dokumentasi" } diff --git a/web/public/locales/id/components/dialog.json b/web/public/locales/id/components/dialog.json index 5d5f20fb81..1a2c284fb7 100644 --- a/web/public/locales/id/components/dialog.json +++ b/web/public/locales/id/components/dialog.json @@ -17,7 +17,9 @@ "review": { "question": { "label": "Konfirmasi label ini untuk Frigate Plus", - "ask_a": "Apakah objek ini adalah sebuah{{label}}?" + "ask_a": "Apakah objek ini adalah sebuah{{label}}?", + "ask_an": "Apakah objek ini {{label}}?", + "ask_full": "Apakah ini object {{untranslatedLabel}} ({{translatedLabel}})?" } } } diff --git a/web/public/locales/id/components/filter.json b/web/public/locales/id/components/filter.json index 0ea01e61b6..070963badb 100644 --- a/web/public/locales/id/components/filter.json +++ b/web/public/locales/id/components/filter.json @@ -15,5 +15,13 @@ "title": "Semua Zona", "short": "Zona" } + }, + "classes": { + "label": "Kelas", + "all": { + "title": "Semua Kelas" + }, + "count_one": "{{count}} Kelas", + "count_other": "{{count}} Kelas" } } diff --git a/web/public/locales/id/components/player.json b/web/public/locales/id/components/player.json index 097e50a686..151fd41ceb 100644 --- a/web/public/locales/id/components/player.json +++ b/web/public/locales/id/components/player.json @@ -14,7 +14,8 @@ "cameraDisabled": "Kamera dinonaktifkan", "stats": { "streamType": { - "title": "Tipe stream:" + "title": "Tipe stream:", + "short": "Jenis" } } } diff --git a/web/public/locales/id/objects.json b/web/public/locales/id/objects.json index bfeeca8ea8..43d98cddeb 100644 --- a/web/public/locales/id/objects.json +++ b/web/public/locales/id/objects.json @@ -16,5 +16,6 @@ "horse": "Kuda", "goat": "Kambing", "sheep": "Domba", - "bird": "Burung" + "bird": "Burung", + "street_sign": "Rambu Jalan" } diff --git a/web/public/locales/id/views/classificationModel.json b/web/public/locales/id/views/classificationModel.json index 0967ef424b..6ea3a79158 100644 --- a/web/public/locales/id/views/classificationModel.json +++ b/web/public/locales/id/views/classificationModel.json @@ -1 +1,16 @@ -{} +{ + "documentTitle": "Klasifikasi Model - Frigate", + "details": { + "scoreInfo": "Skor tersebut mewakili rata-rata kepercayaan klasifikasi di seluruh deteksi objek ini." + }, + "button": { + "deleteClassificationAttempts": "Hapus Gambar Klasifikasi", + "renameCategory": "Ubah Nama Kelas", + "deleteCategory": "Hapus Kelas", + "deleteImages": "Hapus Gambar", + "trainModel": "Latih Model", + "addClassification": "Tambah Klasifikasi", + "deleteModels": "Hapus Model", + "editModel": "Ubah Model" + } +} diff --git a/web/public/locales/id/views/explore.json b/web/public/locales/id/views/explore.json index de062e132b..979ceaf7b5 100644 --- a/web/public/locales/id/views/explore.json +++ b/web/public/locales/id/views/explore.json @@ -9,7 +9,8 @@ "estimatedTime": "Perkiraan waktu tersisa:", "finishingShortly": "Selesai sesaat lagi", "step": { - "thumbnailsEmbedded": "Keluku dilampirkan " + "thumbnailsEmbedded": "Keluku dilampirkan ", + "descriptionsEmbedded": "Deskripsi terlampir: " } } }, diff --git a/web/public/locales/id/views/live.json b/web/public/locales/id/views/live.json index 97a733541f..51817a84b0 100644 --- a/web/public/locales/id/views/live.json +++ b/web/public/locales/id/views/live.json @@ -14,7 +14,8 @@ "move": { "clickMove": { "label": "Klik kotak ini untuk menengahkan kamera", - "enable": "Aktifkan klik untuk bergerak" + "enable": "Aktifkan klik untuk bergerak", + "disable": "Non-aktifkan klik untuk bergerak" } } } diff --git a/web/public/locales/id/views/search.json b/web/public/locales/id/views/search.json index c4c5989904..fbca84c8d4 100644 --- a/web/public/locales/id/views/search.json +++ b/web/public/locales/id/views/search.json @@ -9,5 +9,10 @@ "filterInformation": "Saring Informasi", "filterActive": "Filter aktif" }, - "trackedObjectId": "Tracked Object ID" + "trackedObjectId": "Tracked Object ID", + "filter": { + "label": { + "cameras": "Kamera" + } + } } diff --git a/web/public/locales/id/views/settings.json b/web/public/locales/id/views/settings.json index 8d1b4dec81..45e49fd1ef 100644 --- a/web/public/locales/id/views/settings.json +++ b/web/public/locales/id/views/settings.json @@ -9,10 +9,40 @@ "general": "Frigate - Pengaturan Umum", "object": "Debug - Frigate", "enrichments": "Frigate - Pengaturan Pengayaan", - "cameraManagement": "Pengaturan Kamera - Frigate" + "cameraManagement": "Pengaturan Kamera - Frigate", + "cameraReview": "Pengaturan Ulasan Kamera - Frigate", + "frigatePlus": "Pengaturan Frigate+ - Frigate" }, "menu": { - "cameraManagement": "Pengaturan", - "notifications": "Notifikasi" + "cameraManagement": "Manajemen", + "notifications": "Notifikasi", + "ui": "Antarmuka Pengguna", + "enrichments": "Peningkatan", + "cameraReview": "Ulasan", + "motionTuner": "Pengatur Gerak", + "triggers": "Pemicu", + "users": "Pengguna", + "roles": "Peran", + "frigateplus": "Frigate+" + }, + "dialog": { + "unsavedChanges": { + "title": "Anda memiliki perubahan yang belum disimpan.", + "desc": "Apakah Anda ingin menyimpan perubahan Anda sebelum melanjutkan?" + } + }, + "cameraSetting": { + "camera": "Kamera", + "noCamera": "Tidak Ada Kamera" + }, + "general": { + "title": "Pengaturan Antarmuka Pengguna", + "liveDashboard": { + "title": "Dashboard Langsung", + "automaticLiveView": { + "label": "Tampilan Langsung Otomatis", + "desc": "Secara otomatis beralih ke tampilan langsung kamera saat aktivitas terdeteksi. Menonaktifkan opsi ini menyebabkan gambar statis kamera di dasbor langsung hanya diperbarui sekali per menit." + } + } } } diff --git a/web/public/locales/id/views/system.json b/web/public/locales/id/views/system.json index 183e7ca34b..9a70ff6ca2 100644 --- a/web/public/locales/id/views/system.json +++ b/web/public/locales/id/views/system.json @@ -11,5 +11,10 @@ } }, "title": "Sistem", - "metrics": "Metrik sistem" + "metrics": "Metrik sistem", + "logs": { + "download": { + "label": "Unduh Log" + } + } } From 225c5f0d7178fd0ce9e96cae38d9e0f19ba8dc7d Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:27 +0100 Subject: [PATCH 57/78] Translated using Weblate (Dutch) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (654 of 654 strings) Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Marijn <168113859+Marijn0@users.noreply.github.com> Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/nl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/nl/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/ Translation: Frigate NVR/common Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings --- web/public/locales/nl/components/filter.json | 4 ++++ .../locales/nl/views/classificationModel.json | 9 ++++++--- web/public/locales/nl/views/explore.json | 13 ++++++++++--- web/public/locales/nl/views/search.json | 3 ++- web/public/locales/nl/views/settings.json | 10 ++++++---- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/web/public/locales/nl/components/filter.json b/web/public/locales/nl/components/filter.json index 95a98146f6..e910acd832 100644 --- a/web/public/locales/nl/components/filter.json +++ b/web/public/locales/nl/components/filter.json @@ -133,5 +133,9 @@ }, "count_one": "{{count}} klasse", "count_other": "{{count}} Klassen" + }, + "attributes": { + "label": "Classificatie-kenmerken", + "all": "Alle attributen" } } diff --git a/web/public/locales/nl/views/classificationModel.json b/web/public/locales/nl/views/classificationModel.json index 5674ca30c6..a94c7956b7 100644 --- a/web/public/locales/nl/views/classificationModel.json +++ b/web/public/locales/nl/views/classificationModel.json @@ -113,7 +113,8 @@ "classesUnique": "Klassennamen moeten uniek zijn", "stateRequiresTwoClasses": "Statusmodellen vereisen minimaal 2 klassen", "objectLabelRequired": "Selecteer een objectlabel", - "objectTypeRequired": "Selecteer een classificatietype" + "objectTypeRequired": "Selecteer een classificatietype", + "noneNotAllowed": "De klasse 'none' is niet toegestaan" }, "states": "Staten" }, @@ -167,7 +168,9 @@ "states": "Staten" }, "details": { - "scoreInfo": "Score geeft het gemiddelde classificatievertrouwen weer over alle detecties van dit object." + "scoreInfo": "Score geeft het gemiddelde classificatievertrouwen weer over alle detecties van dit object.", + "none": "Geen overeenkomst", + "unknown": "Onbekend" }, "edit": { "title": "Classificatiemodel bewerken", @@ -181,5 +184,5 @@ "modelNotReady": "Model is niet klaar voor training", "noChanges": "Geen wijzigingen in de dataset sinds de laatste training." }, - "none": "Geen herkenning" + "none": "Geen overeenkomst" } diff --git a/web/public/locales/nl/views/explore.json b/web/public/locales/nl/views/explore.json index b67bea20c2..7372925d77 100644 --- a/web/public/locales/nl/views/explore.json +++ b/web/public/locales/nl/views/explore.json @@ -105,13 +105,15 @@ "regenerate": "Er is een nieuwe beschrijving aangevraagd bij {{provider}}. Afhankelijk van de snelheid van je provider kan het regenereren van de nieuwe beschrijving enige tijd duren.", "updatedSublabel": "Sublabel succesvol bijgewerkt.", "updatedLPR": "Kenteken succesvol bijgewerkt.", - "audioTranscription": "Audio-transcriptie succesvol aangevraagd. Afhankelijk van de snelheid van uw Frigate-server kan het even duren voordat de transcriptie voltooid is." + "audioTranscription": "Audio-transcriptie succesvol aangevraagd. Afhankelijk van de snelheid van uw Frigate-server kan het even duren voordat de transcriptie voltooid is.", + "updatedAttributes": "Attributen succesvol bijgewerkt." }, "error": { "updatedSublabelFailed": "Het is niet gelukt om het sublabel bij te werken: {{errorMessage}}", "regenerate": "Het is niet gelukt om {{provider}} aan te roepen voor een nieuwe beschrijving: {{errorMessage}}", "updatedLPRFailed": "Kentekenplaat bijwerken mislukt: {{errorMessage}}", - "audioTranscription": "Audiotranscriptie aanvragen mislukt: {{errorMessage}}" + "audioTranscription": "Audiotranscriptie aanvragen mislukt: {{errorMessage}}", + "updatedAttributesFailed": "Attributen konden niet worden bijgewerkt: {{errorMessage}}" } } }, @@ -160,7 +162,12 @@ }, "score": { "label": "Score" - } + }, + "editAttributes": { + "title": "Bewerk attributen", + "desc": "Selecteer classificatiekenmerken voor dit {{label}}" + }, + "attributes": "Classificatie-kenmerken" }, "itemMenu": { "downloadVideo": { diff --git a/web/public/locales/nl/views/search.json b/web/public/locales/nl/views/search.json index 47487be38b..7552d14395 100644 --- a/web/public/locales/nl/views/search.json +++ b/web/public/locales/nl/views/search.json @@ -26,7 +26,8 @@ "search_type": "Zoektype", "zones": "Zones", "max_speed": "Max snelheid", - "after": "Na" + "after": "Na", + "attributes": "Kenmerken" }, "toast": { "error": { diff --git a/web/public/locales/nl/views/settings.json b/web/public/locales/nl/views/settings.json index fdc439b463..dade19f15f 100644 --- a/web/public/locales/nl/views/settings.json +++ b/web/public/locales/nl/views/settings.json @@ -495,7 +495,7 @@ "title": "Gebruikersbeheer" }, "addUser": "Gebruiker toevoegen", - "updatePassword": "Wachtwoord bijwerken", + "updatePassword": "Wachtwoord opnieuw instellen", "toast": { "success": { "createUser": "Gebruiker {{user}} succesvol aangemaakt", @@ -515,7 +515,7 @@ "role": "Rol", "noUsers": "Geen gebruikers gevonden.", "changeRole": "Gebruikersrol wijzigen", - "password": "Wachtwoord", + "password": "Wachtwoord opnieuw instellen", "deleteUser": "Verwijder gebruiker", "username": "Gebruikersnaam" }, @@ -599,7 +599,8 @@ "currentPasswordRequired": "Huidig wachtwoord is vereist", "incorrectCurrentPassword": "Het huidige wachtwoord is onjuist", "passwordVerificationFailed": "Wachtwoord kan niet worden geverifieerd", - "multiDeviceWarning": "Op alle andere apparaten waarop u bent ingelogd, wordt u binnen {{refresh_time}} gevraagd opnieuw in te loggen. U kunt ook alle gebruikers direct opnieuw laten inloggen door uw JWT-secret te vernieuwen." + "multiDeviceWarning": "Op alle andere apparaten waarop u bent ingelogd, moet u binnen {{refresh_time}} opnieuw inloggen.", + "multiDeviceAdmin": "Je kunt ook alle gebruikers forceren zich onmiddellijk opnieuw te authenticeren door je JWT-geheim te roteren." } } }, @@ -1217,7 +1218,8 @@ "audioCodecRequired": "Ter ondersteuning van audiodetectie is een audiostream vereist.", "restreamingWarning": "Als u het aantal verbindingen met de camera voor de opnamestream vermindert, kan het CPU-gebruik iets toenemen.", "brands": { - "reolink-rtsp": "Reolink RTSP wordt niet aanbevolen. Schakel HTTP in via de firmware-instellingen van de camera en start de wizard opnieuw." + "reolink-rtsp": "Reolink RTSP wordt niet aanbevolen. Schakel HTTP in via de firmware-instellingen van de camera en start de wizard opnieuw.", + "reolink-http": "Reolink HTTP-streams moeten FFmpeg gebruiken voor een betere compatibiliteit. Schakel ‘stream-compatibiliteitsmodus’ in voor deze stream." }, "dahua": { "substreamWarning": "Substream 1 is beperkt tot een lage resolutie. Veel Dahua / Amcrest / EmpireTech camera’s ondersteunen extra substreams die in de instellingen van de camera ingeschakeld moeten worden. Het wordt aanbevolen deze streams te controleren en te gebruiken indien beschikbaar." From 57d344a441530d626d91875d6069b9f5da67c3cf Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:28 +0100 Subject: [PATCH 58/78] Translated using Weblate (French) Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (French) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (French) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (French) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (French) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (French) Currently translated at 100.0% (121 of 121 strings) Translated using Weblate (French) Currently translated at 100.0% (654 of 654 strings) Co-authored-by: Apocoloquintose Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/fr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/fr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/fr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fr/ Translation: Frigate NVR/components-filter Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/fr/components/filter.json | 4 ++++ .../locales/fr/views/classificationModel.json | 7 +++++-- web/public/locales/fr/views/explore.json | 13 ++++++++++--- web/public/locales/fr/views/search.json | 3 ++- web/public/locales/fr/views/settings.json | 4 ++-- web/public/locales/fr/views/system.json | 5 ++++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/web/public/locales/fr/components/filter.json b/web/public/locales/fr/components/filter.json index b8af1d69c0..0af924f8b2 100644 --- a/web/public/locales/fr/components/filter.json +++ b/web/public/locales/fr/components/filter.json @@ -133,5 +133,9 @@ }, "count_one": "{{count}} classe", "count_other": "{{count}} classes" + }, + "attributes": { + "label": "Attributs de classification", + "all": "Tous les attributs" } } diff --git a/web/public/locales/fr/views/classificationModel.json b/web/public/locales/fr/views/classificationModel.json index c18944fb9b..0926f4cd68 100644 --- a/web/public/locales/fr/views/classificationModel.json +++ b/web/public/locales/fr/views/classificationModel.json @@ -116,7 +116,8 @@ "classesUnique": "Les noms de classe doivent être uniques.", "stateRequiresTwoClasses": "Les modèles d'état nécessitent au moins deux classes.", "objectLabelRequired": "Veuillez sélectionner une étiquette d'objet.", - "objectTypeRequired": "Veuillez sélectionner un type de classification." + "objectTypeRequired": "Veuillez sélectionner un type de classification.", + "noneNotAllowed": "La classe 'aucun' n'est pas autorisée." }, "states": "États" }, @@ -172,7 +173,9 @@ "states": "États" }, "details": { - "scoreInfo": "Le score représente la moyenne de la confiance de classification pour toutes les détections de cet objet." + "scoreInfo": "Le score représente la moyenne de la confiance de classification pour toutes les détections de cet objet.", + "none": "Aucun", + "unknown": "Inconnu" }, "edit": { "title": "Modifier le modèle de classification", diff --git a/web/public/locales/fr/views/explore.json b/web/public/locales/fr/views/explore.json index 542999b3a6..652c8bf565 100644 --- a/web/public/locales/fr/views/explore.json +++ b/web/public/locales/fr/views/explore.json @@ -42,13 +42,15 @@ "regenerate": "Une nouvelle description a été demandée à {{provider}}. Selon la vitesse de votre fournisseur, la régénération de la nouvelle description peut prendre un certain temps.", "updatedSublabel": "Sous-étiquette mise à jour avec succès", "updatedLPR": "Plaque d'immatriculation mise à jour avec succès", - "audioTranscription": "Transcription audio demandée avec succès. Selon la vitesse de votre serveur Frigate, la transcription peut prendre un certain temps." + "audioTranscription": "Transcription audio demandée avec succès. Selon la vitesse de votre serveur Frigate, la transcription peut prendre un certain temps.", + "updatedAttributes": "Attributs mis à jour avec succès" }, "error": { "regenerate": "Échec de l'appel de {{provider}} pour une nouvelle description : {{errorMessage}}", "updatedSublabelFailed": "Échec de la mise à jour de la sous-étiquette : {{errorMessage}}", "updatedLPRFailed": "Échec de la mise à jour de la plaque d'immatriculation : {{errorMessage}}", - "audioTranscription": "Échec de la demande de transcription audio : {{errorMessage}}" + "audioTranscription": "Échec de la demande de transcription audio : {{errorMessage}}", + "updatedAttributesFailed": "Échec de la mise à jour des attributs  : {{errorMessage}}" } }, "tips": { @@ -103,7 +105,12 @@ }, "score": { "label": "Score" - } + }, + "editAttributes": { + "title": "Modifier les attributs", + "desc": "Sélectionnez les attributs de classification pour : {{label}}" + }, + "attributes": "Attributs de classification" }, "type": { "details": "détails", diff --git a/web/public/locales/fr/views/search.json b/web/public/locales/fr/views/search.json index 8b76ebe5d2..a9938bff7d 100644 --- a/web/public/locales/fr/views/search.json +++ b/web/public/locales/fr/views/search.json @@ -26,7 +26,8 @@ "recognized_license_plate": "Plaque d'immatriculation reconnue", "has_clip": "Avec une séquence vidéo", "has_snapshot": "Avec un instantané", - "max_score": "Score maximum" + "max_score": "Score maximum", + "attributes": "Attributs" }, "searchType": { "thumbnail": "Miniature", diff --git a/web/public/locales/fr/views/settings.json b/web/public/locales/fr/views/settings.json index cae223813a..d44b0117ac 100644 --- a/web/public/locales/fr/views/settings.json +++ b/web/public/locales/fr/views/settings.json @@ -598,7 +598,7 @@ "desc": "Gérez les comptes utilisateurs de cette instance Frigate." }, "addUser": "Ajouter un utilisateur", - "updatePassword": "Mettre à jour le mot de passe", + "updatePassword": "Réinitialiser le mot de passe", "toast": { "success": { "roleUpdated": "Rôle mis à jour pour {{user}}", @@ -618,7 +618,7 @@ "actions": "Actions", "noUsers": "Aucun utilisateur trouvé.", "changeRole": "Changer le rôle d'utilisateur", - "password": "Mot de passe", + "password": "Réinitialiser le mot de passe", "deleteUser": "Supprimer un utilisateur", "role": "Rôle" }, diff --git a/web/public/locales/fr/views/system.json b/web/public/locales/fr/views/system.json index 53f12b0030..f261fa9966 100644 --- a/web/public/locales/fr/views/system.json +++ b/web/public/locales/fr/views/system.json @@ -191,7 +191,10 @@ "review_description_events_per_second": "Description de l'activité", "object_description": "Description de l'objet", "object_description_speed": "Vitesse de la description d'objet", - "object_description_events_per_second": "Description de l'objet" + "object_description_events_per_second": "Description de l'objet", + "classification": "Classification {{name}}", + "classification_speed": "Vitesse de classification {{name}}", + "classification_events_per_second": "Événements de classification par seconde {{name}}" }, "averageInf": "Temps d'inférence moyen" } From f34e2200b58f317eeaa50f50aefbb21d97421b19 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:29 +0100 Subject: [PATCH 59/78] Translated using Weblate (Swedish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (131 of 131 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (122 of 122 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (135 of 135 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (49 of 49 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (74 of 74 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (25 of 25 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (121 of 121 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (53 of 53 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (654 of 654 strings) Co-authored-by: Felix Boström Co-authored-by: Hosted Weblate Co-authored-by: Kristian Johansson Co-authored-by: Samuel Åkesson Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sv/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sv/ Translation: Frigate NVR/components-filter Translation: Frigate NVR/components-player Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/sv/components/filter.json | 4 ++++ web/public/locales/sv/components/player.json | 2 +- .../locales/sv/views/classificationModel.json | 7 +++++-- web/public/locales/sv/views/explore.json | 13 ++++++++++--- web/public/locales/sv/views/faceLibrary.json | 2 +- web/public/locales/sv/views/search.json | 3 ++- web/public/locales/sv/views/settings.json | 4 ++-- web/public/locales/sv/views/system.json | 5 ++++- 8 files changed, 29 insertions(+), 11 deletions(-) diff --git a/web/public/locales/sv/components/filter.json b/web/public/locales/sv/components/filter.json index 90eda7c5a1..efb50d919c 100644 --- a/web/public/locales/sv/components/filter.json +++ b/web/public/locales/sv/components/filter.json @@ -133,5 +133,9 @@ }, "zoneMask": { "filterBy": "Filtrera på zonmaskering" + }, + "attributes": { + "label": "Klassificeringsattribut", + "all": "Alla attribut" } } diff --git a/web/public/locales/sv/components/player.json b/web/public/locales/sv/components/player.json index 24f1b1e962..7c6301ca11 100644 --- a/web/public/locales/sv/components/player.json +++ b/web/public/locales/sv/components/player.json @@ -1,5 +1,5 @@ { - "noPreviewFound": "Ingen Förhandsvisning Hittad", + "noPreviewFound": "Ingen förhandsvisning hittad", "noRecordingsFoundForThisTime": "Inga inspelningar hittade för denna tid", "noPreviewFoundFor": "Ingen förhandsvisning hittad för {{cameraName}}", "submitFrigatePlus": { diff --git a/web/public/locales/sv/views/classificationModel.json b/web/public/locales/sv/views/classificationModel.json index 2b6c61d0ff..5b5c5b77fc 100644 --- a/web/public/locales/sv/views/classificationModel.json +++ b/web/public/locales/sv/views/classificationModel.json @@ -114,7 +114,8 @@ "classesUnique": "Klassnamn måste vara unika", "stateRequiresTwoClasses": "Tillståndsmodeller kräver minst två klasser", "objectLabelRequired": "Välj en objektetikett", - "objectTypeRequired": "Vänligen välj en klassificeringstyp" + "objectTypeRequired": "Vänligen välj en klassificeringstyp", + "noneNotAllowed": "Klassen 'none' är inte tillåten" } }, "step2": { @@ -167,7 +168,9 @@ "states": "Tillstånd" }, "details": { - "scoreInfo": "Poängen representerar den genomsnittliga klassificeringssäkerheten för alla upptäckter av detta objekt." + "scoreInfo": "Poängen representerar den genomsnittliga klassificeringssäkerheten för alla upptäckter av detta objekt.", + "none": "Ingen", + "unknown": "Okänd" }, "edit": { "title": "Redigera klassificeringsmodell", diff --git a/web/public/locales/sv/views/explore.json b/web/public/locales/sv/views/explore.json index 62ea933384..95e7b5b054 100644 --- a/web/public/locales/sv/views/explore.json +++ b/web/public/locales/sv/views/explore.json @@ -48,13 +48,15 @@ "regenerate": "En ny beskrivning har begärts från {{provider}}. Beroende på din leverantörs hastighet kan det ta lite tid att generera den nya beskrivningen.", "updatedSublabel": "Underetiketten har uppdaterats.", "updatedLPR": "Nummerplåt har uppdaterats.", - "audioTranscription": "Ljudtranskription har begärts. Beroende på hastigheten på din Frigate-server kan transkriptionen ta lite tid att slutföra." + "audioTranscription": "Ljudtranskription har begärts. Beroende på hastigheten på din Frigate-server kan transkriptionen ta lite tid att slutföra.", + "updatedAttributes": "Attributen har uppdaterats." }, "error": { "regenerate": "Kunde inte ringa {{provider}} för en ny beskrivning: {{errorMessage}}", "updatedSublabelFailed": "Misslyckades med att uppdatera underetiketten: {{errorMessage}}", "audioTranscription": "Misslyckades med att begära ljudtranskription: {{errorMessage}}", - "updatedLPRFailed": "Misslyckades med att uppdatera nummerplåten: {{errorMessage}}" + "updatedLPRFailed": "Misslyckades med att uppdatera nummerplåten: {{errorMessage}}", + "updatedAttributesFailed": "Misslyckades med att uppdatera attribut: {{errorMessage}}" } } }, @@ -102,7 +104,12 @@ "tips": { "descriptionSaved": "Beskrivningen har sparats", "saveDescriptionFailed": "Misslyckades med att uppdatera beskrivningen: {{errorMessage}}" - } + }, + "editAttributes": { + "title": "Redigera attribut", + "desc": "Välj klassificeringsattribut för denna {{label}}" + }, + "attributes": "Klassificeringsattribut" }, "exploreMore": "Utforska fler {{label}} objekt", "type": { diff --git a/web/public/locales/sv/views/faceLibrary.json b/web/public/locales/sv/views/faceLibrary.json index 45a80bf500..485dfdd1f3 100644 --- a/web/public/locales/sv/views/faceLibrary.json +++ b/web/public/locales/sv/views/faceLibrary.json @@ -5,7 +5,7 @@ "face": "Ansiktsdetaljer", "timestamp": "tidsstämpel", "faceDesc": "Detaljer om det spårade objektet som genererade detta ansikte", - "unknown": "Okänt", + "unknown": "Okänd", "subLabelScore": "Underetikettpoäng", "scoreInfo": "Underetikettpoängen är den viktade poängen för alla igenkända ansiktskonfidenser, så detta kan skilja sig från poängen som visas på ögonblicksbilden." }, diff --git a/web/public/locales/sv/views/search.json b/web/public/locales/sv/views/search.json index 5fd24ab76d..2e9f4e0072 100644 --- a/web/public/locales/sv/views/search.json +++ b/web/public/locales/sv/views/search.json @@ -43,7 +43,8 @@ "has_clip": "Har klipp", "has_snapshot": "Har Ögonblicksbild", "labels": "Etiketter", - "max_score": "Högsta Poäng" + "max_score": "Högsta Poäng", + "attributes": "Attribut" }, "searchType": { "thumbnail": "Miniatyrbild", diff --git a/web/public/locales/sv/views/settings.json b/web/public/locales/sv/views/settings.json index 99f74a47dc..2c054001cb 100644 --- a/web/public/locales/sv/views/settings.json +++ b/web/public/locales/sv/views/settings.json @@ -489,7 +489,7 @@ "desc": "Hantera användarkonton för denna Frigate-instans." }, "addUser": "Lägg till användare", - "updatePassword": "Uppdatera lösenord", + "updatePassword": "Återställ lösenord", "toast": { "success": { "createUser": "Användaren {{user}} har skapats", @@ -510,7 +510,7 @@ "role": "Roll", "noUsers": "Inga användare hittades.", "changeRole": "Ändra användarroll", - "password": "Lösenord", + "password": "Återställ Lösenord", "deleteUser": "Ta bort användare" }, "dialog": { diff --git a/web/public/locales/sv/views/system.json b/web/public/locales/sv/views/system.json index 2056b38d95..55d757cc1a 100644 --- a/web/public/locales/sv/views/system.json +++ b/web/public/locales/sv/views/system.json @@ -191,7 +191,10 @@ "review_description_events_per_second": "Recensionsbeskrivning", "object_description": "Objekt beskrivning", "object_description_speed": "Objekt beskrivning hastighet", - "object_description_events_per_second": "Objekt beskrivning" + "object_description_events_per_second": "Objekt beskrivning", + "classification_events_per_second": "{{name}} Klassificering Händelser per sekund", + "classification": "{{name}} Klassificering", + "classification_speed": "{{name}} Klassificeringshastighet" }, "averageInf": "Genomsnittlig inferenstid" } From edeb47a08ea5a478d080831352e87648004dabe4 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:30 +0100 Subject: [PATCH 60/78] Translated using Weblate (Persian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 9.4% (5 of 53 strings) Translated using Weblate (Persian) Currently translated at 8.6% (4 of 46 strings) Translated using Weblate (Persian) Currently translated at 3.8% (5 of 131 strings) Translated using Weblate (Persian) Currently translated at 9.7% (4 of 41 strings) Translated using Weblate (Persian) Currently translated at 5.4% (4 of 74 strings) Translated using Weblate (Persian) Currently translated at 16.0% (4 of 25 strings) Translated using Weblate (Persian) Currently translated at 4.4% (6 of 135 strings) Translated using Weblate (Persian) Currently translated at 66.6% (4 of 6 strings) Translated using Weblate (Persian) Currently translated at 10.2% (5 of 49 strings) Translated using Weblate (Persian) Currently translated at 0.7% (5 of 654 strings) Translated using Weblate (Persian) Currently translated at 5.4% (5 of 92 strings) Translated using Weblate (Persian) Currently translated at 30.7% (4 of 13 strings) Translated using Weblate (Persian) Currently translated at 13.9% (17 of 122 strings) Translated using Weblate (Persian) Currently translated at 40.0% (4 of 10 strings) Translated using Weblate (Persian) Currently translated at 9.0% (5 of 55 strings) Translated using Weblate (Persian) Currently translated at 2.3% (5 of 214 strings) Translated using Weblate (Persian) Currently translated at 50.0% (5 of 10 strings) Translated using Weblate (Persian) Currently translated at 8.1% (4 of 49 strings) Translated using Weblate (Persian) Currently translated at 12.3% (15 of 121 strings) Translated using Weblate (Persian) Currently translated at 5.6% (3 of 53 strings) Translated using Weblate (Persian) Currently translated at 2.2% (3 of 135 strings) Translated using Weblate (Persian) Currently translated at 30.0% (3 of 10 strings) Translated using Weblate (Persian) Currently translated at 0.4% (3 of 654 strings) Translated using Weblate (Persian) Currently translated at 4.0% (3 of 74 strings) Translated using Weblate (Persian) Currently translated at 1.8% (4 of 214 strings) Co-authored-by: Hosted Weblate Co-authored-by: حمید ملک محمدی Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fa/ Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fa/ Translation: Frigate NVR/common Translation: Frigate NVR/components-auth Translation: Frigate NVR/components-camera Translation: Frigate NVR/components-dialog Translation: Frigate NVR/components-filter Translation: Frigate NVR/components-player Translation: Frigate NVR/views-classificationmodel Translation: Frigate NVR/views-configeditor Translation: Frigate NVR/views-events Translation: Frigate NVR/views-explore Translation: Frigate NVR/views-exports Translation: Frigate NVR/views-facelibrary Translation: Frigate NVR/views-live Translation: Frigate NVR/views-recording Translation: Frigate NVR/views-search Translation: Frigate NVR/views-settings Translation: Frigate NVR/views-system --- web/public/locales/fa/common.json | 4 +++- web/public/locales/fa/components/auth.json | 3 ++- web/public/locales/fa/components/camera.json | 5 ++++- web/public/locales/fa/components/dialog.json | 4 +++- web/public/locales/fa/components/filter.json | 6 +++++- web/public/locales/fa/components/player.json | 5 ++++- web/public/locales/fa/views/classificationModel.json | 9 +++++++++ web/public/locales/fa/views/configEditor.json | 5 ++++- web/public/locales/fa/views/events.json | 3 ++- web/public/locales/fa/views/explore.json | 12 +++++++++++- web/public/locales/fa/views/exports.json | 3 ++- web/public/locales/fa/views/faceLibrary.json | 9 +++++++-- web/public/locales/fa/views/live.json | 9 ++++++++- web/public/locales/fa/views/recording.json | 3 ++- web/public/locales/fa/views/search.json | 6 +++++- web/public/locales/fa/views/settings.json | 5 ++++- web/public/locales/fa/views/system.json | 6 +++++- 17 files changed, 80 insertions(+), 17 deletions(-) diff --git a/web/public/locales/fa/common.json b/web/public/locales/fa/common.json index e3b44ba550..2bb9555cbf 100644 --- a/web/public/locales/fa/common.json +++ b/web/public/locales/fa/common.json @@ -2,6 +2,8 @@ "time": { "untilForTime": "تا {{time}}", "untilForRestart": "تا زمانی که فریگیت دوباره شروع به کار کند.", - "untilRestart": "تا زمان ری‌استارت" + "untilRestart": "تا زمان ری‌استارت", + "ago": "{{timeAgo}} قبل", + "justNow": "هم اکنون" } } diff --git a/web/public/locales/fa/components/auth.json b/web/public/locales/fa/components/auth.json index b74e4b03f6..6b87e72575 100644 --- a/web/public/locales/fa/components/auth.json +++ b/web/public/locales/fa/components/auth.json @@ -2,6 +2,7 @@ "form": { "user": "نام کاربری", "password": "رمز عبور", - "login": "ورود" + "login": "ورود", + "firstTimeLogin": "اولین باز است وارد می شود؟ اطلاعات هویتی در ثبت رخداد های فریگیت چاپ خواهد شد." } } diff --git a/web/public/locales/fa/components/camera.json b/web/public/locales/fa/components/camera.json index b5567927ba..ee4d1d1720 100644 --- a/web/public/locales/fa/components/camera.json +++ b/web/public/locales/fa/components/camera.json @@ -2,6 +2,9 @@ "group": { "label": "گروه‌های دوربین", "add": "افزودن گروه دوربین", - "edit": "ویرایش گروه دوربین" + "edit": "ویرایش گروه دوربین", + "delete": { + "label": "حذف گروه دوربین ها" + } } } diff --git a/web/public/locales/fa/components/dialog.json b/web/public/locales/fa/components/dialog.json index 05f66ce60d..121a732242 100644 --- a/web/public/locales/fa/components/dialog.json +++ b/web/public/locales/fa/components/dialog.json @@ -3,7 +3,9 @@ "title": "آیا از ری‌استارت فریگیت اطمینان دارید؟", "button": "ری‌استارت", "restarting": { - "title": "فریگیت در حال ری‌استارت شدن" + "title": "فریگیت در حال ری‌استارت شدن", + "content": "صفحه تا {{countdown}} ثانیه دیگر مجددا بارگزاری خواهد شد.", + "button": "بارگزاری مجدد هم اکنون اجرا شود" } } } diff --git a/web/public/locales/fa/components/filter.json b/web/public/locales/fa/components/filter.json index 97410c81e6..ad7827e253 100644 --- a/web/public/locales/fa/components/filter.json +++ b/web/public/locales/fa/components/filter.json @@ -1,6 +1,10 @@ { "filter": "فیلتر", "classes": { - "label": "کلاس‌ها" + "label": "کلاس‌ها", + "all": { + "title": "تمامی کلاس ها" + }, + "count_one": "{{count}} کلاس" } } diff --git a/web/public/locales/fa/components/player.json b/web/public/locales/fa/components/player.json index dae6c8b373..d82c22d9bc 100644 --- a/web/public/locales/fa/components/player.json +++ b/web/public/locales/fa/components/player.json @@ -1,5 +1,8 @@ { "noRecordingsFoundForThisTime": "ویدیویی برای این زمان وجود ندارد", "noPreviewFound": "پیش‌نمایش پیدا نشد", - "noPreviewFoundFor": "هیچ پیش‌نمایشی برای {{cameraName}} پیدا نشد" + "noPreviewFoundFor": "هیچ پیش‌نمایشی برای {{cameraName}} پیدا نشد", + "submitFrigatePlus": { + "title": "این فریم به فریگیت+ ارسال شود؟" + } } diff --git a/web/public/locales/fa/views/classificationModel.json b/web/public/locales/fa/views/classificationModel.json index a4bdd37e4d..7369674e5a 100644 --- a/web/public/locales/fa/views/classificationModel.json +++ b/web/public/locales/fa/views/classificationModel.json @@ -18,5 +18,14 @@ "deleteImageFailed": "حذف نشد:{{پیغام خطا}}", "deleteCategoryFailed": "کلاس حذف نشد:{{پیغام خطا}}" } + }, + "documentTitle": "دسته بندی مدل ها - فریگیت", + "description": { + "invalidName": "نام نامعتبر، نام ها فقط می توانند شامل حروف، اعداد، فاصله، آپستروف، زیرخط و خط فاصله باشند." + }, + "details": { + "none": "هیچکدام", + "scoreInfo": "امتیاز، نشان دهنده میانگین دقت در تشخیص و دسته بندی این شیء در بین تمام تشخیص‌هاست.", + "unknown": "ناشناخته" } } diff --git a/web/public/locales/fa/views/configEditor.json b/web/public/locales/fa/views/configEditor.json index 0a57836db0..0c97e3f624 100644 --- a/web/public/locales/fa/views/configEditor.json +++ b/web/public/locales/fa/views/configEditor.json @@ -1,4 +1,7 @@ { "documentTitle": "ویرایشگر کانفیگ - فریگیت", - "configEditor": "ویرایشگر کانفیگ" + "configEditor": "ویرایشگر کانفیگ", + "safeConfigEditor": "ویرایشگر تنظیمات (حالت امن)", + "safeModeDescription": "فریگیت به دلیل خطا در صحت سنجی پیکربندی، در حالت امن می باشد.", + "copyConfig": "کپی پیکربندی" } diff --git a/web/public/locales/fa/views/events.json b/web/public/locales/fa/views/events.json index 4fc9338d64..a566947124 100644 --- a/web/public/locales/fa/views/events.json +++ b/web/public/locales/fa/views/events.json @@ -2,6 +2,7 @@ "alerts": "هشدار‌ها", "detections": "تشخیص‌ها", "motion": { - "label": "حرکت" + "label": "حرکت", + "only": "فقط حرکتی" } } diff --git a/web/public/locales/fa/views/explore.json b/web/public/locales/fa/views/explore.json index 8cbff25823..7ee77df16e 100644 --- a/web/public/locales/fa/views/explore.json +++ b/web/public/locales/fa/views/explore.json @@ -1,4 +1,14 @@ { "generativeAI": "هوش مصنوعی تولید کننده", - "documentTitle": "کاوش کردن - فرایگیت" + "documentTitle": "کاوش کردن - فرایگیت", + "exploreMore": "نمایش اشیا {{label}} بیشتر", + "details": { + "timestamp": "زمان دقیق" + }, + "exploreIsUnavailable": { + "title": "نمایش کلی موجود نمی باشد", + "embeddingsReindexing": { + "startingUp": "درحال شروع…" + } + } } diff --git a/web/public/locales/fa/views/exports.json b/web/public/locales/fa/views/exports.json index a025b07523..30532619c8 100644 --- a/web/public/locales/fa/views/exports.json +++ b/web/public/locales/fa/views/exports.json @@ -1,5 +1,6 @@ { "search": "جستجو", "documentTitle": "گرفتن خروجی - فریگیت", - "noExports": "هیچ خروجی یافت نشد" + "noExports": "هیچ خروجی یافت نشد", + "deleteExport": "حذف خروجی" } diff --git a/web/public/locales/fa/views/faceLibrary.json b/web/public/locales/fa/views/faceLibrary.json index d14ad4fdc4..fa2595f8e4 100644 --- a/web/public/locales/fa/views/faceLibrary.json +++ b/web/public/locales/fa/views/faceLibrary.json @@ -1,6 +1,11 @@ { "description": { - "addFace": "مراحل اضافه کردن یک مجموعه جدید به کتابخانه چهره را دنبال کنید.", - "placeholder": "نامی برای این مجموعه وارد کنید" + "addFace": "با بارگزاری اولین عکستان، یک مجموعه جدید به کتابخانه چهره اضافه کنید.", + "placeholder": "نامی برای این مجموعه وارد کنید", + "invalidName": "نام نامعتبر، نام ها فقط می توانند شامل حروف، اعداد، فاصله، آپستروف، زیرخط و خط فاصله باشند." + }, + "details": { + "timestamp": "زمان دقیق", + "unknown": "ناشناخته" } } diff --git a/web/public/locales/fa/views/live.json b/web/public/locales/fa/views/live.json index b459a7cff1..97faeda8bd 100644 --- a/web/public/locales/fa/views/live.json +++ b/web/public/locales/fa/views/live.json @@ -1,4 +1,11 @@ { "documentTitle": "زنده - فریگیت", - "documentTitle.withCamera": "{{camera}} - زنده - فریگیت" + "documentTitle.withCamera": "{{camera}} - زنده - فریگیت", + "lowBandwidthMode": "حالت کاهش مصرف پهنای باند", + "twoWayTalk": { + "enable": "فعال سازی مکالمه دوطرفه" + }, + "cameraAudio": { + "enable": "فعالسازی صدای دوربین" + } } diff --git a/web/public/locales/fa/views/recording.json b/web/public/locales/fa/views/recording.json index 5dbfe95bef..664f93ef69 100644 --- a/web/public/locales/fa/views/recording.json +++ b/web/public/locales/fa/views/recording.json @@ -1,5 +1,6 @@ { "filter": "فیلتر", "export": "گرفتن خروجی", - "calendar": "تفویم" + "calendar": "تفویم", + "filters": "فیلترها" } diff --git a/web/public/locales/fa/views/search.json b/web/public/locales/fa/views/search.json index b4931ae700..092c45f82c 100644 --- a/web/public/locales/fa/views/search.json +++ b/web/public/locales/fa/views/search.json @@ -1,5 +1,9 @@ { "search": "جستجو", "savedSearches": "جستجوهای ذخیره شده", - "searchFor": "جستجو برای {{inputValue}}" + "searchFor": "جستجو برای {{inputValue}}", + "button": { + "clear": "پاک کردن جستجو", + "save": "ذخیره جستجو" + } } diff --git a/web/public/locales/fa/views/settings.json b/web/public/locales/fa/views/settings.json index ea7753a5be..4861489d3e 100644 --- a/web/public/locales/fa/views/settings.json +++ b/web/public/locales/fa/views/settings.json @@ -2,6 +2,9 @@ "documentTitle": { "default": "تنظیمات - فریگیت", "authentication": "تنظیمات احراز هویت - فریگیت", - "camera": "تنظیمات دوربین - فریگیت" + "camera": "تنظیمات دوربین - فریگیت", + "cameraManagement": "مدیریت دوربین ها - فریگیت", + "cameraReview": "بازبینی تنظیمات دوربین - فریگیت", + "masksAndZones": "ویرایشگر ماسک و منطقه - فریگیت" } } diff --git a/web/public/locales/fa/views/system.json b/web/public/locales/fa/views/system.json index 4cc9550d7e..b33d6e5053 100644 --- a/web/public/locales/fa/views/system.json +++ b/web/public/locales/fa/views/system.json @@ -2,6 +2,10 @@ "documentTitle": { "cameras": "آمار دوربین‌ها - فریگیت", "storage": "آمار حافظه - فریگیت", - "general": "آمار عمومی - فریگیت" + "general": "آمار عمومی - فریگیت", + "enrichments": "آمار بهینه سازی - فریگیت", + "logs": { + "frigate": "ثبت رخدادهای فریگیت - فریگیت" + } } } From b54cb219f8622e7a0069b0f64866fa038b9ce076 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:32 +0100 Subject: [PATCH 61/78] Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/sl/views/classificationModel.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/public/locales/sl/views/classificationModel.json b/web/public/locales/sl/views/classificationModel.json index aceedb094b..3ba3e676da 100644 --- a/web/public/locales/sl/views/classificationModel.json +++ b/web/public/locales/sl/views/classificationModel.json @@ -10,7 +10,8 @@ "renameCategory": "Preimenuj razred", "deleteCategory": "Zbriši razred", "deleteImages": "Zbriši slike", - "trainModel": "Treniraj model" + "trainModel": "Treniraj model", + "deleteClassificationAttempts": "Izbriši klasifikacijske slike" }, "toast": { "success": { @@ -46,5 +47,9 @@ "object": { "title": "Ni modelov za razvrščanje objektov" } + }, + "documentTitle": "Klasifikacijski modeli - fregate", + "details": { + "scoreInfo": "Razultat predstavlja povprečno stopnjo sigurnosti čez vsa zaznavynja objekta." } } From a2e98dc89b6524c90a0c1117c0e9a9648f50c7d9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:33 +0100 Subject: [PATCH 62/78] Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/zh-Hant/common.json | 14 +++++------ .../zh-Hant/views/classificationModel.json | 25 ++++++++++++++++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/web/public/locales/zh-Hant/common.json b/web/public/locales/zh-Hant/common.json index b785c8b52b..f512332f05 100644 --- a/web/public/locales/zh-Hant/common.json +++ b/web/public/locales/zh-Hant/common.json @@ -64,7 +64,7 @@ }, "formattedTimestampMonthDay": "M 月 d 日", "formattedTimestampFilename": { - "12hour": "MM-dd-yy-h-mm-ss-a", + "12hour": "yy年MM月dd日 h時mm分ss秒", "24hour": "yy年MM月dd日 HH時mm分ss秒" }, "inProgress": "處理中", @@ -81,9 +81,9 @@ "meters": "公尺" }, "data": { - "kbps": "kB/秒", - "mbps": "MB/秒", - "gbps": "GB/秒", + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", "kbph": "kB/小時", "mbph": "MB/小時", "gbph": "GB/小時" @@ -276,13 +276,13 @@ "selectItem": "選擇 {{item}}", "readTheDocumentation": "閱讀文件", "list": { - "two": "{{0}} 和 {{1}}", - "many": "{{items}}, 及 {{last}}", + "two": "{{0}}和{{1}}", + "many": "{{items}}和{{last}}", "separatorWithSpace": ", " }, "field": { "optional": "可選的", - "internalID": "在Frigate 設定檔與資料庫使用的內部ID" + "internalID": "在Frigate 設定檔和資料庫使用的內部ID" }, "information": { "pixels": "{{area}}px" diff --git a/web/public/locales/zh-Hant/views/classificationModel.json b/web/public/locales/zh-Hant/views/classificationModel.json index f0b1d0cc91..32fce24236 100644 --- a/web/public/locales/zh-Hant/views/classificationModel.json +++ b/web/public/locales/zh-Hant/views/classificationModel.json @@ -2,8 +2,8 @@ "toast": { "success": { "deletedImage": "已刪除的圖片", - "deletedModel_other": "成功刪除 {{count}} 個模型", - "deletedCategory": "刪除分類", + "deletedModel_other": "已成功刪除 {{count}} 個模型", + "deletedCategory": "已刪除分類", "categorizedImage": "成功分類圖片", "trainedModel": "訓練模型成功。", "trainingModel": "已開始訓練模型。", @@ -23,7 +23,9 @@ }, "documentTitle": "分類模型", "details": { - "scoreInfo": "分數表示該目標所有偵測結果的平均分類置信度。" + "scoreInfo": "分數表示該目標所有偵測結果的平均分類置信度。", + "none": "沒有", + "unknown": "未知" }, "button": { "deleteClassificationAttempts": "刪除分類圖片", @@ -81,5 +83,22 @@ "categories": "類別", "createCategory": { "new": "建立新的類別" + }, + "wizard": { + "step1": { + "objectLabel": "物件標籤", + "objectLabelPlaceholder": "請選擇物件類型...", + "classificationType": "分類類型", + "classificationTypeTip": "學習更多有關分類類型", + "description": "狀態模型監視固定攝像頭區域的變化(例如:開關門)。物件模型為檢測到的物件(例如:已知動物、送貨員等等)添加分類。", + "name": "名稱", + "namePlaceholder": "請輸入模型名稱...", + "type": "類別", + "typeState": "狀態", + "typeObject": "物件" + }, + "steps": { + "chooseExamples": "選擇範本" + } } } From ca0e53f671dcce6b144fcb0d3ef21fa2f6328282 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:34 +0100 Subject: [PATCH 63/78] Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/zh-CN/audio.json | 2 +- .../locales/zh-CN/components/camera.json | 2 +- .../locales/zh-CN/components/dialog.json | 2 +- .../locales/zh-CN/components/filter.json | 4 ++ web/public/locales/zh-CN/objects.json | 2 +- .../zh-CN/views/classificationModel.json | 7 ++- web/public/locales/zh-CN/views/explore.json | 17 +++++-- .../locales/zh-CN/views/faceLibrary.json | 12 ++--- web/public/locales/zh-CN/views/live.json | 4 +- web/public/locales/zh-CN/views/search.json | 3 +- web/public/locales/zh-CN/views/settings.json | 50 +++++++++---------- web/public/locales/zh-CN/views/system.json | 13 +++-- 12 files changed, 68 insertions(+), 50 deletions(-) diff --git a/web/public/locales/zh-CN/audio.json b/web/public/locales/zh-CN/audio.json index 848418f847..4c321c2e99 100644 --- a/web/public/locales/zh-CN/audio.json +++ b/web/public/locales/zh-CN/audio.json @@ -60,7 +60,7 @@ "bow_wow": "汪汪", "growling": "咆哮", "whimper_dog": "狗呜咽", - "cat": "猫", + "cat": "猫叫", "purr": "咕噜", "meow": "喵喵", "hiss": "嘶嘶声", diff --git a/web/public/locales/zh-CN/components/camera.json b/web/public/locales/zh-CN/components/camera.json index 1ac6a63d53..e01d5e9aa5 100644 --- a/web/public/locales/zh-CN/components/camera.json +++ b/web/public/locales/zh-CN/components/camera.json @@ -32,7 +32,7 @@ "title": "{{cameraName}} 视频流设置", "desc": "更改此摄像头组仪表板的实时视频流选项。这些设置特定于设备/浏览器。", "audioIsAvailable": "此视频流支持音频", - "audioIsUnavailable": "此视频流不支持音频", + "audioIsUnavailable": "此视频流不支持音频传输", "audio": { "tips": { "title": "音频必须从您的摄像头输出并在 go2rtc 中配置此流。", diff --git a/web/public/locales/zh-CN/components/dialog.json b/web/public/locales/zh-CN/components/dialog.json index 3d9da612a6..d84e125cf0 100644 --- a/web/public/locales/zh-CN/components/dialog.json +++ b/web/public/locales/zh-CN/components/dialog.json @@ -12,7 +12,7 @@ "plus": { "submitToPlus": { "label": "提交至 Frigate+", - "desc": "您希望避开的地点中的物体不应被视为误报。若将其作为误报提交,可能会导致AI模型容易混淆相关物体的识别。" + "desc": "你不希望检测指定地点中的目标或物体不应被视为误报。若将其作为误报提交,可能会导致 AI 模型容易混淆相关目标或物体的识别。" }, "review": { "true": { diff --git a/web/public/locales/zh-CN/components/filter.json b/web/public/locales/zh-CN/components/filter.json index 8911c6acb1..9bf90d2918 100644 --- a/web/public/locales/zh-CN/components/filter.json +++ b/web/public/locales/zh-CN/components/filter.json @@ -133,5 +133,9 @@ }, "count_one": "{{count}} 个分类", "count_other": "{{count}} 个分类" + }, + "attributes": { + "label": "分类属性", + "all": "所有属性" } } diff --git a/web/public/locales/zh-CN/objects.json b/web/public/locales/zh-CN/objects.json index 193f871790..6c155b8fb7 100644 --- a/web/public/locales/zh-CN/objects.json +++ b/web/public/locales/zh-CN/objects.json @@ -14,7 +14,7 @@ "parking_meter": "停车计时器", "bench": "长椅", "bird": "鸟", - "cat": "猫", + "cat": "猫叫", "dog": "狗", "horse": "马", "sheep": "绵羊", diff --git a/web/public/locales/zh-CN/views/classificationModel.json b/web/public/locales/zh-CN/views/classificationModel.json index a3fa01a609..3e9cf67fe6 100644 --- a/web/public/locales/zh-CN/views/classificationModel.json +++ b/web/public/locales/zh-CN/views/classificationModel.json @@ -110,7 +110,8 @@ "classesUnique": "类别名称必须唯一", "stateRequiresTwoClasses": "状态模型至少需要两个类别", "objectLabelRequired": "请选择一个目标标签", - "objectTypeRequired": "请选择一个目标标签" + "objectTypeRequired": "请选择一个目标标签", + "noneNotAllowed": "不能创建“none”(无标签)类别" }, "states": "状态" }, @@ -162,7 +163,9 @@ "states": "状态" }, "details": { - "scoreInfo": "得分表示该目标所有检测结果的平均分类置信度。" + "scoreInfo": "得分表示该目标所有检测结果的平均分类置信度。", + "none": "无分类", + "unknown": "未知" }, "edit": { "title": "编辑分类模型", diff --git a/web/public/locales/zh-CN/views/explore.json b/web/public/locales/zh-CN/views/explore.json index e94442eafd..45b89169a4 100644 --- a/web/public/locales/zh-CN/views/explore.json +++ b/web/public/locales/zh-CN/views/explore.json @@ -104,13 +104,15 @@ "regenerate": "已向 {{provider}} 请求新的描述。根据提供商的速度,生成新描述可能需要一些时间。", "updatedSublabel": "成功更新子标签。", "updatedLPR": "成功更新车牌。", - "audioTranscription": "成功请求音频转录。根据你运行 Frigate 的服务器速度,转录可能需要一些时间才能完成。" + "audioTranscription": "成功请求音频转录。根据你运行 Frigate 的服务器速度,转录可能需要一些时间才能完成。", + "updatedAttributes": "更新属性成功。" }, "error": { "regenerate": "调用 {{provider}} 生成新描述失败:{{errorMessage}}", "updatedSublabelFailed": "更新子标签失败:{{errorMessage}}", "updatedLPRFailed": "更新车牌失败:{{errorMessage}}", - "audioTranscription": "请求音频转录失败:{{errorMessage}}" + "audioTranscription": "请求音频转录失败:{{errorMessage}}", + "updatedAttributesFailed": "更新属性失败:{{errorMessage}}" } } }, @@ -159,7 +161,12 @@ }, "score": { "label": "分值" - } + }, + "editAttributes": { + "title": "编辑属性", + "desc": "为 {{label}} 选择分类属性" + }, + "attributes": "分类属性" }, "itemMenu": { "downloadVideo": { @@ -262,8 +269,8 @@ "external": "已检测到 {{label}}", "header": { "zones": "区", - "ratio": "占比", - "area": "坐标区域", + "ratio": "比例", + "area": "大小", "score": "分数" } }, diff --git a/web/public/locales/zh-CN/views/faceLibrary.json b/web/public/locales/zh-CN/views/faceLibrary.json index b4bde067b7..0e05f0df79 100644 --- a/web/public/locales/zh-CN/views/faceLibrary.json +++ b/web/public/locales/zh-CN/views/faceLibrary.json @@ -1,7 +1,7 @@ { "description": { - "addFace": "我们将引导你如何向人脸库中添加新的特征库。", - "placeholder": "请输入此特征库的名称", + "addFace": "我们将引导你如何向人脸库中添加新的合集。", + "placeholder": "请输入此合集的名称", "invalidName": "名称无效。名称只能包含字母、数字、空格、撇号、下划线和连字符。" }, "details": { @@ -23,7 +23,7 @@ "title": "创建特征库", "desc": "创建一个新的特征库", "new": "新建人脸", - "nextSteps": "建议按以下步骤建立可靠的特征库:
  • 使用近期识别记录选项卡为每个检测到的人员选择并训练图像
  • 优先使用正脸图像以获得最佳效果,尽可能避免使用侧脸图像进行训练
  • " + "nextSteps": "建议按以下步骤建立可靠的数据集:
  • 使用近期识别记录选项卡为每个检测到的人员选择并训练图像
  • 优先使用正脸图像以获得最佳效果,尽可能避免使用侧脸图像进行训练
  • " }, "train": { "title": "近期识别记录", @@ -35,7 +35,7 @@ "selectFace": "选择人脸", "deleteFaceLibrary": { "title": "删除名称", - "desc": "确定要删除特征库 {{name}} 吗?此操作将永久删除所有关联的人脸特征数据。" + "desc": "确定要删除数据集 {{name}} 吗?此操作将永久删除所有关联的人脸特征数据。" }, "button": { "deleteFaceAttempts": "删除人脸", @@ -70,7 +70,7 @@ "uploadingImageFailed": "图片上传失败:{{errorMessage}}", "addFaceLibraryFailed": "人脸命名失败:{{errorMessage}}", "deleteFaceFailed": "删除失败:{{errorMessage}}", - "deleteNameFailed": "特征集删除失败:{{errorMessage}}", + "deleteNameFailed": "数据集删除失败:{{errorMessage}}", "trainFailed": "训练失败:{{errorMessage}}", "updateFaceScoreFailed": "更新人脸评分失败:{{errorMessage}}", "renameFaceFailed": "重命名人脸失败:{{errorMessage}}" @@ -88,7 +88,7 @@ "desc": "为 {{name}} 输入新的名称", "title": "重命名人脸" }, - "collections": "特征库", + "collections": "合集", "deleteFaceAttempts": { "desc_other": "你确定要删除 {{count}} 张人脸数据吗?此操作不可撤销。", "title": "删除人脸" diff --git a/web/public/locales/zh-CN/views/live.json b/web/public/locales/zh-CN/views/live.json index aeefa18261..021bafc66a 100644 --- a/web/public/locales/zh-CN/views/live.json +++ b/web/public/locales/zh-CN/views/live.json @@ -115,11 +115,11 @@ "title": "视频流", "audio": { "tips": { - "title": "音频必须从摄像头输出并在 go2rtc 中配置为此视频流使用。", + "title": "必须要摄像头能够播放音频,以及需要 go2rtc 支持并配置。", "documentation": "阅读文档 " }, "available": "此视频流支持音频", - "unavailable": "此视频流不支持音频" + "unavailable": "此视频流不支持音频传输" }, "twoWayTalk": { "tips": "您的设备必须支持此功能,并且必须配置 WebRTC 以支持双向对讲。", diff --git a/web/public/locales/zh-CN/views/search.json b/web/public/locales/zh-CN/views/search.json index 8a25c11f52..51fe47c8ef 100644 --- a/web/public/locales/zh-CN/views/search.json +++ b/web/public/locales/zh-CN/views/search.json @@ -26,7 +26,8 @@ "max_speed": "最高速度", "recognized_license_plate": "识别的车牌", "has_clip": "包含片段", - "has_snapshot": "包含快照" + "has_snapshot": "包含快照", + "attributes": "属性" }, "searchType": { "thumbnail": "缩略图", diff --git a/web/public/locales/zh-CN/views/settings.json b/web/public/locales/zh-CN/views/settings.json index 5a5130fa0b..700b22377a 100644 --- a/web/public/locales/zh-CN/views/settings.json +++ b/web/public/locales/zh-CN/views/settings.json @@ -5,7 +5,7 @@ "camera": "摄像头设置 - Frigate", "classification": "分类设置 - Frigate", "masksAndZones": "遮罩和区域编辑器 - Frigate", - "motionTuner": "画面变动调整器 - Frigate", + "motionTuner": "画面变动调整 - Frigate", "object": "调试 - Frigate", "general": "页面设置 - Frigate", "frigatePlus": "Frigate+ 设置 - Frigate", @@ -19,7 +19,7 @@ "classification": "分类设置", "cameras": "摄像头设置", "masksAndZones": "遮罩/ 区域", - "motionTuner": "画面变动调整器", + "motionTuner": "画面变动调整", "debug": "调试", "users": "用户", "notifications": "通知", @@ -403,7 +403,7 @@ "objectMaskLabel": "目标/物体遮罩 {{number}}({{label}})" }, "motionDetectionTuner": { - "title": "画面变动检测调整器", + "title": "画面变动检测调整", "desc": { "title": "Frigate 将使用画面变化检测作为首个步骤,以确认一帧画面中是否有目标或物体需要使用目标检测。", "documentation": "阅读有关画面变动检测的文档" @@ -414,7 +414,7 @@ }, "contourArea": { "title": "轮廓面积", - "desc": "轮廓面积值用于判断哪些相连的像素变化区域可被认定为画面变动。默认值:10" + "desc": "轮廓面积值用于判断产生了多大的变化区域可被认定为画面变动。默认值:10" }, "improveContrast": { "title": "提高对比度", @@ -423,7 +423,7 @@ "toast": { "success": "画面变动设置已保存。" }, - "unsavedChanges": "{{camera}} 的画面变动调整器设置未保存" + "unsavedChanges": "{{camera}} 的画面变动调整设置未保存" }, "debug": { "title": "调试", @@ -513,7 +513,7 @@ "role": "权限组", "noUsers": "未找到用户。", "changeRole": "更改用户角色", - "password": "密码", + "password": "修改密码", "deleteUser": "删除用户" }, "dialog": { @@ -704,12 +704,12 @@ "enrichments": { "title": "增强功能设置", "birdClassification": { - "desc": "鸟类分类通过量化的TensorFlow模型识别已知鸟类。当识别到已知鸟类时,其通用名称将作为子标签(sub_label)添加。此信息包含在用户界面、筛选器以及通知中。", + "desc": "鸟类分类通过量化的 TensorFlow 模型识别已知鸟类。当识别到已知鸟类时,其通用名称将作为子标签(sub_label)添加。此信息包含在用户界面、筛选器以及通知中。", "title": "鸟类分类" }, "semanticSearch": { "reindexNow": { - "desc": "重建索引将为所有追踪目标重新生成特征向量信息。该过程将在后台进行,可能会使CPU满载,所需时间取决于追踪目标的数量。", + "desc": "重建索引将为所有追踪的目标重新生成特征向量信息。该过程将在后台进行,期间可能会使 CPU 满载,所需时间取决于追踪目标的数量。", "label": "立即重建索引", "confirmTitle": "确认重建索引", "confirmDesc": "确定要为所有追踪目标重建特征向量索引信息吗?此过程将在后台进行,但可能会导致CPU满载并耗费较长时间。您可以在 浏览 页面查看进度。", @@ -723,15 +723,15 @@ "desc": "用于语义搜索的语言模型大小。", "small": { "title": "小", - "desc": "将使用 模型。该模型将使用少量的内存,在CPU上也能较快的运行,质量较好。" + "desc": "将使用 模型。该模型使用的内存较少,在 CPU 上也能较快的运行,质量较好。" }, "large": { "title": "大", - "desc": "将使用 模型。该选项使用了完整的Jina模型,在合适的时候将自动使用GPU。" + "desc": "将使用 模型。该选项使用了完整的 Jina 模型,条件允许的情况下将自动使用 GPU 运行。" } }, "title": "分类搜索", - "desc": "Frigate中的语义搜索功能允许您通过图片、用户自定义的文本描述,或自动生成的文本描述等方式在核查项目中查找目标/物体。", + "desc": "Frigate 中的语义搜索功能将能够让你通过图片、用户自定义的文本描述,或自动生成的文本描述等方式在核查项目中查找目标/物体。", "readTheDocumentation": "阅读文档" }, "licensePlateRecognition": { @@ -748,11 +748,11 @@ "desc": "用于人脸识别的模型大小。", "small": { "title": "小", - "desc": "将使用模型。该选项采用FaceNet人脸特征提取模型,可在大多数CPU上高效运行。" + "desc": "将使用模型。该选项采用 FaceNet 人脸特征提取模型,可在大多数 CPU 上高效运行。" }, "large": { "title": "大", - "desc": "将使用模型。该选项使用ArcFace人脸特征提取模型,在需要的时候自动使用GPU运行。" + "desc": "将使用模型。该选项使用 ArcFace 人脸特征提取模型,条件允许的情况下将自动使用 GPU 运行。" } } }, @@ -767,7 +767,7 @@ "documentTitle": "触发器", "management": { "title": "触发器", - "desc": "管理 {{camera}} 的触发器。你可以使用“缩略图”类型,将通过与追踪目标相似的缩略图来触发;也可以使用“描述”类型,基于与你指定的文本相似的描述来触发(中文描述需要使用jina v2模型,对配置要求更高)。" + "desc": "管理 {{camera}} 的触发器。你可以选择“缩略图”类型,将通过与追踪目标相似的缩略图来触发;也可以通过“描述”类型,与你描述的文本相似来触发(中文描述需要使用 jina v2模型,对配置要求更高)。" }, "addTrigger": "添加触发器", "table": { @@ -952,7 +952,7 @@ }, "cameraWizard": { "title": "添加摄像头", - "description": "请按照以下步骤添加摄像头至Frigate中。", + "description": "请按照以下步骤添加摄像头至 Frigate 中。", "steps": { "nameAndConnection": "名称与连接", "streamConfiguration": "视频流配置", @@ -1020,7 +1020,7 @@ "onvifPort": "ONVIF 端口", "probeMode": "探测摄像头", "manualMode": "手动选择", - "detectionMethodDescription": "如果支持 ONVIF 协议,将使用该协议探测摄像头,以自动获取摄像头视频流地址;若不支持,也可手动选择摄像头品牌来使用预设地址。如需输入自定义RTSP地址,请选择手动模式并选取\"其他\"选项。", + "detectionMethodDescription": "如果摄像头支持 ONVIF 协议,将使用该协议探测摄像头,以自动获取摄像头视频流地址;若不支持,也可手动选择摄像头品牌来使用预设地址。如需输入自定义RTSP地址,请选择“手动选择”并选择“其他”选项。", "onvifPortDescription": "对于支持ONVIF协议的摄像头,该端口通常为80或8080。", "useDigestAuth": "使用摘要认证", "useDigestAuthDescription": "为ONVIF协议启用HTTP摘要认证。部分摄像头可能需要专用的 ONVIF 用户名/密码,而非默认的admin账户。" @@ -1234,7 +1234,7 @@ "backToSettings": "返回摄像头设置", "streams": { "title": "开启或关闭摄像头", - "desc": "将临时禁用摄像头直至Frigate重启。禁用摄像头将完全停止Frigate对该摄像头视频流的处理,届时检测、录制及调试功能均不可用。
    注意:此操作不会影响go2rtc的转流服务。" + "desc": "将临时禁用摄像头,直至 Frigate 重启。禁用摄像头将完全停止 Frigate 对该摄像头视频流的处理,届时检测、录制及调试功能均不可用。
    注意:go2rtc 的转流服务不受影响。" }, "cameraConfig": { "add": "添加摄像头", @@ -1273,31 +1273,31 @@ "desc": "临时启用或禁用此摄像头的 生成式AI目标描述 功能。禁用后,系统将不再请求该摄像头追踪目标和物体的AI生成描述。" }, "review_descriptions": { - "title": "生成式AI核查描述", - "desc": "临时启用或禁用此摄像头的 生成式AI核查描述 功能。禁用后,系统将不再请求该摄像头核查项目的AI生成描述。" + "title": "生成式 AI 核查总结", + "desc": "临时开关该摄像头的 生成式 AI 核查总结 功能。禁用后,系统将不再请求 AI 生成该摄像头核查项目的总结。" }, "review": { "title": "核查", - "desc": "临时禁用/启用此摄像头的警报与检测功能,直至Frigate重启。禁用期间,系统将不再生成新的核查项目。 ", + "desc": "临时开关该摄像头的警报与检测项生成功能,直到 Frigate 重启后恢复。禁用期间,系统将不再生成新的核查项目。 ", "alerts": "警报 ", "detections": "检测 " }, "reviewClassification": { "title": "核查分类", - "desc": "Frigate 将核查项分为“警报”和“检测”。默认情况下,所有的汽车 目标都将视为警报。你可以通过修改配置文件配置区域来细分。", + "desc": "Frigate 将核查项的严重程度分为“警报”和“检测”两个等级。默认情况下,所有的汽车 目标都将视为警报。你可以通过修改配置文件配置区域来细分。", "noDefinedZones": "此摄像头未设置任何监控区。", "objectAlertsTips": "所有 {{alertsLabels}} 类目标或物体在 {{cameraName}} 下都将显示为警报。", - "zoneObjectAlertsTips": "所有 {{alertsLabels}} 类目标或物体在 {{cameraName}} 下的 {{zone}} 区内都将显示为警报。", + "zoneObjectAlertsTips": "所有 {{alertsLabels}} 类目标或物体在 {{cameraName}} 下的 {{zone}} 区域内都将显示为警报。", "objectDetectionsTips": "所有在摄像头 {{cameraName}} 上,检测到的 {{detectionsLabels}} 目标或物体,无论它位于哪个区,都将显示为检测。", "zoneObjectDetectionsTips": { - "text": "所有在摄像头 {{cameraName}} 下的 {{zone}} 区内检测到未分类的 {{detectionsLabels}} 目标或物体,都将显示为检测。", - "notSelectDetections": "所有在摄像头 {{cameraName}}下的 {{zone}} 区内检测到的 {{detectionsLabels}} 目标或物体,如果它未归类为警报,无论它位于哪个区,都将显示为检测。", + "text": "所有在摄像头 {{cameraName}} 下的 {{zone}} 区域内检测到未分类的 {{detectionsLabels}} 目标或物体,都将显示为检测。", + "notSelectDetections": "所有在摄像头 {{cameraName}}下的 {{zone}} 区域内检测到的 {{detectionsLabels}} 目标或物体,如果它未归类为警报,无论它位于哪个区,都将显示为检测。", "regardlessOfZoneObjectDetectionsTips": "在摄像头 {{cameraName}} 上,所有未分类的 {{detectionsLabels}} 检测目标或物体,无论出现在哪个区域,都将显示为检测。" }, "unsavedChanges": "摄像头 {{camera}} 的核查分类设置尚未保存", "selectAlertsZones": "选择警报区", "selectDetectionsZones": "选择检测区", - "limitDetections": "限制仅在特定区内进行检测", + "limitDetections": "限制仅在特定区域内进行检测", "toast": { "success": "核查分类设置已保存,重启后生效。" } diff --git a/web/public/locales/zh-CN/views/system.json b/web/public/locales/zh-CN/views/system.json index 3d6eff072f..94ef1b7c86 100644 --- a/web/public/locales/zh-CN/views/system.json +++ b/web/public/locales/zh-CN/views/system.json @@ -118,7 +118,7 @@ "title": "摄像头", "overview": "概览", "info": { - "cameraProbeInfo": "{{camera}} 的摄像头信息", + "cameraProbeInfo": "摄像头 {{camera}} 的信息", "streamDataFromFFPROBE": "流数据信息通过ffprobe获取。", "fetching": "正在获取摄像头数据", "stream": "视频流{{idx}}", @@ -186,12 +186,15 @@ "plate_recognition": "车牌识别", "yolov9_plate_detection_speed": "YOLOv9 车牌检测速度", "yolov9_plate_detection": "YOLOv9 车牌检测", - "review_description": "核查描述", - "review_description_speed": "核查描述速度", - "review_description_events_per_second": "核查描述", + "review_description": "核查总结", + "review_description_speed": "核查总结速度", + "review_description_events_per_second": "核查总结", "object_description": "目标描述", "object_description_speed": "目标描述速度", - "object_description_events_per_second": "目标描述" + "object_description_events_per_second": "目标描述", + "classification": "分类 {{name}}", + "classification_speed": "{{name}} 的分类速度", + "classification_events_per_second": "{{name}} 的每秒分类速度" }, "averageInf": "平均推理时间" } From e20b324e0af4b363f761da718fe117527b22f408 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 24 Dec 2025 15:17:35 +0100 Subject: [PATCH 64/78] Update translation files Updated by "Squash Git commits" add-on in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ Translation: Frigate NVR/common --- web/public/locales/nb-NO/components/filter.json | 4 ++++ .../locales/nb-NO/views/classificationModel.json | 7 +++++-- web/public/locales/nb-NO/views/explore.json | 13 ++++++++++--- web/public/locales/nb-NO/views/search.json | 3 ++- web/public/locales/nb-NO/views/settings.json | 4 ++-- web/public/locales/nb-NO/views/system.json | 5 ++++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/web/public/locales/nb-NO/components/filter.json b/web/public/locales/nb-NO/components/filter.json index 55fff1b0a5..eb06846199 100644 --- a/web/public/locales/nb-NO/components/filter.json +++ b/web/public/locales/nb-NO/components/filter.json @@ -133,5 +133,9 @@ }, "count_one": "{{count}} Klasse", "count_other": "{{count}} Klasser" + }, + "attributes": { + "label": "Klassifiseringsattributter", + "all": "Alle attributter" } } diff --git a/web/public/locales/nb-NO/views/classificationModel.json b/web/public/locales/nb-NO/views/classificationModel.json index 186312293d..e7ee73f088 100644 --- a/web/public/locales/nb-NO/views/classificationModel.json +++ b/web/public/locales/nb-NO/views/classificationModel.json @@ -111,7 +111,8 @@ "classesUnique": "Klassenavn må være unike", "stateRequiresTwoClasses": "Tilstandsmodeller krever minst 2 klasser", "objectLabelRequired": "Velg en objektetikett", - "objectTypeRequired": "Velg en klassifiseringstype" + "objectTypeRequired": "Velg en klassifiseringstype", + "noneNotAllowed": "Klassen 'ingen' er ikke tillatt" }, "states": "Tilstander" }, @@ -164,7 +165,9 @@ "states": "Tilstander" }, "details": { - "scoreInfo": "Score representerer gjennomsnittlig klassifiseringskonfidens på tvers av alle deteksjoner av dette objektet." + "scoreInfo": "Score representerer gjennomsnittlig klassifiseringskonfidens på tvers av alle deteksjoner av dette objektet.", + "none": "Ingen", + "unknown": "Ukjent" }, "tooltip": { "trainingInProgress": "Modellen trenes nå", diff --git a/web/public/locales/nb-NO/views/explore.json b/web/public/locales/nb-NO/views/explore.json index c6a8e4e73c..c0d3956036 100644 --- a/web/public/locales/nb-NO/views/explore.json +++ b/web/public/locales/nb-NO/views/explore.json @@ -90,13 +90,15 @@ "updatedSublabel": "Underetikett ble oppdatert.", "updatedLPR": "Vellykket oppdatering av kjennemerke.", "regenerate": "En ny beskrivelse har blitt anmodet fra {{provider}}. Avhengig av hastigheten til leverandøren din, kan den nye beskrivelsen ta litt tid å regenerere.", - "audioTranscription": "Lydtranskripsjon ble forespurt. Avhengig av ytelsen på din Frigate server kan transkripsjonen ta noe tid å fullføre." + "audioTranscription": "Lydtranskripsjon ble forespurt. Avhengig av ytelsen på din Frigate server kan transkripsjonen ta noe tid å fullføre.", + "updatedAttributes": "Attributter ble oppdatert." }, "error": { "regenerate": "Feil ved anrop til {{provider}} for en ny beskrivelse: {{errorMessage}}", "updatedLPRFailed": "Oppdatering av kjennemerke feilet: {{errorMessage}}", "updatedSublabelFailed": "Feil ved oppdatering av underetikett: {{errorMessage}}", - "audioTranscription": "Forespørsel om lydtranskripsjon feilet: {{errorMessage}}" + "audioTranscription": "Forespørsel om lydtranskripsjon feilet: {{errorMessage}}", + "updatedAttributesFailed": "Kunne ikke oppdatere attributter: {{errorMessage}}" } }, "desc": "Detaljer for inspeksjonselement", @@ -151,7 +153,12 @@ }, "score": { "label": "Score" - } + }, + "editAttributes": { + "title": "Rediger attributter", + "desc": "Velg klassifiseringsattributter for denne {{label}}" + }, + "attributes": "Klassifiseringsattributter" }, "itemMenu": { "viewInHistory": { diff --git a/web/public/locales/nb-NO/views/search.json b/web/public/locales/nb-NO/views/search.json index 07e57f44c5..f25bf709ed 100644 --- a/web/public/locales/nb-NO/views/search.json +++ b/web/public/locales/nb-NO/views/search.json @@ -25,7 +25,8 @@ "max_speed": "Maks. hastighet", "recognized_license_plate": "Gjenkjent kjennemerke", "has_clip": "Har videoklipp", - "has_snapshot": "Har stillbilde" + "has_snapshot": "Har stillbilde", + "attributes": "Attributter" }, "searchType": { "thumbnail": "Miniatyrbilde", diff --git a/web/public/locales/nb-NO/views/settings.json b/web/public/locales/nb-NO/views/settings.json index f2563ccc22..0ea43438d2 100644 --- a/web/public/locales/nb-NO/views/settings.json +++ b/web/public/locales/nb-NO/views/settings.json @@ -495,7 +495,7 @@ "desc": "Administrer brukerprofiler for denne Frigate-instansen." }, "addUser": "Legg til bruker", - "updatePassword": "Oppdater passord", + "updatePassword": "Nullstill passord", "toast": { "success": { "deleteUser": "Bruker {{user}} ble slettet", @@ -599,7 +599,7 @@ "actions": "Handlinger", "role": "Rolle", "changeRole": "Endre brukerrolle", - "password": "Passord", + "password": "Nullstill passord", "deleteUser": "Slett bruker", "noUsers": "Ingen brukere funnet." } diff --git a/web/public/locales/nb-NO/views/system.json b/web/public/locales/nb-NO/views/system.json index 8aaca628a3..8348b8baf1 100644 --- a/web/public/locales/nb-NO/views/system.json +++ b/web/public/locales/nb-NO/views/system.json @@ -176,7 +176,10 @@ "object_description_events_per_second": "Objektbeskrivelse", "review_description": "Inspeksjonsbeskrivelse", "review_description_events_per_second": "Inspeksjonsbeskrivelse", - "review_description_speed": "Inspeksjonsbeskrivelse hastighet" + "review_description_speed": "Inspeksjonsbeskrivelse hastighet", + "classification": "{{name}} Klassifisering", + "classification_speed": "{{name}} klassifisering hastighet", + "classification_events_per_second": "{{name}} klassifiseringshendelser per sekund" }, "title": "Utvidelser", "infPerSecond": "Inferenser per sekund", From 3c5eb1aee5cc54fee91b55942a65b224e3891d1c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 26 Dec 2025 07:45:03 -0700 Subject: [PATCH 65/78] Miscellaneous fixes (0.17 beta) (#21431) * Add shortSummary field to review summary to be used for notifications * pull in current config version into default config * fix crash when dynamically adding cameras depending on where we are in the update loop, camera configs might not be updated yet and we are receiving detections already * add no tracked objects and icon to explore summary view * reset add camera wizard when closing and saving * don't flash no exports icon while loading * Improve handling of homekit config * Increase prompt tokens reservation * Adjust * Catch event not found object detection * Use thread lock for JinaV2 in onnxruntime * remove incorrect embeddings process from memray docs * only show transcribe button if audio event has video * apply aspect ratio and margin constraints to path overlay in detail stream on mobile improves a specific case where the overlay was not aligned with 4:3 cameras on mobile phones * show metadata title as tooltip on icon hover in detail stream --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- .../rootfs/etc/s6-overlay/s6-rc.d/go2rtc/run | 8 +++-- .../configuration/genai/review_summaries.md | 3 +- docs/docs/troubleshooting/memory.md | 1 - frigate/comms/webpush.py | 2 +- frigate/config/config.py | 9 +++--- .../post/object_descriptions.py | 6 +++- .../post/review_descriptions.py | 2 +- frigate/data_processing/post/types.py | 3 ++ frigate/detectors/detection_runners.py | 32 +++++++++++++++++-- frigate/embeddings/maintainer.py | 2 +- frigate/genai/__init__.py | 3 ++ frigate/output/output.py | 6 ++-- .../overlay/detail/SearchDetailDialog.tsx | 3 +- web/src/components/player/HlsVideoPlayer.tsx | 12 ++++++- .../settings/CameraWizardDialog.tsx | 12 +++++-- web/src/components/timeline/DetailStream.tsx | 9 +++++- web/src/pages/Exports.tsx | 4 +-- web/src/views/explore/ExploreView.tsx | 10 ++++++ 18 files changed, 102 insertions(+), 25 deletions(-) diff --git a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/go2rtc/run b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/go2rtc/run index 8f5b1c2673..d3aa34236d 100755 --- a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/go2rtc/run +++ b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/go2rtc/run @@ -55,7 +55,7 @@ function setup_homekit_config() { if [[ ! -f "${config_path}" ]]; then echo "[INFO] Creating empty HomeKit config file..." - echo '{}' > "${config_path}" + echo 'homekit: {}' > "${config_path}" fi # Convert YAML to JSON for jq processing @@ -70,12 +70,14 @@ function setup_homekit_config() { jq ' # Keep only the homekit section if it exists, otherwise empty object if has("homekit") then {homekit: .homekit} else {homekit: {}} end - ' "${temp_json}" > "${cleaned_json}" 2>/dev/null || echo '{"homekit": {}}' > "${cleaned_json}" + ' "${temp_json}" > "${cleaned_json}" 2>/dev/null || { + echo '{"homekit": {}}' > "${cleaned_json}" + } # Convert back to YAML and write to the config file yq eval -P "${cleaned_json}" > "${config_path}" 2>/dev/null || { echo "[WARNING] Failed to convert cleaned config to YAML, creating minimal config" - echo '{"homekit": {}}' > "${config_path}" + echo 'homekit: {}' > "${config_path}" } # Clean up temp files diff --git a/docs/docs/configuration/genai/review_summaries.md b/docs/docs/configuration/genai/review_summaries.md index 8a492f4b8f..04a5b5e94b 100644 --- a/docs/docs/configuration/genai/review_summaries.md +++ b/docs/docs/configuration/genai/review_summaries.md @@ -16,12 +16,13 @@ Review summaries provide structured JSON responses that are saved for each revie ``` - `title` (string): A concise, direct title that describes the purpose or overall action (e.g., "Person taking out trash", "Joe walking dog"). - `scene` (string): A narrative description of what happens across the sequence from start to finish, including setting, detected objects, and their observable actions. +- `shortSummary` (string): A brief 2-sentence summary of the scene, suitable for notifications. This is a condensed version of the scene description. - `confidence` (float): 0-1 confidence in the analysis. Higher confidence when objects/actions are clearly visible and context is unambiguous. - `other_concerns` (list): List of user-defined concerns that may need additional investigation. - `potential_threat_level` (integer): 0, 1, or 2 as defined below. ``` -This will show in multiple places in the UI to give additional context about each activity, and allow viewing more details when extra attention is required. Frigate's built in notifications will also automatically show the title and description when the data is available. +This will show in multiple places in the UI to give additional context about each activity, and allow viewing more details when extra attention is required. Frigate's built in notifications will automatically show the title and `shortSummary` when the data is available, while the full `scene` description is available in the UI for detailed review. ### Defining Typical Activity diff --git a/docs/docs/troubleshooting/memory.md b/docs/docs/troubleshooting/memory.md index 338037c7aa..c74729e5f6 100644 --- a/docs/docs/troubleshooting/memory.md +++ b/docs/docs/troubleshooting/memory.md @@ -36,7 +36,6 @@ Frigate processes are named using a module-based naming scheme. Common module na - `frigate.output` - Output processing - `frigate.audio_manager` - Audio processing - `frigate.embeddings` - Embeddings processing -- `frigate.embeddings_manager` - Embeddings manager You can also specify the full process name (including camera-specific identifiers) if you want to profile a specific camera: diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 32eeb40e84..62cc12c9a8 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -388,7 +388,7 @@ def send_alert(self, payload: dict[str, Any]) -> None: else: title = base_title - message = payload["after"]["data"]["metadata"]["scene"] + message = payload["after"]["data"]["metadata"]["shortSummary"] else: zone_names = payload["after"]["data"]["zones"] formatted_zone_names = [] diff --git a/frigate/config/config.py b/frigate/config/config.py index 6342c13bf0..a9c54976ee 100644 --- a/frigate/config/config.py +++ b/frigate/config/config.py @@ -28,6 +28,7 @@ get_ffmpeg_arg_list, ) from frigate.util.config import ( + CURRENT_CONFIG_VERSION, StreamInfoRetriever, convert_area_to_pixels, find_config_file, @@ -76,11 +77,12 @@ yaml = YAML() -DEFAULT_CONFIG = """ +DEFAULT_CONFIG = f""" mqtt: enabled: False -cameras: {} # No cameras defined, UI wizard should be used +cameras: {{}} # No cameras defined, UI wizard should be used +version: {CURRENT_CONFIG_VERSION} """ DEFAULT_DETECTORS = {"cpu": {"type": "cpu"}} @@ -753,8 +755,7 @@ def load(cls, **kwargs): if new_config and f.tell() == 0: f.write(DEFAULT_CONFIG) logger.info( - "Created default config file, see the getting started docs \ - for configuration https://docs.frigate.video/guides/getting_started" + "Created default config file, see the getting started docs for configuration: https://docs.frigate.video/guides/getting_started" ) f.seek(0) diff --git a/frigate/data_processing/post/object_descriptions.py b/frigate/data_processing/post/object_descriptions.py index 7bd38bfa8c..cdb5f4fc37 100644 --- a/frigate/data_processing/post/object_descriptions.py +++ b/frigate/data_processing/post/object_descriptions.py @@ -86,7 +86,11 @@ def __handle_frame_update( and data["id"] not in self.early_request_sent ): if data["has_clip"] and data["has_snapshot"]: - event: Event = Event.get(Event.id == data["id"]) + try: + event: Event = Event.get(Event.id == data["id"]) + except DoesNotExist: + logger.error(f"Event {data['id']} not found") + return if ( not camera_config.objects.genai.objects diff --git a/frigate/data_processing/post/review_descriptions.py b/frigate/data_processing/post/review_descriptions.py index 0b12aa1a00..0a2754468f 100644 --- a/frigate/data_processing/post/review_descriptions.py +++ b/frigate/data_processing/post/review_descriptions.py @@ -92,7 +92,7 @@ def calculate_frame_count( pixels_per_image = width * height tokens_per_image = pixels_per_image / 1250 - prompt_tokens = 3500 + prompt_tokens = 3800 response_tokens = 300 available_tokens = context_size - prompt_tokens - response_tokens max_frames = int(available_tokens / tokens_per_image) diff --git a/frigate/data_processing/post/types.py b/frigate/data_processing/post/types.py index 70fec9b348..44bb09fb02 100644 --- a/frigate/data_processing/post/types.py +++ b/frigate/data_processing/post/types.py @@ -8,6 +8,9 @@ class ReviewMetadata(BaseModel): scene: str = Field( description="A comprehensive description of the setting and entities, including relevant context and plausible inferences if supported by visual evidence." ) + shortSummary: str = Field( + description="A brief 2-sentence summary of the scene, suitable for notifications. Should capture the key activity and context without full detail." + ) confidence: float = Field( description="A float between 0 and 1 representing your overall confidence in this analysis." ) diff --git a/frigate/detectors/detection_runners.py b/frigate/detectors/detection_runners.py index 56b49ec67d..fcbb41e661 100644 --- a/frigate/detectors/detection_runners.py +++ b/frigate/detectors/detection_runners.py @@ -139,8 +139,31 @@ def is_migraphx_complex_model(model_type: str) -> bool: ModelTypeEnum.dfine.value, ] - def __init__(self, ort: ort.InferenceSession): + @staticmethod + def is_concurrent_model(model_type: str | None) -> bool: + """Check if model requires thread locking for concurrent inference. + + Some models (like JinaV2) share one runner between text and vision embeddings + called from different threads, requiring thread synchronization. + """ + if not model_type: + return False + + # Import here to avoid circular imports + from frigate.embeddings.types import EnrichmentModelTypeEnum + + return model_type == EnrichmentModelTypeEnum.jina_v2.value + + def __init__(self, ort: ort.InferenceSession, model_type: str | None = None): self.ort = ort + self.model_type = model_type + + # Thread lock to prevent concurrent inference (needed for JinaV2 which shares + # one runner between text and vision embeddings called from different threads) + if self.is_concurrent_model(model_type): + self._inference_lock = threading.Lock() + else: + self._inference_lock = None def get_input_names(self) -> list[str]: return [input.name for input in self.ort.get_inputs()] @@ -150,6 +173,10 @@ def get_input_width(self) -> int: return self.ort.get_inputs()[0].shape[3] def run(self, input: dict[str, Any]) -> Any | None: + if self._inference_lock: + with self._inference_lock: + return self.ort.run(None, input) + return self.ort.run(None, input) @@ -576,5 +603,6 @@ def get_optimized_runner( ), providers=providers, provider_options=options, - ) + ), + model_type=model_type, ) diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index c74bc2310c..1a0950cbbc 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -633,7 +633,7 @@ def _process_frame_updates(self) -> None: camera, frame_name, _, _, motion_boxes, _ = data - if not camera or len(motion_boxes) == 0: + if not camera or len(motion_boxes) == 0 or camera not in self.config.cameras: return camera_config = self.config.cameras[camera] diff --git a/frigate/genai/__init__.py b/frigate/genai/__init__.py index 5e1a742798..7f0192912a 100644 --- a/frigate/genai/__init__.py +++ b/frigate/genai/__init__.py @@ -101,6 +101,7 @@ def get_objects_list() -> str: Your response MUST be a flat JSON object with: - `title` (string): A concise, direct title that describes the primary action or event in the sequence, not just what you literally see. Use spatial context when available to make titles more meaningful. When multiple objects/actions are present, prioritize whichever is most prominent or occurs first. Use names from "Objects in Scene" based on what you visually observe. If you see both a name and an unidentified object of the same type but visually observe only one person/object, use ONLY the name. Examples: "Joe walking dog", "Person taking out trash", "Vehicle arriving in driveway", "Joe accessing vehicle", "Person leaving porch for driveway". - `scene` (string): A narrative description of what happens across the sequence from start to finish, in chronological order. Start by describing how the sequence begins, then describe the progression of events. **Describe all significant movements and actions in the order they occur.** For example, if a vehicle arrives and then a person exits, describe both actions sequentially. **Only describe actions you can actually observe happening in the frames provided.** Do not infer or assume actions that aren't visible (e.g., if you see someone walking but never see them sit, don't say they sat down). Include setting, detected objects, and their observable actions. Avoid speculation or filling in assumed behaviors. Your description should align with and support the threat level you assign. +- `shortSummary` (string): A brief 2-sentence summary of the scene, suitable for notifications. Should capture the key activity and context without full detail. This should be a condensed version of the scene description above. - `confidence` (float): 0-1 confidence in your analysis. Higher confidence when objects/actions are clearly visible and context is unambiguous. Lower confidence when the sequence is unclear, objects are partially obscured, or context is ambiguous. - `potential_threat_level` (integer): 0, 1, or 2 as defined in "Normal Activity Patterns for This Property" above. Your threat level must be consistent with your scene description and the guidance above. {get_concern_prompt()} @@ -192,6 +193,8 @@ def generate_review_summary( - "title", "scene", "confidence", "potential_threat_level" (0-2), "other_concerns", "camera", "time", "start_time", "end_time" - "context": array of related events from other cameras that occurred during overlapping time periods +**Note: Use the "scene" field for event descriptions in the report. Ignore any "shortSummary" field if present.** + Report Structure - Use this EXACT format: # Security Summary - {time_range} diff --git a/frigate/output/output.py b/frigate/output/output.py index 674c02b783..a444150007 100644 --- a/frigate/output/output.py +++ b/frigate/output/output.py @@ -139,9 +139,11 @@ def run(self) -> None: if CameraConfigUpdateEnum.add in updates: for camera in updates["add"]: jsmpeg_cameras[camera] = JsmpegCamera( - cam_config, self.stop_event, websocket_server + self.config.cameras[camera], self.stop_event, websocket_server + ) + preview_recorders[camera] = PreviewRecorder( + self.config.cameras[camera] ) - preview_recorders[camera] = PreviewRecorder(cam_config) preview_write_times[camera] = 0 if ( diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index e3ae19159c..bd4368ebe2 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -1604,7 +1604,8 @@ function ObjectDetailsTab({ {config?.cameras[search?.camera].audio_transcription.enabled && search?.label == "speech" && - search?.end_time && ( + search?.end_time && + search?.has_clip && (
    {event.data.metadata?.title && ( -
    - - - {event.data.metadata.title} - -
    + +
    + + + {event.data.metadata.title} + +
    +
    )}
    ); diff --git a/web/src/components/menu/SearchResultActions.tsx b/web/src/components/menu/SearchResultActions.tsx index 3116ae463b..2313b5a03a 100644 --- a/web/src/components/menu/SearchResultActions.tsx +++ b/web/src/components/menu/SearchResultActions.tsx @@ -195,7 +195,7 @@ export default function SearchResultActions({ ) : ( <> - + diff --git a/web/src/components/overlay/chip/GenAISummaryChip.tsx b/web/src/components/overlay/chip/GenAISummaryChip.tsx index 64eb4a10b8..ead0104b14 100644 --- a/web/src/components/overlay/chip/GenAISummaryChip.tsx +++ b/web/src/components/overlay/chip/GenAISummaryChip.tsx @@ -6,16 +6,15 @@ import { ThreatLevel, THREAT_LEVEL_LABELS, } from "@/types/review"; -import { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { isDesktop } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { MdAutoAwesome } from "react-icons/md"; type GenAISummaryChipProps = { review?: ReviewSegment; - onClick: () => void; }; -export function GenAISummaryChip({ review, onClick }: GenAISummaryChipProps) { +export function GenAISummaryChip({ review }: GenAISummaryChipProps) { const [isVisible, setIsVisible] = useState(false); useEffect(() => { @@ -29,7 +28,6 @@ export function GenAISummaryChip({ review, onClick }: GenAISummaryChipProps) { isVisible ? "translate-y-0 opacity-100" : "-translate-y-4 opacity-0", isDesktop ? "bg-card" : "bg-secondary-foreground", )} - onClick={onClick} > {review?.data.metadata?.title} @@ -40,10 +38,12 @@ export function GenAISummaryChip({ review, onClick }: GenAISummaryChipProps) { type GenAISummaryDialogProps = { review?: ReviewSegment; onOpen?: (open: boolean) => void; + children: React.ReactNode; }; export function GenAISummaryDialog({ review, onOpen, + children, }: GenAISummaryDialogProps) { const { t } = useTranslation(["views/explore"]); @@ -104,7 +104,7 @@ export function GenAISummaryDialog({ return ( - setOpen(true)} /> +
    {children}
    {t("aiAnalysis.title")} +
    + {t("details.title.label")} +
    +
    {aiAnalysis.title}
    {t("details.description.label")}
    diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index c18cf9dffc..9258ca457b 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -25,10 +25,13 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { Link } from "react-router-dom"; import { Switch } from "@/components/ui/switch"; import { useUserPersistence } from "@/hooks/use-user-persistence"; -import { isDesktop } from "react-device-detect"; +import { isDesktop, isIOS, isMobile } from "react-device-detect"; import { resolveZoneName } from "@/hooks/use-zone-friendly-name"; import { PiSlidersHorizontalBold } from "react-icons/pi"; import { MdAutoAwesome } from "react-icons/md"; +import { isPWA } from "@/utils/isPWA"; +import { isInIframe } from "@/utils/isIFrame"; +import { GenAISummaryDialog } from "../overlay/chip/GenAISummaryChip"; type DetailStreamProps = { reviewItems?: ReviewSegment[]; @@ -100,7 +103,25 @@ export default function DetailStream({ } }, [reviewItems, activeReviewId, effectiveTime]); - // Auto-scroll to current time + // Initial scroll to active review (runs immediately when user selects, not during playback) + useEffect(() => { + if (!scrollRef.current || !activeReviewId || userInteracting || isPlaying) + return; + + const element = scrollRef.current.querySelector( + `[data-review-id="${activeReviewId}"]`, + ) as HTMLElement; + + if (element) { + setProgrammaticScroll(); + scrollIntoView(element, { + scrollMode: "if-needed", + behavior: isMobile && isIOS && !isPWA && isInIframe ? "auto" : "smooth", + }); + } + }, [activeReviewId, setProgrammaticScroll, userInteracting, isPlaying]); + + // Auto-scroll to current time during playback useEffect(() => { if (!scrollRef.current || userInteracting || !isPlaying) return; // Prefer the review whose range contains the effectiveTime. If none @@ -145,7 +166,8 @@ export default function DetailStream({ setProgrammaticScroll(); scrollIntoView(element, { scrollMode: "if-needed", - behavior: "smooth", + behavior: + isMobile && isIOS && !isPWA && isInIframe ? "auto" : "smooth", }); } } @@ -417,7 +439,18 @@ function ReviewGroup({ {review.data.metadata.title} - {review.data.metadata.title} + { + if (open) { + onSeek(review.start_time, false); + } + }} + > + + {review.data.metadata.title} + +
    )}
    @@ -782,21 +815,27 @@ function LifecycleItem({
    - {t("trackingDetails.lifecycleItemDesc.header.score")} + {t("trackingDetails.lifecycleItemDesc.header.score", { + ns: "views/explore", + })} {score}
    - {t("trackingDetails.lifecycleItemDesc.header.ratio")} + {t("trackingDetails.lifecycleItemDesc.header.ratio", { + ns: "views/explore", + })} {ratio}
    - {t("trackingDetails.lifecycleItemDesc.header.area")}{" "} + {t("trackingDetails.lifecycleItemDesc.header.area", { + ns: "views/explore", + })}{" "} {attributeAreaPx !== undefined && attributeAreaPct !== undefined && ( @@ -806,7 +845,7 @@ function LifecycleItem({ {areaPx !== undefined && areaPct !== undefined ? ( - {areaPx} {t("pixels", { ns: "common" })}{" "} + {areaPx} {t("information.pixels", { ns: "common" })}{" "} ·{" "} {areaPct}% @@ -819,7 +858,9 @@ function LifecycleItem({ attributeAreaPct !== undefined && (
    - {t("trackingDetails.lifecycleItemDesc.header.area")}{" "} + {t("trackingDetails.lifecycleItemDesc.header.area", { + ns: "views/explore", + })}{" "} {attributeAreaPx !== undefined && attributeAreaPct !== undefined && ( @@ -828,7 +869,8 @@ function LifecycleItem({ )} - {attributeAreaPx} {t("pixels", { ns: "common" })}{" "} + {attributeAreaPx}{" "} + {t("information.pixels", { ns: "common" })}{" "} ·{" "} {attributeAreaPct}% diff --git a/web/src/components/timeline/MotionReviewTimeline.tsx b/web/src/components/timeline/MotionReviewTimeline.tsx index cbecff3304..cefd8f2d33 100644 --- a/web/src/components/timeline/MotionReviewTimeline.tsx +++ b/web/src/components/timeline/MotionReviewTimeline.tsx @@ -111,7 +111,7 @@ export function MotionReviewTimeline({ const getRecordingAvailability = useCallback( (time: number): boolean | undefined => { - if (!noRecordingRanges?.length) return undefined; + if (noRecordingRanges == undefined) return undefined; return !noRecordingRanges.some( (range) => time >= range.start_time && time < range.end_time, diff --git a/web/src/types/card.ts b/web/src/types/card.ts new file mode 100644 index 0000000000..9e1da1632c --- /dev/null +++ b/web/src/types/card.ts @@ -0,0 +1,4 @@ +export type EmptyCardData = { + title: string; + description?: string; +}; diff --git a/web/src/utils/i18n.ts b/web/src/utils/i18n.ts index ca7ad8e258..f149f467ed 100644 --- a/web/src/utils/i18n.ts +++ b/web/src/utils/i18n.ts @@ -79,9 +79,6 @@ i18n parseMissingKeyHandler: (key: string) => { const parts = key.split("."); - // eslint-disable-next-line no-console - console.warn(`Missing translation key: ${key}`); - if (parts[0] === "time" && parts[1]?.includes("formattedTimestamp")) { // Extract the format type from the last part (12hour, 24hour) const formatType = parts[parts.length - 1]; diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 417c3231db..9e015dfe47 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -56,6 +56,8 @@ import { GiSoundWaves } from "react-icons/gi"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; import { useTimelineZoom } from "@/hooks/use-timeline-zoom"; import { useTranslation } from "react-i18next"; +import { EmptyCard } from "@/components/card/EmptyCard"; +import { EmptyCardData } from "@/types/card"; type EventViewProps = { reviewItems?: SegmentedReviewData; @@ -132,6 +134,24 @@ export default function EventView({ } }, [filter, showReviewed, reviewSummary]); + const emptyCardData: EmptyCardData = useMemo(() => { + if ( + !config || + Object.values(config.cameras).find( + (cam) => cam.record.enabled_in_config, + ) != undefined + ) { + return { + title: t("empty." + severity.replace(/_/g, " ")), + }; + } + + return { + title: t("empty.recordingsDisabled.title"), + description: t("empty.recordingsDisabled.description"), + }; + }, [config, severity, t]); + // review interaction const [selectedReviews, setSelectedReviews] = useState([]); @@ -412,6 +432,7 @@ export default function EventView({ timeRange={timeRange} startTime={startTime} loading={severity != severityToggle} + emptyCardData={emptyCardData} markItemAsReviewed={markItemAsReviewed} markAllItemsAsReviewed={markAllItemsAsReviewed} onSelectReview={onSelectReview} @@ -430,6 +451,7 @@ export default function EventView({ startTime={startTime} filter={filter} motionOnly={motionOnly} + emptyCardData={emptyCardData} onOpenRecording={onOpenRecording} /> )} @@ -455,6 +477,7 @@ type DetectionReviewProps = { timeRange: { before: number; after: number }; startTime?: number; loading: boolean; + emptyCardData: EmptyCardData; markItemAsReviewed: (review: ReviewSegment) => void; markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void; onSelectReview: ( @@ -478,6 +501,7 @@ function DetectionReview({ timeRange, startTime, loading, + emptyCardData, markItemAsReviewed, markAllItemsAsReviewed, onSelectReview, @@ -737,10 +761,12 @@ function DetectionReview({ )} {!loading && currentItems?.length === 0 && ( -
    - - {t("empty." + severity.replace(/_/g, " "))} -
    + } + /> )}
    void; }; function MotionReview({ @@ -885,9 +912,9 @@ function MotionReview({ startTime, filter, motionOnly = false, + emptyCardData, onOpenRecording, }: MotionReviewProps) { - const { t } = useTranslation(["views/events"]); const segmentDuration = 30; const { data: config } = useSWR("config"); @@ -1080,9 +1107,12 @@ function MotionReview({ if (motionData?.length === 0) { return ( -
    - - {t("empty.motion")} +
    + } + />
    ); } diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 839186d8ea..aee3d09dac 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -66,7 +66,10 @@ import { import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel"; import { useAllowedCameras } from "@/hooks/use-allowed-cameras"; import { DetailStreamProvider } from "@/context/detail-stream-context"; -import { GenAISummaryDialog } from "@/components/overlay/chip/GenAISummaryChip"; +import { + GenAISummaryDialog, + GenAISummaryChip, +} from "@/components/overlay/chip/GenAISummaryChip"; const DATA_REFRESH_TIME = 600000; // 10 minutes @@ -309,10 +312,18 @@ export function RecordingView({ currentTimeRange.after <= currentTime && currentTimeRange.before >= currentTime ) { - mainControllerRef.current?.seekToTimestamp( - currentTime, - mainControllerRef.current.isPlaying(), - ); + if (mainControllerRef.current != undefined) { + let shouldPlayback = true; + + if (timelineType == "detail") { + shouldPlayback = mainControllerRef.current.isPlaying(); + } + + mainControllerRef.current.seekToTimestamp( + currentTime, + shouldPlayback, + ); + } } else { updateSelectedSegment(currentTime, true); } @@ -731,7 +742,9 @@ export function RecordingView({ + > + + )} {isMobile && timelineType == "timeline" && ( - + + + )} {timelineType != "detail" && ( From b5d2f86a9b07a79eb1ef4fde53d2a114cbca9c80 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 31 Dec 2025 06:37:52 -0700 Subject: [PATCH 70/78] Refactor hardware acceleration docs (#21488) * Refactor hardware acceleration docs * Add a linking header * Add RPi --- .../hardware_acceleration_video.md | 144 +++++++++++------- 1 file changed, 91 insertions(+), 53 deletions(-) diff --git a/docs/docs/configuration/hardware_acceleration_video.md b/docs/docs/configuration/hardware_acceleration_video.md index b7cb794aeb..f7368e623d 100644 --- a/docs/docs/configuration/hardware_acceleration_video.md +++ b/docs/docs/configuration/hardware_acceleration_video.md @@ -5,76 +5,61 @@ title: Video Decoding # Video Decoding -It is highly recommended to use a GPU for hardware acceleration video decoding in Frigate. Some types of hardware acceleration are detected and used automatically, but you may need to update your configuration to enable hardware accelerated decoding in ffmpeg. +It is highly recommended to use an integrated or discrete GPU for hardware acceleration video decoding in Frigate. -Depending on your system, these parameters may not be compatible. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro +Some types of hardware acceleration are detected and used automatically, but you may need to update your configuration to enable hardware accelerated decoding in ffmpeg. To verify that hardware acceleration is working: +- Check the logs: A message will either say that hardware acceleration was automatically detected, or there will be a warning that no hardware acceleration was automatically detected +- If hardware acceleration is specified in the config, verification can be done by ensuring the logs are free from errors. There is no CPU fallback for hardware acceleration. +:::info -## Raspberry Pi 3/4 +Frigate supports presets for optimal hardware accelerated video decoding: -Ensure you increase the allocated RAM for your GPU to at least 128 (`raspi-config` > Performance Options > GPU Memory). -If you are using the HA Add-on, you may need to use the full access variant and turn off _Protection mode_ for hardware acceleration. +**AMD** -```yaml -# if you want to decode a h264 stream -ffmpeg: - hwaccel_args: preset-rpi-64-h264 +- [AMD](#amd-based-cpus): Frigate can utilize modern AMD integrated GPUs and AMD discrete GPUs to accelerate video decoding. -# if you want to decode a h265 (hevc) stream -ffmpeg: - hwaccel_args: preset-rpi-64-h265 -``` +**Intel** -:::note +- [Intel](#intel-based-cpus): Frigate can utilize most Intel integrated GPUs and Arc GPUs to accelerate video decoding. -If running Frigate through Docker, you either need to run in privileged mode or -map the `/dev/video*` devices to Frigate. With Docker Compose add: +**Nvidia GPU** -```yaml -services: - frigate: - ... - devices: - - /dev/video11:/dev/video11 -``` +- [Nvidia GPU](#nvidia-gpus): Frigate can utilize most modern Nvidia GPUs to accelerate video decoding. -Or with `docker run`: +**Raspberry Pi 3/4** -```bash -docker run -d \ - --name frigate \ - ... - --device /dev/video11 \ - ghcr.io/blakeblackshear/frigate:stable -``` +- [Raspberry Pi](#raspberry-pi-34): Frigate can utilize the media engine in the Raspberry Pi 3 and 4 to slightly accelerate video decoding. -`/dev/video11` is the correct device (on Raspberry Pi 4B). You can check -by running the following and looking for `H264`: +**Nvidia Jetson** -```bash -for d in /dev/video*; do - echo -e "---\n$d" - v4l2-ctl --list-formats-ext -d $d -done -``` +- [Jetson](#nvidia-jetson): Frigate can utilize the media engine in Jetson hardware to accelerate video decoding. -Or map in all the `/dev/video*` devices. +**Rockchip** + +- [RKNN](#rockchip-platform): Frigate can utilize the media engine in RockChip SOCs to accelerate video decoding. + +**Other Hardware** + +Depending on your system, these presets may not be compatible, and you may need to use manual hwaccel args to take advantage of your hardware. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro ::: ## Intel-based CPUs +Frigate can utilize most Intel integrated GPUs and Arc GPUs to accelerate video decoding. + :::info **Recommended hwaccel Preset** -| CPU Generation | Intel Driver | Recommended Preset | Notes | -| -------------- | ------------ | ------------------- | ------------------------------------ | -| gen1 - gen5 | i965 | preset-vaapi | qsv is not supported | -| gen6 - gen7 | iHD | 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-\* | | +| CPU Generation | Intel Driver | Recommended Preset | Notes | +| -------------- | ------------ | ------------------- | ------------------------------------------- | +| gen1 - gen5 | i965 | preset-vaapi | qsv is not supported, may not support H.265 | +| gen6 - gen7 | iHD | 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-\* | | ::: @@ -195,9 +180,9 @@ telemetry: If you are passing in a device path, make sure you've passed the device through to the container. -## AMD/ATI GPUs (Radeon HD 2000 and newer GPUs) via libva-mesa-driver +## AMD-based CPUs -VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. +Frigate can utilize modern AMD integrated GPUs and AMD GPUs to accelerate video decoding using VAAPI. :::note @@ -205,6 +190,8 @@ You need to change the driver to `radeonsi` by adding the following environment ::: +VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. + ```yaml ffmpeg: hwaccel_args: preset-vaapi @@ -264,7 +251,7 @@ processes: :::note -`nvidia-smi` may not show `ffmpeg` processes when run inside the container [due to docker limitations](https://github.com/NVIDIA/nvidia-docker/issues/179#issuecomment-645579458). +`nvidia-smi` will not show `ffmpeg` processes when run inside the container [due to docker limitations](https://github.com/NVIDIA/nvidia-docker/issues/179#issuecomment-645579458). ::: @@ -300,12 +287,63 @@ If you do not see these processes, check the `docker logs` for the container and These instructions were originally based on the [Jellyfin documentation](https://jellyfin.org/docs/general/administration/hardware-acceleration.html#nvidia-hardware-acceleration-on-docker-linux). +## Raspberry Pi 3/4 + +Ensure you increase the allocated RAM for your GPU to at least 128 (`raspi-config` > Performance Options > GPU Memory). +If you are using the HA Add-on, you may need to use the full access variant and turn off _Protection mode_ for hardware acceleration. + +```yaml +# if you want to decode a h264 stream +ffmpeg: + hwaccel_args: preset-rpi-64-h264 + +# if you want to decode a h265 (hevc) stream +ffmpeg: + hwaccel_args: preset-rpi-64-h265 +``` + +:::note + +If running Frigate through Docker, you either need to run in privileged mode or +map the `/dev/video*` devices to Frigate. With Docker Compose add: + +```yaml +services: + frigate: + ... + devices: + - /dev/video11:/dev/video11 +``` + +Or with `docker run`: + +```bash +docker run -d \ + --name frigate \ + ... + --device /dev/video11 \ + ghcr.io/blakeblackshear/frigate:stable +``` + +`/dev/video11` is the correct device (on Raspberry Pi 4B). You can check +by running the following and looking for `H264`: + +```bash +for d in /dev/video*; do + echo -e "---\n$d" + v4l2-ctl --list-formats-ext -d $d +done +``` + +Or map in all the `/dev/video*` devices. + +::: + # Community Supported -## NVIDIA Jetson (Orin AGX, Orin NX, Orin Nano\*, Xavier AGX, Xavier NX, TX2, TX1, Nano) +## NVIDIA Jetson -A separate set of docker images is available that is based on Jetpack/L4T. They come with an `ffmpeg` build -with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 6.0+ use the `stable-tensorrt-jp6` tagged image. Note that the Orin Nano has no video encoder, so frigate will use software encoding on this platform, but the image will still allow hardware decoding and tensorrt object detection. +A separate set of docker images is available for Jetson devices. They come with an `ffmpeg` build with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 6.0+ use the `stable-tensorrt-jp6` tagged image. Note that the Orin Nano has no video encoder, so frigate will use software encoding on this platform, but the image will still allow hardware decoding and tensorrt object detection. You will need to use the image with the nvidia container runtime: From d1f28eb8e177461a010f06db366f331b44dbe25f Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Thu, 1 Jan 2026 09:56:09 -0600 Subject: [PATCH 71/78] llc to inc and 2025 to 2026 (#21484) --- LICENSE | 2 +- README.md | 4 ++-- README_CN.md | 4 ++-- TRADEMARK.md | 8 ++++---- docs/docusaurus.config.ts | 2 +- docs/static/img/branding/LICENSE.md | 10 +++++----- web/images/branding/LICENSE | 10 +++++----- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/LICENSE b/LICENSE index 0c1fc1f536..924cb4148c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2025 Frigate LLC (Frigate™) +Copyright (c) 2026 Frigate, Inc. (Frigate™) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b1eab6c534..c25585a1d7 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If you would like to make a donation to support development, please use [Github This project is licensed under the **MIT License**. - **Code:** The source code, configuration files, and documentation in this repository are available under the [MIT License](LICENSE). You are free to use, modify, and distribute the code as long as you include the original copyright notice. -- **Trademarks:** The "Frigate" name, the "Frigate NVR" brand, and the Frigate logo are **trademarks of Frigate LLC** and are **not** covered by the MIT License. +- **Trademarks:** The "Frigate" name, the "Frigate NVR" brand, and the Frigate logo are **trademarks of Frigate, Inc.** and are **not** covered by the MIT License. Please see our [Trademark Policy](TRADEMARK.md) for details on acceptable use of our brand assets. @@ -80,4 +80,4 @@ We use [Weblate](https://hosted.weblate.org/projects/frigate-nvr/) to support la --- -**Copyright © 2025 Frigate LLC.** +**Copyright © 2026 Frigate, Inc.** diff --git a/README_CN.md b/README_CN.md index f2433b2489..62df77b5c9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -41,7 +41,7 @@ **代码部分**:本代码库中的源代码、配置文件和文档均遵循 [MIT 许可证](LICENSE)。您可以自由使用、修改和分发这些代码,但必须保留原始版权声明。 -**商标部分**:“Frigate”名称、“Frigate NVR”品牌以及 Frigate 的 Logo 为 **Frigate LLC 的商标**,**不在** MIT 许可证覆盖范围内。 +**商标部分**:“Frigate”名称、“Frigate NVR”品牌以及 Frigate 的 Logo 为 **Frigate, Inc. 的商标**,**不在** MIT 许可证覆盖范围内。 有关品牌资产的规范使用详情,请参阅我们的[《商标政策》](TRADEMARK.md)。 ## 截图 @@ -87,4 +87,4 @@ Bilibili:https://space.bilibili.com/3546894915602564 --- -**Copyright © 2025 Frigate LLC.** +**Copyright © 2026 Frigate, Inc.** diff --git a/TRADEMARK.md b/TRADEMARK.md index ef3681cfa1..fdbdc14d22 100644 --- a/TRADEMARK.md +++ b/TRADEMARK.md @@ -6,7 +6,7 @@ This document outlines the policy regarding the use of the trademarks associated ## 1. Our Trademarks -The following terms and visual assets are trademarks (the "Marks") of **Frigate LLC**: +The following terms and visual assets are trademarks (the "Marks") of **Frigate, Inc.**: - **Frigate™** - **Frigate NVR™** @@ -14,7 +14,7 @@ The following terms and visual assets are trademarks (the "Marks") of **Frigate - **The Frigate Logo** **Note on Common Law Rights:** -Frigate LLC asserts all common law rights in these Marks. The absence of a federal registration symbol (®) does not constitute a waiver of our intellectual property rights. +Frigate, Inc. asserts all common law rights in these Marks. The absence of a federal registration symbol (®) does not constitute a waiver of our intellectual property rights. ## 2. Interaction with the MIT License @@ -25,7 +25,7 @@ The software in this repository is licensed under the [MIT License](LICENSE). - The **Code** is free to use, modify, and distribute under the MIT terms. - The **Brand (Trademarks)** is **NOT** licensed under MIT. -You may not use the Marks in any way that is not explicitly permitted by this policy or by written agreement with Frigate LLC. +You may not use the Marks in any way that is not explicitly permitted by this policy or by written agreement with Frigate, Inc. ## 3. Acceptable Use @@ -40,7 +40,7 @@ You may use the Marks without prior written permission in the following specific You may **NOT** use the Marks in the following ways: - **Commercial Products:** You may not use "Frigate" in the name of a commercial product, service, or app (e.g., selling an app named _"Frigate Viewer"_ is prohibited). -- **Implying Affiliation:** You may not use the Marks in a way that suggests your project is official, sponsored by, or endorsed by Frigate LLC. +- **Implying Affiliation:** You may not use the Marks in a way that suggests your project is official, sponsored by, or endorsed by Frigate, Inc. - **Confusing Forks:** If you fork this repository to create a derivative work, you **must** remove the Frigate logo and rename your project to avoid user confusion. You cannot distribute a modified version of the software under the name "Frigate". - **Domain Names:** You may not register domain names containing "Frigate" that are likely to confuse users (e.g., `frigate-official-support.com`). diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 8ab9d857e6..dca948953f 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -170,7 +170,7 @@ const config: Config = { ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} Frigate LLC`, + copyright: `Copyright © ${new Date().getFullYear()} Frigate, Inc.`, }, }, plugins: [ diff --git a/docs/static/img/branding/LICENSE.md b/docs/static/img/branding/LICENSE.md index 4975f03f90..abb0cb3503 100644 --- a/docs/static/img/branding/LICENSE.md +++ b/docs/static/img/branding/LICENSE.md @@ -1,12 +1,12 @@ # COPYRIGHT AND TRADEMARK NOTICE The images, logos, and icons contained in this directory (the "Brand Assets") are -proprietary to Frigate LLC and are NOT covered by the MIT License governing the +proprietary to Frigate, Inc. and are NOT covered by the MIT License governing the rest of this repository. 1. TRADEMARK STATUS The "Frigate" name and the accompanying logo are common law trademarks™ of - Frigate LLC. Frigate LLC reserves all rights to these marks. + Frigate, Inc. Frigate, Inc. reserves all rights to these marks. 2. LIMITED PERMISSION FOR USE Permission is hereby granted to display these Brand Assets strictly for the @@ -17,9 +17,9 @@ rest of this repository. 3. RESTRICTIONS You may NOT: a. Use these Brand Assets to represent a derivative work (fork) as an official - product of Frigate LLC. + product of Frigate, Inc. b. Use these Brand Assets in a way that implies endorsement, sponsorship, or - commercial affiliation with Frigate LLC. + commercial affiliation with Frigate, Inc. c. Modify or alter the Brand Assets. If you fork this repository with the intent to distribute a modified or competing @@ -27,4 +27,4 @@ version of the software, you must replace these Brand Assets with your own original content. ALL RIGHTS RESERVED. -Copyright (c) 2025 Frigate LLC. +Copyright (c) 2026 Frigate, Inc. diff --git a/web/images/branding/LICENSE b/web/images/branding/LICENSE index 42913f9ca1..6dbfbe6449 100644 --- a/web/images/branding/LICENSE +++ b/web/images/branding/LICENSE @@ -1,12 +1,12 @@ # COPYRIGHT AND TRADEMARK NOTICE The images, logos, and icons contained in this directory (the "Brand Assets") are -proprietary to Frigate LLC and are NOT covered by the MIT License governing the +proprietary to Frigate, Inc. and are NOT covered by the MIT License governing the rest of this repository. 1. TRADEMARK STATUS The "Frigate" name and the accompanying logo are common law trademarks™ of - Frigate LLC. Frigate LLC reserves all rights to these marks. + Frigate, Inc. Frigate, Inc. reserves all rights to these marks. 2. LIMITED PERMISSION FOR USE Permission is hereby granted to display these Brand Assets strictly for the @@ -17,9 +17,9 @@ rest of this repository. 3. RESTRICTIONS You may NOT: a. Use these Brand Assets to represent a derivative work (fork) as an official - product of Frigate LLC. + product of Frigate, Inc. b. Use these Brand Assets in a way that implies endorsement, sponsorship, or - commercial affiliation with Frigate LLC. + commercial affiliation with Frigate, Inc. c. Modify or alter the Brand Assets. If you fork this repository with the intent to distribute a modified or competing @@ -30,4 +30,4 @@ For full usage guidelines, strictly see the TRADEMARK.md file in the repository root. ALL RIGHTS RESERVED. -Copyright (c) 2025 Frigate LLC. +Copyright (c) 2026 Frigate, Inc. From 047ae191914f7559a3064e9bb9d75aa62527afeb Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 4 Jan 2026 11:12:03 -0700 Subject: [PATCH 72/78] Miscellaneous fixes (0.17 Beta) (#21489) * Correctly set query padding * Adjust AMD headers and add community badge * Simplify getting started guide for camera wizard * add optimizing performance guide * tweaks * fix character issue * fix more characters * fix links * fix more links * Refactor new docs * Add import * Fix link * Don't list hardware * Reduce redundancy in titles * Add note about Intel NPU and addon * Fix ability to specify if card is using heading * improve display of area percentage * fix text color on genai summary chip * fix indentation in genai docs * Adjust default config model to align with recommended * add correct genai key --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- docs/docs/configuration/genai.md | 2 +- docs/docs/configuration/genai/objects.md | 3 +- .../hardware_acceleration_video.md | 10 ++- docs/docs/frigate/installation.md | 1 + docs/docs/guides/getting_started.md | 28 ++----- docs/docs/troubleshooting/cpu.md | 73 +++++++++++++++++++ docs/docs/troubleshooting/dummy-camera.md | 2 +- docs/docs/troubleshooting/edgetpu.md | 2 +- docs/docs/troubleshooting/gpu.md | 2 +- docs/docs/troubleshooting/memory.md | 2 +- docs/docs/troubleshooting/recordings.md | 2 +- docs/sidebars.ts | 23 +++++- frigate/api/media.py | 2 +- web/src/components/card/EmptyCard.tsx | 12 ++- .../overlay/chip/GenAISummaryChip.tsx | 4 +- .../overlay/detail/TrackingDetails.tsx | 8 +- web/src/components/timeline/DetailStream.tsx | 10 ++- web/src/views/events/EventView.tsx | 3 +- 18 files changed, 143 insertions(+), 46 deletions(-) create mode 100644 docs/docs/troubleshooting/cpu.md diff --git a/docs/docs/configuration/genai.md b/docs/docs/configuration/genai.md index 9915628714..8756087667 100644 --- a/docs/docs/configuration/genai.md +++ b/docs/docs/configuration/genai.md @@ -211,7 +211,7 @@ You are also able to define custom prompts in your configuration. genai: provider: ollama base_url: http://localhost:11434 - model: llava + model: qwen3-vl:8b-instruct objects: prompt: "Analyze the {label} in these images from the {camera} security camera. Focus on the actions, behavior, and potential intent of the {label}, rather than just describing its appearance." diff --git a/docs/docs/configuration/genai/objects.md b/docs/docs/configuration/genai/objects.md index e5aa92cc0c..e3ae31393d 100644 --- a/docs/docs/configuration/genai/objects.md +++ b/docs/docs/configuration/genai/objects.md @@ -39,9 +39,10 @@ You are also able to define custom prompts in your configuration. genai: provider: ollama base_url: http://localhost:11434 - model: llava + model: qwen3-vl:8b-instruct objects: + genai: prompt: "Analyze the {label} in these images from the {camera} security camera. Focus on the actions, behavior, and potential intent of the {label}, rather than just describing its appearance." object_prompts: person: "Examine the main person in these images. What are they doing and what might their actions suggest about their intent (e.g., approaching a door, leaving an area, standing still)? Do not describe the surroundings or static details." diff --git a/docs/docs/configuration/hardware_acceleration_video.md b/docs/docs/configuration/hardware_acceleration_video.md index f7368e623d..bbbf5a6406 100644 --- a/docs/docs/configuration/hardware_acceleration_video.md +++ b/docs/docs/configuration/hardware_acceleration_video.md @@ -3,6 +3,8 @@ id: hardware_acceleration_video title: Video Decoding --- +import CommunityBadge from '@site/src/components/CommunityBadge'; + # Video Decoding It is highly recommended to use an integrated or discrete GPU for hardware acceleration video decoding in Frigate. @@ -31,11 +33,11 @@ Frigate supports presets for optimal hardware accelerated video decoding: - [Raspberry Pi](#raspberry-pi-34): Frigate can utilize the media engine in the Raspberry Pi 3 and 4 to slightly accelerate video decoding. -**Nvidia Jetson** +**Nvidia Jetson** - [Jetson](#nvidia-jetson): Frigate can utilize the media engine in Jetson hardware to accelerate video decoding. -**Rockchip** +**Rockchip** - [RKNN](#rockchip-platform): Frigate can utilize the media engine in RockChip SOCs to accelerate video decoding. @@ -184,11 +186,11 @@ If you are passing in a device path, make sure you've passed the device through Frigate can utilize modern AMD integrated GPUs and AMD GPUs to accelerate video decoding using VAAPI. -:::note +### Configuring Radeon Driver You need to change the driver to `radeonsi` by adding the following environment variable `LIBVA_DRIVER_NAME=radeonsi` to your docker-compose file or [in the `config.yml` for HA Add-on users](advanced.md#environment_vars). -::: +### Via VAAPI VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. diff --git a/docs/docs/frigate/installation.md b/docs/docs/frigate/installation.md index 29ddbd79f5..70b4b5bc14 100644 --- a/docs/docs/frigate/installation.md +++ b/docs/docs/frigate/installation.md @@ -465,6 +465,7 @@ There are important limitations in HA OS to be aware of: - Separate local storage for media is not yet supported by Home Assistant - AMD GPUs are not supported because HA OS does not include the mesa driver. +- Intel NPUs are not supported because HA OS does not include the NPU firmware. - Nvidia GPUs are not supported because addons do not support the nvidia runtime. ::: diff --git a/docs/docs/guides/getting_started.md b/docs/docs/guides/getting_started.md index 3b07d8d5ba..8c90a6f336 100644 --- a/docs/docs/guides/getting_started.md +++ b/docs/docs/guides/getting_started.md @@ -134,31 +134,13 @@ Now you should be able to start Frigate by running `docker compose up -d` from w This section assumes that you already have an environment setup as described in [Installation](../frigate/installation.md). You should also configure your cameras according to the [camera setup guide](/frigate/camera_setup). Pay particular attention to the section on choosing a detect resolution. -### Step 1: Add a detect stream +### Step 1: Start Frigate -First we will add the detect stream for the camera: +At this point you should be able to start Frigate and a basic config will be created automatically. -```yaml -mqtt: - enabled: False - -cameras: - name_of_your_camera: # <------ Name the camera - enabled: True - ffmpeg: - inputs: - - path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection - roles: - - detect -``` - -### Step 2: Start Frigate - -At this point you should be able to start Frigate and see the video feed in the UI. - -If you get an error image from the camera, this means ffmpeg was not able to get the video feed from your camera. Check the logs for error messages from ffmpeg. The default ffmpeg arguments are designed to work with H264 RTSP cameras that support TCP connections. +### Step 2: Add a camera -FFmpeg arguments for other types of cameras can be found [here](../configuration/camera_specific.md). +You can click the `Add Camera` button to use the camera setup wizard to get your first camera added into Frigate. ### Step 3: Configure hardware acceleration (recommended) @@ -173,7 +155,7 @@ services: frigate: ... devices: - - /dev/dri/renderD128:/dev/dri/renderD128 # for intel hwaccel, needs to be updated for your hardware + - /dev/dri/renderD128:/dev/dri/renderD128 # for intel & amd hwaccel, needs to be updated for your hardware ... ``` diff --git a/docs/docs/troubleshooting/cpu.md b/docs/docs/troubleshooting/cpu.md new file mode 100644 index 0000000000..a9f449ad88 --- /dev/null +++ b/docs/docs/troubleshooting/cpu.md @@ -0,0 +1,73 @@ +--- +id: cpu +title: High CPU Usage +--- + +High CPU usage can impact Frigate's performance and responsiveness. This guide outlines the most effective configuration changes to help reduce CPU consumption and optimize resource usage. + +## 1. Hardware Acceleration for Video Decoding + +**Priority: Critical** + +Video decoding is one of the most CPU-intensive tasks in Frigate. While an AI accelerator handles object detection, it does not assist with decoding video streams. Hardware acceleration (hwaccel) offloads this work to your GPU or specialized video decode hardware, significantly reducing CPU usage and enabling you to support more cameras on the same hardware. + +### Key Concepts + +**Resolution & FPS Impact:** The decoding burden grows exponentially with resolution and frame rate. A 4K stream at 30 FPS requires roughly 4 times the processing power of a 1080p stream at the same frame rate, and doubling the frame rate doubles the decode workload. This is why hardware acceleration becomes critical when working with multiple high-resolution cameras. + +**Hardware Acceleration Benefits:** By using dedicated video decode hardware, you can: + +- Significantly reduce CPU usage per camera stream +- Support 2-3x more cameras on the same hardware +- Free up CPU resources for motion detection and other Frigate processes +- Reduce system heat and power consumption + +### Configuration + +Frigate provides preset configurations for common hardware acceleration scenarios. Set up `hwaccel_args` based on your hardware in your [configuration](../configuration/reference) as described in the [getting started guide](../guides/getting_started). + +### Troubleshooting Hardware Acceleration + +If hardware acceleration isn't working: + +1. Check Frigate logs for FFmpeg errors related to hwaccel +2. Verify the hardware device is accessible inside the container +3. Ensure your camera streams use H.264 or H.265 codecs (most common) +4. Try different presets if the automatic detection fails +5. Check that your GPU drivers are properly installed on the host system + +## 2. Detector Selection and Configuration + +**Priority: Critical** + +Choosing the right detector for your hardware is the single most important factor for detection performance. The detector is responsible for running the AI model that identifies objects in video frames. Different detector types have vastly different performance characteristics and hardware requirements, as detailed in the [hardware documentation](../frigate/hardware). + +### Understanding Detector Performance + +Frigate uses motion detection as a first-line check before running expensive object detection, as explained in the [motion detection documentation](../configuration/motion_detection). When motion is detected, Frigate creates a "region" (the green boxes in the debug viewer) and sends it to the detector. The detector's inference speed determines how many detections per second your system can handle. + +**Calculating Detector Capacity:** Your detector has a finite capacity measured in detections per second. With an inference speed of 10ms, your detector can handle approximately 100 detections per second (1000ms / 10ms = 100).If your cameras collectively require more than this capacity, you'll experience delays, missed detections, or the system will fall behind. + +### Choosing the Right Detector + +Different detectors have vastly different performance characteristics, see the expected performance for object detectors in [the hardware docs](../frigate/hardware) + +### Multiple Detector Instances + +When a single detector cannot keep up with your camera count, some detector types (`openvino`, `onnx`) allow you to define multiple detector instances to share the workload. This is particularly useful with GPU-based detectors that have sufficient VRAM to run multiple inference processes. + +For detailed instructions on configuring multiple detectors, see the [Object Detectors documentation](../configuration/object_detectors). + + +**When to add a second detector:** + +- Skipped FPS is consistently > 0 even during normal activity + +### Model Selection and Optimization + +The model you use significantly impacts detector performance. Frigate provides default models optimized for each detector type, but you can customize them as described in the [detector documentation](../configuration/object_detectors). + +**Model Size Trade-offs:** + +- Smaller models (320x320): Faster inference, Frigate is specifically optimized for a 320x320 size model. +- Larger models (640x640): Slower inference, can sometimes have higher accuracy on very large objects that take up a majority of the frame. \ No newline at end of file diff --git a/docs/docs/troubleshooting/dummy-camera.md b/docs/docs/troubleshooting/dummy-camera.md index 7e7c26ae92..c510f2ba82 100644 --- a/docs/docs/troubleshooting/dummy-camera.md +++ b/docs/docs/troubleshooting/dummy-camera.md @@ -1,6 +1,6 @@ --- id: dummy-camera -title: Troubleshooting Detection +title: Analyzing Object Detection --- When investigating object detection or tracking problems, it can be helpful to replay an exported video as a temporary "dummy" camera. This lets you reproduce issues locally, iterate on configuration (detections, zones, enrichment settings), and capture logs and clips for analysis. diff --git a/docs/docs/troubleshooting/edgetpu.md b/docs/docs/troubleshooting/edgetpu.md index af94a3d84a..97b2b00402 100644 --- a/docs/docs/troubleshooting/edgetpu.md +++ b/docs/docs/troubleshooting/edgetpu.md @@ -1,6 +1,6 @@ --- id: edgetpu -title: Troubleshooting EdgeTPU +title: EdgeTPU Errors --- ## USB Coral Not Detected diff --git a/docs/docs/troubleshooting/gpu.md b/docs/docs/troubleshooting/gpu.md index a5b48246af..6399f92d8b 100644 --- a/docs/docs/troubleshooting/gpu.md +++ b/docs/docs/troubleshooting/gpu.md @@ -1,6 +1,6 @@ --- id: gpu -title: Troubleshooting GPU +title: GPU Errors --- ## OpenVINO diff --git a/docs/docs/troubleshooting/memory.md b/docs/docs/troubleshooting/memory.md index c74729e5f6..d062944e5c 100644 --- a/docs/docs/troubleshooting/memory.md +++ b/docs/docs/troubleshooting/memory.md @@ -1,6 +1,6 @@ --- id: memory -title: Memory Troubleshooting +title: Memory Usage --- Frigate includes built-in memory profiling using [memray](https://bloomberg.github.io/memray/) to help diagnose memory issues. This feature allows you to profile specific Frigate modules to identify memory leaks, excessive allocations, or other memory-related problems. diff --git a/docs/docs/troubleshooting/recordings.md b/docs/docs/troubleshooting/recordings.md index d26a3614e9..b1f180a82d 100644 --- a/docs/docs/troubleshooting/recordings.md +++ b/docs/docs/troubleshooting/recordings.md @@ -1,6 +1,6 @@ --- id: recordings -title: Troubleshooting Recordings +title: Recordings Errors --- ## I have Frigate configured for motion recording only, but it still seems to be recording even with no motion. Why? diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 4c8effeecd..ea0d2f5c81 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -129,10 +129,27 @@ const sidebars: SidebarsConfig = { Troubleshooting: [ "troubleshooting/faqs", "troubleshooting/recordings", - "troubleshooting/gpu", - "troubleshooting/edgetpu", - "troubleshooting/memory", "troubleshooting/dummy-camera", + { + type: "category", + label: "Troubleshooting Hardware", + link: { + type: "generated-index", + title: "Troubleshooting Hardware", + description: "Troubleshooting Problems with Hardware", + }, + items: ["troubleshooting/gpu", "troubleshooting/edgetpu"], + }, + { + type: "category", + label: "Troubleshooting Resource Usage", + link: { + type: "generated-index", + title: "Troubleshooting Resource Usage", + description: "Troubleshooting issues with resource usage", + }, + items: ["troubleshooting/cpu", "troubleshooting/memory"], + }, ], Development: [ "development/contributing", diff --git a/frigate/api/media.py b/frigate/api/media.py index 783b42e97b..971bfef83b 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -1935,7 +1935,7 @@ async def label_clip(request: Request, camera_name: str, label: str): try: event = event_query.get() - return await event_clip(request, event.id) + return await event_clip(request, event.id, 0) except DoesNotExist: return JSONResponse( content={"success": False, "message": "Event not found"}, status_code=404 diff --git a/web/src/components/card/EmptyCard.tsx b/web/src/components/card/EmptyCard.tsx index 8d6b67a684..00b22d1977 100644 --- a/web/src/components/card/EmptyCard.tsx +++ b/web/src/components/card/EmptyCard.tsx @@ -8,6 +8,7 @@ type EmptyCardProps = { className?: string; icon: React.ReactNode; title: string; + titleHeading?: boolean; description?: string; buttonText?: string; link?: string; @@ -16,14 +17,23 @@ export function EmptyCard({ className, icon, title, + titleHeading = true, description, buttonText, link, }: EmptyCardProps) { + let TitleComponent; + + if (titleHeading) { + TitleComponent = {title}; + } else { + TitleComponent =
    {title}
    ; + } + return (
    {icon} - {title} + {TitleComponent} {description && (
    {description}
    )} diff --git a/web/src/components/overlay/chip/GenAISummaryChip.tsx b/web/src/components/overlay/chip/GenAISummaryChip.tsx index ead0104b14..46fefdc676 100644 --- a/web/src/components/overlay/chip/GenAISummaryChip.tsx +++ b/web/src/components/overlay/chip/GenAISummaryChip.tsx @@ -26,7 +26,9 @@ export function GenAISummaryChip({ review }: GenAISummaryChipProps) { className={cn( "absolute left-1/2 top-8 z-30 flex max-w-[90vw] -translate-x-[50%] cursor-pointer select-none items-center gap-2 rounded-full p-2 text-sm transition-all duration-500", isVisible ? "translate-y-0 opacity-100" : "-translate-y-4 opacity-0", - isDesktop ? "bg-card" : "bg-secondary-foreground", + isDesktop + ? "bg-card text-primary" + : "bg-secondary-foreground text-white", )} > diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx index 42535d5e11..80471b8bdf 100644 --- a/web/src/components/overlay/detail/TrackingDetails.tsx +++ b/web/src/components/overlay/detail/TrackingDetails.tsx @@ -849,7 +849,11 @@ function LifecycleIconRow({ () => Array.isArray(item.data.attribute_box) && item.data.attribute_box.length >= 4 - ? (item.data.attribute_box[2] * item.data.attribute_box[3]).toFixed(4) + ? ( + item.data.attribute_box[2] * + item.data.attribute_box[3] * + 100 + ).toFixed(2) : undefined, [item.data.attribute_box], ); @@ -857,7 +861,7 @@ function LifecycleIconRow({ const areaPct = useMemo( () => Array.isArray(item.data.box) && item.data.box.length >= 4 - ? (item.data.box[2] * item.data.box[3]).toFixed(4) + ? (item.data.box[2] * item.data.box[3] * 100).toFixed(2) : undefined, [item.data.box], ); diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index 9258ca457b..c6413ed976 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -744,7 +744,7 @@ function LifecycleItem({ const areaPct = useMemo( () => Array.isArray(item?.data.box) && item?.data.box.length >= 4 - ? (item?.data.box[2] * item?.data.box[3]).toFixed(4) + ? (item?.data.box[2] * item?.data.box[3] * 100).toFixed(2) : undefined, [item], ); @@ -766,7 +766,11 @@ function LifecycleItem({ () => Array.isArray(item?.data.attribute_box) && item?.data.attribute_box.length >= 4 - ? (item?.data.attribute_box[2] * item?.data.attribute_box[3]).toFixed(4) + ? ( + item?.data.attribute_box[2] * + item?.data.attribute_box[3] * + 100 + ).toFixed(2) : undefined, [item], ); @@ -845,7 +849,7 @@ function LifecycleItem({ {areaPx !== undefined && areaPct !== undefined ? ( - {areaPx} {t("information.pixels", { ns: "common" })}{" "} + {t("information.pixels", { ns: "common", area: areaPx })}{" "} ·{" "} {areaPct}% diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 9e015dfe47..70067ff5c0 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -762,8 +762,9 @@ function DetectionReview({ {!loading && currentItems?.length === 0 && ( } /> From c8f55ac41f050853a62243583a5271f5293447e5 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 6 Jan 2026 08:07:51 -0700 Subject: [PATCH 73/78] Restrict go2rtc exec sources by default (#21543) * Restrict go2rtc exec sources by default * add docs --- .../rootfs/usr/local/go2rtc/create_config.py | 47 ++++++++++++++++--- docs/docs/configuration/restream.md | 25 ++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/docker/main/rootfs/usr/local/go2rtc/create_config.py b/docker/main/rootfs/usr/local/go2rtc/create_config.py index 1b44a8067a..8b8aef6d98 100644 --- a/docker/main/rootfs/usr/local/go2rtc/create_config.py +++ b/docker/main/rootfs/usr/local/go2rtc/create_config.py @@ -22,6 +22,11 @@ yaml = YAML() +# Check if arbitrary exec sources are allowed (defaults to False for security) +ALLOW_ARBITRARY_EXEC = os.environ.get( + "GO2RTC_ALLOW_ARBITRARY_EXEC", "false" +).lower() in ("true", "1", "yes") + FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")} # read docker secret files as env vars too if os.path.isdir("/run/secrets"): @@ -109,14 +114,26 @@ elif go2rtc_config["ffmpeg"].get("rtsp") is None: go2rtc_config["ffmpeg"]["rtsp"] = rtsp_args -for name in go2rtc_config.get("streams", {}): + +def is_restricted_source(stream_source: str) -> bool: + """Check if a stream source is restricted (echo, expr, or exec).""" + return stream_source.strip().startswith(("echo:", "expr:", "exec:")) + + +for name in list(go2rtc_config.get("streams", {})): stream = go2rtc_config["streams"][name] if isinstance(stream, str): try: - go2rtc_config["streams"][name] = go2rtc_config["streams"][name].format( - **FRIGATE_ENV_VARS - ) + formatted_stream = stream.format(**FRIGATE_ENV_VARS) + if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream): + print( + f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. " + f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources." + ) + del go2rtc_config["streams"][name] + continue + go2rtc_config["streams"][name] = formatted_stream except KeyError as e: print( "[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info." @@ -124,15 +141,33 @@ sys.exit(e) elif isinstance(stream, list): - for i, stream in enumerate(stream): + filtered_streams = [] + for i, stream_item in enumerate(stream): try: - go2rtc_config["streams"][name][i] = stream.format(**FRIGATE_ENV_VARS) + formatted_stream = stream_item.format(**FRIGATE_ENV_VARS) + if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream): + print( + f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. " + f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources." + ) + continue + + filtered_streams.append(formatted_stream) except KeyError as e: print( "[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info." ) sys.exit(e) + if filtered_streams: + go2rtc_config["streams"][name] = filtered_streams + else: + print( + f"[ERROR] Stream '{name}' was removed because all sources were restricted (echo/expr/exec). " + f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources." + ) + del go2rtc_config["streams"][name] + # add birdseye restream stream if enabled if config.get("birdseye", {}).get("restream", False): birdseye: dict[str, Any] = config.get("birdseye") diff --git a/docs/docs/configuration/restream.md b/docs/docs/configuration/restream.md index 9b93a60eb9..d6a623ccbc 100644 --- a/docs/docs/configuration/restream.md +++ b/docs/docs/configuration/restream.md @@ -185,10 +185,35 @@ In this configuration: - `front_door` stream is used by Frigate for viewing, recording, and detection. The `#backchannel=0` parameter prevents go2rtc from establishing the audio output backchannel, so it won't block two-way talk access. - `front_door_twoway` stream is used for two-way talk functionality. This stream can be used by Frigate's WebRTC viewer when two-way talk is enabled, or by other applications (like Home Assistant Advanced Camera Card) that need access to the camera's audio output channel. +## Security: Restricted Stream Sources + +For security reasons, the `echo:`, `expr:`, and `exec:` stream sources are disabled by default in go2rtc. These sources allow arbitrary command execution and can pose security risks if misconfigured. + +If you attempt to use these sources in your configuration, the streams will be removed and an error message will be printed in the logs. + +To enable these sources, you must set the environment variable `GO2RTC_ALLOW_ARBITRARY_EXEC=true`. This can be done in your Docker Compose file or container environment: + +```yaml +environment: + - GO2RTC_ALLOW_ARBITRARY_EXEC=true +``` + +:::warning + +Enabling arbitrary exec sources allows execution of arbitrary commands through go2rtc stream configurations. Only enable this if you understand the security implications and trust all sources of your configuration. + +::: + ## Advanced Restream Configurations The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.9.10#source-exec) source in go2rtc can be used for custom ffmpeg commands. An example is below: +:::warning + +The `exec:`, `echo:`, and `expr:` sources are disabled by default for security. You must set `GO2RTC_ALLOW_ARBITRARY_EXEC=true` to use them. See [Security: Restricted Stream Sources](#security-restricted-stream-sources) for more information. + +::: + NOTE: The output will need to be passed with two curly braces `{{output}}` ```yaml From 99d48ecbc3195a8a1924f9ba67d9a4205d395876 Mon Sep 17 00:00:00 2001 From: Sai Bharat Kumar <48505814+saibharath48@users.noreply.github.com> Date: Wed, 7 Jan 2026 19:52:05 +0530 Subject: [PATCH 74/78] docs: fix alt text and capitalization in documentation (#21551) - Fix incorrect alt text in README.md for mask and zone editor screenshot - Capitalize 'Frigate' in audio_detectors.md for consistency --- README.md | 2 +- docs/docs/configuration/audio_detectors.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c25585a1d7..1fb158b191 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Please see our [Trademark Policy](TRADEMARK.md) for details on acceptable use of ### Built-in mask and zone editor
    -Multi-camera scrubbing +Built-in mask and zone editor
    ## Translations diff --git a/docs/docs/configuration/audio_detectors.md b/docs/docs/configuration/audio_detectors.md index f2ff99b6b4..9576679147 100644 --- a/docs/docs/configuration/audio_detectors.md +++ b/docs/docs/configuration/audio_detectors.md @@ -50,7 +50,7 @@ cameras: ### Configuring Minimum Volume -The audio detector uses volume levels in the same way that motion in a camera feed is used for object detection. This means that frigate will not run audio detection unless the audio volume is above the configured level in order to reduce resource usage. Audio levels can vary widely between camera models so it is important to run tests to see what volume levels are. The Debug view in the Frigate UI has an Audio tab for cameras that have the `audio` role assigned where a graph and the current levels are is displayed. The `min_volume` parameter should be set to the minimum the `RMS` level required to run audio detection. +The audio detector uses volume levels in the same way that motion in a camera feed is used for object detection. This means that Frigate will not run audio detection unless the audio volume is above the configured level in order to reduce resource usage. Audio levels can vary widely between camera models so it is important to run tests to see what volume levels are. The Debug view in the Frigate UI has an Audio tab for cameras that have the `audio` role assigned where a graph and the current levels are is displayed. The `min_volume` parameter should be set to the minimum the `RMS` level required to run audio detection. :::tip From 74d14cb8cae775f333f35e4272b41ddfcb4fda93 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:29:19 -0600 Subject: [PATCH 75/78] Miscellaneous Fixes (0.17 beta) (#21558) * mse player improvements - fix WebSocket race condition by registering message handlers before sending and avoid closing CONNECTING sockets to eliminate "Socket is not connected" errors. - attempt to resolve Safari MSE timeout and handler issues by wrapping temporary handlers in try/catch and stabilizing the permanent mse handler so SourceBuffer setup completes reliably. - add intentional disconnect tracking to prevent unwanted reconnects during navigation/StrictMode cycles * Update Ollama * additional MSE tweaks * Turn activity context prompt into a yaml example --------- Co-authored-by: Nicolas Mowen --- docker/main/requirements-wheels.txt | 2 +- .../configuration/genai/review_summaries.md | 71 ++++---- web/src/components/player/MsePlayer.tsx | 159 +++++++++++++++--- 3 files changed, 174 insertions(+), 58 deletions(-) diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 1a1043b19c..30e32ba6a4 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -48,7 +48,7 @@ onnxruntime == 1.22.* transformers == 4.45.* # Generative AI google-generativeai == 0.8.* -ollama == 0.5.* +ollama == 0.6.* openai == 1.65.* # push notifications py-vapid == 1.9.* diff --git a/docs/docs/configuration/genai/review_summaries.md b/docs/docs/configuration/genai/review_summaries.md index 04a5b5e94b..99ee48d0ff 100644 --- a/docs/docs/configuration/genai/review_summaries.md +++ b/docs/docs/configuration/genai/review_summaries.md @@ -31,40 +31,43 @@ Each installation and even camera can have different parameters for what is cons
    Default Activity Context Prompt -``` -### Normal Activity Indicators (Level 0) -- Known/verified people in any zone at any time -- People with pets in residential areas -- Deliveries or services during daytime/evening (6 AM - 10 PM): carrying packages to doors/porches, placing items, leaving -- Services/maintenance workers with visible tools, uniforms, or service vehicles during daytime -- Activity confined to public areas only (sidewalks, streets) without entering property at any time - -### Suspicious Activity Indicators (Level 1) -- **Testing or attempting to open doors/windows/handles on vehicles or buildings** — ALWAYS Level 1 regardless of time or duration -- **Unidentified person in private areas (driveways, near vehicles/buildings) during late night/early morning (11 PM - 5 AM)** — ALWAYS Level 1 regardless of activity or duration -- Taking items that don't belong to them (packages, objects from porches/driveways) -- Climbing or jumping fences/barriers to access property -- Attempting to conceal actions or items from view -- Prolonged loitering: remaining in same area without visible purpose throughout most of the sequence - -### Critical Threat Indicators (Level 2) -- Holding break-in tools (crowbars, pry bars, bolt cutters) -- Weapons visible (guns, knives, bats used aggressively) -- Forced entry in progress -- Physical aggression or violence -- Active property damage or theft in progress - -### Assessment Guidance -Evaluate in this order: - -1. **If person is verified/known** → Level 0 regardless of time or activity -2. **If person is unidentified:** - - Check time: If late night/early morning (11 PM - 5 AM) AND in private areas (driveways, near vehicles/buildings) → Level 1 - - Check actions: If testing doors/handles, taking items, climbing → Level 1 - - Otherwise, if daytime/evening (6 AM - 10 PM) with clear legitimate purpose (delivery, service worker) → Level 0 -3. **Escalate to Level 2 if:** Weapons, break-in tools, forced entry in progress, violence, or active property damage visible (escalates from Level 0 or 1) - -The mere presence of an unidentified person in private areas during late night hours is inherently suspicious and warrants human review, regardless of what activity they appear to be doing or how brief the sequence is. +```yaml +review: + genai: + activity_context_prompt: | + ### Normal Activity Indicators (Level 0) + - Known/verified people in any zone at any time + - People with pets in residential areas + - Deliveries or services during daytime/evening (6 AM - 10 PM): carrying packages to doors/porches, placing items, leaving + - Services/maintenance workers with visible tools, uniforms, or service vehicles during daytime + - Activity confined to public areas only (sidewalks, streets) without entering property at any time + + ### Suspicious Activity Indicators (Level 1) + - **Testing or attempting to open doors/windows/handles on vehicles or buildings** — ALWAYS Level 1 regardless of time or duration + - **Unidentified person in private areas (driveways, near vehicles/buildings) during late night/early morning (11 PM - 5 AM)** — ALWAYS Level 1 regardless of activity or duration + - Taking items that don't belong to them (packages, objects from porches/driveways) + - Climbing or jumping fences/barriers to access property + - Attempting to conceal actions or items from view + - Prolonged loitering: remaining in same area without visible purpose throughout most of the sequence + + ### Critical Threat Indicators (Level 2) + - Holding break-in tools (crowbars, pry bars, bolt cutters) + - Weapons visible (guns, knives, bats used aggressively) + - Forced entry in progress + - Physical aggression or violence + - Active property damage or theft in progress + + ### Assessment Guidance + Evaluate in this order: + + 1. **If person is verified/known** → Level 0 regardless of time or activity + 2. **If person is unidentified:** + - Check time: If late night/early morning (11 PM - 5 AM) AND in private areas (driveways, near vehicles/buildings) → Level 1 + - Check actions: If testing doors/handles, taking items, climbing → Level 1 + - Otherwise, if daytime/evening (6 AM - 10 PM) with clear legitimate purpose (delivery, service worker) → Level 0 + 3. **Escalate to Level 2 if:** Weapons, break-in tools, forced entry in progress, violence, or active property damage visible (escalates from Level 0 or 1) + + The mere presence of an unidentified person in private areas during late night hours is inherently suspicious and warrants human review, regardless of what activity they appear to be doing or how brief the sequence is. ```
    diff --git a/web/src/components/player/MsePlayer.tsx b/web/src/components/player/MsePlayer.tsx index bab00a2f81..1a2b1b6cbf 100644 --- a/web/src/components/player/MsePlayer.tsx +++ b/web/src/components/player/MsePlayer.tsx @@ -80,12 +80,15 @@ function MSEPlayer({ const videoRef = useRef(null); const wsRef = useRef(null); const reconnectTIDRef = useRef(null); + const intentionalDisconnectRef = useRef(false); const ondataRef = useRef<((data: ArrayBufferLike) => void) | null>(null); const onmessageRef = useRef<{ [key: string]: (msg: { value: string; type: string }) => void; }>({}); const msRef = useRef(null); const mseCodecRef = useRef(null); + const mseTimeoutRef = useRef | null>(null); + const mseResponseReceivedRef = useRef(false); const wsURL = useMemo(() => { return `${baseUrl.replace(/^http/, "ws")}live/mse/api/ws?src=${camera}`; @@ -152,8 +155,11 @@ function MSEPlayer({ }, []); const onConnect = useCallback(() => { - if (!videoRef.current?.isConnected || !wsURL || wsRef.current) return false; + if (!videoRef.current?.isConnected || !wsURL || wsRef.current) { + return false; + } + intentionalDisconnectRef.current = false; setWsState(WebSocket.CONNECTING); setConnectTS(Date.now()); @@ -172,13 +178,50 @@ function MSEPlayer({ setBufferTimeout(undefined); } + // Clear any pending MSE timeout + if (mseTimeoutRef.current !== null) { + clearTimeout(mseTimeoutRef.current); + mseTimeoutRef.current = null; + } + + // Clear any pending reconnect attempts + if (reconnectTIDRef.current !== null) { + clearTimeout(reconnectTIDRef.current); + reconnectTIDRef.current = null; + } + setIsPlaying(false); if (wsRef.current) { - setWsState(WebSocket.CLOSED); - wsRef.current.close(); + const ws = wsRef.current; wsRef.current = null; + const currentReadyState = ws.readyState; + + intentionalDisconnectRef.current = true; + setWsState(WebSocket.CLOSED); + + // Remove event listeners to prevent them firing during close + try { + ws.removeEventListener("open", onOpen); + ws.removeEventListener("close", onClose); + } catch { + // Ignore errors removing listeners + } + + // Only call close() if the socket is OPEN or CLOSING + // For CONNECTING or CLOSED sockets, just let it die + if ( + currentReadyState === WebSocket.OPEN || + currentReadyState === WebSocket.CLOSING + ) { + try { + ws.close(); + } catch { + // Ignore close errors + } + } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [bufferTimeout]); const handlePause = useCallback(() => { @@ -188,7 +231,14 @@ function MSEPlayer({ } }, [isPlaying, playbackEnabled]); - const onOpen = () => { + const onOpen = useCallback(() => { + // If we were marked for intentional disconnect while connecting, close immediately + if (intentionalDisconnectRef.current) { + wsRef.current?.close(); + wsRef.current = null; + return; + } + setWsState(WebSocket.OPEN); wsRef.current?.addEventListener("message", (ev) => { @@ -205,10 +255,27 @@ function MSEPlayer({ ondataRef.current = null; onmessageRef.current = {}; + // Reset the MSE response flag for this new connection + mseResponseReceivedRef.current = false; + + // Create a fresh MediaSource for this connection to avoid stale sourceopen events + // from previous connections interfering with this one + const MediaSourceConstructor = + "ManagedMediaSource" in window ? window.ManagedMediaSource : MediaSource; + // @ts-expect-error for typing + msRef.current = new MediaSourceConstructor(); + onMse(); - }; + // onMse is defined below and stable + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const reconnect = (timeout?: number) => { + // Don't reconnect if intentional disconnect was flagged + if (intentionalDisconnectRef.current) { + return; + } + setWsState(WebSocket.CONNECTING); wsRef.current = null; @@ -221,28 +288,79 @@ function MSEPlayer({ }, delay); }; - const onClose = () => { + const onClose = useCallback(() => { + // Don't reconnect if this was an intentional disconnect + if (intentionalDisconnectRef.current) { + // Reset the flag so future connects are allowed + intentionalDisconnectRef.current = false; + return; + } + if (wsState === WebSocket.CLOSED) return; reconnect(); - }; + // reconnect is defined below and stable + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [wsState]); const sendWithTimeout = (value: object, timeout: number) => { return new Promise((resolve, reject) => { + // Don't start timeout if WS isn't connected - this can happen when + // sourceopen fires from a previous connection after we've already disconnected + if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { + // Reject so caller knows this didn't work + reject(new Error("WebSocket not connected")); + return; + } + + // If we've already received an MSE response for this connection, don't start another timeout + if (mseResponseReceivedRef.current) { + resolve(); + return; + } + + // Clear any existing MSE timeout from a previous attempt + if (mseTimeoutRef.current !== null) { + clearTimeout(mseTimeoutRef.current); + mseTimeoutRef.current = null; + } + const timeoutId = setTimeout(() => { - reject(new Error("Timeout waiting for response")); + // Only reject if we haven't received a response yet + if (!mseResponseReceivedRef.current) { + mseTimeoutRef.current = null; + reject(new Error("Timeout waiting for response")); + } }, timeout); - send(value); + mseTimeoutRef.current = timeoutId; // Override the onmessageRef handler for mse type to resolve the promise on response const originalHandler = onmessageRef.current["mse"]; onmessageRef.current["mse"] = (msg) => { if (msg.type === "mse") { - clearTimeout(timeoutId); - if (originalHandler) originalHandler(msg); + // Mark that we've received the response + mseResponseReceivedRef.current = true; + + // Clear the timeout (use ref to clear the current one, not closure) + if (mseTimeoutRef.current !== null) { + clearTimeout(mseTimeoutRef.current); + mseTimeoutRef.current = null; + } + + // Call original handler in try-catch so errors don't prevent promise resolution + if (originalHandler) { + try { + originalHandler(msg); + } catch (e) { + // Don't reject - we got the response, just let the error bubble + } + } + resolve(); } }; + + send(value); }); }; @@ -292,13 +410,15 @@ function MSEPlayer({ }, (fallbackTimeout ?? 3) * 1000, ).catch(() => { + // Only report errors if we actually had a connection that failed + // If WS wasn't connected, this is a stale sourceopen event from a previous connection if (wsRef.current) { onDisconnect(); - } - if (isIOS || isSafari) { - handleError("mse-decode", "Safari cannot open MediaSource."); - } else { - handleError("startup", "Error opening MediaSource."); + if (isIOS || isSafari) { + handleError("mse-decode", "Safari cannot open MediaSource."); + } else { + handleError("startup", "Error opening MediaSource."); + } } }); }, @@ -532,13 +652,6 @@ function MSEPlayer({ return; } - // iOS 17.1+ uses ManagedMediaSource - const MediaSourceConstructor = - "ManagedMediaSource" in window ? window.ManagedMediaSource : MediaSource; - - // @ts-expect-error for typing - msRef.current = new MediaSourceConstructor(); - onConnect(); return () => { From f3543cfee272b41f1fbbe7eb53a97e239ebe9091 Mon Sep 17 00:00:00 2001 From: GuoQing Liu <842607283@qq.com> Date: Fri, 9 Jan 2026 05:28:18 +0800 Subject: [PATCH 76/78] I18N Miscellaneous Fixes (#21573) * fix: fix classification none tag i18n wrong * fix: fix set password dialog jwt time i18n wrong * fix: fix wizard other camera i18n * fix: fix explore tracking detail audio i18n * feat: add system processes info i18n * fix: fix live page label i18n --- web/public/locales/en/common.json | 3 +- web/public/locales/en/views/system.json | 9 ++++- .../components/card/ClassificationCard.tsx | 2 +- .../components/overlay/SetPasswordDialog.tsx | 4 ++- .../overlay/detail/TrackingDetails.tsx | 4 +-- web/src/components/player/LivePlayer.tsx | 33 ++++++++++--------- .../settings/wizard/Step1NameCamera.tsx | 4 ++- web/src/utils/dateUtil.ts | 9 +++-- web/src/utils/lifecycleUtil.ts | 5 ++- web/src/views/system/GeneralMetrics.tsx | 2 +- 10 files changed, 49 insertions(+), 26 deletions(-) diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index aa841c30b5..2ae6297a12 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -101,7 +101,8 @@ "show": "Show {{item}}", "ID": "ID", "none": "None", - "all": "All" + "all": "All", + "other": "Other" }, "list": { "two": "{{0}} and {{1}}", diff --git a/web/public/locales/en/views/system.json b/web/public/locales/en/views/system.json index ada23cdc26..da774e3022 100644 --- a/web/public/locales/en/views/system.json +++ b/web/public/locales/en/views/system.json @@ -86,7 +86,14 @@ "otherProcesses": { "title": "Other Processes", "processCpuUsage": "Process CPU Usage", - "processMemoryUsage": "Process Memory Usage" + "processMemoryUsage": "Process Memory Usage", + "series": { + "go2rtc": "go2rtc", + "recording": "recording", + "review_segment": "review segment", + "embeddings": "embeddings", + "audio_detector": "audio detector" + } } }, "storage": { diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index a8ed12f241..ffd28a0d06 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -166,7 +166,7 @@ export const ClassificationCard = forwardRef<
    {data.name == "unknown" ? t("details.unknown") - : data.name == "none" + : data.name.toLowerCase() == "none" ? t("details.none") : data.name}
    diff --git a/web/src/components/overlay/SetPasswordDialog.tsx b/web/src/components/overlay/SetPasswordDialog.tsx index c6d861a6ba..7708201aa7 100644 --- a/web/src/components/overlay/SetPasswordDialog.tsx +++ b/web/src/components/overlay/SetPasswordDialog.tsx @@ -22,6 +22,7 @@ import { useTranslation } from "react-i18next"; import { useDocDomain } from "@/hooks/use-doc-domain"; import useSWR from "swr"; import { formatSecondsToDuration } from "@/utils/dateUtil"; +import { useDateLocale } from "@/hooks/use-date-locale"; import ActivityIndicator from "../indicators/activity-indicator"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; @@ -48,12 +49,13 @@ export default function SetPasswordDialog({ const { t } = useTranslation(["views/settings", "common"]); const { getLocaleDocUrl } = useDocDomain(); const isAdmin = useIsAdmin(); + const dateLocale = useDateLocale(); const { data: config } = useSWR("config"); const refreshSeconds: number | undefined = config?.auth?.refresh_time ?? undefined; const refreshTimeLabel = refreshSeconds - ? formatSecondsToDuration(refreshSeconds) + ? formatSecondsToDuration(refreshSeconds, dateLocale) : t("time.30minutes", { ns: "common" }); // visibility toggles for password fields diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx index 80471b8bdf..f2eb111437 100644 --- a/web/src/components/overlay/detail/TrackingDetails.tsx +++ b/web/src/components/overlay/detail/TrackingDetails.tsx @@ -266,7 +266,7 @@ export function TrackingDetails({ const label = event.sub_label ? event.sub_label - : getTranslatedLabel(event.label); + : getTranslatedLabel(event.label, event.data.type); const getZoneColor = useCallback( (zoneName: string) => { @@ -998,7 +998,7 @@ function LifecycleIconRow({
    {formattedEventTimestamp}
    - {((isAdmin && config?.plus?.enabled) || item.data.box) && ( + {isAdmin && config?.plus?.enabled && item.data.box && (
    diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 9500688f57..ed359a0c92 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -16,7 +16,6 @@ import { } from "@/types/live"; import { getIconForLabel } from "@/utils/iconUtil"; import Chip from "../indicators/Chip"; -import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { cn } from "@/lib/utils"; import { TbExclamationCircle } from "react-icons/tb"; import { TooltipPortal } from "@radix-ui/react-tooltip"; @@ -26,6 +25,8 @@ import { LuVideoOff } from "react-icons/lu"; import { Trans, useTranslation } from "react-i18next"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay"; +import { getTranslatedLabel } from "@/utils/i18n"; +import { formatList } from "@/utils/stringUtil"; type LivePlayerProps = { cameraRef?: (ref: HTMLDivElement | null) => void; @@ -367,20 +368,22 @@ export default function LivePlayer({
    - {[ - ...new Set([ - ...(objects || []).map(({ label, sub_label }) => - label.endsWith("verified") - ? sub_label - : label.replaceAll("_", " "), - ), - ]), - ] - .filter((label) => label?.includes("-verified") == false) - .map((label) => capitalizeFirstLetter(label)) - .sort() - .join(", ") - .replaceAll("-verified", "")} + {formatList( + [ + ...new Set([ + ...(objects || []).map(({ label, sub_label }) => + label.endsWith("verified") + ? sub_label + : label.replaceAll("_", " "), + ), + ]), + ] + .filter((label) => label?.includes("-verified") == false) + .map((label) => + getTranslatedLabel(label.replace("-verified", "")), + ) + .sort(), + )} diff --git a/web/src/components/settings/wizard/Step1NameCamera.tsx b/web/src/components/settings/wizard/Step1NameCamera.tsx index eb0dbe9fe8..741aa4b052 100644 --- a/web/src/components/settings/wizard/Step1NameCamera.tsx +++ b/web/src/components/settings/wizard/Step1NameCamera.tsx @@ -417,7 +417,9 @@ export default function Step1NameCamera({ {CAMERA_BRANDS.map((brand) => ( - {brand.label} + {brand.label.toLowerCase() === "other" + ? t("label.other", { ns: "common" }) + : brand.label} ))} diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts index db6e0b1cb7..b007a9573f 100644 --- a/web/src/utils/dateUtil.ts +++ b/web/src/utils/dateUtil.ts @@ -1,5 +1,5 @@ import { fromUnixTime, intervalToDuration, formatDuration } from "date-fns"; -import { Locale } from "date-fns/locale"; +import { enUS, Locale } from "date-fns/locale"; import { formatInTimeZone } from "date-fns-tz"; import i18n from "@/utils/i18n"; export const longToDate = (long: number): Date => new Date(long * 1000); @@ -293,9 +293,13 @@ export const getDurationFromTimestamps = ( /** * * @param seconds - number of seconds to convert into hours, minutes and seconds + * @param locale - the date-fns locale to use for formatting * @returns string - formatted duration in hours, minutes and seconds */ -export const formatSecondsToDuration = (seconds: number): string => { +export const formatSecondsToDuration = ( + seconds: number, + locale?: Locale, +): string => { if (isNaN(seconds) || seconds < 0) { return "Invalid duration"; } @@ -304,6 +308,7 @@ export const formatSecondsToDuration = (seconds: number): string => { return formatDuration(duration, { format: ["hours", "minutes", "seconds"], delimiter: ", ", + locale: locale ?? enUS, }); }; diff --git a/web/src/utils/lifecycleUtil.ts b/web/src/utils/lifecycleUtil.ts index 7ed90c5f8f..4e43de9c2f 100644 --- a/web/src/utils/lifecycleUtil.ts +++ b/web/src/utils/lifecycleUtil.ts @@ -12,7 +12,10 @@ export function getLifecycleItemDescription( const label = lifecycleItem.data.sub_label ? capitalizeFirstLetter(rawLabel) - : getTranslatedLabel(rawLabel); + : getTranslatedLabel( + rawLabel, + lifecycleItem.class_type === "heard" ? "audio" : "object", + ); switch (lifecycleItem.class_type) { case "visible": diff --git a/web/src/views/system/GeneralMetrics.tsx b/web/src/views/system/GeneralMetrics.tsx index a05b1b82ac..f8ce648515 100644 --- a/web/src/views/system/GeneralMetrics.tsx +++ b/web/src/views/system/GeneralMetrics.tsx @@ -855,7 +855,7 @@ export default function GeneralMetrics({ Date: Fri, 9 Jan 2026 17:23:33 -0600 Subject: [PATCH 77/78] Miscellaneous Fixes (0.17 beta) (#21575) * icon improvements add type to getIconForLabel provide default icon for audio events * Add preferred language to review docs * prevent react Suspense crash during auth redirect add redirect-check guards to stop rendering lazy routes while navigation is pending (fixes some users seeing React error #426 when auth expires) * Uppsercase model name --------- Co-authored-by: Nicolas Mowen --- .../configuration/genai/review_summaries.md | 11 +++++ web/src/App.tsx | 11 +++++ web/src/components/auth/ProtectedRoute.tsx | 8 ++++ web/src/components/card/ReviewCard.tsx | 4 +- web/src/components/card/SearchThumbnail.tsx | 6 ++- .../overlay/detail/SearchDetailDialog.tsx | 6 ++- .../overlay/detail/TrackingDetails.tsx | 1 + web/src/components/player/LivePlayer.tsx | 6 ++- .../player/PreviewThumbnailPlayer.tsx | 12 +++++- web/src/components/timeline/DetailStream.tsx | 41 ++++++++++++------- web/src/utils/iconUtil.tsx | 31 ++++++++++---- .../classification/ModelTrainingView.tsx | 2 +- web/src/views/settings/ObjectSettingsView.tsx | 4 +- 13 files changed, 112 insertions(+), 31 deletions(-) diff --git a/docs/docs/configuration/genai/review_summaries.md b/docs/docs/configuration/genai/review_summaries.md index 99ee48d0ff..9851ec2f62 100644 --- a/docs/docs/configuration/genai/review_summaries.md +++ b/docs/docs/configuration/genai/review_summaries.md @@ -112,6 +112,17 @@ review: - animals in the garden ``` +### Preferred Language + +By default, review summaries are generated in English. You can configure Frigate to generate summaries in your preferred language by setting the `preferred_language` option: + +```yaml +review: + genai: + enabled: true + preferred_language: Spanish +``` + ## Review Reports Along with individual review item summaries, Generative AI provides the ability to request a report of a given time period. For example, you can get a daily report while on a vacation of any suspicious activity or other concerns that may require review. diff --git a/web/src/App.tsx b/web/src/App.tsx index b458d9ec3c..d7a9ec3e9d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -15,6 +15,7 @@ import { AuthProvider } from "@/context/auth-context"; import useSWR from "swr"; import { FrigateConfig } from "./types/frigateConfig"; import ActivityIndicator from "@/components/indicators/activity-indicator"; +import { isRedirectingToLogin } from "@/api/auth-redirect"; const Live = lazy(() => import("@/pages/Live")); const Events = lazy(() => import("@/pages/Events")); @@ -58,6 +59,16 @@ function DefaultAppView() { ? Object.keys(config.auth.roles) : undefined; + // Show loading indicator during redirect to prevent React from attempting to render + // lazy components, which would cause error #426 (suspension during synchronous navigation) + if (isRedirectingToLogin()) { + return ( +
    + +
    + ); + } + return (
    {isDesktop && } diff --git a/web/src/components/auth/ProtectedRoute.tsx b/web/src/components/auth/ProtectedRoute.tsx index 55edc60bd1..cedf5a15ac 100644 --- a/web/src/components/auth/ProtectedRoute.tsx +++ b/web/src/components/auth/ProtectedRoute.tsx @@ -28,6 +28,14 @@ export default function ProtectedRoute({ } }, [auth.isLoading, auth.isAuthenticated, auth.user]); + // Show loading indicator during redirect to prevent React from attempting to render + // lazy components, which would cause error #426 (suspension during synchronous navigation) + if (isRedirectingToLogin()) { + return ( + + ); + } + if (auth.isLoading) { return ( diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 85b5df235c..b5ba5cfea1 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -181,7 +181,7 @@ export default function ReviewCard({ key={`${object}-${idx}`} className="rounded-full bg-muted-foreground p-1" > - {getIconForLabel(object, "size-3 text-white")} + {getIconForLabel(object, "object", "size-3 text-white")}
    ))} {event.data.audio.map((audio, idx) => ( @@ -189,7 +189,7 @@ export default function ReviewCard({ key={`${audio}-${idx}`} className="rounded-full bg-muted-foreground p-1" > - {getIconForLabel(audio, "size-3 text-white")} + {getIconForLabel(audio, "audio", "size-3 text-white")}
    ))}
    diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 0b82475c84..66f58f4fd9 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -133,7 +133,11 @@ export default function SearchThumbnail({ className={`z-0 flex items-center justify-between gap-1 space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize`} onClick={() => onClick(searchResult, false, true)} > - {getIconForLabel(objectLabel, "size-3 text-white")} + {getIconForLabel( + objectLabel, + searchResult.data.type, + "size-3 text-white", + )} {Math.floor( (searchResult.data.score ?? searchResult.data.top_score ?? diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index bd4368ebe2..01e211eec5 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -1296,7 +1296,11 @@ function ObjectDetailsTab({ {t("details.label")}
    - {getIconForLabel(search.label, "size-4 text-primary")} + {getIconForLabel( + search.label, + search.data.type, + "size-4 text-primary", + )} {getTranslatedLabel(search.label, search.data.type)} {search.sub_label && ` (${search.sub_label})`} {isAdmin && search.end_time && ( diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx index f2eb111437..767a7072ab 100644 --- a/web/src/components/overlay/detail/TrackingDetails.tsx +++ b/web/src/components/overlay/detail/TrackingDetails.tsx @@ -665,6 +665,7 @@ export function TrackingDetails({ > {getIconForLabel( event.sub_label ? event.label + "-verified" : event.label, + event.data.type, "size-4 text-white", )}
    diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index ed359a0c92..1dd7a10a65 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -359,7 +359,11 @@ export default function LivePlayer({ ]), ] .map((label) => { - return getIconForLabel(label, "size-3 text-white"); + return getIconForLabel( + label, + "object", + "size-3 text-white", + ); }) .sort()} diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 872f7c98a7..30339599ab 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -262,10 +262,18 @@ export default function PreviewThumbnailPlayer({ onClick={() => onClick(review, false, true)} > {review.data.objects.sort().map((object) => { - return getIconForLabel(object, "size-3 text-white"); + return getIconForLabel( + object, + "object", + "size-3 text-white", + ); })} {review.data.audio.map((audio) => { - return getIconForLabel(audio, "size-3 text-white"); + return getIconForLabel( + audio, + "audio", + "size-3 text-white", + ); })} diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index c6413ed976..ac560a4df0 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -14,6 +14,7 @@ import { FrigateConfig } from "@/types/frigateConfig"; import useSWR from "swr"; import ActivityIndicator from "../indicators/activity-indicator"; import { Event } from "@/types/event"; +import { EventType } from "@/types/search"; import { getIconForLabel } from "@/utils/iconUtil"; import { REVIEW_PADDING, ReviewSegment } from "@/types/review"; import { LuChevronDown, LuCircle, LuChevronRight } from "react-icons/lu"; @@ -346,22 +347,29 @@ function ReviewGroup({ : null, ); - const rawIconLabels: string[] = [ + const rawIconLabels: Array<{ label: string; type: EventType }> = [ ...(fetchedEvents - ? fetchedEvents.map((e) => - e.sub_label ? e.label + "-verified" : e.label, - ) - : (review.data?.objects ?? [])), - ...(review.data?.audio ?? []), + ? fetchedEvents.map((e) => ({ + label: e.sub_label ? e.label + "-verified" : e.label, + type: e.data.type, + })) + : (review.data?.objects ?? []).map((obj) => ({ + label: obj, + type: "object" as EventType, + }))), + ...(review.data?.audio ?? []).map((audio) => ({ + label: audio, + type: "audio" as EventType, + })), ]; // limit to 5 icons const seen = new Set(); - const iconLabels: string[] = []; - for (const lbl of rawIconLabels) { - if (!seen.has(lbl)) { - seen.add(lbl); - iconLabels.push(lbl); + const iconLabels: Array<{ label: string; type: EventType }> = []; + for (const item of rawIconLabels) { + if (!seen.has(item.label)) { + seen.add(item.label); + iconLabels.push(item); if (iconLabels.length >= 5) break; } } @@ -418,12 +426,12 @@ function ReviewGroup({
    {displayTime}
    - {iconLabels.slice(0, 5).map((lbl, idx) => ( + {iconLabels.slice(0, 5).map(({ label: lbl, type }, idx) => (
    - {getIconForLabel(lbl, "size-3 text-white")} + {getIconForLabel(lbl, type, "size-3 text-white")}
    ))}
    @@ -516,7 +524,11 @@ function ReviewGroup({ >
    - {getIconForLabel(audioLabel, "size-3 text-white")} + {getIconForLabel( + audioLabel, + "audio", + "size-3 text-white", + )}
    {getTranslatedLabel(audioLabel, "audio")}
    @@ -618,6 +630,7 @@ function EventList({ > {getIconForLabel( event.sub_label ? event.label + "-verified" : event.label, + event.data.type, "size-3 text-white", )}
    diff --git a/web/src/utils/iconUtil.tsx b/web/src/utils/iconUtil.tsx index 11be7cb9e7..156b4529bd 100644 --- a/web/src/utils/iconUtil.tsx +++ b/web/src/utils/iconUtil.tsx @@ -1,5 +1,6 @@ import { IconName } from "@/components/icons/IconPicker"; import { FrigateConfig } from "@/types/frigateConfig"; +import { EventType } from "@/types/search"; import { BsPersonWalking } from "react-icons/bs"; import { FaAmazon, @@ -32,6 +33,7 @@ import { GiRabbit, GiRaccoonHead, GiSailboat, + GiSoundWaves, GiSquirrel, } from "react-icons/gi"; import { LuBox, LuLassoSelect, LuScanBarcode } from "react-icons/lu"; @@ -56,11 +58,15 @@ export function isValidIconName(value: string): value is IconName { return Object.keys(LuIcons).includes(value as IconName); } -export function getIconForLabel(label: string, className?: string) { +export function getIconForLabel( + label: string, + type: EventType = "object", + className?: string, +) { if (label.endsWith("-verified")) { - return getVerifiedIcon(label, className); + return getVerifiedIcon(label, className, type); } else if (label.endsWith("-plate")) { - return getRecognizedPlateIcon(label, className); + return getRecognizedPlateIcon(label, className, type); } switch (label) { @@ -152,27 +158,38 @@ export function getIconForLabel(label: string, className?: string) { case "usps": return ; default: + if (type === "audio") { + return ; + } return ; } } -function getVerifiedIcon(label: string, className?: string) { +function getVerifiedIcon( + label: string, + className?: string, + type: EventType = "object", +) { const simpleLabel = label.substring(0, label.lastIndexOf("-")); return (
    - {getIconForLabel(simpleLabel, className)} + {getIconForLabel(simpleLabel, type, className)}
    ); } -function getRecognizedPlateIcon(label: string, className?: string) { +function getRecognizedPlateIcon( + label: string, + className?: string, + type: EventType = "object", +) { const simpleLabel = label.substring(0, label.lastIndexOf("-")); return (
    - {getIconForLabel(simpleLabel, className)} + {getIconForLabel(simpleLabel, type, className)}
    ); diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index ec7ce0472c..10d52075d6 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -88,7 +88,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { // title useEffect(() => { - document.title = `${model.name} - ${t("documentTitle")}`; + document.title = `${model.name.toUpperCase()} - ${t("documentTitle")}`; }, [model.name, t]); // model state diff --git a/web/src/views/settings/ObjectSettingsView.tsx b/web/src/views/settings/ObjectSettingsView.tsx index 82977e80c3..3c03269b50 100644 --- a/web/src/views/settings/ObjectSettingsView.tsx +++ b/web/src/views/settings/ObjectSettingsView.tsx @@ -406,7 +406,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) { : getColorForObjectName(obj.label), }} > - {getIconForLabel(obj.label, "size-5 text-white")} + {getIconForLabel(obj.label, "object", "size-5 text-white")}
    {getTranslatedLabel(obj.label)} @@ -494,7 +494,7 @@ function AudioList({ cameraConfig, audioDetections }: AudioListProps) {
    - {getIconForLabel(key, "size-5 text-white")} + {getIconForLabel(key, "audio", "size-5 text-white")}
    {getTranslatedLabel(key)}
    From 93016c662f1cfffa6465f7538068d850d130cb2c Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sat, 10 Jan 2026 11:50:37 -0600 Subject: [PATCH 78/78] add synaptics to release (#21591) --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6961fcffb6..1fbf58f6e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,14 +39,14 @@ jobs: STABLE_TAG=${BASE}:stable PULL_TAG=${BASE}:${BUILD_TAG} docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${VERSION_TAG} - for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm; do + for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm synaptics; do docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${VERSION_TAG}-${variant} done # stable tag if [[ "${BUILD_TYPE}" == "stable" ]]; then docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${STABLE_TAG} - for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm; do + for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm synaptics; do docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${STABLE_TAG}-${variant} done fi