Compare commits

...
18 Commits
Author SHA1 Message Date
Weblate (bot)andGitHub dba40997a5 Merge 8d71a65804 into 6f24f5a595 2026-07-14 03:09:31 +00:00
Hosted WeblateandOverTheHillsAndFarAway 8d71a65804 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translation: Frigate NVR/components-dialog
2026-07-14 03:08:09 +00:00
Hosted WeblateandGuoQing Liu cf0d6e2429 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1294 of 1294 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (474 of 474 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-14 03:08:06 +00:00
Hosted Weblateand莊凱鈞 9a0855d819 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1294 of 1294 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 莊凱鈞 <kcchuang88@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hant/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-14 03:08:02 +00:00
Hosted WeblateandHosted Weblate user 157871 df85266976 Translated using Weblate (Slovak)
Currently translated at 93.0% (120 of 129 strings)

Translated using Weblate (Slovak)

Currently translated at 93.0% (120 of 129 strings)

Translated using Weblate (Slovak)

Currently translated at 72.3% (136 of 188 strings)

Translated using Weblate (Slovak)

Currently translated at 49.0% (631 of 1287 strings)

Translated using Weblate (Slovak)

Currently translated at 60.9% (39 of 64 strings)

Translated using Weblate (Slovak)

Currently translated at 90.0% (54 of 60 strings)

Translated using Weblate (Slovak)

Currently translated at 61.4% (67 of 109 strings)

Translated using Weblate (Slovak)

Currently translated at 97.9% (234 of 239 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hosted Weblate user 157871 <gop60@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sk/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-14 03:07:59 +00:00
Hosted WeblateandDalibor Radovanović 178e7b17ff Translated using Weblate (Serbian)
Currently translated at 96.7% (1245 of 1287 strings)

Translated using Weblate (Serbian)

Currently translated at 17.5% (42 of 239 strings)

Co-authored-by: Dalibor Radovanović <darkobg@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sr/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-settings
2026-07-14 03:07:56 +00:00
Hosted WeblateandFelix Boström 6ebb02e960 Translated using Weblate (Swedish)
Currently translated at 2.2% (18 of 808 strings)

Translated using Weblate (Swedish)

Currently translated at 5.4% (26 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Felix Boström <felix.bostrum@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sv/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
2026-07-14 03:07:54 +00:00
Hosted WeblateandFabien LAMAISON 2ac7cb7563 Translated using Weblate (French)
Currently translated at 91.7% (100 of 109 strings)

Co-authored-by: Fabien LAMAISON <kerin@kerin444.net>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fr/
Translation: Frigate NVR/components-dialog
2026-07-14 03:07:50 +00:00
Hosted WeblateandMark Holtkamp 4105c44451 Translated using Weblate (Dutch)
Currently translated at 92.6% (101 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mark Holtkamp <markholtkamp85@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translation: Frigate NVR/components-dialog
2026-07-14 03:07:48 +00:00
Hosted WeblateandSurya Desktop bd47b92b3b Translated using Weblate (Indonesian)
Currently translated at 67.8% (74 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Surya Desktop <desktopsurya@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/id/
Translation: Frigate NVR/components-dialog
2026-07-14 03:07:44 +00:00
Hosted WeblateandEduardo Pastor Fernández a696e2b34e Translated using Weblate (Catalan)
Currently translated at 100.0% (1294 of 1294 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (474 of 474 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2026-07-14 03:07:42 +00:00
Hosted Weblateandlukasig 28df9f70e7 Translated using Weblate (Romanian)
Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-14 03:07:39 +00:00
ee6725cdeb Translated using Weblate (Estonian)
Currently translated at 20.2% (164 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 15.1% (72 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 28.5% (367 of 1287 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (45 of 45 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Estonian)

Currently translated at 20.2% (164 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 20.2% (164 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 14.9% (71 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 14.9% (71 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 80.8% (152 of 188 strings)

Translated using Weblate (Estonian)

Currently translated at 28.4% (366 of 1287 strings)

Translated using Weblate (Estonian)

Currently translated at 28.4% (366 of 1287 strings)

Translated using Weblate (Estonian)

Currently translated at 98.3% (59 of 60 strings)

Translated using Weblate (Estonian)

Currently translated at 61.6% (53 of 86 strings)

Translated using Weblate (Estonian)

Currently translated at 75.8% (110 of 145 strings)

Translated using Weblate (Estonian)

Currently translated at 14.7% (19 of 129 strings)

Translated using Weblate (Estonian)

Currently translated at 11.3% (92 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 8.2% (39 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 47.8% (90 of 188 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Estonian)

Currently translated at 67.6% (339 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Rasmus Kuusmann <rasmus.kuusmann@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/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-exports/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/et/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-14 03:07:35 +00:00
Hosted WeblateandRinaldo Pitzer Júnior 4ec83172e0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 33.9% (274 of 808 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 58.2% (276 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 53.4% (69 of 129 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 39.6% (510 of 1287 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Rinaldo Pitzer Júnior <rinaldo90@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pt_BR/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-settings
2026-07-14 03:07:31 +00:00
Hosted WeblateandAlex K 5fa1e401b5 Translated using Weblate (Latvian)
Currently translated at 27.1% (136 of 501 strings)

Co-authored-by: Alex K <kamonishe@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/lv/
Translation: Frigate NVR/audio
2026-07-14 03:07:28 +00:00
Jozef HuscavaandGitHub 6f24f5a595 Convert face crops to RGB before embedding (#23712)
* Convert face crops to RGB before embedding

Face crops flow through the cv2 pipeline as BGR arrays, but
_process_image passes ndarrays to PIL without any channel conversion,
so the FaceNet and ArcFace embedders receive BGR input while both
models expect RGB. The error is symmetric between enrollment and
recognition so it partially cancels, but it still costs accuracy.

* Move BGR to RGB conversion into a shared helper

Deduplicate the channel swap from both _preprocess_inputs methods
into a BaseEmbedding._bgr_to_rgb static helper, as suggested in
review.
2026-07-13 16:45:29 -06:00
Nicolas MowenandGitHub 65af0b1351 GenAI Fixes (#23708)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Fix Gemini tool calling

* Catch openai bug

* Implement tool calling tests for GenAI

* Expose if embeddings are supported for a given provider
2026-07-13 07:33:15 -06:00
Josh HawkinsandGitHub fcd05ec7bc UI improvements and fixes (#23690)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* add ability to edit enabled and save_attempts for classification models in the UI

* add state motion and interval configs to edit dialog

* fix preview playback rate for motion previews

* add docs note about environment vars and go2rtc

* update live view faq
2026-07-13 06:30:15 -06:00
70 changed files with 4639 additions and 360 deletions
@@ -67,6 +67,12 @@ This section can be used to set environment variables for those unable to modify
Variables prefixed with `FRIGATE_` can be referenced in config fields that support environment variable substitution (such as MQTT host and credentials, camera stream URLs, and ONVIF host and credentials) using the `{FRIGATE_VARIABLE_NAME}` syntax.
:::note
The `go2rtc` section is an exception. go2rtc runs as a separate process, so its stream definitions can only be substituted with variables that exist in the container's environment (set via Docker `-e`, the `environment:` section of `docker-compose.yml`, or Docker secrets). Variables defined in the `environment_vars` block above are not available to go2rtc streams. Home Assistant app users, who cannot set container environment variables, must instead put credentials directly in their go2rtc stream URLs.
:::
<ConfigTabs>
<TabItem value="ui">
+121 -65
View File
@@ -6,6 +6,7 @@ title: Live View
import ConfigTabs from "@site/src/components/ConfigTabs";
import TabItem from "@theme/TabItem";
import NavPath from "@site/src/components/NavPath";
import FaqItem from "@site/src/components/FaqItem";
Frigate intelligently displays your camera streams on the Live view dashboard. By default, Frigate employs "smart streaming" where camera images update once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any motion or active objects are detected, cameras seamlessly switch to a live stream.
@@ -341,100 +342,155 @@ When your browser runs into problems playing back your camera streams, it will l
## Live view FAQ
1. **Why don't I have audio in my Live view?**
### Getting Live View Working
You must use go2rtc to hear audio in your live streams. If you have go2rtc already configured, you need to ensure your camera is sending PCMA/PCMU or AAC audio. If you can't change your camera's audio codec, you need to [transcode the audio](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg) using go2rtc.
<FaqItem id="why-dont-i-have-audio-in-my-live-view" question="Why don't I have audio in my Live view?">
Note that the low bandwidth mode player is a video-only stream. You should not expect to hear audio when in low bandwidth mode, even if you've set up go2rtc.
You must use go2rtc to hear audio in your live streams. If you have go2rtc already configured, you need to ensure your camera is sending PCMA/PCMU or AAC audio. If you can't change your camera's audio codec, you need to [transcode the audio](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg) using go2rtc.
2. **Frigate shows that my live stream is in "low bandwidth mode". What does this mean?**
If the audio controls don't appear in the UI at all, verify that the Live view is actually using your go2rtc stream. If your go2rtc stream names don't match your Frigate camera name, you must map them with the `live -> streams` config (see [Setting Streams For Live UI](#setting-streams-for-live-ui) above); otherwise the UI falls back to the video-only jsmpeg player.
Frigate intelligently selects the live streaming technology based on a number of factors (user-selected modes like two-way talk, camera settings, browser capabilities, available bandwidth) and prioritizes showing an actual up-to-date live view of your camera's stream as quickly as possible.
Note that the low bandwidth mode player is a video-only stream. You should not expect to hear audio when in low bandwidth mode, even if you've set up go2rtc.
When you have go2rtc configured, Live view initially attempts to load and play back your stream with a clearer, fluent stream technology (MSE). An initial timeout, a low bandwidth condition that would cause buffering of the stream, or decoding errors in the stream will cause Frigate to switch to the stream defined by the `detect` role, using the jsmpeg format. This is what the UI labels as "low bandwidth mode". On Live dashboards, the mode will automatically reset when smart streaming is configured and activity stops. Continuous streaming mode does not have an automatic reset mechanism, but you can use the _Reset_ option to force a reload of your stream.
</FaqItem>
If you are using continuous streaming or you are loading more than a few high resolution streams at once on the dashboard, your browser may struggle to begin playback of your streams before the timeout. Frigate always prioritizes showing a live stream as quickly as possible, even if it is a lower quality jsmpeg stream. You can use the "Reset" link/button to try loading your high resolution stream again.
<FaqItem id="i-have-unmuted-some-cameras-on-my-dashboard-but-i-do-not-hear-sound-why" question="I have unmuted some cameras on my dashboard, but I do not hear sound. Why?">
Errors in stream playback (e.g., connection failures, codec issues, or buffering timeouts) that cause the fallback to low bandwidth mode (jsmpeg) are logged to the browser console for easier debugging. These errors may include:
- Network issues (e.g., MSE or WebRTC network connection problems).
- Unsupported codecs or stream formats (e.g., H.265 in WebRTC, which is not supported in some browsers).
- Buffering timeouts or low bandwidth conditions causing fallback to jsmpeg.
- Browser compatibility problems (e.g., iOS Safari limitations with MSE).
If your camera is streaming (as indicated by a red dot in the upper right, or if it has been set to continuous streaming mode), your browser may be blocking audio until you interact with the page. This is an intentional browser limitation. See [this article](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability). Many browsers have a whitelist feature to change this behavior.
To view browser console logs:
1. Open the Frigate Live View in your browser.
2. Open the browser's Developer Tools (F12 or right-click > Inspect > Console tab).
3. Reproduce the error (e.g., load a problematic stream or simulate network issues).
4. Look for messages prefixed with the camera name.
</FaqItem>
These logs help identify if the issue is player-specific (MSE vs. WebRTC) or related to camera configuration (e.g., go2rtc streams, codecs). If you see frequent errors:
- Verify your camera's H.264/AAC settings (see [Frigate's camera settings recommendations](#camera-settings-recommendations)).
- Check go2rtc configuration for transcoding (e.g., audio to AAC/OPUS).
- Test with a different stream via the UI dropdown (if `live -> streams` is configured).
- For WebRTC-specific issues, ensure port 8555 is forwarded and candidates are set (see [WebRTC Extra Configuration](#webrtc-extra-configuration)).
- If your cameras are streaming at a high resolution, your browser may be struggling to load all of the streams before the buffering timeout occurs. Frigate prioritizes showing a true live view as quickly as possible. If the fallback occurs often, change your live view settings to use a lower bandwidth substream.
<FaqItem id="my-live-view-shows-a-black-screen-or-doesnt-load-but-the-debug-view-works-why" question="My live view shows a black screen or doesn't load, but the debug view works. Why?">
3. **It doesn't seem like my cameras are streaming on the Live dashboard. Why?**
The debug view plays the `detect` stream processed by Frigate itself, while the Live view plays your go2rtc stream directly in the browser. If the debug view works but the Live view doesn't, your browser usually can't decode what the camera is sending, most often H.265 video or an incompatible audio track.
On the default Live dashboard ("All Cameras"), your camera images will update once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity is detected, cameras seamlessly switch to a full-resolution live stream. If you want to customize this behavior, use a camera group.
Work through the [go2rtc troubleshooting guide](/troubleshooting/go2rtc#live-view-is-black-buffering-or-stuck-in-low-bandwidth-mode) to isolate the problem. Two fixes resolve the majority of cases:
4. **I see a strange diagonal line on my live view, but my recordings look fine. How can I fix it?**
1. Restream through go2rtc's FFmpeg module by prefixing your source with `ffmpeg:`, for example `- ffmpeg:rtsp://user:password@192.168.1.5:554/stream`.
2. If that doesn't help, transcode to compatible codecs: `- ffmpeg:rtsp://user:password@192.168.1.5:554/stream#video=h264#audio=aac#hardware`.
This is caused by incorrect dimensions set in your detect width or height (or incorrectly auto-detected), causing the jsmpeg player's rendering engine to display a slightly distorted image. You should enlarge the width and height of your `detect` resolution up to a standard aspect ratio (example: 640x352 becomes 640x360, and 800x443 becomes 800x450, 2688x1520 becomes 2688x1512, etc). If changing the resolution to match a standard (4:3, 16:9, or 32:9, etc) aspect ratio does not solve the issue, you can enable "compatibility mode" in your camera group dashboard's stream settings. Depending on your browser and device, more than a few cameras in compatibility mode may not be supported, so only use this option if changing your `detect` width and height fails to resolve the color artifacts and diagonal line.
</FaqItem>
5. **How does "smart streaming" work?**
<FaqItem id="how-do-i-get-the-best-live-view-experience-in-home-assistant" question="How do I get the best live view experience in Home Assistant?">
Because a static image of a scene looks exactly the same as a live stream with no motion or activity, smart streaming updates your camera images once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity (motion or object/audio detection) occurs, cameras seamlessly switch to a live stream.
For a full-resolution, low-latency live view in Home Assistant dashboards, use the [Advanced Camera Card](https://card.camera) with the [go2rtc live provider](https://card.camera/#/configuration/cameras/live-provider?id=go2rtc), which streams directly from Frigate's bundled go2rtc. This also supports audio and [two-way talk](#two-way-talk) on capable cameras. See the [Home Assistant integration docs](/integrations/home-assistant) for setup.
This static image is pulled from the stream defined in your config with the `detect` role. When activity is detected, images from the `detect` stream immediately begin updating at ~5 frames per second so you can see the activity until the live player is loaded and begins playing. This usually only takes a second or two. If the live player times out, buffers, or has streaming errors, the jsmpeg player is loaded and plays a video-only stream from the `detect` role. When activity ends, the players are destroyed and a static image is displayed until activity is detected again, and the process repeats.
</FaqItem>
Smart streaming depends on having your camera's motion `threshold` and `contour_area` config values dialed in. Use the Motion Tuner in Settings in the UI to tune these values in real-time.
### Streaming Behavior
This is Frigate's default and recommended setting because it results in a significant bandwidth savings, especially for high resolution cameras.
<FaqItem id="how-does-smart-streaming-work" question={'How does "smart streaming" work?'}>
6. **I have unmuted some cameras on my dashboard, but I do not hear sound. Why?**
Because a static image of a scene looks exactly the same as a live stream with no motion or activity, smart streaming updates your camera images once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity (motion or object/audio detection) occurs, cameras seamlessly switch to a live stream.
If your camera is streaming (as indicated by a red dot in the upper right, or if it has been set to continuous streaming mode), your browser may be blocking audio until you interact with the page. This is an intentional browser limitation. See [this article](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability). Many browsers have a whitelist feature to change this behavior.
This static image is pulled from the stream defined in your config with the `detect` role. When activity is detected, images from the `detect` stream immediately begin updating at ~5 frames per second so you can see the activity until the live player is loaded and begins playing. This usually only takes a second or two. If the live player times out, buffers, or has streaming errors, the jsmpeg player is loaded and plays a video-only stream from the `detect` role. When activity ends, the players are destroyed and a static image is displayed until activity is detected again, and the process repeats.
7. **My camera streams have lots of visual artifacts / distortion.**
Smart streaming depends on having your camera's motion `threshold` and `contour_area` config values dialed in. Use the Motion Tuner in Settings in the UI to tune these values in real-time.
Some cameras don't include the hardware to support multiple connections to the high resolution stream, and this can cause unexpected behavior. In this case it is recommended to [restream](./restream.md) the high resolution stream so that it can be used for live view and recordings.
This is Frigate's default and recommended setting because it results in a significant bandwidth savings, especially for high resolution cameras.
8. **Why does my camera stream switch aspect ratios on the Live dashboard?**
</FaqItem>
Your camera may change aspect ratios on the dashboard because Frigate uses different streams for different purposes. With go2rtc and Smart Streaming, Frigate shows a static image from the `detect` stream when no activity is present, and switches to the live stream when motion is detected. The camera image will change size if your streams use different aspect ratios.
<FaqItem id="it-doesnt-seem-like-my-cameras-are-streaming-on-the-live-dashboard-why" question="It doesn't seem like my cameras are streaming on the Live dashboard. Why?">
To prevent this, make the `detect` stream match the go2rtc live stream's aspect ratio (resolution does not need to match, just the aspect ratio). You can either adjust the camera's output resolution or set the `width` and `height` values in your config's `detect` section to a resolution with an aspect ratio that matches.
On the default Live dashboard ("All Cameras"), your camera images will update once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity is detected, cameras seamlessly switch to a full-resolution live stream. If you want to customize this behavior, use a camera group.
Example: Resolutions from two streams
- Mismatched (may cause aspect ratio switching on the dashboard):
- Live/go2rtc stream: 1920x1080 (16:9)
- Detect stream: 640x352 (~1.82:1, not 16:9)
</FaqItem>
- Matched (prevents switching):
- Live/go2rtc stream: 1920x1080 (16:9)
- Detect stream: 640x360 (16:9)
<FaqItem id="frigate-shows-that-my-live-stream-is-in-low-bandwidth-mode-what-does-this-mean" question={'Frigate shows that my live stream is in "low bandwidth mode". What does this mean?'}>
You can update the detect settings in your camera config to match the aspect ratio of your go2rtc live stream. For example:
Frigate intelligently selects the live streaming technology based on a number of factors (user-selected modes like two-way talk, camera settings, browser capabilities, available bandwidth) and prioritizes showing an actual up-to-date live view of your camera's stream as quickly as possible.
```yaml
cameras:
front_door:
detect:
width: 640
height: 360 # set this to 360 instead of 352
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/front_door # main stream 1920x1080
roles:
- record
- path: rtsp://127.0.0.1:8554/front_door_sub # sub stream 640x352
roles:
- detect
```
When you have go2rtc configured, Live view initially attempts to load and play back your stream with a clearer, fluent stream technology (MSE). An initial timeout, a low bandwidth condition that would cause buffering of the stream, or decoding errors in the stream will cause Frigate to switch to the stream defined by the `detect` role, using the jsmpeg format. This is what the UI labels as "low bandwidth mode". On Live dashboards, the mode will automatically reset when smart streaming is configured and activity stops. Continuous streaming mode does not have an automatic reset mechanism, but you can use the _Reset_ option to force a reload of your stream.
The same applies to your `record` stream: if its aspect ratio differs from your `detect` stream, your recordings will appear in a different shape than the live view. For consistent framing across live view and recordings, use the same aspect ratio for all of a camera's streams (the resolution can still differ).
If you are using continuous streaming or you are loading more than a few high resolution streams at once on the dashboard, your browser may struggle to begin playback of your streams before the timeout. Frigate always prioritizes showing a live stream as quickly as possible, even if it is a lower quality jsmpeg stream. You can use the "Reset" link/button to try loading your high resolution stream again.
9. **Why does Frigate prefer MSE over WebRTC for live view?**
Errors in stream playback (e.g., connection failures, codec issues, or buffering timeouts) that cause the fallback to low bandwidth mode (jsmpeg) are logged to the browser console for easier debugging. These errors may include:
Frigate prefers MSE because it delivers a better out-of-the-box experience than WebRTC on nearly every axis that matters for a security camera system. MSE is an open standard optimized and supported by all modern browsers, works without any extra configuration (WebRTC requires port forwarding and candidate setup, and lacks H.265 support in some browsers), and requires no internet access for NAT traversal. More importantly, MSE runs over TCP, so every frame arrives and is decoded in order, so nothing is ever silently skipped. WebRTC optimizes for latency over UDP by discarding late or incomplete frames, which works against you on cellular or spotty Wi-Fi: you can end up with frozen video, visual corruption, or gaps in the feed without ever knowing you missed something. Frigate's enhanced MSE player has adaptive speed playback and has been tuned for latency and connection robustness that meets or exceeds WebRTC, so you get near-real-time playback with a guarantee that when the video plays, every frame is actually there - which, for an NVR whose whole purpose is letting you see what happened, matters more than shaving fractions of a second off a latency number. That's why Frigate defaults to MSE and reserves WebRTC for cases that require it, like two-way talk.
- Network issues (e.g., MSE or WebRTC network connection problems).
- Unsupported codecs or stream formats (e.g., H.265 in WebRTC, which is not supported in some browsers).
- Buffering timeouts or low bandwidth conditions causing fallback to jsmpeg.
- Browser compatibility problems (e.g., iOS Safari limitations with MSE).
To view browser console logs:
1. Open the Frigate Live View in your browser.
2. Open the browser's Developer Tools (F12 or right-click > Inspect > Console tab).
3. Reproduce the error (e.g., load a problematic stream or simulate network issues).
4. Look for messages prefixed with the camera name.
These logs help identify if the issue is player-specific (MSE vs. WebRTC) or related to camera configuration (e.g., go2rtc streams, codecs). If you see frequent errors:
- Verify your camera's H.264/AAC settings (see [Frigate's camera settings recommendations](#camera-settings-recommendations)).
- Check go2rtc configuration for transcoding (e.g., audio to AAC/OPUS).
- Test with a different stream via the UI dropdown (if `live -> streams` is configured).
- For WebRTC-specific issues, ensure port 8555 is forwarded and candidates are set (see [WebRTC Extra Configuration](#webrtc-extra-configuration)).
- If your cameras are streaming at a high resolution, your browser may be struggling to load all of the streams before the buffering timeout occurs. Frigate prioritizes showing a true live view as quickly as possible. If the fallback occurs often, change your live view settings to use a lower bandwidth substream.
</FaqItem>
<FaqItem id="why-is-my-live-view-delayed-or-lagging-behind-real-time" question="Why is my live view delayed or lagging behind real time?">
A delay when a stream first starts is usually caused by your camera's I-frame (keyframe) interval. Playback cannot begin until a keyframe arrives, so an interval set higher than your camera's frame rate makes the stream take longer to start. Set the I-frame interval to match the frame rate (or "1x" on Reolink) per the [camera settings recommendations](#camera-settings-recommendations).
A stream that starts on time but falls further behind live is buffering, which is usually the browser struggling to decode too many high-resolution streams at once. Select a lower-bandwidth substream for your dashboards (see [Setting Streams For Live UI](#setting-streams-for-live-ui)), reduce the number of streams open at once, or improve the network connection between your browser and Frigate. Frigate's player automatically speeds up playback to catch up to live after buffering, and falls back to low bandwidth mode if it stalls for too long. The _Reset_ option forces a fresh connection at the live edge.
</FaqItem>
<FaqItem id="why-does-frigate-prefer-mse-over-webrtc-for-live-view" question="Why does Frigate prefer MSE over WebRTC for live view?">
Frigate prefers MSE because it delivers a better out-of-the-box experience than WebRTC on nearly every axis that matters for a security camera system. MSE is an open standard optimized and supported by all modern browsers, works without any extra configuration (WebRTC requires port forwarding and candidate setup, and lacks H.265 support in some browsers), and requires no internet access for NAT traversal. More importantly, MSE runs over TCP, so every frame arrives and is decoded in order, so nothing is ever silently skipped. WebRTC optimizes for latency over UDP by discarding late or incomplete frames, which works against you on cellular or spotty Wi-Fi: you can end up with frozen video, visual corruption, or gaps in the feed without ever knowing you missed something. Frigate's enhanced MSE player has adaptive speed playback and has been tuned for latency and connection robustness that meets or exceeds WebRTC, so you get near-real-time playback with a guarantee that when the video plays, every frame is actually there - which, for an NVR whose whole purpose is letting you see what happened, matters more than shaving fractions of a second off a latency number. That's why Frigate defaults to MSE and reserves WebRTC for cases that require it, like two-way talk.
</FaqItem>
### Video Quality Issues
<FaqItem id="i-see-a-strange-diagonal-line-on-my-live-view-but-my-recordings-look-fine-how-can-i-fix-it" question="I see a strange diagonal line on my live view, but my recordings look fine. How can I fix it?">
This is caused by incorrect dimensions set in your detect width or height (or incorrectly auto-detected), causing the jsmpeg player's rendering engine to display a slightly distorted image. You should enlarge the width and height of your `detect` resolution up to a standard aspect ratio (example: 640x352 becomes 640x360, and 800x443 becomes 800x450, 2688x1520 becomes 2688x1512, etc). If changing the resolution to match a standard (4:3, 16:9, or 32:9, etc) aspect ratio does not solve the issue, you can enable "compatibility mode" in your camera group dashboard's stream settings. Depending on your browser and device, more than a few cameras in compatibility mode may not be supported, so only use this option if changing your `detect` width and height fails to resolve the color artifacts and diagonal line.
</FaqItem>
<FaqItem id="my-camera-streams-have-lots-of-visual-artifacts-or-distortion" question="My camera streams have lots of visual artifacts / distortion.">
Some cameras don't include the hardware to support multiple connections to the high resolution stream, and this can cause unexpected behavior. In this case it is recommended to [restream](./restream.md) the high resolution stream so that it can be used for live view and recordings.
</FaqItem>
<FaqItem id="why-does-my-camera-stream-switch-aspect-ratios-on-the-live-dashboard" question="Why does my camera stream switch aspect ratios on the Live dashboard?">
Your camera may change aspect ratios on the dashboard because Frigate uses different streams for different purposes. With go2rtc and Smart Streaming, Frigate shows a static image from the `detect` stream when no activity is present, and switches to the live stream when motion is detected. The camera image will change size if your streams use different aspect ratios.
To prevent this, make the `detect` stream match the go2rtc live stream's aspect ratio (resolution does not need to match, just the aspect ratio). You can either adjust the camera's output resolution or set the `width` and `height` values in your config's `detect` section to a resolution with an aspect ratio that matches.
Example: Resolutions from two streams
- Mismatched (may cause aspect ratio switching on the dashboard):
- Live/go2rtc stream: 1920x1080 (16:9)
- Detect stream: 640x352 (~1.82:1, not 16:9)
- Matched (prevents switching):
- Live/go2rtc stream: 1920x1080 (16:9)
- Detect stream: 640x360 (16:9)
You can update the detect settings in your camera config to match the aspect ratio of your go2rtc live stream. For example:
```yaml
cameras:
front_door:
detect:
width: 640
height: 360 # set this to 360 instead of 352
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/front_door # main stream 1920x1080
roles:
- record
- path: rtsp://127.0.0.1:8554/front_door_sub # sub stream 640x352
roles:
- detect
```
The same applies to your `record` stream: if its aspect ratio differs from your `detect` stream, your recordings will appear in a different shape than the live view. For consistent framing across live view and recordings, use the same aspect ratio for all of a camera's streams (the resolution can still differ).
</FaqItem>
+14 -12
View File
@@ -376,20 +376,22 @@ class EmbeddingMaintainer(threading.Thread):
logger.info(f"Disabled classification processor for model: {model_name}")
return
# Check if processor already exists
for processor in self.realtime_processors:
if isinstance(
processor,
(
CustomStateClassificationProcessor,
CustomObjectClassificationProcessor,
),
if (
isinstance(
processor,
(
CustomStateClassificationProcessor,
CustomObjectClassificationProcessor,
),
)
and processor.model_config.name == model_name
):
if processor.model_config.name == model_name:
logger.debug(
f"Classification processor for model {model_name} already exists, skipping"
)
return
processor.model_config = model_config
logger.debug(
f"Updated config for classification processor: {model_name}"
)
return
if model_config.state_config is not None:
processor = CustomStateClassificationProcessor(
@@ -57,6 +57,12 @@ class BaseEmbedding(ABC):
def _preprocess_inputs(self, raw_inputs: Any) -> Any:
pass
@staticmethod
def _bgr_to_rgb(frame: Any) -> Any:
if isinstance(frame, np.ndarray) and frame.ndim == 3:
return np.ascontiguousarray(frame[:, :, ::-1])
return frame
def _process_image(self, image, output: str = "RGB") -> Image.Image:
if isinstance(image, str):
if image.startswith("http"):
+2 -2
View File
@@ -73,7 +73,7 @@ class FaceNetEmbedding(BaseEmbedding):
self.tensor_output_details = self.runner.get_output_details()
def _preprocess_inputs(self, raw_inputs):
pil = self._process_image(raw_inputs[0])
pil = self._process_image(self._bgr_to_rgb(raw_inputs[0]))
# handle images larger than input size
width, height = pil.size
@@ -159,7 +159,7 @@ class ArcfaceEmbedding(BaseEmbedding):
)
def _preprocess_inputs(self, raw_inputs):
pil = self._process_image(raw_inputs[0])
pil = self._process_image(self._bgr_to_rgb(raw_inputs[0]))
# handle images larger than input size
width, height = pil.size
+5
View File
@@ -281,6 +281,11 @@ class GenAIClient:
"""Whether the configured model exposes a per-request thinking toggle."""
return False
@property
def supports_embeddings(self) -> bool:
"""Whether the configured model can generate embeddings via embed()."""
return False
def list_models(self) -> list[str]:
"""Return the list of model names available from this provider.
+1
View File
@@ -121,5 +121,6 @@ class GenAIClientManager:
"models": client.list_models(),
"roles": [r.value for r in genai_cfg.roles],
"supports_toggleable_thinking": client.supports_toggleable_thinking,
"supports_embeddings": client.supports_embeddings,
}
return result
+62 -60
View File
@@ -38,6 +38,37 @@ def _encode_thought_signature(signature: bytes | None) -> str | None:
return base64.b64encode(signature).decode("ascii")
def _decode_data_uri(url: str) -> tuple[str, bytes] | None:
"""Decode a ``data:`` URI into ``(mime_type, bytes)``; None if not a data URI."""
if not isinstance(url, str) or not url.startswith("data:"):
return None
try:
header, b64 = url.split(",", 1)
mime = header[len("data:") :].split(";")[0] or "image/jpeg"
return mime, base64.b64decode(b64)
except (ValueError, binascii.Error):
return None
def _parts_from_content(content: Any) -> list[types.Part]:
"""Convert OpenAI-style message content (str or multimodal list) to Gemini parts."""
if isinstance(content, list):
parts: list[types.Part] = []
for item in content:
if not isinstance(item, dict):
continue
if item.get("type") == "text":
parts.append(types.Part.from_text(text=item.get("text") or ""))
elif item.get("type") == "image_url":
decoded = _decode_data_uri((item.get("image_url") or {}).get("url", ""))
if decoded is not None:
mime, data = decoded
parts.append(types.Part.from_bytes(data=data, mime_type=mime))
# Gemini rejects empty parts; fall back to a single space.
return parts or [types.Part.from_text(text=" ")]
return [types.Part.from_text(text=content or "")]
def _stats_from_gemini_usage(usage: Any) -> dict[str, Any] | None:
"""Build a stats dict from a Gemini usage_metadata object."""
prompt_tokens = getattr(usage, "prompt_token_count", None)
@@ -227,9 +258,7 @@ class GeminiClient(GenAIClient):
)
else: # user
gemini_messages.append(
types.Content(
role="user", parts=[types.Part.from_text(text=content)]
)
types.Content(role="user", parts=_parts_from_content(content))
)
# Convert tools to Gemini format
@@ -485,9 +514,7 @@ class GeminiClient(GenAIClient):
)
else: # user
gemini_messages.append(
types.Content(
role="user", parts=[types.Part.from_text(text=content)]
)
types.Content(role="user", parts=_parts_from_content(content))
)
# Convert tools to Gemini format
@@ -553,7 +580,7 @@ class GeminiClient(GenAIClient):
# Use streaming API
content_parts: list[str] = []
reasoning_parts: list[str] = []
tool_calls_by_index: dict[int, dict[str, Any]] = {}
tool_calls_accum: list[dict[str, Any]] = []
finish_reason = "stop"
usage_stats: dict[str, Any] | None = None
@@ -600,7 +627,11 @@ class GeminiClient(GenAIClient):
content_parts.append(part.text)
yield ("content_delta", part.text)
elif part.function_call:
# Handle function call
# Gemini streams complete function calls (not partial
# argument deltas), so each part is a distinct tool
# call. Append rather than accumulate by name — the
# latter concatenated parallel/repeated calls into one
# invalid arguments string (e.g. `{...}{...}`).
try:
arguments = (
dict(part.function_call.args)
@@ -610,40 +641,16 @@ class GeminiClient(GenAIClient):
except Exception:
arguments = {}
# Store tool call
tool_call_id = part.function_call.name or ""
tool_call_name = part.function_call.name or ""
# Check if we already have this tool call
found_index = None
for idx, tc in tool_calls_by_index.items():
if tc["name"] == tool_call_name:
found_index = idx
break
if found_index is None:
found_index = len(tool_calls_by_index)
tool_calls_by_index[found_index] = {
"id": tool_call_id,
"name": tool_call_name,
"arguments": "",
"thought_signature": None,
tool_calls_accum.append(
{
"id": part.function_call.name or "",
"name": part.function_call.name or "",
"arguments": arguments,
"thought_signature": getattr(
part, "thought_signature", None
),
}
# Accumulate arguments
if arguments:
tool_calls_by_index[found_index]["arguments"] += (
json.dumps(arguments)
if isinstance(arguments, dict)
else str(arguments)
)
# Capture latest thought_signature for this call
chunk_sig = getattr(part, "thought_signature", None)
if chunk_sig:
tool_calls_by_index[found_index][
"thought_signature"
] = chunk_sig
)
# Build final message
full_content = "".join(content_parts).strip() or None
@@ -651,25 +658,20 @@ class GeminiClient(GenAIClient):
# Convert tool calls to list format
tool_calls_list = None
if tool_calls_by_index:
tool_calls_list = []
for tc in tool_calls_by_index.values():
try:
# Try to parse accumulated arguments as JSON
parsed_args = json.loads(tc["arguments"])
except (json.JSONDecodeError, Exception):
parsed_args = tc["arguments"]
tool_calls_list.append(
{
"id": tc["id"],
"name": tc["name"],
"arguments": parsed_args,
"thought_signature": _encode_thought_signature(
tc.get("thought_signature")
),
}
)
if tool_calls_accum:
tool_calls_list = [
{
"id": tc["id"],
"name": tc["name"],
"arguments": tc["arguments"]
if isinstance(tc["arguments"], dict)
else {},
"thought_signature": _encode_thought_signature(
tc.get("thought_signature")
),
}
for tc in tool_calls_accum
]
finish_reason = "tool_calls"
if usage_stats is not None:
+5
View File
@@ -128,6 +128,11 @@ class LlamaCppClient(GenAIClient):
_text_baseline_tokens: int | None
_media_marker: str
@property
def supports_embeddings(self) -> bool:
"""llama.cpp exposes an /embeddings endpoint for any loaded model."""
return True
def _init_provider(self) -> str | None:
"""Initialize the client and query model metadata from the server."""
self.provider_options = {
+12 -3
View File
@@ -423,9 +423,18 @@ class OpenAIClient(GenAIClient):
for tc in tool_calls_by_index.values():
try:
# Parse accumulated arguments as JSON
parsed_args = json.loads(tc["arguments"])
except (json.JSONDecodeError, Exception):
parsed_args = tc["arguments"]
parsed_args = json.loads(tc["arguments"] or "{}")
except (json.JSONDecodeError, ValueError):
logger.warning(
"Failed to parse streamed tool call arguments for %s",
tc["name"],
)
parsed_args = {}
# Downstream (ToolCall model) requires a dict; never leak a
# partial/invalid arguments string.
if not isinstance(parsed_args, dict):
parsed_args = {}
tool_calls_list.append(
{
+496
View File
@@ -0,0 +1,496 @@
"""Smoke tests for GenAI chat providers.
Each provider's ``chat_with_tools_stream`` is driven with a canned "test
response" so the two conversion layers are exercised without any network:
1. Frigate (OpenAI-style) messages -> provider-native request format
2. provider-native response -> Frigate ``("kind", value)`` stream events
These guard against regressions such as tool-call arguments arriving as raw
strings instead of dicts (which crash the ``ToolCall`` model), and multimodal
user content (a list of text/image parts, as injected by ``get_live_context``)
crashing message conversion.
"""
import asyncio
import base64
import json
import unittest
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
from frigate.config import GenAIConfig, GenAIProviderEnum
from frigate.genai import PROVIDERS, load_providers
load_providers()
# A minimal but valid JPEG data URI, mirroring what get_live_context injects.
_TINY_JPEG = base64.b64encode(b"\xff\xd8\xff\xd9").decode("ascii")
_IMAGE_DATA_URI = f"data:image/jpeg;base64,{_TINY_JPEG}"
# Conversation ending in a multimodal user message (text + live image), the
# exact shape the chat endpoint builds after a get_live_context tool result.
MULTIMODAL_MESSAGES = [
{"role": "system", "content": "You are a test assistant."},
{"role": "user", "content": "what do you see on the front camera?"},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "get_live_context",
"arguments": json.dumps({"camera": "front"}),
},
}
],
},
{
"role": "tool",
"tool_call_id": "call_1",
"name": "get_live_context",
"content": json.dumps({"camera": "front"}),
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "Here is the current live image from camera 'front'.",
},
{"type": "image_url", "image_url": {"url": _IMAGE_DATA_URI}},
],
},
]
SIMPLE_MESSAGES = [
{"role": "system", "content": "You are a test assistant."},
{"role": "user", "content": "hello"},
]
TOOLS = [
{
"type": "function",
"function": {
"name": "search_objects",
"description": "Search tracked objects",
"parameters": {
"type": "object",
"properties": {"label": {"type": "string"}},
},
},
}
]
def _make_client(provider: str, **cfg_overrides):
"""Build a provider client offline (no model validation, no network)."""
cfg = GenAIConfig(provider=provider, **cfg_overrides)
cls = PROVIDERS[GenAIProviderEnum(provider)]
return cls(cfg, timeout=5, validate_model=False)
def _collect(client, messages, tools=TOOLS):
"""Drain chat_with_tools_stream into a list of (kind, value) events."""
async def _run():
events = []
async for event in client.chat_with_tools_stream(
messages=messages, tools=tools, tool_choice="auto"
):
events.append(event)
return events
return asyncio.run(_run())
def _final_message(events) -> dict:
messages = [value for (kind, value) in events if kind == "message"]
assert messages, f"stream produced no final message: {events}"
return messages[-1]
def _assert_tool_args_are_dicts(final: dict) -> None:
"""Every returned tool call must expose arguments as a dict, never a string."""
for tool_call in final.get("tool_calls") or []:
assert isinstance(tool_call["arguments"], dict), (
f"tool call arguments must be a dict, got "
f"{type(tool_call['arguments']).__name__}: {tool_call['arguments']!r}"
)
# ---------------------------------------------------------------------------
# OpenAI
# ---------------------------------------------------------------------------
def _openai_tc(index, id=None, name=None, arguments=None):
return SimpleNamespace(
index=index,
id=id,
function=SimpleNamespace(name=name, arguments=arguments),
)
def _openai_chunk(content=None, tool_calls=None, finish_reason=None, usage=None):
delta = SimpleNamespace(
content=content,
tool_calls=tool_calls,
reasoning_content=None,
reasoning=None,
)
choice = SimpleNamespace(delta=delta, finish_reason=finish_reason)
return SimpleNamespace(choices=[choice], usage=usage)
class TestOpenAIProvider(unittest.TestCase):
def _client(self):
return _make_client(
"openai", model="gpt-4o", api_key="k", base_url="http://localhost:9999/v1"
)
def test_stream_tool_call_arguments_are_dict(self):
# Arguments arrive split across chunks, as the real API streams them.
chunks = [
_openai_chunk(
tool_calls=[
_openai_tc(0, id="c1", name="search_objects", arguments='{"label":')
]
),
_openai_chunk(tool_calls=[_openai_tc(0, arguments=' "person"}')]),
_openai_chunk(finish_reason="tool_calls"),
]
client = self._client()
client.provider.chat.completions.create = MagicMock(return_value=iter(chunks))
final = _final_message(_collect(client, SIMPLE_MESSAGES))
self.assertEqual(final["finish_reason"], "tool_calls")
self.assertEqual(len(final["tool_calls"]), 1)
_assert_tool_args_are_dicts(final)
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
def test_stream_content_response(self):
chunks = [
_openai_chunk(content="hel"),
_openai_chunk(content="lo"),
_openai_chunk(finish_reason="stop"),
]
client = self._client()
client.provider.chat.completions.create = MagicMock(return_value=iter(chunks))
events = _collect(client, SIMPLE_MESSAGES)
deltas = [v for (k, v) in events if k == "content_delta"]
self.assertEqual("".join(deltas), "hello")
self.assertEqual(_final_message(events)["content"], "hello")
def test_multimodal_message_does_not_crash(self):
client = self._client()
client.provider.chat.completions.create = MagicMock(
return_value=iter([_openai_chunk(content="ok", finish_reason="stop")])
)
# Passing the OpenAI-native multimodal list through must not raise.
final = _final_message(_collect(client, MULTIMODAL_MESSAGES))
self.assertEqual(final["content"], "ok")
# ---------------------------------------------------------------------------
# Gemini
# ---------------------------------------------------------------------------
def _gemini_part(text=None, thought=False, function_call=None, thought_signature=None):
return SimpleNamespace(
text=text,
thought=thought,
function_call=function_call,
thought_signature=thought_signature,
)
def _gemini_chunk(parts, finish_reason=None, usage_metadata=None):
candidate = SimpleNamespace(
content=SimpleNamespace(parts=parts), finish_reason=finish_reason
)
return SimpleNamespace(candidates=[candidate], usage_metadata=usage_metadata)
def _gemini_stream(chunks):
async def _agen(*args, **kwargs):
for chunk in chunks:
yield chunk
return _agen
class TestGeminiProvider(unittest.TestCase):
def _client(self):
return _make_client("gemini", model="gemini-2.5-flash", api_key="k")
def _patch_stream(self, client, chunks):
client.provider = MagicMock()
client.provider.aio.models.generate_content_stream = AsyncMock(
side_effect=_gemini_stream(chunks)
)
def test_stream_parallel_tool_calls_stay_separate_dicts(self):
# Regression: Gemini streams complete function calls. Two calls to the
# same tool must NOT be merged into one concatenated arguments string.
from google.genai.types import FinishReason
chunks = [
_gemini_chunk(
parts=[
_gemini_part(
function_call=SimpleNamespace(
name="search_objects", args={"label": "person"}
)
),
_gemini_part(
function_call=SimpleNamespace(
name="search_objects", args={"limit": 1}
)
),
],
finish_reason=FinishReason.STOP,
),
]
client = self._client()
self._patch_stream(client, chunks)
final = _final_message(_collect(client, SIMPLE_MESSAGES))
self.assertEqual(final["finish_reason"], "tool_calls")
self.assertEqual(len(final["tool_calls"]), 2)
_assert_tool_args_are_dicts(final)
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
self.assertEqual(final["tool_calls"][1]["arguments"], {"limit": 1})
def test_stream_content_response(self):
from google.genai.types import FinishReason
chunks = [
_gemini_chunk(parts=[_gemini_part(text="hel")]),
_gemini_chunk(
parts=[_gemini_part(text="lo")], finish_reason=FinishReason.STOP
),
]
client = self._client()
self._patch_stream(client, chunks)
events = _collect(client, SIMPLE_MESSAGES)
deltas = [v for (k, v) in events if k == "content_delta"]
self.assertEqual("".join(deltas), "hello")
self.assertEqual(_final_message(events)["content"], "hello")
def test_multimodal_message_converts_without_crash(self):
# Regression: a user message with list content (text + image_url) used
# to be handed to Part.from_text(text=<list>) and raise ValidationError.
from google.genai.types import FinishReason
client = self._client()
self._patch_stream(
client,
[
_gemini_chunk(
parts=[_gemini_part(text="ok")], finish_reason=FinishReason.STOP
)
],
)
final = _final_message(_collect(client, MULTIMODAL_MESSAGES))
self.assertEqual(final["content"], "ok")
# ---------------------------------------------------------------------------
# Ollama
# ---------------------------------------------------------------------------
class TestOllamaProvider(unittest.TestCase):
def _client(self):
return _make_client("ollama", model="llama3", base_url="http://localhost:9999")
def _run_with_response(self, client, response, messages):
# Ollama uses a non-streaming call when tools are present, via an
# internally-constructed async client.
fake_async = MagicMock()
fake_async.chat = AsyncMock(return_value=response)
with patch(
"frigate.genai.plugins.ollama.OllamaAsyncClient",
return_value=fake_async,
):
return _collect(client, messages)
def test_tool_call_arguments_are_dict(self):
response = {
"message": {
"content": "",
"tool_calls": [
{
"function": {
"name": "search_objects",
"arguments": {"label": "person"},
}
}
],
},
"done": True,
"done_reason": "stop",
"eval_count": 5,
"prompt_eval_count": 3,
"eval_duration": 1_000_000,
}
client = self._client()
final = _final_message(
self._run_with_response(client, response, SIMPLE_MESSAGES)
)
self.assertEqual(final["finish_reason"], "tool_calls")
_assert_tool_args_are_dicts(final)
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
def test_multimodal_message_normalizes_image(self):
# Ollama needs content as a string with images pulled into a separate
# field; the normalizer must extract both without crashing.
response = {
"message": {"content": "ok"},
"done": True,
"done_reason": "stop",
}
client = self._client()
final = _final_message(
self._run_with_response(client, response, MULTIMODAL_MESSAGES)
)
self.assertEqual(final["content"], "ok")
def test_normalize_multimodal_content(self):
from frigate.genai.plugins.ollama import _normalize_multimodal_content
text, images = _normalize_multimodal_content(MULTIMODAL_MESSAGES[-1]["content"])
self.assertIn("live image", text)
self.assertEqual(len(images), 1)
self.assertEqual(images[0], b"\xff\xd8\xff\xd9")
# ---------------------------------------------------------------------------
# llama.cpp
# ---------------------------------------------------------------------------
class _FakeStreamResponse:
def __init__(self, lines):
self._lines = lines
def raise_for_status(self):
return None
async def aiter_lines(self):
for line in self._lines:
yield line
class _FakeStreamCtx:
def __init__(self, lines):
self._resp = _FakeStreamResponse(lines)
async def __aenter__(self):
return self._resp
async def __aexit__(self, *exc):
return False
class _FakeAsyncClient:
def __init__(self, lines):
self._lines = lines
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
def stream(self, method, url, json=None):
return _FakeStreamCtx(self._lines)
class TestLlamaCppProvider(unittest.TestCase):
def _client(self):
return _make_client("llamacpp", model="m", base_url="http://localhost:9999")
def _run_with_lines(self, client, lines, messages):
with patch(
"frigate.genai.plugins.llama_cpp.httpx.AsyncClient",
return_value=_FakeAsyncClient(lines),
):
return _collect(client, messages)
def test_stream_tool_call_arguments_are_dict(self):
lines = [
"data: "
+ json.dumps(
{
"choices": [
{
"delta": {
"tool_calls": [
{
"index": 0,
"id": "c1",
"function": {
"name": "search_objects",
"arguments": '{"label":',
},
}
]
}
}
]
}
),
"data: "
+ json.dumps(
{
"choices": [
{
"delta": {
"tool_calls": [
{
"index": 0,
"function": {"arguments": ' "person"}'},
}
]
}
}
]
}
),
"data: "
+ json.dumps({"choices": [{"delta": {}, "finish_reason": "tool_calls"}]}),
"data: [DONE]",
]
client = self._client()
final = _final_message(self._run_with_lines(client, lines, SIMPLE_MESSAGES))
self.assertEqual(final["finish_reason"], "tool_calls")
_assert_tool_args_are_dicts(final)
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
def test_stream_content_response(self):
lines = [
"data: " + json.dumps({"choices": [{"delta": {"content": "hel"}}]}),
"data: " + json.dumps({"choices": [{"delta": {"content": "lo"}}]}),
"data: "
+ json.dumps({"choices": [{"delta": {}, "finish_reason": "stop"}]}),
"data: [DONE]",
]
client = self._client()
events = self._run_with_lines(client, lines, SIMPLE_MESSAGES)
deltas = [v for (k, v) in events if k == "content_delta"]
self.assertEqual("".join(deltas), "hello")
self.assertEqual(_final_message(events)["content"], "hello")
def test_multimodal_message_does_not_crash(self):
lines = [
"data: " + json.dumps({"choices": [{"delta": {"content": "ok"}}]}),
"data: "
+ json.dumps({"choices": [{"delta": {}, "finish_reason": "stop"}]}),
"data: [DONE]",
]
client = self._client()
final = _final_message(self._run_with_lines(client, lines, MULTIMODAL_MESSAGES))
self.assertEqual(final["content"], "ok")
if __name__ == "__main__":
unittest.main()
+2 -1
View File
@@ -61,7 +61,8 @@
"error": {
"endTimeMustAfterStartTime": "L'hora de finalització ha de ser posterior a l'hora d'inici",
"noVaildTimeSelected": "No s'ha seleccionat un rang de temps vàlid",
"failed": "No s'ha pogut inciar l'exportació: {{error}}"
"failed": "No s'ha pogut inciar l'exportació: {{error}}",
"noValidTimeSelected": "No s'ha seleccionat cap interval de temps vàlid"
},
"view": "Vista",
"queued": "Exporta a la cua. Mostra el progrés a la pàgina d'exportacions.",
@@ -493,6 +493,9 @@
"max_concurrent": {
"label": "Màxim d'exportacions concurrents",
"description": "Nombre màxim de treballs d'exportació a processar al mateix temps."
},
"chapters": {
"label": "Metadades de capítol per incrustar en els enregistraments exportats"
}
},
"preview": {
+3
View File
@@ -380,6 +380,9 @@
"max_concurrent": {
"label": "Màxim d'exportacions concurrents",
"description": "Nombre màxim de treballs d'exportació a processar al mateix temps."
},
"chapters": {
"label": "Metadades de capítol per incrustar en els enregistraments exportats"
}
},
"preview": {
+1 -1
View File
@@ -303,7 +303,7 @@
},
"offset": {
"label": "Òfset d'Anotació",
"desc": "Aquestes dades provenen del flux de detecció de la càmera, però se superposen a les imatges del flux de gravació. És poc probable que els dos fluxos estiguin perfectament sincronitzats. Com a resultat, el quadre delimitador i les imatges no s'alinearan perfectament. Tanmateix, es pot utilitzar el camp <code>annotation_offset</code> per ajustar-ho.",
"desc": "Aquestes dades provenen del canal de detecció de la càmera, però estan sobreposades a les imatges del canal de registre. És poc probable que els dos corrents estiguin perfectament sincronitzats. Com a resultat, la caixa contenidora i les imatges no s'alinearan perfectament. Podeu utilitzar aquest paràmetre per a compensar les anotacions cap endavant o cap enrere en el temps per a alinear-les millor amb el metratge gravat.",
"millisecondsToOffset": "Millisegons per l'òfset de detecció d'anotacions per. <em>Per defecte: 0</em>",
"tips": "Reduïu el valor si la reproducció del vídeo es troba per davant dels quadres i els punts de ruta, i augmenteu-lo si es troba per darrere. Aquest valor pot ser negatiu.",
"toast": {
+11 -4
View File
@@ -698,7 +698,7 @@
"title": "Crear un nou usuari",
"confirmPassword": "Siusplau, confirma la contrasenya",
"usernameOnlyInclude": "El nom d'usuari només pot contenir lletres, números, . o _",
"desc": "Afegeix un nou compte d'usuari i especifica un rol per accedir a àrees de la interfície de Frigate."
"desc": "Afegeix un compte d'usuari nou i especifica un rol per a l'accés a les àrees de la interfície d'usuari de Frigate."
}
},
"title": "Usuaris",
@@ -1323,7 +1323,7 @@
"details": {
"edit": "Edita els detalls de la càmera",
"title": "Edita els detalls de la càmera",
"description": "Actualitza el nom de visualització, l'URL extern i la visibilitat utilitzada per a aquesta càmera a tota la interfície d'usuari de la Fragata.",
"description": "Actualitza el nom de visualització, l'URL extern i la visibilitat utilitzada per a aquesta càmera a tota la interfície d'usuari de Frigate.",
"friendlyNameLabel": "Nom a mostrar",
"friendlyNameHelp": "Nom amistós que es mostra per a aquesta càmera a tota la interfície d'usuari de Frigate. Deixeu-ho en blanc per utilitzar l'ID de la càmera.",
"webuiUrlLabel": "URL de la interfície web de la càmera",
@@ -1484,7 +1484,7 @@
"successMulti_other": "Configuració copiada a {{count}} càmeres",
"successMultiWithRestart_one": "Configuració copiada a la càmera {{count}}. Reinicia Frigate per aplicar tots els canvis.",
"successMultiWithRestart_many": "Configuració copiada a {{count}} càmeres. Reinicia Frigate per aplicar tots els canvis.",
"successMultiWithRestart_other": "Configuració copiada a {{count}} càmeres. Reinicia la fragata per aplicar tots els canvis.",
"successMultiWithRestart_other": "Configuració copiada a {{count}} càmeres. Reinicia Frigate per aplicar tots els canvis.",
"partialFailure": "{{successCount}} seccions aplicades; «{{failedSection}}» ha fallat: {{errorMessage}}",
"partialFailureMulti": "S'ha copiat a {{successCount}} càmera(es); ha fallat {{failed}}: {{errorMessage}}",
"newCameraPartialFailure": "S'ha creat la càmera {{cameraName}} però no s'han pogut copiar alguns paràmetres: {{errorMessage}}",
@@ -1641,7 +1641,14 @@
"keyLabel": "Clau",
"valueLabel": "Valor",
"keyPlaceholder": "Nou valor",
"remove": "Elimina"
"remove": "Elimina",
"providerNameLabel": "Nom del proveïdor",
"providerNamePlaceholder": "p. ex., openai",
"variableNameLabel": "Nom de la variable",
"variableNamePlaceholder": ". ex., La_Meva_Variable",
"loggerNameLabel": "Nom del registrador",
"loggerNamePlaceholder": "p. ex., friagte.registre",
"keyPatternError": "Utilitza només lletres, números, guions i guions baixos (sense espais)"
},
"timezone": {
"defaultOption": "Utilitza la zona horària del navegador"
@@ -1,5 +1,6 @@
{
"documentTitle": "Classification Models - Frigate",
"disabled": "Disabled",
"details": {
"scoreInfo": "Score represents the average classification confidence across all detections of this object.",
"none": "None",
@@ -64,7 +65,20 @@
"title": "Edit Classification Model",
"descriptionState": "Edit the classes for this state classification model. Changes will require retraining the model.",
"descriptionObject": "Edit the object type and classification type for this object classification model.",
"stateClassesInfo": "Note: Changing state classes requires retraining the model with the updated classes."
"enabled": "Enabled",
"enabledDesc": "Run this model. When disabled, it stops running and no longer classifies.",
"saveAttempts": "Save Attempts",
"saveAttemptsDesc": "Number of classification attempt images to keep for the recent classifications UI.",
"motion": "Run on Motion",
"motionDesc": "Run classification when motion is detected within the configured crop.",
"interval": "Interval",
"intervalDesc": "Seconds between periodic classification runs. Leave empty to run only on motion.",
"intervalPlaceholder": "No interval",
"stateClassesInfo": "Model updated. Retrain the model for the class changes to take effect.",
"errors": {
"saveAttemptsInvalid": "Save attempts must be a whole number of 0 or greater",
"intervalInvalid": "Interval must be a whole number greater than 0"
}
},
"deleteDatasetImages": {
"title": "Delete Dataset Images",
+65 -1
View File
@@ -273,5 +273,69 @@
"sailboat": "Purjekas",
"soundtrack_music": "Filmimuusika",
"jingle": "Kõlisemine/tilisemine",
"theme_music": "Tunnusmuusika"
"theme_music": "Tunnusmuusika",
"steel_guitar": "Steel Kitarr",
"tapping": "Koputamine",
"strum": "Klimberdus",
"drum_machine": "Trummimasin",
"drum": "Trumm",
"maraca": "Marakas",
"bowed_string_instrument": "Poogenkeelpill",
"singing_bowl": "Helikauss",
"wind_noise": "Tuulemüra",
"rustling_leaves": "Sahisevad lehed",
"waves": "Lained",
"steam": "Aur",
"ship": "Laev",
"motor_vehicle": "Mootorsõiduk",
"rowboat": "Sõudepaat",
"motorboat": "Mootorpaat",
"waterfall": "Kosk",
"ocean": "Ookean",
"rain_on_surface": "Vihm pinnal",
"stream": "Oja",
"fire": "Tuli",
"crackle": "Praksumine",
"car_alarm": "Auto alarm",
"truck": "Veoauto",
"police_car": "Politseiauto",
"ambulance": "Kiirabi",
"fire_engine": "Tuletõrjeauto",
"aircraft": "Lennuk",
"aircraft_engine": "Lennukimootor",
"jet_engine": "Reaktiivmootor",
"propeller": "Propeller",
"helicopter": "Helikopter",
"fixed-wing_aircraft": "Fikseeritud tiivaga õhusõiduk",
"train_horn": "Rongisignaal",
"railroad_car": "Kaubavagun",
"lawn_mower": "Muruniiduk",
"chainsaw": "mootorsaag",
"engine": "Mootor",
"knock": "Koputus",
"alarm": "Häire",
"siren": "Sireen",
"fire_alarm": "Tulekahjuhäire",
"telephone": "Telefon",
"telephone_bell_ringing": "Helisev telefon",
"ringtone": "Telefonihelin",
"smoke_detector": "Suitsuandur",
"foghorn": "Udupasun",
"whistle": "Vile",
"printer": "Printer",
"drill": "Puur",
"explosion": "Plahvatus",
"hammer": "Haamer",
"air_conditioning": "Õhukonditsioneer",
"gunshot": "Püssilask",
"glass": "Klaas",
"boom": "Pauk",
"fireworks": "Ilutulestik",
"static": "Staatiline",
"white_noise": "Valge müra",
"radio": "Raadio",
"television": "Televiisor",
"scream": "Karjumine",
"pour": "Valamine",
"drip": "Tilkumine"
}
+77 -5
View File
@@ -37,6 +37,9 @@
"ask_an": "Kas see objekt on <code>{{label}}</code>?",
"ask_full": "Kas see objekt on <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?",
"label": "Kinnita see silt Frigate+ teenuse jaoks"
},
"toast": {
"error": "Frigate+ teenusesse saatmine ebaõnnestus. Palun kontrolli oma võrguühendust ja proovi uuesti."
}
},
"submitToPlus": {
@@ -70,18 +73,79 @@
"success": "Eksportimise käivitamine õnnestus. Faili leiad eksportimise lehelt.",
"view": "Vaata",
"error": {
"failed": "Eksportimise käivitamine ei õnnestunud: {{error}}",
"failed": "Eksportimise järjekorda lisamine ei õnnestunud: {{error}}",
"endTimeMustAfterStartTime": "Ajavahemiku lõpp peab olema peale algust",
"noVaildTimeSelected": "Ühtegi kehtivat ajavahemikku pole valitud"
}
"noVaildTimeSelected": "Ühtegi kehtivat ajavahemikku pole valitud",
"noValidTimeSelected": "Ühtegi korrektset ajavahemikku pole valitud"
},
"queued": "Eksport on järjekorda lisatud. Vaata progressi eksportide lehelt.",
"batchSuccess_one": "Alustasin 1 ekspordiga. Avan juhtumi kohe.",
"batchSuccess_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.",
"batchPartial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestunud kaamerad: {{failedCameras}}",
"batchFailed": "{{total}} eksporti ebaõnnestus algatada. Ebaõnnestunud kaamerad: {{failedCameras}}",
"batchQueuedSuccess_one": "1 eksport järjekorda lisatud. Avan juhtumi kohe.",
"batchQueuedSuccess_other": "{{count}} eksporti järjekorda lisatud. Avan juhtumi kohe.",
"batchQueuedPartial": "{{successful}}/{{total}} ekspordist on järjekorda lisatud. Ebaõnnestunud kaamerad: {{failedCameras}}",
"batchQueueFailed": "{{total}} eksporti ebaõnnestus järjekorda lisada. Ebaõnnestunud kaamerad: {{failedCameras}}"
},
"fromTimeline": {
"saveExport": "Salvesta eksporditud sisu",
"previewExport": "Eksporditud sisu eelvaade"
"previewExport": "Eksporditud sisu eelvaade",
"queueingExport": "Ekspordi järjekorda lisamine...",
"useThisRange": "Kasuta seda vahemikku"
},
"case": {
"label": "Juhtum",
"placeholder": "Vali juhtum"
"placeholder": "Vali juhtum",
"newCaseOption": "Ava uus juhtum",
"newCaseNamePlaceholder": "Uue juhtumi nimi",
"newCaseDescriptionPlaceholder": "Juhtumi kirjeldus",
"nonAdminHelp": "Uus juhtum avatakse järnevatele eksportidele."
},
"queueing": "Ekspordi järjekorda lisamine...",
"tabs": {
"export": "Üksik kaamera",
"multiCamera": "Mitu kaamerat"
},
"multiCamera": {
"timeRange": "Ajavahemik",
"selectFromTimeline": "Vali ajajoonelt",
"cameraSelection": "Kaamerad",
"cameraSelectionHelp": "Selles ajavahemikus tuvastatud objektidega kaamerad on eelvalitud",
"searchOrSelectGroup": "Otsi või vali kaamera grupp...",
"selectAll": "Vali kõik kaamerad",
"clearSelection": "Tühista valik",
"selectWithActivity": "Jälgitud objektidega kaamerad",
"selectGroup": "Vali grupp",
"noMatchingCameras": "Ükski kaamera ei sobitunud otsinguga",
"selectedCount": "{{selected}} / {{total}} valitud",
"checkingActivity": "Kaamera aktiivsuse kontrollimine...",
"noCameras": "Ühtegi kaamerat pole saadaval",
"detectionCount_one": "1 jälgitav objekt",
"detectionCount_other": "{{count}} jälgitavat objekti",
"nameLabel": "Ekspordi nimi",
"namePlaceholder": "Valikuline baasnimi nendele eksportidele",
"queueingButton": "Ekspordi järjekorda lisamine...",
"exportButton_one": "Ekspordi 1 kaamera",
"exportButton_other": "Ekspordi {{count}} kaamerat"
},
"multi": {
"title_one": "Ekspordi 1 ülevaade",
"title_other": "Espordi {{count}} ülevaadet",
"description": "Ekspordi valitud ülevaated. Kõik ekspordid on grupeeritud ühte juhtumisse.",
"descriptionNoCase": "Ekspordi kõik valitud juhtumid.",
"caseNamePlaceholder": "Ülevaate eksport - {{date}}",
"exportButton_one": "Ekspordi 1 ülevaade",
"exportButton_other": "Ekspordi {{count}} ülevaadet",
"exportingButton": "Ekspordin...",
"toast": {
"started_one": "Alustasin 1 eksporti. Avan juhtumi kohe.",
"started_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.",
"startedNoCase_one": "Alustasin 1 ekspordiga.",
"startedNoCase_other": "Alustasin {{count}} ekspordiga.",
"partial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestusid: {{failedItems}}",
"failed": "{{total}} ekspordi algatamine ebaõnnestus. Ebaõnnestunud: {{failedItems}}"
}
}
},
"streaming": {
@@ -114,6 +178,14 @@
"success": "Selle ülevaadatava objektiga seotud videosisu on kustutatud.",
"error": "Kustutamine ei õnnestunud: {{error}}"
}
},
"shareTimestamp": {
"label": "Jaotise ajatempel",
"title": "Jaotise ajatempel",
"description": "Jaga praeguse esituskoha ajatempliga URL-i või vali kohandatud ajatempel. Arvesta, et see ei ole avalik jagamislink ja on kättesaadav ainult kasutajatele, kellel on ligipääs Frigate'ile ja sellele kaamerale.",
"custom": "Kohandatud ajatempel",
"button": "Jaga ajatempliga linki",
"shareTitle": "Frigate ülevaate ajatempel: {{camera}}"
}
},
"imagePicker": {
+177 -2
View File
@@ -12,11 +12,20 @@
"description": "Kasutusel"
},
"audio": {
"label": "Helisündmused"
"label": "Heli tuvastus",
"min_volume": {
"label": "Minimaalne helitase"
},
"filters": {
"label": "Audio filtrid"
}
},
"birdseye": {
"mode": {
"label": "Jälgimisrežiim"
},
"order": {
"label": "Positsioon"
}
},
"label": "Kaameraseadistus",
@@ -25,7 +34,8 @@
"threshold": {
"description": "Minimaalne sarnasuse punktiskoor (0-1), mis on vajalik selle päästiku käivitamiseks."
}
}
},
"label": "Semantiline otsing"
},
"lpr": {
"label": "Sõidukite numbrimärkide tuvastus",
@@ -34,6 +44,171 @@
"review": {
"genai": {
"description": "Kontrollib generatiivse tehisaru kasutamist kirjelduste ja kokkuvõtete koostamiseks ülevaatamisele kuuluvate objektide jaoks."
},
"alerts": {
"enabled": {
"label": "Luba häired"
}
}
},
"audio_transcription": {
"label": "Audio transkriptsioon",
"live_enabled": {
"label": "Reaalajas transkriptsioon"
},
"enabled": {
"label": "Luba heli üleskirjutamine tekstina"
}
},
"detect": {
"label": "Objekti tuvastus",
"enabled": {
"label": "Luba objektituvastus"
},
"height": {
"label": "Tuvastamise kõrgus"
},
"width": {
"label": "Tuvastamise laius"
},
"fps": {
"label": "Tuvastamise kaadrisagedus"
},
"stationary": {
"label": "Püsivate objektide sätted"
}
},
"face_recognition": {
"label": "Näotuvastus"
},
"ffmpeg": {
"path": {
"label": "FFmpeg asukoht"
},
"gpu": {
"label": "GPU indeks"
},
"inputs": {
"global_args": {
"label": "FFmpeg globaalsed argumendid"
}
}
},
"live": {
"label": "Reaalajas mahamängimine",
"height": {
"label": "Otseülekande kõrgus"
},
"quality": {
"label": "Otseülekande kvaliteet"
},
"streams": {
"label": "Otseülekande voo nimed"
}
},
"motion": {
"label": "Liikumistuvastus",
"frame_height": {
"label": "Kaadri kõrgus"
},
"enabled": {
"label": "Luba liikumistuvastus"
}
},
"objects": {
"label": "Objektid",
"filters": {
"min_area": {
"label": "Minimaalne objekti ala"
},
"max_area": {
"label": "Maksimaalne objekti ala"
}
},
"genai": {
"label": "GenAI objekti konfiguratsioon",
"required_zones": {
"label": "Nõutud tsoonid"
},
"debug_save_thumbnails": {
"label": "Salvesta pisipildid"
},
"enabled": {
"label": "Luba GenAI"
},
"use_snapshot": {
"label": "Kasuta hetktõmmiseid"
}
}
},
"mqtt": {
"label": "MQTT"
},
"notifications": {
"enabled": {
"label": "Luba teavitused"
},
"email": {
"label": "Teavituste email"
},
"label": "Teavitused"
},
"ui": {
"label": "Kaamera kasutajaliides"
},
"record": {
"label": "Salvestus",
"enabled": {
"label": "Luba salvestamine"
}
},
"snapshots": {
"enabled": {
"label": "Luba hetktõmmised"
}
},
"timestamp_style": {
"color": {
"red": {
"label": "Punane",
"description": "Punase komponent (0–255) ajatempli värvi jaoks."
},
"green": {
"label": "Roheline",
"description": "Rohelise komponent (0–255) ajatempli värvi jaoks."
},
"blue": {
"label": "Sinine",
"description": "Sinise komponent (0–255) ajatempli värvi jaoks."
},
"label": "Ajatempli värv",
"description": "Ajatempli teksti RGB värviväärtused (kõik väärtused 0–255)."
},
"thickness": {
"label": "Ajatempli paksus",
"description": "Ajatempli teksti joone paksus."
},
"effect": {
"label": "Ajatempli efekt",
"description": "Ajatempli teksti visuaalne efekt (puudub, ühtlane, vari)."
}
},
"onvif": {
"user": {
"label": "ONVIF kasutajanimi"
},
"password": {
"label": "ONVIF parool"
},
"port": {
"label": "ONVIF port"
},
"label": "ONVIF",
"host": {
"label": "ONVIF host"
}
},
"profiles": {
"label": "Profiilid"
}
}
+424 -2
View File
@@ -1,10 +1,31 @@
{
"audio": {
"label": "Helisündmused"
"label": "Heli tuvastus",
"min_volume": {
"label": "Minimaalne helitase"
},
"filters": {
"label": "Audio filtrid"
}
},
"birdseye": {
"mode": {
"label": "Jälgimisrežiim"
},
"order": {
"label": "Positsioon"
},
"height": {
"label": "Kõrgus"
},
"width": {
"label": "Laius"
},
"layout": {
"label": "Paigutus",
"scaling_factor": {
"label": "Skaleerimistegur"
}
}
},
"version": {
@@ -22,6 +43,15 @@
"threshold": {
"label": "Punktiskoori lävend",
"description": "Punktiskoori lävend, mida kasutatakse klassifitseerimise oleku muutmiseks."
},
"enabled": {
"label": "Luba mudel"
},
"name": {
"label": "Mudeli nimi"
},
"save_attempts": {
"label": "Salvestamiskatsed"
}
}
},
@@ -30,11 +60,25 @@
"threshold": {
"description": "Minimaalne sarnasuse punktiskoor (0-1), mis on vajalik selle päästiku käivitamiseks."
}
},
"label": "Semantiline otsing",
"model_size": {
"label": "Mudeli suurus"
},
"device": {
"label": "Seade"
}
},
"face_recognition": {
"unknown_score": {
"label": "Tundmatu punktiskoori lävend"
},
"label": "Näotuvastus",
"model_size": {
"label": "Mudeli suurus"
},
"device": {
"label": "Seade"
}
},
"lpr": {
@@ -42,15 +86,393 @@
"description": "Sõidukite numbrimärkide tuvastuse seadistus sisaldab tuvastuse lävendeid, vormindust ja teadaolevaid numbrimärke.",
"enabled": {
"description": "Lülita sõidukite numbrimärkide tuvastus kõikide kaamerate jaoks sisse; seda saad kaamerakohaselt ka sürjutada."
},
"model_size": {
"label": "Mudeli suurus"
},
"device": {
"label": "Seade"
}
},
"genai": {
"label": "Generatiivse tehisaru seadistus",
"description": "Seadistsued generatiivse tehisaru teenusepakkujate kasutamisel kirjelduste ja kokkuvõtete loomiseks ülevaatamisele kuuluvate objektide jaoks."
"description": "Seadistsued generatiivse tehisaru teenusepakkujate kasutamisel kirjelduste ja kokkuvõtete loomiseks ülevaatamisele kuuluvate objektide jaoks.",
"model": {
"label": "Mudel"
},
"roles": {
"label": "Rollid"
},
"api_key": {
"label": "API võti"
},
"base_url": {
"label": "Baas-URL"
}
},
"review": {
"genai": {
"description": "Kontrollib generatiivse tehisaru kasutamist kirjelduste ja kokkuvõtete koostamiseks ülevaatamisele kuuluvate objektide jaoks."
},
"alerts": {
"enabled": {
"label": "Luba häired",
"description": "Luba või keela kõigi kaamerate häirete genereerimine; seda saab kaamerati eraldi muuta."
}
}
},
"audio_transcription": {
"label": "Audio transkriptsioon",
"live_enabled": {
"label": "Reaalajas transkriptsioon"
},
"enabled": {
"label": "Luba heli üleskirjutamine tekstina",
"description": "Luba või keela automaatne heli üleskirjutamine tektina kõigi kaamerate jaoks; seda saad kaamerati eraldi muuta."
},
"language": {
"label": "Üleskirjutuse keel",
"description": "Üleskirjutuse/tõlkimise jaoks kasutatav keelekood (näiteks „en” inglise keele puhul). Toetatud keelekoodid leiad lehelt https://whisper-api.com/docs/languages/."
},
"device": {
"label": "Üleskirjutusseade"
},
"model_size": {
"label": "Mudeli suurus"
}
},
"detect": {
"label": "Objekti tuvastus",
"enabled": {
"label": "Luba objektituvastus"
},
"height": {
"label": "Tuvastamise kõrgus"
},
"width": {
"label": "Tuvastamise laius"
},
"fps": {
"label": "Tuvastamise kaadrisagedus"
},
"stationary": {
"label": "Püsivate objektide sätted"
}
},
"ffmpeg": {
"path": {
"label": "FFmpeg asukoht"
},
"gpu": {
"label": "GPU indeks"
},
"inputs": {
"global_args": {
"label": "FFmpeg globaalsed argumendid"
}
},
"label": "FFmpeg"
},
"live": {
"label": "Reaalajas mahamängimine",
"height": {
"label": "Otseülekande kõrgus"
},
"quality": {
"label": "Otseülekande kvaliteet"
},
"streams": {
"label": "Otseülekande voo nimed"
}
},
"motion": {
"label": "Liikumistuvastus",
"frame_height": {
"label": "Kaadri kõrgus"
},
"enabled": {
"label": "Luba liikumistuvastus"
}
},
"objects": {
"label": "Objektid",
"filters": {
"min_area": {
"label": "Minimaalne objekti ala"
},
"max_area": {
"label": "Maksimaalne objekti ala"
}
},
"genai": {
"label": "GenAI objekti konfiguratsioon",
"required_zones": {
"label": "Nõutud tsoonid"
},
"debug_save_thumbnails": {
"label": "Salvesta pisipildid"
},
"enabled": {
"label": "Luba GenAI"
},
"use_snapshot": {
"label": "Kasuta hetktõmmiseid"
}
}
},
"auth": {
"reset_admin_password": {
"label": "Lähtesta administraatori parool"
},
"cookie_name": {
"label": "JWT küpsise nimi"
},
"enabled": {
"label": "Luba autentimine"
},
"label": "Autentimine",
"session_length": {
"label": "Sessiooni pikkus"
}
},
"database": {
"label": "Andmebaas"
},
"mqtt": {
"label": "MQTT",
"enabled": {
"label": "Luba MQTT"
},
"user": {
"label": "MQTT kasutajanimi"
},
"tls_ca_certs": {
"label": "TLS CA sertifikaat"
},
"tls_client_cert": {
"label": "Kliendi sertifikaat"
},
"host": {
"label": "MQTT host",
"description": "MQTT maakleri hostinimi või IP-aadress."
},
"port": {
"label": "MQTT port"
},
"client_id": {
"label": "Kliendi ID"
},
"stats_interval": {
"label": "Statistika intervall"
},
"tls_client_key": {
"label": "Kliendi võti"
},
"qos": {
"label": "MQTT QoS"
}
},
"go2rtc": {
"label": "go2rtc"
},
"notifications": {
"enabled": {
"label": "Luba teavitused"
},
"email": {
"label": "Teavituste email"
},
"label": "Teavitused"
},
"networking": {
"ipv6": {
"label": "IPv6 sätted",
"enabled": {
"label": "Luba IPv6"
}
},
"label": "Võrguühendus",
"listen": {
"internal": {
"label": "Sisemine port"
},
"external": {
"label": "Välimine port"
}
}
},
"proxy": {
"label": "Vaheserver",
"logout_url": {
"label": "Väljalogimise url"
},
"default_role": {
"label": "Vaikimisi roll"
}
},
"telemetry": {
"stats": {
"label": "Süsteemi statistika",
"amd_gpu_stats": {
"label": "AMD GPU statistika"
},
"intel_gpu_stats": {
"label": "Intel GPU statistika"
},
"intel_gpu_device": {
"label": "Intel GPU seade"
},
"network_bandwidth": {
"label": "Võrgu ribalaius"
}
},
"label": "Telemeetria",
"network_interfaces": {
"label": "Võrguliides"
},
"version_check": {
"label": "Versiooni kontroll",
"description": "Luba väljaminev ühendus, et kontrollida uuema Frigate versooni olemasolu."
}
},
"tls": {
"label": "TLS",
"enabled": {
"label": "Luba TLS"
}
},
"ui": {
"timezone": {
"label": "ajavöönd"
},
"label": "Kasutajaliides",
"description": "Kasutajaliidese eelistused nagu ajavöönd, aja ja kuupäeva formaat ning ühikud.",
"unit_system": {
"label": "Ühikute süsteem"
}
},
"detectors": {
"label": "Detektori riistvara",
"cpu": {
"label": "CPU"
},
"deepstack": {
"label": "DeepStack"
},
"edgetpu": {
"label": "EdgeTPU"
},
"onnx": {
"label": "ONNX",
"device": {
"label": "Seadme tüüp"
}
},
"rknn": {
"label": "RKNN"
},
"openvino": {
"label": "OpenVINO"
},
"tensorrt": {
"label": "TensorRT"
},
"teflon_tfl": {
"label": "Teflon"
},
"synaptics": {
"label": "Synaptics"
},
"memryx": {
"device": {
"label": "Seadme asukoht"
},
"label": "MemryX"
},
"hailo8l": {
"label": "Hailo-8/Hailo-8L"
},
"zmq": {
"label": "ZMQ IPC"
},
"axengine": {
"label": "AXEngine NPU"
},
"degirum": {
"label": "DeGirum"
}
},
"logger": {
"label": "Logimine"
},
"model": {
"label": "Tuvastusmudel"
},
"record": {
"label": "Salvestus",
"enabled": {
"label": "Luba salvestamine",
"description": "Luba või keela salvestamine kõigi kaamerate jaoks; seda saab kaamerati eraldi muuta."
}
},
"snapshots": {
"enabled": {
"label": "Luba hetktõmmised",
"description": "Luba või keela kõigi kaamerate hetktõmmiste salvestamine; seda saab kaamerati eraldi muuta."
}
},
"timestamp_style": {
"color": {
"red": {
"label": "Punane",
"description": "Punase komponent (0–255) ajatempli värvi jaoks."
},
"green": {
"label": "Roheline",
"description": "Rohelise komponent (0–255) ajatempli värvi jaoks."
},
"blue": {
"label": "Sinine",
"description": "Sinise komponent (0–255) ajatempli värvi jaoks."
},
"label": "Ajatempli värv",
"description": "Ajatempli teksti RGB värviväärtused (kõik väärtused 0–255)."
},
"thickness": {
"label": "Ajatempli paksus",
"description": "Ajatempli teksti joone paksus."
},
"effect": {
"label": "Ajatempli efekt",
"description": "Ajatempli teksti visuaalne efekt (puudub, ühtlane, vari)."
}
},
"onvif": {
"user": {
"label": "ONVIF kasutajanimi"
},
"password": {
"label": "ONVIF parool"
},
"port": {
"label": "ONVIF port"
},
"label": "ONVIF",
"host": {
"label": "ONVIF host"
}
},
"camera_mqtt": {
"quality": {
"label": "JPEG kvaliteet",
"description": "MQTT-sse saadetud piltide JPEG-kvaliteet (0–100)."
},
"enabled": {
"label": "Saada pilt"
},
"label": "MQTT"
},
"profiles": {
"label": "Profiilid"
}
}
+63 -1
View File
@@ -6,5 +6,67 @@
"error": "Midagi läks valesti. Palun proovi uuesti.",
"processing": "Töötlen…",
"toolsUsed": "Kasutatud: {{tools}}",
"similarity_score": "Sarnasus"
"similarity_score": "Sarnasus",
"showTools": "Näita tööriistu ({{count}})",
"hideTools": "Peida tööriistad",
"call": "Kutse",
"result": "Tulemus",
"arguments": "Argumendid:",
"response": "Vastus:",
"send": "Saada",
"new_chat": "Uus vestlus",
"settings": {
"title": "Vestluse sätted",
"show_stats": {
"title": "Näita statistikat",
"always": "Alati",
"desc": "Kuva vastuste genereerimise kiirus ja konteksti suurus.",
"while_generating": "Genereerimise ajal"
},
"auto_scroll": {
"title": "Automaatne kerimine",
"desc": "Jälgi uusi sõnumeid kohe, kui need saabuvad."
}
},
"stats": {
"context": "{{tokens}} tokenit",
"tokens_per_second": "{{rate}} t/s"
},
"starting_requests_prompts": {
"recap": "Mis juhtus kui ma olin ära?",
"watch_camera": "Jälgi ust ja anna mulle teada kui keegi tuleb",
"show_camera_status": "Milline on minu kaamerate praegune seis?",
"show_recent_events": "Näita mulle viimase tunni viimaseid sündmusi"
},
"starting_requests": {
"show_camera_status": "Näita kaamera olekut",
"show_recent_events": "Kuva hiljutised sündmused",
"recap": "Mis juhtus kui ma olin ära?",
"watch_camera": "Jälgi kaamerat aktiivsuse osas"
},
"suggested_requests": "Proovi küsida:",
"semantic_search_required": "Sarnaste objektide leidmiseks peab olema lubatud semantiline otsing.",
"no_similar_objects_found": "Sarnaseid objekte ei leitud.",
"quick_reply_when_else": "Millal seda veel nähti?",
"quick_reply_find_similar_text": "Leia sarnaseid vaatepilte.",
"quick_reply_tell_me_more_text": "Räägi mulle sellest lähemalt.",
"quick_reply_when_else_text": "Millal seda veel nähtud on?",
"attach_event_aria": "Lisa sündmus {{eventId}}",
"attachment_picker_paste_label": "Või kleebi sündmuse ID",
"attachment_picker_attach": "Lisa",
"attachment_picker_placeholder": "Lisa sündmus",
"quick_reply_find_similar": "Leia sarnaseid vaatlusi",
"quick_reply_tell_me_more": "Räägi mulle sellest lähemalt",
"attachment_chip_remove": "Eemalda manus",
"attachment_chip_label": "{{label}} kaameras {{camera}}",
"open_in_explore": "Ava uurimisvaates",
"anchor": "Viide",
"reasoning": {
"active": "Otsin põhjendust…",
"show": "Näita põhjendust",
"hide": "Peida põhjendus"
},
"thinking": {
"toggle": "Näita mõtlemise olekut või peida see"
}
}
@@ -43,5 +43,8 @@
},
"tooltip": {
"trainingInProgress": "Mudel on parasjagu õppimas"
},
"train": {
"titleShort": "Hiljutised"
}
}
+91 -7
View File
@@ -5,7 +5,8 @@
"noTrackedObjects": "Ühtegi jälgitavat objekti ei leidunud",
"itemMenu": {
"findSimilar": {
"aria": "Otsi sarnaseid jälgitavaid objekte"
"aria": "Otsi sarnaseid jälgitavaid objekte",
"label": "Leia sarnane"
},
"downloadSnapshot": {
"label": "Laadi hetkvõte alla",
@@ -14,6 +15,14 @@
"downloadCleanSnapshot": {
"label": "Laadi puhas hetkvõte alla",
"aria": "Laadi puhas hetkvõte alla"
},
"viewTrackingDetails": {
"label": "Vaata jälgimise üksikasju",
"aria": "Näita jälgimise üksikasju"
},
"downloadVideo": {
"label": "Laadi video alla",
"aria": "Laadi video alla"
}
},
"trackingDetails": {
@@ -21,11 +30,22 @@
"showAllZones": {
"title": "Näita kõiki tsoone",
"desc": "Kui objekt on sisenenud tsooni, siis alati näida tsooni märgistust."
},
"title": "Annotatsioonide seaded",
"offset": {
"desc": "Need andmed pärinevad teie kaamera tuvastusvoost, kuid on salvestusvoo piltide peal. On ebatõenäoline, et need kaks voogu on ideaalselt sünkroonis. Seetõttu ei joondu piirav kast ja kaader ideaalselt. Selle säte abil saad annotatsioone ajas edasi või tagasi nihutada, et need salvestatud kaadriga paremini joonduks.",
"millisecondsToOffset": "Millisekundid annotatsioonide tuvastuse nihutamiseks. <em>Vaikimisi: 0</em>",
"tips": "Vähendage väärtust, kui video taasesitus on kastidest ja teekonnapunktidest ees, ning suurendage väärtust, kui video taasesitus on neist maas. See väärtus võib olla negatiivne.",
"toast": {
"success": "Kaamera '{{camera}}' annotatsiooni nihe on konfiguratsioonifaili salvestatud."
},
"label": "Annotatsiooni nihe"
}
},
"lifecycleItemDesc": {
"attribute": {
"other": "{{label}} on tuvastatud kui {{attribute}}"
"other": "{{label}} on tuvastatud kui {{attribute}}",
"faceOrLicense_plate": "{{attribute}} tuvastatud objektil {{label}}"
},
"stationary": "{{label}} jäi paigale",
"active": "{{label}} muutus aktiivseks",
@@ -37,7 +57,8 @@
"area": "Ala",
"score": "Punktiskoor",
"computedScore": "Arvutatud punktiskoor",
"topScore": "Suuremad punktiskoorid"
"topScore": "Suuremad punktiskoorid",
"toggleAdvancedScores": "Täpsemate tulemuste sisse-/väljalülitamine"
},
"external": "{{label}} on tuvastatud",
"heard": "{{label}} on kuuldud",
@@ -50,7 +71,11 @@
"previous": "Eelmine slaid",
"next": "Järgmine slaid"
},
"count": "{{first}} / {{second}}"
"count": "{{first}} / {{second}}",
"adjustAnnotationSettings": "Korrigeeri annotatsioonide seadeid",
"scrollViewTips": "Klõpsake selle objekti elutsükli oluliste hetkede vaatamiseks.",
"autoTrackingTips": "Piirdekastide asukohad on automaatselt jälgivate kaamerate puhul ebatäpsed.",
"trackedPoint": "Jälgitav punkt"
},
"documentTitle": "Avasta - Frigate",
"generativeAI": "Generatiivne tehisaru",
@@ -64,7 +89,22 @@
},
"startingUp": "Käivitun…",
"estimatedTime": "Hinnanguliselt jäänud aega:",
"finishingShortly": "Lõpetan õige pea"
"finishingShortly": "Lõpetan õige pea",
"context": "Avastamist saab kasutada pärast seda, kui jälgitavate objektide manustamine on uuesti indekseerimise lõpetanud."
},
"title": "Avastamine pole saadaval",
"downloadingModels": {
"context": "Frigate laadib alla semantilise otsingu funktsiooni toetamiseks vajalikke manustamismudeleid. See võib võtta mitu minutit, olenevalt teie võrguühenduse kiirusest.",
"setup": {
"visionModel": "Nägemismudel",
"visionModelFeatureExtractor": "Nägemismudeli tunnuste eraldaja",
"textModel": "Tekstimudel",
"textTokenizer": "Teksti tokenisaator"
},
"tips": {
"context": "Pärast mudelite allalaadimist tuleks oma jälgitavate objektide manused uuesti indekseerida."
},
"error": "Tekkis viga. Kontrollige Frigate'i logisid."
}
},
"type": {
@@ -107,8 +147,52 @@
},
"recognizedLicensePlate": "Tuvastatud sõiduki numbrimärk",
"description": {
"aiTips": "Frigate ei küsi sinu generatiivse tehisaru teenusepakkujalt kirjeldust enne, kui jälgitava objekti elutsükkel on lõppenud."
"aiTips": "Frigate ei küsi sinu generatiivse tehisaru teenusepakkujalt kirjeldust enne, kui jälgitava objekti elutsükkel on lõppenud.",
"label": "Kirjeldus",
"placeholder": "Jälgitava objekti kirjeldus"
},
"label": "Silt",
"editSubLabel": {
"title": "Muuda alamsilti",
"desc": "Sisesta sildile '{{label}}' uus alamsilt",
"descNoLabel": "Sisesta sellele jälgitavale objektile uus alamsilt"
},
"camera": "Kaamera",
"zones": "Tsoonid",
"title": {
"label": "Pealkiri"
},
"button": {
"findSimilar": "Leia sarnane",
"regenerate": {
"title": "Taasloomine",
"label": "Jälgitava objekti kirjelduse uuesti genereerimine"
}
},
"topScore": {
"label": "Parim punktiskoor",
"info": "Kõrgeim skoor on jälgitava objekti kõrgeim mediaanskoor, seega võib see erineda otsingutulemuste pisipildil kuvatavast skoorist."
},
"objects": "Objektid",
"tips": {
"descriptionSaved": "Kirjeldus salvestati edukalt",
"saveDescriptionFailed": "Kirjelduse uuendamine ebaõnnestus: {{errorMessage}}"
},
"attributes": "Klassifikatsiooni atribuudid",
"estimatedSpeed": "Hinnanguline kiirus",
"editAttributes": {
"title": "Atribuutide muutmine",
"desc": "Valige selle sildi '{{label}}' jaoks klassifikatsiooniatribuudid"
}
},
"trackedObjectDetails": "Jälgitava objekti üksikasjad"
"trackedObjectDetails": "Jälgitava objekti üksikasjad",
"aiAnalysis": {
"title": "Tehisintellekti analüüs"
},
"concerns": {
"label": "Mured"
},
"objectLifecycle": {
"noImageFound": "Selle jälgitava objekti kohta ei leitud pilti."
}
}
+44 -2
View File
@@ -16,12 +16,15 @@
"downloadVideo": "Laadi video alla",
"editName": "Muuda nime",
"deleteExport": "Kustuta eksporditud sisu",
"assignToCase": "Lisa juhtumile"
"assignToCase": "Lisa juhtumile",
"removeFromCase": "Eemalda juhtumist"
},
"toast": {
"error": {
"renameExportFailed": "Eksporditud sisu nime muutmine ei õnnestunud: {{errorMessage}}",
"assignCaseFailed": "Juhtumiga seose uuendamine ei õnnestunud: {{errorMessage}}"
"assignCaseFailed": "Juhtumiga seose uuendamine ei õnnestunud: {{errorMessage}}",
"caseSaveFailed": "Juhtumi salvestamine ei õnnestunud: {{errorMessage}}",
"caseDeleteFailed": "Juhtumi kustutamine ei õnnestunud: {{errorMessage}}"
}
},
"headings": {
@@ -35,5 +38,44 @@
"nameLabel": "Juhtumi nimi",
"descriptionLabel": "Kirjeldus",
"description": "Vali olemasolev juhtum või lisa uus."
},
"toolbar": {
"newCase": "Uus juhtum",
"addExport": "Lisa eksportimiseks",
"editCase": "Muuda juhtumit",
"deleteCase": "Kustuta juhtum"
},
"deleteCase": {
"label": "Kustuta juhtum",
"desc": "Kas oled kindel, et soovid „{{caseName}}“ juhtumi kustutada?"
},
"caseCard": {
"emptyCase": "Eksportimise veel pole"
},
"jobCard": {
"defaultName": "Eksportimine kaamerast „{{camera}}“",
"queued": "Lisatud järjekorda",
"running": "Töös",
"preparing": "Ettevalmistamisel",
"copying": "Kopeerimisel",
"encoding": "Kodeerimisel",
"encodingRetry": "Kodeerimisel (uuesti)",
"finalizing": "Lõpetamisel"
},
"caseView": {
"noDescription": "Kirjeldust pole",
"createdAt": "Loodud {{value}}",
"exportCount_one": "1 eksportimine",
"exportCount_other": "{{count}} eksportimist",
"cameraCount_one": "1 kaamera",
"cameraCount_other": "{{count}} kaamerat",
"showMore": "Näita rohkem",
"showLess": "Näita vähem",
"emptyTitle": "See juhtum on tühi"
},
"caseEditor": {
"namePlaceholder": "Juhtumi nimi",
"editTitle": "Muuda juhtumit",
"createTitle": "Lisa juhtum"
}
}
+61 -8
View File
@@ -1,6 +1,11 @@
{
"button": {
"uploadImage": "Laadi pilt üles"
"uploadImage": "Laadi pilt üles",
"deleteFaceAttempts": "Kustuta näod",
"addFace": "Lisa nägu",
"renameFace": "Nimeta nägu ümber",
"deleteFace": "Kustuta nägu",
"reprocessFace": "Käivita näotuvastus uuesti"
},
"collections": "Kogumikud",
"description": {
@@ -11,28 +16,41 @@
},
"documentTitle": "Näoteek - Frigate",
"createFaceLibrary": {
"new": "Lisa uus nägu"
"new": "Lisa uus nägu",
"nextSteps": "Tugeva tuvastusaluse loomiseks:<li>Kasuta vahekaarti \"Hiljutised tuvastused\", et valida pilte ja treenida isikutuvastust.</li><li>Parima tulemuse saavutamiseks keskendu otsepiltidele; väldi nurga all olevaid nägusid treenimiseks.</li></ul>"
},
"deleteFaceLibrary": {
"title": "Kustuta nimi"
"title": "Kustuta nimi",
"desc": "Kas oled kindel, et soovid isiku '{{name}}' kollektsiooni kustutada? See kustutab jäädavalt kõik seotud näod."
},
"toast": {
"error": {
"addFaceLibraryFailed": "Näo sidumine nimega ei õnnestunud: {{errorMessage}}",
"updateFaceScoreFailed": "Näo punktiskoori uuendamine ei õnnestunud: {{errorMessage}}"
"updateFaceScoreFailed": "Näo punktiskoori uuendamine ei õnnestunud: {{errorMessage}}",
"uploadingImageFailed": "Pildi üleslaadimine ebaõnnestus: {{errorMessage}}",
"deleteFaceFailed": "Kustutamine ebaõnnestus: {{errorMessage}}",
"deleteNameFailed": "Nime kustutamine ebaõnnestus: {{errorMessage}}",
"renameFaceFailed": "Näo ümbernimetamine ebaõnnestus: {{errorMessage}}",
"trainFailed": "Treenimine ebaõnnestus: {{errorMessage}}",
"reclassifyFailed": "Näo ümberklassifitseerimine ebaõnnestus: {{errorMessage}}"
},
"success": {
"addFaceLibrary": "Lisamine Näoteeki õnnestus: {{name}}!",
"addFaceLibrary": "Lisamine nägude kogusse õ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.",
"updatedFaceScore": "Näo punktiskoori uuendamine õnnestus: {{name}} ({{score}})."
"updatedFaceScore": "Näo punktiskoori uuendamine õnnestus: {{name}} ({{score}}).",
"uploadedImage": "Pildi üleslaadimine õnnestus.",
"renamedFace": "Näo ümbernimetamine õnnestus, uus nimi on {{name}}",
"trainedFace": "Edukalt treenitud nägu.",
"reclassifiedFace": "Näo ümberklassifitseerimine õ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."
"desc_other": "Kas oled kindel, et soovid kustutada {{count}} nägu? Seda tegevust ei saa tagasi pöörata.",
"title": "Kustuta näod"
},
"details": {
"timestamp": "Ajatampel",
@@ -42,5 +60,40 @@
"uploadFaceImage": {
"title": "Laadi näopilt üles",
"desc": "Laadi üles pilt, et otsida sellelt nägusid ja lisada see {{pageToggle}}'i jaoks"
}
},
"steps": {
"faceName": "Lisa näole nimi",
"uploadFace": "Lae näopilt üles",
"nextSteps": "Järgmised sammud",
"description": {
"uploadFace": "Laadi üles pilt isikust '{{name}}', mis näitab tema nägu eestvaates. Ainult nägu ei pea pildilt välja lõikama."
}
},
"train": {
"title": "Hiljutised tuvastamised",
"titleShort": "Hiljutised",
"aria": "Vali hiljutised tuvastamised",
"empty": "Hiljutisi näotuvastuse katseid pole",
"emptyNoLibrary": {
"title": "Laadi üles nägu",
"description": "Näotuvastuse toimimiseks peate lisama vähemalt ühe näo kogusse."
}
},
"renameFace": {
"title": "Nimeta nägu ümber",
"desc": "Sisesta uus nimi isiku '{{name}}' jaoks"
},
"imageEntry": {
"validation": {
"selectImage": "Palun vali pildifail."
},
"dropActive": "Lohista pilt siia…",
"dropInstructions": "Lohistage või kleepige pilt siia või klõpsake valimiseks",
"maxSize": "Maksimum suurus: {{size}}MB"
},
"nofaces": "Nägusid pole saadaval",
"trainFaceAs": "Treeni nägu kui:",
"trainFace": "Treeni nägu",
"reclassifyFaceAs": "Liigita nägu ümber järgmiselt:",
"reclassifyFace": "Näo ümberklassifitseerimine"
}
+75 -1
View File
@@ -1,4 +1,78 @@
{
"documentTitle": "Liikumise tuvastus - Frigate",
"title": "Liikumise otsing"
"title": "Liikumise otsing",
"cancelSearch": "Tühista",
"startSearch": "Alusta otsingut",
"selectCamera": "Liikumisotsingut laaditakse",
"description": "Joonesta hulknurk, et määratleda huvipakkuv piirkond, ja määra ajavahemik, mille jooksul otsida liikumise muutusi selles piirkonnas.",
"searchStarted": "Otsing alustatud",
"searchCancelled": "Otsing tühistatud",
"searching": "Otsing on pooleli.",
"searchComplete": "Otsing lõpetatud",
"noResultsYet": "Käivita otsing valitud piirkonnas liikumise muutuste leidmiseks",
"noChangesFound": "Valitud piirkonnas ei tuvastatud pikslimuutusi",
"results": "Tulemused",
"polygonControls": {
"drawMode": "Joonista",
"moveMode": "Liiguta",
"reset": "Lähtesta hulknurk",
"undo": "Tühista viimane punkt",
"points_one": "{{count}} punkt",
"points_other": "{{count}} punkti"
},
"newSearch": "Uus otsing",
"clearResults": "Tühista tulemused",
"clearROI": "Tühista hulknurk",
"dialog": {
"cameraLabel": "Kaamera",
"title": "Liikumisotsing",
"previewAlt": "Kaamera '{{camera}}' eelvaade"
},
"timeRange": {
"title": "Otsinguvahemik",
"start": "Algusaeg",
"end": "Lõppaeg"
},
"settings": {
"title": "Otsinguseaded",
"parallelMode": "Paralleelrežiim",
"parallelModeDesc": "Skanni mitut salvestusvahemikku korraga (kiirem; kasutab rohkem dekodeerimisressursse)",
"threshold": "Tundlikkuse lävi",
"thresholdDesc": "Väiksemad väärtused tuvastavad väiksemaid muutusi (1–255)",
"minArea": "Minimaalne muutusala",
"minAreaDesc": "Ühe liikuva piirkonna minimaalne suurus protsentides huvipakkuvast piirkonnast",
"maxResults": "Maksimaalselt tulemusi",
"maxResultsDesc": "Peata pärast nii paljude ajatemplite sobivust"
},
"errors": {
"noCamera": "Palun vali kaamera",
"noROI": "Palun joonistage huvipakkuv piirkond",
"noTimeRange": "Palun valige ajavahemik",
"invalidTimeRange": "Lõppaeg peab olema pärast algusaega",
"searchFailed": "Otsing ebaõnnestus: {{message}}",
"polygonTooSmall": "Hulknurgal peab olema vähemalt 3 punkti",
"unknown": "Tundmatu viga"
},
"changePercentage": "{{percentage}}% muutus",
"metrics": {
"title": "Otsingumõõdikud",
"segmentsScanned": "Skannitud segmente",
"segmentsProcessed": "Töödeldud",
"segmentsSkippedInactive": "Vahele jäetud (tegevust pole)",
"segmentsSkippedHeatmap": "Vahele jäetud (kattuvaid piirkondi pole)",
"fallbackFullRange": "Varurežiimis täisulatusega skaneerimine",
"framesDecoded": "Kaadreid dekodeeritud",
"wallTime": "Otsingu aeg",
"seconds": "{{seconds}}s",
"minutesSeconds": "{{minutes}}m {{seconds}}s",
"segmentErrors": "Segmendi vead",
"scanSummary": "{{segments}} segmenti · {{time}}"
},
"jumpToTime": "Hüppa sellele ajale",
"framesProcessed": "{{count}} kaadrit töödeldud",
"changesFound_one": "Tuvastatud {{count}} liikumine",
"changesFound_other": "Tuvastatud {{count}} liikumist",
"motionHeatmapLabel": "Liikumise soojakaart",
"showSegmentHeatmap": "Soojakaart",
"scanning": "Skannimine {{time}}"
}
+43 -2
View File
@@ -12,7 +12,48 @@
"selectFromTimeline": "Vali",
"starting": "Käivitan kordust…",
"startLabel": "Algus",
"endLabel": "Lõpp"
"endLabel": "Lõpp",
"title": "Alusta silumise taasesitust",
"description": "Looge ajutine taasesituskaamera, mis kordab ajaloolist salvestist objektide tuvastamise ja jälgimise probleemide silumiseks. Taasesituskaameral on sama tuvastusseadistus mis lähtekaameral. Valige algusaeg.",
"toast": {
"error": "Silumise taasesitus ebaõnnestus: {{error}}",
"alreadyActive": "Kordusseanss on juba aktiivne",
"stopError": "Silumise taasesituse peatamine ebaõnnestus: {{error}}",
"goToReplay": "Mine kordusesse"
}
},
"title": "Kordus veaotsinguks"
"title": "Kordus veaotsinguks",
"websocket_messages": "Sõnumid",
"description": "Mängi kaamera salvestisi veaotsinguks. Objektide loend näitab tuvastatud objektide ajalist viivitust ja vahekaart „Sõnumid” näitab Fregati sisemiste sõnumite voogu taasesituse materjalist.",
"page": {
"noSession": "Aktiivset silumis- ja taasesitusseanssi pole",
"noSessionDesc": "Alusta silumissalvestise taasesitust ajaloo vaates, klõpsates tööriistaribal nupul \"Toimingud\" ja valides silumissalvestise taasesituse.",
"goToRecordings": "Mine ajaloo vaatesse",
"preparingClip": "Klipi ettevalmistamine…",
"preparingClipDesc": "Frigate koondab valitud ajavahemiku salvestisi. Pikemate vahemike puhul võib see võtta kauem.",
"startingCamera": "Silumise taasesituse käivitamine…",
"startError": {
"title": "Silumise taasesituse käivitamine ebaõnnestus",
"back": "Tagasi ajaloo vaatesse"
},
"sourceCamera": "Lähtekaamera",
"replayCamera": "Taasesituse kaamera",
"initializingReplay": "Silumise taasesituse initsialiseerimine...",
"stoppingReplay": "Silumise taasesituse peatamine...",
"stopReplay": "Peata kordusesitus",
"confirmStop": {
"title": "Peata silumise kordusesitus?",
"description": "See peatab seansi ja kustutab kõik ajutised andmed. Kas oled kindel?",
"confirm": "Peata kordusesitus",
"cancel": "Tühista"
},
"activity": "Toimingud",
"objects": "Objekti loend",
"audioDetections": "Audio tuvastused",
"noActivity": "Ühtegi tegevust ei tuvastatud",
"activeTracking": "Aktiivne jälgimine",
"noActiveTracking": "Aktiivset jälgimist pole",
"configuration": "Seaded",
"configurationDesc": "Peenhäälesta liikumistuvastuse ja objektide jälgimise sätteid silumis- ja taasesituskaamera jaoks. Frigate'i konfiguratsiooni faili muudatusi ei salvestata."
}
}
+174 -21
View File
@@ -7,7 +7,28 @@
"connectionSettings": "Ühenduse seadistused",
"port": "Port",
"username": "Kasutajanimi",
"usernamePlaceholder": "Valikuline"
"usernamePlaceholder": "Valikuline",
"cameraName": "Kaamera nimi",
"cameraNamePlaceholder": "näiteks: eesmine_uks või Hoovi ülevaade",
"host": "Host/IP-aadress",
"cameraBrand": "Kaamera bränd",
"selectBrand": "Vali URL malli jaoks kaamera bränd",
"customUrl": "Kohandatud voo URL",
"brandInformation": "Brändi teave",
"brandUrlFormat": "Kaamerate puhul, mille RTSP URL-i vorming on järgmine: {{exampleUrl}}",
"selectTransport": "Valige transpordiprotokoll",
"description": "Sisestage oma kaamera andmed ja valige, kas soovite kaamerat tuvastada või valida tootja käsitsi.",
"onvifPort": "ONVIF port",
"probeMode": "Tuvasta kaamera",
"manualMode": "Manuaalne valik",
"errors": {
"nameRequired": "Kaamera nimi on kohustuslik",
"nameLength": "Kaamera nimi peab olema kuni 64 tähemärki pikk",
"invalidCharacters": "Kaamera nimi sisaldab sobimatuid märke",
"nameExists": "Kaamera nimi on juba olemas",
"brandOrCustomUrlRequired": "Valige kas kaamera bränd koos hosti/IP-aadressiga või valige „Muu” kohandatud URL-iga"
},
"detectionMethod": "Vootuvastus meetod"
},
"step3": {
"streamUrlPlaceholder": "rtsp://kasutajanimi:salasõna@host:port/asukoht",
@@ -17,17 +38,37 @@
"roles": "Rollid",
"roleLabels": {
"record": "Salvestamine",
"audio": "Heliriba"
"audio": "Heliriba",
"detect": "Objektituvastus"
},
"connected": "Ühendatud",
"featuresTitle": "Funktsionaalsused"
"featuresTitle": "Funktsionaalsused",
"selectStream": "Vali voog",
"selectQuality": "Valige kvaliteet",
"notConnected": "Pole ühendatud",
"testFailedTitle": "Test ebaõnnestus",
"testStream": "Testi ühendust",
"testSuccess": "Voo test edukas!",
"testFailed": "Voo test ebaõnnestus",
"selectResolution": "Vali resolutsioon",
"noStreamFound": "Voogu ei leitud",
"addAnotherStream": "Lisa järgmine voog",
"streamTitle": "Voog {{number}}",
"streamUrl": "Voo URL",
"addStream": "Lisa voog",
"description": "Seadista voogedastusrolle ja lisa oma kaamerale täiendavaid vooge.",
"streamsTitle": "Kaamera vood"
},
"steps": {
"probeOrSnapshot": "Võta proov või tee hetkvõte"
"probeOrSnapshot": "Võta proov või tee hetkvõte",
"nameAndConnection": "Nimi ja ühendus",
"streamConfiguration": "Voo seaded",
"validationAndTesting": "Valideerimine ja testimine"
},
"step2": {
"testing": {
"fetchingSnapshot": "Laadin kaamera hetkvõtet alla..."
"fetchingSnapshot": "Laadin kaamera hetkvõtet alla...",
"probingMetadata": "Tuvastan kaamera metaandmeid..."
},
"retry": "Proovi uuesti",
"manufacturer": "Tootja",
@@ -37,7 +78,30 @@
"presets": "Eelseadistused",
"useCandidate": "Kasuta",
"uriCopy": "Kopeeri",
"connected": "Ühendatud"
"connected": "Ühendatud",
"testSuccess": "Ühenduse test õnnestus!",
"testFailed": "Ühenduse test ebaõnnestus. Palun kontrollige sisestatud andmeid ja proovige uuesti.",
"testFailedTitle": "Test ebaõnnestus",
"streamDetails": "Voo üksikasjad",
"probing": "Tuvastan kaamerat...",
"ptzSupport": "PTZ tugi",
"testConnection": "Testi ühendust",
"toggleUriView": "Klõpsake täieliku URI vaate sisse/välja lülitamiseks",
"notConnected": "Pole ühendatud",
"errors": {
"hostRequired": "Hosti/IP-aadress on kohustuslik"
},
"candidateStreamTitle": "Kandidaat {{number}}",
"probeSuccessful": "Tuvastamine õnnestus",
"probeError": "Tuvastamise viga",
"probeNoSuccess": "Tuvastamine ebaõnnestus",
"deviceInfo": "Seadme info",
"autotrackingSupport": "Automaatse jälgimise tugi",
"uriCopied": "URI kopeeriti lõikelauale",
"rtspCandidates": "RTSP kandidaadid",
"probingDevice": "Tuvastan seadet...",
"description": "Tuvasta kaamera vooge või seadista käsitsi seaded vastavalt valitud tuvastusmeetodile.",
"probeFailed": "Kaamera tuvastamine ebaõnnestus: {{error}}"
},
"testResultLabels": {
"resolution": "Resolutsioon",
@@ -56,7 +120,17 @@
"roles": "Rollid",
"none": "Määramata",
"error": "Viga"
}
},
"commonErrors": {
"noUrl": "Palun sisestage kehtiv voogesituse URL",
"testFailed": "Voo test ebaõnnestus: {{error}}"
},
"save": {
"success": "Uue kaamera '{{cameraName}}' salvestamine õnnestus.",
"failure": "Viga kaamera '{{cameraName}}' salvestamisel."
},
"title": "Lisa kaamera",
"description": "Uue kaamera lisamiseks oma Frigate paigaldisesse järgige alltoodud samme."
},
"users": {
"updatePassword": "Lähtesta salasõna",
@@ -172,24 +246,32 @@
},
"documentTitle": {
"default": "Seadistused - Frigate",
"authentication": "Autentimise seadistused - Frigate",
"cameraReview": "Kaamerate kordusvaatuste seadistused - Frigate",
"general": "Profiili seadistused - Frigate",
"frigatePlus": "Frigate+ seadistused - Frigate",
"notifications": "Teavituste seadistused - Frigate",
"authentication": "Autentimise seaded - Frigate",
"cameraReview": "Kaamerate kordusvaatuste seaded - Frigate",
"general": "Profiili seaded - Frigate",
"frigatePlus": "Frigate+ seaded - Frigate",
"notifications": "Teavituste seaded - Frigate",
"cameraManagement": "Kaamerate haldus - Frigate",
"masksAndZones": "Maskide ja tsoonide haldus - Frigate",
"object": "Silumine ja veaotsing - Frigate"
"object": "Silumine ja veaotsing - Frigate",
"enrichments": "Rikastamise seaded - Frigate",
"motionTuner": "Liikumise häälestaja – Frigate",
"globalConfig": "Globaalsed seaded – Frigate",
"cameraConfig": "Kaamera seaded - Frigate",
"detectorsAndModel": "Detektorid ja mudel – Frigate",
"maintenance": "Hooldus – Frigate",
"profiles": "Profiilid - Frigate"
},
"general": {
"title": "Profiili seadistused",
"title": "Kasutajaliidese seadistused",
"cameraGroupStreaming": {
"clearAll": "Kustuta kõik voogedastuse seadistused"
},
"liveDashboard": {
"title": "Töölaud reaalajas",
"automaticLiveView": {
"label": "Automaatne otseülekande vaade"
"label": "Automaatne otseülekande vaade",
"desc": "Aktiveerib automaatselt kaamera reaalajas vaate, kui tuvastatakse tegevus. Selle valiku keelamisel uuendatakse kaamerapilti töölaual ainult üks kord minutis."
}
},
"calendar": {
@@ -335,7 +417,7 @@
},
"menu": {
"ui": "Kasutajaliides",
"cameraManagement": "Haldus",
"cameraManagement": "Kaamera haldus",
"masksAndZones": "Maskid ja tsoonid",
"triggers": "Päästikud",
"debug": "Silumine ja veaotsing",
@@ -373,7 +455,37 @@
"systemMqtt": "MQTT",
"systemGo2rtcStreams": "go2rtc voogedastus",
"integrationSemanticSearch": "Semantiline otsing",
"integrationGenerativeAi": "Generatiivne tehisaru"
"integrationGenerativeAi": "Generatiivne tehisaru",
"general": "Üldine",
"globalConfig": "Globaalsed seaded",
"system": "Süsteem",
"integrations": "Integratsioonid",
"cameras": "Kaamera seaded",
"integrationFaceRecognition": "Nöotuvastus",
"cameraDetect": "Objektituvastus",
"cameraFfmpeg": "Vood (FFmpeg)",
"cameraRecording": "Salvestus",
"cameraSnapshots": "Hetktõmmised",
"cameraMotion": "Liikumistuvastus",
"cameraObjects": "Objektid",
"cameraConfigReview": "Ülevaade",
"cameraAudioEvents": "audiotuvastus",
"cameraAudioTranscription": "Audio transkriptsioon",
"cameraNotifications": "Teated",
"cameraLivePlayback": "Otseülekanne",
"integrationAudioTranscription": "Audio transkriptsioon",
"integrationObjectClassification": "Objektide klassifitseerimine",
"cameraFaceRecognition": "Näotuvastus",
"cameraMqttConfig": "MQTT",
"cameraOnvif": "ONVIF",
"cameraUi": "Kaamera kasutajaliides",
"cameraTimestampStyle": "Ajatempli stiil",
"cameraMqtt": "Kaamera MQTT",
"maintenance": "Hooldus",
"mediaSync": "Meedia sünkroonimine",
"regionGrid": "Regioonide ruudustik",
"enrichments": "Andmete rikastamine",
"motionTuner": "Liikumistuvastuse seadistaja"
},
"dialog": {
"unsavedChanges": {
@@ -389,7 +501,11 @@
"semanticSearch": {
"reindexNow": {
"confirmButton": "Indekseeri uuesti",
"label": "Indekseeri uuesti kohe"
"label": "Indekseeri kohe uuesti",
"alreadyInProgress": "Ümberindekseerimine on juba käimas.",
"error": "Uuesti indekseerimise alustamine ebaõnnestus: {{errorMessage}}",
"success": "Ümberindekseerimine algas edukalt.",
"confirmTitle": "Kinnita uuesti indekseerimine"
},
"modelSize": {
"small": {
@@ -397,7 +513,9 @@
},
"large": {
"title": "suur"
}
},
"label": "Mudeli suurus",
"desc": "Semantilise otsingu manustamiseks kasutatava mudeli suurus."
},
"title": "Semantiline otsing"
},
@@ -408,14 +526,23 @@
},
"large": {
"title": "suur"
}
}
},
"label": "Mudeli suurus",
"desc": "Näotuvastuseks kasutatava mudeli suurus."
},
"title": "Näotuvastus"
},
"birdClassification": {
"title": "Lindude klassifikatsioon"
},
"licensePlateRecognition": {
"title": "Sõidukite numbrimärkide tuvastus"
},
"title": "Andmerikastuse seaded",
"restart_required": "Taaskäivitamine on vajalik (Andmerikastuse seadeid on muudetud)",
"toast": {
"success": "Andmerikastamise seaded on salvestatud. Muudatuste rakendamiseks taaskäivitage Frigate.",
"error": "Seadete muudatuste salvestamine ebaõnnestus: {{errorMessage}}"
}
},
"cameraReview": {
@@ -497,5 +624,31 @@
"lpr": {
"vehicleNotTracked": "Sõidukite numbrimärkide tuvastus eeldab, et auto või mootorratas on jälgitav. Lülita menüüst Objektid sell kaamera jaoks sisse valikud „auto“ või „mootorratas“."
}
},
"button": {
"overriddenGlobal": "Ülekirjutatud (Globaalne)",
"overriddenGlobalTooltip": "Käesolev kaamera kirjutab selles jaotises üle globaalsed seaded",
"overriddenGlobalHeading_one": "See kaamera sürjutab {{count}} üldise seadistuse välja:",
"overriddenGlobalHeading_other": "See kaamera sürjutab {{count}} üldise seadistuse välja:"
},
"saveAllPreview": {
"title": "Salvestatavad muudatused",
"triggerLabel": "Vaadake üle ootel olevad muudatused",
"empty": "Ootel muudatusi pole.",
"scope": {
"label": "Ulatus",
"global": "Globaalne",
"camera": "Kaamera: {{cameraName}}"
},
"profile": {
"label": "Profiil"
},
"field": {
"label": "Väli"
},
"value": {
"label": "Uus väärtus",
"reset": "Lähtesta"
}
}
}
+194 -4
View File
@@ -2,7 +2,14 @@
"documentTitle": {
"general": "Üldine statistika - Frigate",
"cameras": "Kaamerate statistika - Frigate",
"storage": "Andmeruumi statistika - Frigate"
"storage": "Andmeruumi statistika - Frigate",
"logs": {
"frigate": "Frigate logid - Frigate",
"go2rtc": "Go2RTC logid - Frigate",
"nginx": "Nginx logid - Frigate",
"websocket": "Sõnumite logid - Frigate"
},
"enrichments": "Rikastus statistika - Frigate"
},
"logs": {
"download": {
@@ -16,11 +23,29 @@
"websocket": {
"filter": {
"cameras_count_one": "{{count}} kaamera",
"cameras_count_other": "{{count}} kaamerat"
"cameras_count_other": "{{count}} kaamerat",
"camera": "Kaamera",
"all_cameras": "Kõik kaamerad",
"events": "Sündmused",
"reviews": "Ülevaated",
"system": "Süsteem",
"topics": "Teemad",
"all": "Kõik teemad",
"face_recognition": "Näotuvastus",
"classification": "Klassifikatsioon",
"camera_activity": "Kaamera aktiivsus",
"lpr": "Numbrimärgituvastus"
},
"empty": "Ühtegi sõnumit pole veel hõivatud",
"count_one": "{{count}} sõnum",
"count_other": "{{count}} sõnumit"
"count_other": "{{count}} sõnumit",
"pause": "Peata",
"resume": "Jätka",
"label": "Sõnumid",
"clear": "Tühista",
"expanded": {
"payload": "Last"
}
},
"type": {
"label": "Tüüp",
@@ -36,5 +61,170 @@
}
}
},
"title": "Süsteem"
"title": "Süsteem",
"enrichments": {
"embeddings": {
"object_description_events_per_second": "Objekti kirjeldus",
"face_recognition": "Näotuvastus",
"plate_recognition": "Numbrimärgi tuvastus",
"object_description": "Objekti kirjeldus",
"review_description_events_per_second": "Ülevaate kirjeldus",
"review_description": "Ülevaate kirjeldus",
"yolov9_plate_detection": "YOLOv9 numbrimärgi tuvastus",
"yolov9_plate_detection_speed": "YOLOv9 numbrimärgi tuvastuskiirus",
"plate_recognition_speed": "Numbrimärgituvastuse kiirus",
"face_recognition_speed": "Näotuvastuse kiirus"
},
"title": "Andmerikastused"
},
"cameras": {
"connectionQuality": {
"reconnectsLastHour": "Uuesti ühendumisi (viimase tunni jooksul)",
"stallsLastHour": "Seiskumisi (viimase tunni jooksul)",
"title": "Ühenduse kvaliteet",
"excellent": "Suurepärane",
"fair": "Rahuldav",
"poor": "Kehv",
"unusable": "Mittekasutatav",
"fps": "Kaadrisagedus",
"expectedFps": "Eeldatav kaadrisagedus (FPS)"
},
"label": {
"camera": "kaamera",
"detect": "tuvasta",
"skipped": "vahele jäetud",
"ffmpeg": "FFmpeg",
"cameraFfmpeg": "{{camName}} FFmpeg",
"cameraGpu": "{{camName}} GPU",
"cameraDetect": "{{camName}} tuvastus",
"overallFramesPerSecond": "kaadreid sekundis kokku",
"overallDetectionsPerSecond": "kogutuvastusi sekundis",
"overallSkippedDetectionsPerSecond": "vahelejäänud tuvastusi sekundis kokku",
"cameraFramesPerSecond": "{{camName}} kaadreid sekundis",
"cameraDetectionsPerSecond": "{{camName}} tuvastusi sekundis",
"cameraSkippedDetectionsPerSecond": "{{camName}} vahelejäänud tuvastusi sekundis",
"capture": "jäädvustamine"
},
"noCameras": {
"title": "Ühtegi kaamerat ei leitud"
},
"framesAndDetections": "Kaadreid / Tuvastusi",
"info": {
"keyframes": {
"recordDisabled": "Selle kaamera salvestamine on välja lülitatud.",
"segmentLength": "Salvestuse segmendi pikkus:",
"recordStream": "Salvestusvoog:",
"keyframeCount": "Vaadeldud võtmekaadrid:",
"observedDuration": "Vaadeldud kestus:"
},
"audio": "Audio:",
"error": "Viga: {{error}}",
"codec": "Koodek:",
"resolution": "Resolutsioon:",
"video": "Video:",
"aspectRatio": "kuvasuhe",
"unknown": "Tundmatu",
"fetching": "Kaameraandmete toomine",
"stream": "Voog {{idx}}",
"streamDataFromFFPROBE": "Vooandmed saadakse <code>ffprobe</code> abil.",
"fps": "Kaadrisagedus (FPS):"
},
"title": "Kaamerad",
"overview": "Ülevaade"
},
"lastRefreshed": "Viimati uuendatud: ",
"stats": {
"healthy": "Süsteem on töökorras",
"detectIsSlow": "{{detect}} on aeglane ({{speed}} ms)",
"detectIsVerySlow": "{{detect}} on väga aeglane ({{speed}} ms)",
"cameraIsOffline": "{{camera}} on ühenduseta"
},
"storage": {
"cameraStorage": {
"camera": "Kaamera",
"unused": {
"title": "Kasutamata",
"tips": "See väärtus ei pruugi Frigate'i jaoks saadaolevat vaba ruumi täpselt kajastada, kui teie draivil on lisaks Frigate'i salvestistele ka muid faile. Frigate ei jälgi salvestusruumi kasutamist väljaspool oma salvestisi."
},
"bandwidth": "Ribalaius",
"storageUsed": "Salvestusmaht",
"percentageOfTotalUsed": "protsent kogusummast",
"unusedStorageInformation": "Kasutamata salvestusmahu info",
"title": "Kaamera salvestusmaht"
},
"title": "Säilitus",
"overview": "Ülevaade",
"recordings": {
"title": "Salvestused",
"earliestRecording": "Varaseim saadaolev salvestis:"
},
"shm": {
"title": "SHM (jagatud mälu) eraldus",
"frameLifetime": {
"title": "Kaadri eluiga"
}
}
},
"metrics": "Süsteemi mõõdikud",
"general": {
"title": "Üldine",
"hardwareInfo": {
"gpuUsage": "GPU kasutus",
"gpuMemory": "GPU mälu",
"gpuInfo": {
"vainfoOutput": {
"title": "Vainfo väljund",
"processOutput": "Protsessi väljund:",
"processError": "Protsessi viga:",
"returnCode": "Tagastuskood: {{code}}"
},
"closeInfo": {
"label": "Sulge GPU info"
},
"copyInfo": {
"label": "Kopeeri GPU info"
},
"nvidiaSMIOutput": {
"title": "Nvidia SMI väljund",
"name": "Nimi: {{name}}",
"driver": "Draiver: {{driver}}",
"cudaComputerCapability": "CUDA arvutusvõimekus: {{cuda_compute}}",
"vbios": "VBios info: {{vbios}}"
},
"toast": {
"success": "GPU info kopeeriti lõikelauale"
}
},
"gpuDecoder": "GPU dekooder",
"gpuTemperature": "GPU temperatuur",
"gpuEncoder": "GPU kodeerija",
"gpuCompute": "GPU arvutus / kodeerimine",
"title": "Riistvara info",
"npuUsage": "NPU kasutus",
"npuMemory": "NPU mälu",
"npuTemperature": "NPU temperatuur",
"intelGpuWarning": {
"title": "Inteli GPU statistika hoiatus",
"message": "GPU statistika pole saadaval"
}
},
"otherProcesses": {
"series": {
"go2rtc": "go2rtc",
"recording": "salvestus",
"review_segment": "Segmendi ülevaade",
"audio_detector": "audio detektor"
},
"processCpuUsage": "Protsessi CPU kasutus",
"processMemoryUsage": "Protsessi mälukasutus"
},
"detector": {
"title": "Detektorid",
"memoryUsage": "Detektori mälukasutus",
"cpuUsage": "Detektori CPU kasutus",
"temperature": "Detektori temperatuur",
"inferenceSpeed": "Detektori järelduskiirus",
"cpuUsageInformation": "Protsessori võimsus, mida kasutatakse tuvastusmudelite sisend- ja väljundandmete ettevalmistamisel. See väärtus ei mõõda järelduste kasutamist isegi siis, kui kasutatakse graafikaprotsessorit või kiirendit."
}
}
}
+9 -2
View File
@@ -67,7 +67,8 @@
"error": {
"failed": "Échec du démarrage de l'exportation : {{error}}",
"endTimeMustAfterStartTime": "L'heure de fin doit être postérieure à l'heure de début.",
"noVaildTimeSelected": "La plage horaire sélectionnée n'est pas valide."
"noVaildTimeSelected": "La plage horaire sélectionnée n'est pas valide.",
"noValidTimeSelected": "Interval de temps invalide"
},
"success": "Exportation démarrée avec succès. Consultez le fichier sur la page des exportations.",
"view": "Vue",
@@ -88,7 +89,9 @@
"export": "Exporter",
"fromTimeline": {
"saveExport": "Enregistrer l'exportation",
"previewExport": "Aperçu de l'exportation"
"previewExport": "Aperçu de l'exportation",
"queueingExport": "Traitement de l'export...",
"useThisRange": "Utiliser cet interval"
},
"case": {
"label": "Dossier",
@@ -195,6 +198,10 @@
"markAsReviewed": "Marquer comme traité",
"deleteNow": "Supprimer maintenant",
"markAsUnreviewed": "Marquer comme non traité"
},
"shareTimestamp": {
"label": "Partager cette date",
"title": "Partager cette date"
}
},
"imagePicker": {
+8 -2
View File
@@ -24,6 +24,9 @@
},
"state": {
"submitted": "Terkirim"
},
"toast": {
"error": "Gagal submit ke Frigate+. Harap periksa koneksi jaringan Anda dan coba lagi."
}
}
},
@@ -57,7 +60,8 @@
"view": "Melihat",
"batchSuccess_other": "{{count}} Ekspor dimulai. Membuka kasusnya sekarang.",
"batchPartial": "Ekspor berhasil dimulai sebanyak {{successful}} dari total {{total}} ekspor. Kamera yang gagal: {{failedCameras}}",
"batchFailed": "Gagal memulai ekspor sebanyak {{total}}. Kamera yang gagal: {{failedCameras}}"
"batchFailed": "Gagal memulai ekspor sebanyak {{total}}. Kamera yang gagal: {{failedCameras}}",
"batchQueuedPartial": "Antrian ekspor berhasil sebanyak {{successful}} dari total {{total}} ekspor. Kamera yang gagal: {{failedCameras}}"
},
"case": {
"newCaseOption": "Membuat Kasus Baru",
@@ -87,7 +91,9 @@
"selectGroup": "Pilih grup",
"noMatchingCameras": "Tidak ada kamera yang sesuai dengan pencarian Anda",
"selectedCount": "{{terpilih}} / {{total}} terpilih",
"namePlaceholder": "Nama dasar opsional untuk ekspor ini"
"namePlaceholder": "Nama dasar opsional untuk ekspor ini",
"searchOrSelectGroup": "Cari, atau pilih grup kamera...",
"selectAll": "Pilih semua kamera"
},
"multi": {
"title_other": "Ekspor {{count}} Ulasan",
+15 -3
View File
@@ -68,8 +68,8 @@
"choir": "Koris",
"yodeling": "Jodelēšana",
"mantra": "Mantra",
"rapping": "Repot",
"whistling": "Svilpot",
"rapping": "Repošana",
"whistling": "Svilpošana",
"sniff": "Ošņāt",
"hands": "Rokas",
"animal": "Dzīvnieks",
@@ -122,5 +122,17 @@
"fireworks": "Uguņošana",
"glass": "Stikls",
"white_noise": "Baltais troksnis",
"radio": "Radio"
"radio": "Radio",
"chant": "Piedziedājums",
"synthetic_singing": "Sintētiskā dziedāšana",
"groan": "Vaidēšana",
"grunt": "Rukšķēšana",
"breathing": "Elpošana",
"wheeze": "Gārgšana",
"snoring": "Krākšana",
"gasp": "Elsošana",
"pant": "Elšana",
"snort": "Šņaukt",
"cough": "Klepus",
"throat_clearing": "Kakla tīrīšana"
}
@@ -64,7 +64,8 @@
"error": {
"failed": "Kunne ikke legge eksport i kø: {{error}}",
"noVaildTimeSelected": "Ingen gyldig tidsperiode valgt",
"endTimeMustAfterStartTime": "Sluttid må være etter starttid"
"endTimeMustAfterStartTime": "Sluttid må være etter starttid",
"noValidTimeSelected": "Ingen gyldig tidsperiode valgt"
},
"view": "Vis",
"queued": "Eksport lagt i kø. Se fremdrift på eksportsiden.",
+11 -1
View File
@@ -34,6 +34,9 @@
"label": "Bevestig dit label voor Frigate Plus",
"ask_a": "Is dit object een <code>{{label}}</code>?",
"ask_full": "Is dit object een <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
},
"toast": {
"error": "Gefaald bij Frigate+ in te dienen. Controleer je netwerkverbinding en probeer opnieuw."
}
}
},
@@ -104,7 +107,14 @@
"namePlaceholder": "Optionele basisnaam voor deze exporten",
"queueingButton": "Exporten in wachtrij plaatsen...",
"exportButton_one": "Export 1 Camera",
"exportButton_other": "{{count}} camera's exporteren"
"exportButton_other": "{{count}} camera's exporteren",
"searchOrSelectGroup": "Zoek of selecteer een cameragroep...",
"selectAll": "Selecteer alle camera's",
"clearSelection": "Wis selectie",
"selectWithActivity": "Camera's met gevolgde objecten",
"selectGroup": "Selecteer groep",
"noMatchingCameras": "Geen camera's komen overeen met je zoekopdracht",
"selectedCount": "{{selected}} / {{total}} geselecteerd"
},
"multi": {
"title_one": "Review 1 exporteren",
@@ -19,8 +19,8 @@
"question": {
"label": "Confirmar esse rótulo para Frigate Plus",
"ask_a": "Este objeto é um <code>{{label}}</code>?",
"ask_an": "Este objeto é um<code>{{label}}</code>?",
"ask_full": "Este objeto é um<code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
"ask_an": "Este objeto é um <code>{{label}}</code>?",
"ask_full": "Este objeto é um <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
},
"state": {
"submitted": "Enviado"
@@ -71,7 +71,10 @@
"batchQueueFailed": "Falha ao enfileirar {{total}} exportações. Câmeras com falha: {{failedCameras}}",
"batchPartial": "Iniciadas {{successful}} de {{total}} exportações. Câmeras com falha: {{failedCameras}}",
"batchFailed": "Falha ao iniciar {{total}} exportações. Câmeras com falha: {{failedCameras}}",
"queued": "Exportação na fila. Acompanhe o progresso na página de exportações."
"queued": "Exportação na fila. Acompanhe o progresso na página de exportações.",
"batchSuccess_one": "1 exportação iniciada. Abrindo o caso agora.",
"batchSuccess_many": "{{count}} exportações iniciadas. Abrindo o caso agora.",
"batchSuccess_other": "{{count}} exportações iniciadas. Abrindo o caso agora."
},
"fromTimeline": {
"saveExport": "Salvar Exportação",
@@ -132,7 +135,10 @@
"started_many": "Iniciadas {{count}} exportações. Abrindo o caso agora.",
"started_other": "",
"failed": "Falha ao iniciar {{total}} exportações. Falhas: {{failedItems}}",
"partial": "Iniciadas {{successful}} de {{total}} exportações. Falhas: {{failedItems}}"
"partial": "Iniciadas {{successful}} de {{total}} exportações. Falhas: {{failedItems}}",
"startedNoCase_one": "1 exportação iniciada.",
"startedNoCase_many": "{{count}} exportações iniciadas.",
"startedNoCase_other": "{{count}} exportações iniciadas."
}
}
},
+17 -2
View File
@@ -215,7 +215,8 @@
"label": "Birdseye",
"description": "Configurações para a visualização composta \"Birdseye\", que combina múltiplas transmissões de câmera em um único layout.",
"enabled": {
"label": "Ativar Birdseye"
"label": "Ativar Birdseye",
"description": "Habilita ou desabilita a função Birdseye."
}
},
"live": {
@@ -401,7 +402,9 @@
"enhancement": {
"label": "Nível de aprimoramento",
"description": "Nível de aprimoramento (0-10) a ser aplicado aos recortes da placa antes do OCR; valores mais altos nem sempre melhoram os resultados, e níveis acima de 5 podem funcionar apenas com placas noturnas, devendo ser utilizados com cautela."
}
},
"label": "Reconhecimento de placa veicular",
"description": "Configurações de reconhecimento de placa veicular, incluindo limites de detecção, formatação e placas conhecidas."
},
"record": {
"label": "Gravação",
@@ -539,6 +542,18 @@
"enabled": {
"label": "Ativar detecções",
"description": "Ative ou desative eventos de detecção para esta câmera."
},
"labels": {
"label": "Rótulos de detecção",
"description": "Lista de rótulos de objetos que são considerados eventos de detecção."
},
"required_zones": {
"label": "Zonas requeridas",
"description": "Zonas em que um objeto deve entrar para ser considerado detectado. Deixe em branco para permitir qualquer zona."
},
"cutoff_time": {
"label": "Tempo limite de detecção",
"description": "Segundos a esperar após o fim da atividade antes de encerrar a detecção."
}
}
}
+17 -2
View File
@@ -239,7 +239,8 @@
"label": "Birdseye",
"description": "Configurações para a visualização composta \"Birdseye\", que combina múltiplas transmissões de câmera em um único layout.",
"enabled": {
"label": "Ativar Birdseye"
"label": "Ativar Birdseye",
"description": "Habilita ou desabilita a função Birdseye."
}
},
"live": {
@@ -420,7 +421,9 @@
"enhancement": {
"label": "Nível de aprimoramento",
"description": "Nível de aprimoramento (0-10) a ser aplicado aos recortes da placa antes do OCR; valores mais altos nem sempre melhoram os resultados, e níveis acima de 5 podem funcionar apenas com placas noturnas, devendo ser utilizados com cautela."
}
},
"label": "Reconhecimento de placa veicular",
"description": "Configurações de reconhecimento de placa veicular, incluindo limites de detecção, formatação e placas conhecidas."
},
"record": {
"label": "Gravação",
@@ -553,6 +556,18 @@
"description": "Configurações que definem para quais objetos rastreados são geradas detecções (sem gerar alerta) e como essas detecções são retidas.",
"enabled": {
"label": "Ativar detecções"
},
"labels": {
"label": "Rótulos de detecção",
"description": "Lista de rótulos de objetos que são considerados eventos de detecção."
},
"required_zones": {
"label": "Zonas requeridas",
"description": "Zonas em que um objeto deve entrar para ser considerado detectado. Deixe em branco para permitir qualquer zona."
},
"cutoff_time": {
"label": "Tempo limite de detecção",
"description": "Segundos a esperar após o fim da atividade antes de encerrar a detecção."
}
}
}
@@ -12,12 +12,12 @@
},
"toast": {
"success": {
"deletedCategory_one": "Classe Apagada",
"deletedCategory_many": "Classes apagadas",
"deletedCategory_other": "",
"deletedImage_one": "Imagen Apagada",
"deletedImage_many": "Imagens Apagadas",
"deletedImage_other": "",
"deletedCategory_one": "{{count}} classe removida",
"deletedCategory_many": "{{count}} classes removidas",
"deletedCategory_other": "{{count}} classes removidas",
"deletedImage_one": "{{count}} imagem removida",
"deletedImage_many": "{{count}} imagens removidas",
"deletedImage_other": "{{count}} imagens removidas",
"categorizedImage": "Imagem Classificada com Sucesso",
"trainedModel": "Modelo treinado com sucesso.",
"trainingModel": "Treinamento do modelo iniciado com sucesso.",
+3 -3
View File
@@ -296,7 +296,7 @@
}
},
"motionMaskLabel": "Máscara de Movimento {{number}}",
"objectMaskLabel": "Máscara de Objeto {{number}} ({{label}})",
"objectMaskLabel": "Máscara de Objeto {{number}}",
"form": {
"zoneName": {
"error": {
@@ -437,7 +437,7 @@
"add": "Nova Máscara de Movimento",
"edit": "Editar Máscara de Movimento",
"context": {
"title": "Máscaras de movimento são usadas para prevenir tipos de movimento não desejados de ativarem uma detecção (exemplo: galhos de árvores, timestamps de câmeras). Máscaras de movimento devem ser usadas <em> com moderação </em>. Excesso de mascaramento tornará o rastreamento de objetos mais difícil.",
"title": "Máscaras de movimento são usadas para prevenir tipos de movimento não desejados de ativarem uma detecção (exemplo: galhos de árvores, timestamps de câmeras). Máscaras de movimento devem ser usadas <em>com moderação</em>. Excesso de mascaramento tornará o rastreamento de objetos mais difícil.",
"documentation": "Leia a documentação"
},
"point_one": "{{count}} ponto",
@@ -723,7 +723,7 @@
"title": "Configuração de Captura de Imagem",
"desc": "Envios ao Frigate+ requerem tanto a captura de imagem normais quanto a captura de imagem <code>clean_copy</code> estarem habilitadas na sua configuração.",
"documentation": "Leia a documentação",
"cleanCopyWarning": "Algumas câmeras possuem captura de imagem habilitada porém têm a cópia limpa desabilitada. Você precisa habilitar a <code>clean_copy</code> nas suas configurações de captura de imagem para poder submeter imagems dessa câmera ao Frigate+.",
"cleanCopyWarning": "Algumas câmeras estão com os snapshots desativados",
"table": {
"camera": "Câmera",
"snapshots": "Capturas de Imagem",
+13 -2
View File
@@ -32,6 +32,9 @@
"ask_a": "Este acest obiect un <code>{{label}}</code>?",
"ask_an": "Este acest obiect un <code>{{label}}</code>?",
"ask_full": "Este acest obiect un <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
},
"toast": {
"error": "Trimiterea către Frigate+ a eșuat. Te rog să verifici conexiunea la rețea și să încerci din nou."
}
},
"submitToPlus": {
@@ -96,7 +99,8 @@
"error": {
"failed": "Nu s-a putut adăuga exportul în coadă: {{error}}",
"endTimeMustAfterStartTime": "Ora de sfârșit trebuie să fie după ora de început",
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid"
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid",
"noValidTimeSelected": "Niciun interval de timp valid selectat"
},
"view": "Vizualizează",
"queued": "Export pus la coadă. Vezi progresul pe pagina de exporturi.",
@@ -145,7 +149,14 @@
"queueingButton": "Se adaugă exporturile la coadă...",
"exportButton_one": "Exportă 1 cameră",
"exportButton_few": "Exportă {{count}} camere",
"exportButton_other": "Exportă {{count}} de camere"
"exportButton_other": "Exportă {{count}} de camere",
"searchOrSelectGroup": "Caută sau selectează un grup de camere...",
"selectAll": "Selectează toate camerele",
"clearSelection": "Șterge selecția",
"selectWithActivity": "Camere cu obiecte urmărite",
"selectGroup": "Selectează grupul",
"noMatchingCameras": "Nicio cameră nu corespunde căutării tale",
"selectedCount": "{{selected}} / {{total}} selectate"
},
"multi": {
"title_one": "Exportă o revizuire",
@@ -493,6 +493,9 @@
"max_concurrent": {
"description": "Numărul maxim de sarcini de export de procesat în același timp.",
"label": "Număr maxim de exporturi simultane"
},
"chapters": {
"label": "Metadate de capitol de încorporat în înregistrările exportate"
}
},
"preview": {
+3
View File
@@ -637,6 +637,9 @@
"max_concurrent": {
"description": "Numărul maxim de sarcini de export de procesat în același timp.",
"label": "Număr maxim de exporturi simultane"
},
"chapters": {
"label": "Metadate de capitol de încorporat în înregistrările exportate"
}
},
"preview": {
@@ -2098,6 +2098,10 @@
},
"ffmpeg": {
"hwaccelManualNotRecommended": "Argumentele manuale pentru accelerarea hardware nu sunt recomandate. Dacă nu există o cerință specifică, selectează presetarea care se potrivește cu hardware-ul tău."
},
"model": {
"optimizedFor320": "Frigate este optimizat pentru un model de 320x320, care este cea mai bună alegere pentru majoritatea configurațiilor. Un model de 640x640 este mai lent și ajută doar în scenarii specifice.",
"inputDimensionsNotDetectResolution": "Lățimea și înălțimea de intrare ale modelului reprezintă dimensiunile de intrare ale modelului de detectare a obiectelor, nu rezoluția de detecție a camerei tale. Acestea ar trebui să se potrivească cu dimensiunile modelului pe care îl folosești — de obicei o dimensiune pătrată, cum ar fi 320x320 sau 640x640."
}
},
"birdseye": {
+24 -24
View File
@@ -1,9 +1,9 @@
{
"time": {
"untilForTime": "Do{{time}}",
"untilForTime": "Do {{time}}",
"untilForRestart": "Do reštartu Frigate.",
"untilRestart": "Do reštartu",
"ago": "{{timeAgo}} pred časom",
"ago": "pred {{timeAgo}}",
"justNow": "Práve teraz",
"today": "Dnes",
"yesterday": "Včera",
@@ -23,32 +23,32 @@
"am": "ráno",
"yr": "{{time}}r",
"pm": "popoludní",
"year_one": "{{time}}rok",
"year_few": "{{time}}rokov",
"year_other": "{{time}}rokov",
"year_one": "{{time}} rok",
"year_few": "{{time}} roky",
"year_other": "{{time}} rokov",
"mo": "{{time}}mes",
"month_one": "{{time}}mesiac",
"month_one": "{{time}} mesiac",
"month_few": "{{time}} mesiace",
"month_other": "{{time}} mesiaca",
"month_other": "{{time}} mesiacov",
"d": "{{time}}d",
"day_one": "{{time}}deň",
"day_few": "{{time}}dni",
"day_other": "{{time}}dni",
"day_one": "{{time}} deň",
"day_few": "{{time}} dni",
"day_other": "{{time}} dní",
"h": "{{time}}h",
"hour_one": "{{time}}hodina",
"hour_few": "{{time}}hodiny",
"hour_other": "{{time}}hodin",
"hour_one": "{{time}} hodina",
"hour_few": "{{time}} hodiny",
"hour_other": "{{time}} hodín",
"m": "{{time}} min",
"s": "{{time}}s",
"minute_one": "{{time}}minuta",
"minute_few": "{{time}}minuty",
"minute_other": "{{time}}minut",
"second_one": "{{time}}sekunda",
"second_few": "{{time}}sekundy",
"second_other": "{{time}}sekund",
"minute_one": "{{time}} minúta",
"minute_few": "{{time}} minúty",
"minute_other": "{{time}} minút",
"second_one": "{{time}} sekunda",
"second_few": "{{time}} sekundy",
"second_other": "{{time}} sekúnd",
"formattedTimestamp": {
"12hour": "Deň MMM, h:mm:ss aaa",
"24hour": "Deň MMM, HH:mm:ss"
"12hour": "d MMM, h:mm:ss aaa",
"24hour": "d MMM, HH:mm:ss"
},
"formattedTimestamp2": {
"12hour": "MM/dd h:mm:ssa",
@@ -219,9 +219,9 @@
"allCameras": "Všetky kamery",
"cameras": {
"title": "Kamery",
"count_one": "{{count}}kamera",
"count_few": "{{count}}kamery",
"count_other": "{{count}}kamier"
"count_one": "{{count}} kamera",
"count_few": "{{count}} kamery",
"count_other": "{{count}} kamier"
}
},
"export": "Exportovať",
+3 -3
View File
@@ -43,9 +43,9 @@
"title": "Čas ukončenia",
"label": "Vybrat čas ukončenia"
},
"lastHour_one": "Minulu hodinu",
"lastHour_few": "Minule{{count}}hodiny",
"lastHour_other": "Minulych{{count}}hodin"
"lastHour_one": "Posledná hodina",
"lastHour_few": "Posledné {{count}} hodiny",
"lastHour_other": "Posledných {{count}} hodín"
},
"name": {
"placeholder": "Pomenujte Export"
@@ -12,12 +12,12 @@
},
"toast": {
"success": {
"deletedCategory_one": "Vymazaná Trieda",
"deletedCategory_few": "",
"deletedCategory_other": "",
"deletedImage_one": "Vymazané Obrázky",
"deletedImage_few": "",
"deletedImage_other": "",
"deletedCategory_one": "Vymazaná {{count}} trieda",
"deletedCategory_few": "Vymazané {{count}} triedy",
"deletedCategory_other": "Vymazaných {{count}} tried",
"deletedImage_one": "Vymazaný {{count}} obrázok",
"deletedImage_few": "Vymazané {{count}} obrázky",
"deletedImage_other": "Vymazaných {{count}} obrázkov",
"categorizedImage": "Obrázok bol úspešne klasifikovaný",
"trainedModel": "Úspešne vyškolený model.",
"trainingModel": "Úspešne spustené trénovanie modelu.",
@@ -81,7 +81,7 @@
"buttonText": "Vytvorte objektový model"
},
"state": {
"title": "Žiadne modely klasifikácie štátov",
"title": "Žiadne modely klasifikácie stavov",
"description": "Vytvorte si vlastný model na monitorovanie a klasifikáciu zmien stavu v špecifických oblastiach kamery.",
"buttonText": "Vytvorte model stavu"
}
@@ -90,7 +90,7 @@
"title": "Vytvorte novú klasifikáciu",
"steps": {
"nameAndDefine": "Názov a definícia",
"stateArea": "Štátna oblasť",
"stateArea": "Oblasť stavu",
"chooseExamples": "Vyberte Príklady"
},
"step1": {
@@ -98,7 +98,7 @@
"name": "Meno",
"namePlaceholder": "Zadajte názov modelu...",
"type": "Typ",
"typeState": "štátu",
"typeState": "Stav",
"typeObject": "Objekt",
"objectLabel": "Označenie objektu",
"objectLabelPlaceholder": "Vyberte typ objektu...",
@@ -118,12 +118,12 @@
"nameOnlyNumbers": "Názov modelu nemôže obsahovať iba čísla",
"classRequired": "Vyžaduje sa aspoň 1 kurz",
"classesUnique": "Názvy tried musia byť jedinečné",
"stateRequiresTwoClasses": "Modely štátov vyžadujú aspoň 2 triedy",
"stateRequiresTwoClasses": "Stavové modely vyžadujú aspoň 2 triedy",
"objectLabelRequired": "Vyberte označenie objektu",
"objectTypeRequired": "Vyberte typ klasifikácie",
"noneNotAllowed": "Trieda 'none' nie je povolená"
},
"states": "Štátov"
"states": "Stavy"
},
"step2": {
"description": "Vyberte kamery a definujte oblasť, ktorú chcete pre každú kameru monitorovať. Model klasifikuje stav týchto oblastí.",
@@ -134,7 +134,7 @@
},
"step3": {
"selectImagesPrompt": "Vybrať všetky obrázky s: {{className}}",
"selectImagesDescription": "Kliknite na obrázky a vyberte ich. Po dokončení tejto hodiny kliknite na tlačidlo Pokračovať.",
"selectImagesDescription": "Kliknite na obrázky a vyberte ich. Po dokončení tejto triedy kliknite na tlačidlo Pokračovať.",
"generating": {
"title": "Generovanie vzorových obrázkov",
"description": "Frigate načítava reprezentatívne obrázky z vašich nahrávok. Môže to chvíľu trvať..."
@@ -155,9 +155,9 @@
"classifyFailed": "Nepodarilo sa klasifikovať obrázky: {{error}}"
},
"generateSuccess": "Vzorové obrázky boli úspešne vygenerované",
"allImagesRequired_one": "Uveďte všetky obrázky. {{count}} obrázok zostáva.",
"allImagesRequired_few": "Uveďte všetky obrázky. {{count}} obrázky zostávajú.",
"allImagesRequired_other": "Uveďte všetky obrázky. {{count}} obrázkov zostávajú.",
"allImagesRequired_one": "Klasifikujte všetky obrázky. Zostáva {{count}} obrázok.",
"allImagesRequired_few": "Klasifikujte všetky obrázky. Zostávajú {{count}} obrázky.",
"allImagesRequired_other": "Klasifikujte všetky obrázky. Zostáva {{count}} obrázkov.",
"modelCreated": "Model vytvorený úspešne. Použite aktuálne klasifikácie na pridanie obrázkov pre chýbajúce stavy a nasledne dajte trénovať model.",
"missingStatesWarning": {
"title": "Chýbajúce príklady stavov",
@@ -174,7 +174,7 @@
},
"menu": {
"objects": "Objekty",
"states": "Štátov"
"states": "Stavy"
},
"details": {
"scoreInfo": "Skóre predstavuje priemernú istotu klasifikácie naprieč všetkými detekciami tohoto objektu.",
+2 -2
View File
@@ -40,8 +40,8 @@
"detail": {
"noDataFound": "Žiadne podrobné údaje na kontrolu",
"aria": "Prepnúť zobrazenie detailov",
"trackedObject_one": "objekt",
"trackedObject_other": "objekty",
"trackedObject_one": "{{count}} objekt",
"trackedObject_other": "{{count}} objektov",
"noObjectDetailData": "Nie sú k dispozícii žiadne podrobné údaje o objekte.",
"label": "Detail",
"settings": "Nastavenia podrobného zobrazenia",
+11 -9
View File
@@ -18,12 +18,12 @@
"title": "Nahrať obrázok tváre",
"desc": "Nahrajte obrázok na skenovanie tvárí a zahrňte ho do {{pageToggle}}"
},
"collections": "Zbierky",
"collections": "Kolekcie",
"createFaceLibrary": {
"title": "Vytvoriť Zbierku",
"desc": "Vytvoriť novú zbierku",
"new": "Vytvoriť novú tvár",
"nextSteps": "Vybudovanie silného základu:<li>Použite kartu Nedávne rozpoznania na výber a trénovanie obrázkov pre každú rozpoznanú osobu.</li><li>Pre dosiahnutie najlepších výsledkov sa zamerajte na priame obrázky; vyhnite sa trénovaniu obrázkov, ktoré zachytávajú tváre pod uhlom.</li></ul>"
"nextSteps": "Vybudovanie silného základu:<li>Použite kartu Nedávno rozpoznané tváre na výber a trénovanie obrázkov pre každú rozpoznanú osobu.</li><li>Pre dosiahnutie najlepších výsledkov sa zamerajte na priame obrázky; vyhnite sa trénovaniu obrázkov, ktoré zachytávajú tváre pod uhlom.</li></ul>"
},
"steps": {
"faceName": "Zadajte Meno tváre",
@@ -34,8 +34,8 @@
}
},
"train": {
"title": "Nedávne uznania",
"aria": "Vyberte posledné rozpoznania",
"title": "Nedávno rozpoznané tváre",
"aria": "Vyberte nedávno rozpoznané tváre",
"empty": "Neexistujú žiadne predchádzajúce pokusy o rozpoznávanie tváre"
},
"selectItem": "Vyberte {{item}}",
@@ -85,9 +85,10 @@
"deletedName_one": "{{count}} tvár bola úspešne odstránená.",
"deletedName_few": "{{count}} tváre boli úspešne odstránené.",
"deletedName_other": "{{count}} tvárí bolo úspešne odstránených.",
"renamedFace": "Úspešne premenovaná tvár na {{name}}",
"trainedFace": "Úspešne natrénovaná tvár.",
"updatedFaceScore": "Úspešne aktualizované skóre tváre."
"renamedFace": "Tvár bola úspešne premenovaná na {{name}}.",
"trainedFace": "Tvár bola úspešne natrénovaná.",
"updatedFaceScore": "Skóre tváre bolo úspešne aktualizované na {{name}} ({{score}}).",
"reclassifiedFace": "Tvár bola úspešne preklasifikovaná."
},
"error": {
"uploadingImageFailed": "Nepodarilo sa nahrať obrázok: {{errorMessage}}",
@@ -95,8 +96,9 @@
"deleteFaceFailed": "Nepodarilo sa odstrániť: {{errorMessage}}",
"deleteNameFailed": "Nepodarilo sa odstrániť meno: {{errorMessage}}",
"renameFaceFailed": "Nepodarilo sa premenovať tvár: {{errorMessage}}",
"trainFailed": "Nepodarilo sa trénovať: {{errorMessage}}",
"updateFaceScoreFailed": "Nepodarilo sa aktualizovať skóre tváre: {{errorMessage}}"
"trainFailed": "Nepodarilo sa natrénovať tvár: {{errorMessage}}",
"updateFaceScoreFailed": "Nepodarilo sa aktualizovať skóre tváre: {{errorMessage}}",
"reclassifyFailed": "Nepodarilo sa preklasifikovať tvár: {{errorMessage}}"
}
}
}
+6 -6
View File
@@ -339,9 +339,9 @@
},
"add": "Pridať zónu",
"edit": "Upraviť zónu",
"point_one": "{{count}}bod",
"point_few": "{{count}}body",
"point_other": "{{count}}bodov"
"point_one": "{{count}} bod",
"point_few": "{{count}} body",
"point_other": "{{count}} bodov"
},
"motionMasks": {
"label": "Maska Detekcia pohybu",
@@ -380,8 +380,8 @@
"add": "Pridať Masku Objektu",
"edit": "Upraviť Masku Objektu",
"context": "Masky filtrovania objektov slúžia na odfiltrovanie falošných poplachov konkrétneho typu objektu na základe jeho umiestnenia.",
"point_one": "{{count}}bod",
"point_few": "{{count}}body",
"point_one": "{{count}} bod",
"point_few": "{{count}} body",
"point_other": "{{count}} bodov",
"clickDrawPolygon": "Kliknutím nakreslite polygón do obrázku.",
"objects": {
@@ -397,7 +397,7 @@
}
},
"motionMaskLabel": "Maska Detekcia pohybu {{number}}",
"objectMaskLabel": "Maska Objektu {{number}} {{label}}"
"objectMaskLabel": "Maska objektu {{number}}"
},
"motionDetectionTuner": {
"title": "Ladenie detekcie pohybu",
+21 -20
View File
@@ -3,7 +3,7 @@
"cameras": "Štatistiky kamier - Frigate",
"storage": "Štatistiky úložiska - Frigate",
"general": "Všeobecné štatistiky - Frigate",
"enrichments": "Štatistiky obohatenia - Frigate",
"enrichments": "Štatistiky AI funkcií - Frigate",
"logs": {
"frigate": "Protokoly Frigate - Frigate",
"go2rtc": "Protokoly Go2RTC - Frigate",
@@ -36,7 +36,7 @@
}
},
"general": {
"title": "Hlavný",
"title": "Všeobecné",
"detector": {
"title": "Detektory",
"inferenceSpeed": "Detekčná rýchlosť",
@@ -96,7 +96,7 @@
}
},
"storage": {
"title": "Skladovanie",
"title": "Úložisko",
"overview": "Prehľad",
"recordings": {
"title": "Nahrávky",
@@ -108,11 +108,11 @@
"warning": "Aktuálna veľkosť SHM {{total}}MB je príliš malá. Zvýšte ju aspoň na {{min_shm}}MB."
},
"cameraStorage": {
"title": "Úložisko kamery",
"title": "Úložisko kamier",
"camera": "Kamera",
"unusedStorageInformation": "Nepoužité informácie o úložisku",
"storageUsed": "Skladovanie",
"percentageOfTotalUsed": "Percento z celkového počtu",
"unusedStorageInformation": "Informácie o nevyužitom úložisku",
"storageUsed": "Úložisko",
"percentageOfTotalUsed": "Percento z celku",
"bandwidth": "Šírka pásma",
"unused": {
"title": "Nepoužité",
@@ -125,7 +125,7 @@
"overview": "Prehľad",
"info": {
"aspectRatio": "pomer strán",
"cameraProbeInfo": "{{camera}} Informácie o sonde kamery",
"cameraProbeInfo": "Údaje o kamere {{camera}}",
"streamDataFromFFPROBE": "Údaje zo streamu sa získavajú pomocou príkazu <code>ffprobe</code>.",
"fetching": "Načítavajú sa údaje z kamery",
"stream": "Stream {{idx}}",
@@ -137,32 +137,33 @@
"audio": "Zvuk:",
"error": "Chyba: {{error}}",
"tips": {
"title": "Informácie o kamerovej sonde"
"title": "Údaje o kamere"
}
},
"framesAndDetections": "Rámy / Detekcie",
"framesAndDetections": "Snímky / Detekcie",
"label": {
"camera": "kamera",
"detect": "odhaliť",
"detect": "detekcie",
"skipped": "preskočené",
"ffmpeg": "FFmpeg",
"capture": "zachytiť",
"capture": "zachytávanie",
"cameraFfmpeg": "{{camName}} FFmpeg",
"cameraCapture": "zachytiť{{camName}}",
"cameraDetect": "Detekcia {{camName}}",
"cameraCapture": "{{camName}} zachytávanie",
"cameraDetect": "{{camName}} detekcie",
"overallFramesPerSecond": "celkový počet snímok za sekundu",
"overallDetectionsPerSecond": "celkový počet detekcií za sekundu",
"overallSkippedDetectionsPerSecond": "celkový počet vynechaných detekcií za sekundu",
"overallSkippedDetectionsPerSecond": "celkový počet preskočených detekcií za sekundu",
"cameraFramesPerSecond": "{{camName}} snímky za sekundu",
"cameraDetectionsPerSecond": "{{camName}}detekcie za sekundu",
"cameraSkippedDetectionsPerSecond": "{{camName}} vynechaných detekcií za sekundu"
"cameraDetectionsPerSecond": "{{camName}} detekcie za sekundu",
"cameraSkippedDetectionsPerSecond": "{{camName}} preskočené detekcie za sekundu",
"cameraGpu": "{{camName}} GPU"
},
"toast": {
"success": {
"copyToClipboard": "Dáta sondy boli skopírované do schránky."
"copyToClipboard": "Údaje o kamere boli skopírované do schránky."
},
"error": {
"unableToProbeCamera": "Nepodarilo sa overiť kameru: {{errorMessage}}"
"unableToProbeCamera": "Nepodarilo sa načítať údaje z kamery: {{errorMessage}}"
}
}
},
@@ -178,7 +179,7 @@
"shmTooLow": "Alokácia /dev/shm ({{total}} MB) by sa mala zvýšiť aspoň na {{min}} MB."
},
"enrichments": {
"title": "Obohatenia",
"title": "AI funkcie",
"infPerSecond": "Inferencie za sekundu",
"embeddings": {
"image_embedding": "Vkladanie obrázkov",
+5 -1
View File
@@ -75,5 +75,9 @@
"24hour": "MM-dd-yy-HH-mm-ss"
}
},
"readTheDocumentation": "Прочитајте документацију"
"readTheDocumentation": "Прочитајте документацију",
"menu": {
"system": "Систем",
"profiles": "Профили"
}
}
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -24,6 +24,9 @@
},
"state": {
"submitted": "Inskickad"
},
"toast": {
"error": "Misslyckades att skicka till Frigate+. Kontrollera din nätverksanslutning och försök igen."
}
}
},
@@ -106,7 +109,10 @@
"selectAll": "Välj alla kameror",
"selectWithActivity": "Kameror med spårade objekt",
"selectGroup": "Välj grupp",
"noMatchingCameras": "Ingen kamera matchar din sökning"
"noMatchingCameras": "Ingen kamera matchar din sökning",
"searchOrSelectGroup": "Sök, eller välj en kameragrupp...",
"clearSelection": "Rensa val",
"selectedCount": "{{selected}} / {{total}} valda"
},
"multi": {
"title_one": "Exportera 1 recension",
+27 -2
View File
@@ -20,10 +20,35 @@
"description": "Aktivera eller avaktivera ljudbaserad detektering för denna kamera."
},
"max_not_heard": {
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar."
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar.",
"label": "Avbryt paus"
},
"min_volume": {
"label": "Minsta ljudvolym"
"label": "Minsta ljudvolym",
"description": "Lägsta RMS volym för ljuddetektion; lägre värde ökar känslighet (t.e.x. 200 högt, 500 medium, 1000 lågt)."
},
"listen": {
"label": "Lyssningstyper",
"description": "Lista på typer av ljudevent att detektera (till exempel: skälla, brandlarm, tal, skrik)."
},
"filters": {
"label": "Ljudfilter",
"description": "Filterinställningar per ljudtyp, till exempel konfidinströskel som används för att minska falska positiva resultat.",
"threshold": {
"label": "Lägsta ljud-konfidinströskel",
"description": "Lägsta konfidinströskel för att ljudhändelsen ska räknas."
}
},
"enabled_in_config": {
"label": "Ursprungligt ljudtillstånd",
"description": "Indikerar om ljuddetektion ursprungligen var aktiverat i den statiska konfigurationsfilen."
},
"num_threads": {
"label": "Detekteringstrådar",
"description": "Antal trådar att använda för bearbetning av ljuddetektering."
}
},
"audio_transcription": {
"label": "Ljudtranskribering"
}
}
+27 -2
View File
@@ -8,10 +8,35 @@
"label": "Aktivera ljuddetektering"
},
"max_not_heard": {
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar."
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar.",
"label": "Avbryt paus"
},
"min_volume": {
"label": "Minsta ljudvolym"
"label": "Minsta ljudvolym",
"description": "Lägsta RMS volym för ljuddetektion; lägre värde ökar känslighet (t.e.x. 200 högt, 500 medium, 1000 lågt)."
},
"listen": {
"label": "Lyssningstyper",
"description": "Lista på typer av ljudevent att detektera (till exempel: skälla, brandlarm, tal, skrik)."
},
"filters": {
"label": "Ljudfilter",
"description": "Filterinställningar per ljudtyp, till exempel konfidinströskel som används för att minska falska positiva resultat.",
"threshold": {
"label": "Lägsta ljud-konfidinströskel",
"description": "Lägsta konfidinströskel för att ljudhändelsen ska räknas."
}
},
"enabled_in_config": {
"label": "Ursprungligt ljudtillstånd",
"description": "Indikerar om ljuddetektion ursprungligen var aktiverat i den statiska konfigurationsfilen."
},
"num_threads": {
"label": "Detekteringstrådar",
"description": "Antal trådar att använda för bearbetning av ljuddetektering."
}
},
"audio_transcription": {
"label": "Ljudtranskribering"
}
}
@@ -67,7 +67,8 @@
"error": {
"failed": "未能加入导出队列:{{error}}",
"endTimeMustAfterStartTime": "结束时间必须在开始时间之后",
"noVaildTimeSelected": "未选择有效的时间范围"
"noVaildTimeSelected": "未选择有效的时间范围",
"noValidTimeSelected": "未选择有效的时间范围"
},
"view": "查看",
"queued": "导出已加入队列。请在导出页面查看进度。",
+5 -5
View File
@@ -452,15 +452,15 @@
},
"continuous": {
"label": "持续保留",
"description": "无论是否有追踪目标或动作,保留录像的天数。如果只想保留警报和检测的录像,请设置为 0。",
"description": "无论是否有追踪目标或画面变动,保留录像的天数。如果只想保留警报和检测的录制,请设置为 0。",
"days": {
"label": "保留天数",
"description": "保留录像的天数。"
}
},
"motion": {
"label": "动作保留",
"description": "无论是否有追踪目标,由动作触发的录像保留天数。如果只想保留警报和检测的录像,请设置为 0。",
"label": "画面变动录制保留",
"description": "无论是否有追踪目标,只要画面变动触发保存的录制保留天数。如果只想保留警报和检测的录制,请设置为 0。",
"days": {
"label": "保留天数",
"description": "保留录像的天数。"
@@ -486,7 +486,7 @@
},
"mode": {
"label": "保留模式",
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
}
}
},
@@ -510,7 +510,7 @@
},
"mode": {
"label": "保留模式",
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
}
}
},
+9 -9
View File
@@ -1507,10 +1507,10 @@
},
"motion": {
"label": "画面变动检测",
"description": "应用于摄像头的默认动作检测设置,除非按摄像头覆盖。",
"description": "默认情况下,画面变动检测配置统一应用于各摄像头,除非对特定摄像头单独进行自定义。",
"enabled": {
"label": "开启画面变动检测",
"description": "为所有摄像头启用或禁用动作检测;可按摄像头覆盖。"
"description": "为所有摄像头启用或禁用画面变动检测。可以给指定摄像头单独覆盖此设置。"
},
"threshold": {
"label": "画面变动阈值",
@@ -1706,15 +1706,15 @@
},
"continuous": {
"label": "持续保留",
"description": "无论是否有追踪目标或动作,保留录像的天数。如果只想保留警报和检测的录像,请设置为 0。",
"description": "无论是否有追踪目标或画面变动,保留录像的天数。如果只想保留警报和检测的录制,请设置为 0。",
"days": {
"label": "保留天数",
"description": "保留录像的天数。"
}
},
"motion": {
"label": "动作保留",
"description": "无论是否有追踪目标,由动作触发的录像保留天数。如果只想保留警报和检测的录像,请设置为 0。",
"label": "画面变动录制保留",
"description": "无论是否有追踪目标,只要画面变动触发保存的录制保留天数。如果只想保留警报和检测的录制,请设置为 0。",
"days": {
"label": "保留天数",
"description": "保留录像的天数。"
@@ -1740,7 +1740,7 @@
},
"mode": {
"label": "保留模式",
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
}
}
},
@@ -1764,7 +1764,7 @@
},
"mode": {
"label": "保留模式",
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
}
}
},
@@ -2030,8 +2030,8 @@
}
},
"motion": {
"label": "动作时运行",
"description": "启用后,当在指定裁剪区域内检测到动作时运行分类。"
"label": "画面变动时运行",
"description": "启用后,当在指定裁剪区域内检测到画面变动时进行分类。"
},
"interval": {
"label": "分类间隔",
+8 -1
View File
@@ -1655,7 +1655,14 @@
"keyLabel": "键",
"valueLabel": "值",
"keyPlaceholder": "新键名",
"remove": "移除"
"remove": "移除",
"providerNameLabel": "提供商名称",
"providerNamePlaceholder": "例如:openai、deepseek",
"variableNameLabel": "变量名",
"variableNamePlaceholder": "例如:MY_VARIABLE",
"loggerNameLabel": "日志模块名称",
"loggerNamePlaceholder": "例如:frigate.record",
"keyPatternError": "只能使用字母、数字、连字符和下划线(不包含空格)"
},
"roleMap": {
"empty": "未配置权限组映射",
@@ -59,7 +59,8 @@
"error": {
"failed": "匯出失敗:{{error}}",
"endTimeMustAfterStartTime": "結束時間必須要在開始時間之後",
"noVaildTimeSelected": "沒有選取有效的時間範圍"
"noVaildTimeSelected": "沒有選取有效的時間範圍",
"noValidTimeSelected": "未選擇有效的時間範圍"
},
"view": "查看",
"queued": "匯出已加入佇列。請在匯出頁面檢視進度。",
@@ -497,6 +497,9 @@
"max_concurrent": {
"label": "最大併發匯出數",
"description": "同時可處理的最大匯出任務數量。"
},
"chapters": {
"label": "匯出錄影時要嵌入的章節資訊"
}
},
"preview": {
@@ -1043,6 +1043,9 @@
"max_concurrent": {
"label": "最大併發匯出數",
"description": "同時可處理的最大匯出任務數量。"
},
"chapters": {
"label": "匯出錄影時要嵌入的章節資訊"
}
},
"preview": {
@@ -1484,7 +1484,14 @@
"keyLabel": "鍵",
"valueLabel": "值",
"keyPlaceholder": "新鍵名",
"remove": "移除"
"remove": "移除",
"providerNameLabel": "服務提供者名稱",
"providerNamePlaceholder": "例如:openai",
"variableNameLabel": "變數名稱",
"variableNamePlaceholder": "例如:MY_VARIABLE",
"loggerNameLabel": "日誌名稱",
"loggerNamePlaceholder": "例如:frigate.record",
"keyPatternError": "僅能使用英文字母、數字、連字號(-)和底線(_),且不得包含空格"
},
"knownPlates": {
"namePlaceholder": "例如:老婆的車",
@@ -9,6 +9,7 @@ import {
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -17,6 +18,7 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Switch } from "@/components/ui/switch";
import {
Select,
SelectContent,
@@ -50,14 +52,25 @@ type ClassificationModelEditDialogProps = {
type ObjectClassificationType = "sub_label" | "attribute";
type ObjectFormData = {
enabled: boolean;
saveAttempts: number;
objectLabel: string;
objectType: ObjectClassificationType;
};
type StateFormData = {
enabled: boolean;
saveAttempts: number;
motion: boolean;
interval?: number;
classes: string[];
};
const DEFAULT_SAVE_ATTEMPTS = {
object: 200,
state: 100,
} as const;
export default function ClassificationModelEditDialog({
open,
model,
@@ -71,6 +84,10 @@ export default function ClassificationModelEditDialog({
const isStateModel = model.state_config !== undefined;
const isObjectModel = model.object_config !== undefined;
const defaultSaveAttempts = isObjectModel
? DEFAULT_SAVE_ATTEMPTS.object
: DEFAULT_SAVE_ATTEMPTS.state;
const objectLabels = useMemo(() => {
if (!config) return [];
@@ -93,8 +110,17 @@ export default function ClassificationModelEditDialog({
// Define form schema based on model type
const formSchema = useMemo(() => {
const sharedFields = {
enabled: z.boolean(),
saveAttempts: z.coerce
.number({ message: t("edit.errors.saveAttemptsInvalid") })
.int(t("edit.errors.saveAttemptsInvalid"))
.min(0, t("edit.errors.saveAttemptsInvalid")),
};
if (isObjectModel) {
return z.object({
...sharedFields,
objectLabel: z
.string()
.min(1, t("wizard.step1.errors.objectLabelRequired")),
@@ -103,6 +129,17 @@ export default function ClassificationModelEditDialog({
} else {
// State model
return z.object({
...sharedFields,
motion: z.boolean(),
interval: z.preprocess(
(val) =>
val === "" || val === null || val === undefined ? undefined : val,
z.coerce
.number({ message: t("edit.errors.intervalInvalid") })
.int(t("edit.errors.intervalInvalid"))
.positive(t("edit.errors.intervalInvalid"))
.optional(),
),
classes: z
.array(z.string())
.min(1, t("wizard.step1.errors.classRequired"))
@@ -129,12 +166,18 @@ export default function ClassificationModelEditDialog({
resolver: zodResolver(formSchema),
defaultValues: isObjectModel
? ({
enabled: model.enabled,
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
objectLabel: model.object_config?.objects?.[0] || "",
objectType:
(model.object_config
?.classification_type as ObjectClassificationType) || "sub_label",
} as ObjectFormData)
: ({
enabled: model.enabled,
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
motion: model.state_config?.motion ?? false,
interval: model.state_config?.interval,
classes: [""], // Will be populated from dataset
} as StateFormData),
mode: "onChange",
@@ -151,6 +194,8 @@ export default function ClassificationModelEditDialog({
if (open) {
if (isObjectModel) {
form.reset({
enabled: model.enabled,
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
objectLabel: model.object_config?.objects?.[0] || "",
objectType:
(model.object_config
@@ -158,6 +203,10 @@ export default function ClassificationModelEditDialog({
} as ObjectFormData);
} else {
form.reset({
enabled: model.enabled,
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
motion: model.state_config?.motion ?? false,
interval: model.state_config?.interval,
classes: [""],
} as StateFormData);
}
@@ -166,7 +215,15 @@ export default function ClassificationModelEditDialog({
mutateDataset();
}
}
}, [open, isObjectModel, isStateModel, model, form, mutateDataset]);
}, [
open,
isObjectModel,
isStateModel,
model,
form,
mutateDataset,
defaultSaveAttempts,
]);
// Update form with classes from dataset when loaded
useEffect(() => {
@@ -233,6 +290,7 @@ export default function ClassificationModelEditDialog({
setIsSaving(true);
try {
if (isObjectModel) {
// object model save
const objectData = data as ObjectFormData;
// Update the config
@@ -243,9 +301,10 @@ export default function ClassificationModelEditDialog({
classification: {
custom: {
[model.name]: {
enabled: model.enabled,
enabled: objectData.enabled,
name: model.name,
threshold: model.threshold,
save_attempts: objectData.saveAttempts,
object_config: {
objects: [objectData.objectLabel],
classification_type: objectData.objectType,
@@ -260,7 +319,34 @@ export default function ClassificationModelEditDialog({
position: "top-center",
});
} else {
// state model save
const stateData = data as StateFormData;
const stateConfig: { motion: boolean; interval?: number | null } = {
motion: stateData.motion,
};
if (stateData.interval != null) {
stateConfig.interval = stateData.interval;
} else if (model.state_config?.interval != null) {
stateConfig.interval = null;
}
await axios.put("/config/set", {
requires_restart: 0,
update_topic: `config/classification/custom/${model.name}`,
config_data: {
classification: {
custom: {
[model.name]: {
enabled: stateData.enabled,
save_attempts: stateData.saveAttempts,
state_config: stateConfig,
},
},
},
},
});
const newClasses = stateData.classes.filter(
(c) => c.trim().length > 0,
);
@@ -307,11 +393,11 @@ export default function ClassificationModelEditDialog({
if (renamePromises.length > 0) {
await Promise.all(renamePromises);
await mutate(`classification/${model.name}/dataset`);
toast.success(t("toast.success.updatedModel"), {
toast.success(t("edit.stateClassesInfo"), {
position: "top-center",
});
} else {
toast.info(t("edit.stateClassesInfo"), {
toast.success(t("toast.success.updatedModel"), {
position: "top-center",
});
}
@@ -359,6 +445,29 @@ export default function ClassificationModelEditDialog({
<div className="space-y-6">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="enabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between gap-4">
<div className="space-y-0.5">
<FormLabel className="text-primary-variant">
{t("edit.enabled")}
</FormLabel>
<FormDescription className="text-xs">
{t("edit.enabledDesc")}
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
{isObjectModel && (
<>
<FormField
@@ -520,6 +629,77 @@ export default function ClassificationModelEditDialog({
</div>
)}
{isStateModel && (
<>
<FormField
control={form.control}
name="motion"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between gap-4">
<div className="space-y-0.5">
<FormLabel className="text-primary-variant">
{t("edit.motion")}
</FormLabel>
<FormDescription className="text-xs">
{t("edit.motionDesc")}
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="interval"
render={({ field }) => (
<FormItem>
<FormLabel className="text-primary-variant">
{t("edit.interval")}
</FormLabel>
<FormControl>
<Input
className="h-8"
inputMode="numeric"
placeholder={t("edit.intervalPlaceholder")}
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormDescription className="text-xs">
{t("edit.intervalDesc")}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</>
)}
<FormField
control={form.control}
name="saveAttempts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-primary-variant">
{t("edit.saveAttempts")}
</FormLabel>
<FormControl>
<Input className="h-8" inputMode="numeric" {...field} />
</FormControl>
<FormDescription className="text-xs">
{t("edit.saveAttemptsDesc")}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-col gap-3 pt-3 sm:flex-row sm:justify-end sm:gap-4">
<Button
type="button"
@@ -1,8 +1,10 @@
import type { WidgetProps } from "@rjsf/utils";
import { useMemo } from "react";
import { useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import useSWR from "swr";
import { Switch } from "@/components/ui/switch";
import type { ConfigFormContext } from "@/types/configForm";
import type { GenAIModelsResponse } from "@/types/chat";
const GENAI_ROLES = ["embeddings", "descriptions", "chat"] as const;
@@ -37,10 +39,24 @@ export function GenAIRolesWidget(props: WidgetProps) {
const selectedRoles = useMemo(() => normalizeValue(value), [value]);
const providerKey = useMemo(() => getProviderKey(id), [id]);
// Compute occupied roles directly from formData. The computation is
// trivially cheap (iterate providers × 3 roles max) so we skip an
// intermediate memoization layer whose formData dependency would
// never produce a cache hit (new object reference on every change).
const { data: genaiInfo } = useSWR<GenAIModelsResponse>("genai/models", {
revalidateOnFocus: false,
});
const embeddingsSupported = useMemo(() => {
if (!providerKey) return true;
const info = genaiInfo?.[providerKey];
return info ? info.supports_embeddings : true;
}, [genaiInfo, providerKey]);
const availableRoles = useMemo(
() =>
embeddingsSupported
? GENAI_ROLES
: GENAI_ROLES.filter((role) => role !== "embeddings"),
[embeddingsSupported],
);
const occupiedRoles = useMemo(() => {
const occupied = new Set<string>();
const fd = formContext?.formData;
@@ -64,6 +80,12 @@ export function GenAIRolesWidget(props: WidgetProps) {
return occupied;
}, [formContext?.formData, providerKey]);
useEffect(() => {
if (!embeddingsSupported && selectedRoles.includes("embeddings")) {
onChange(selectedRoles.filter((role) => role !== "embeddings"));
}
}, [embeddingsSupported, selectedRoles, onChange]);
const toggleRole = (role: string, enabled: boolean) => {
if (enabled) {
if (!selectedRoles.includes(role)) {
@@ -78,7 +100,7 @@ export function GenAIRolesWidget(props: WidgetProps) {
return (
<div className="rounded-lg border border-secondary-highlight bg-background_alt p-2 pr-0 md:max-w-md">
<div className="grid gap-2">
{GENAI_ROLES.map((role) => {
{availableRoles.map((role) => {
const checked = selectedRoles.includes(role);
const roleDisabled = !checked && occupiedRoles.has(role);
const label = t(`configForm.genaiRoles.options.${role}`, {
+1
View File
@@ -43,6 +43,7 @@ export type GenAIProviderInfo = {
models: string[];
roles: string[];
supports_toggleable_thinking: boolean;
supports_embeddings: boolean;
};
export type GenAIModelsResponse = Record<string, GenAIProviderInfo>;
+1
View File
@@ -370,6 +370,7 @@ export type CustomClassificationModelConfig = {
};
};
motion: boolean;
interval?: number;
};
};
@@ -3,6 +3,7 @@ import ClassificationModelWizardDialog from "@/components/classification/Classif
import ClassificationModelEditDialog from "@/components/classification/ClassificationModelEditDialog";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { ImageShadowOverlay } from "@/components/overlay/ImageShadowOverlay";
import { Badge } from "@/components/ui/badge";
import { Button, buttonVariants } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import useOptimisticState from "@/hooks/use-optimistic-state";
@@ -330,7 +331,10 @@ function ModelCard({ config, onClick, onUpdate, onDelete }: ModelCardProps) {
{coverImage ? (
<>
<img
className="size-full"
className={cn(
"size-full",
!config.enabled && "opacity-50 grayscale",
)}
src={`${baseUrl}clips/${config.name}/dataset/${coverImage.name}/${coverImage.img}`}
/>
<ImageShadowOverlay lowerClassName="h-[30%] z-0" />
@@ -338,6 +342,14 @@ function ModelCard({ config, onClick, onUpdate, onDelete }: ModelCardProps) {
) : (
<Skeleton className="flex size-full items-center justify-center" />
)}
{!config.enabled && (
<Badge
variant="secondary"
className="absolute right-2 top-2 z-40 text-primary-variant"
>
{t("disabled")}
</Badge>
)}
<div className="absolute bottom-2 left-3 text-lg text-white smart-capitalize">
{config.name}
</div>
@@ -348,6 +348,19 @@ function MotionPreviewClip({
}
}, [clipStart, clipEnd, playbackRate, preview]);
useEffect(() => {
if (!videoRef.current || !preview || !videoPlaying) {
return;
}
if (isSafari || (isFirefox && isMobile)) {
// These browsers step frames manually; rebuild the interval at the new rate
resetPlayback();
} else {
videoRef.current.playbackRate = playbackRate;
}
}, [playbackRate, preview, videoPlaying, resetPlayback]);
const drawDimOverlay = useCallback(() => {
if (!dimOverlayCanvasRef.current) {
return;