Compare commits

..
53 Commits
Author SHA1 Message Date
Josh HawkinsandGitHub bb04e6d49d Move genai review frame extraction off the embeddings loop (#24959)
CI / AMD64 Build (push) Waiting to run
CI / AMD64 Smoke Test (push) Blocked by required conditions
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
* move genai review frame extraction off the embeddings loop

- Run frame gathering for finished review items on a thread so per-frame ffmpeg calls don't stall the embeddings maintainer
- Share one path between live and regenerated review descriptions
- Stop padding review start and end times in place, which widened the stored review item
- Lock timestamp expiry in EventsPerSecond since update() and eps() now run on different threads

* serialize recording frame extraction for review descriptions
2026-11-13 06:45:29 -07:00
b2f5315e09 Validate MQTT TLS options before startup (#24951)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* Validate MQTT TLS verification options before client startup

* Test MQTT configuration recovery with authentication available

* Cover both MQTT TLS verification values in safe-mode recovery

* Use a documentation address in MQTT regression tests

* Skip MQTT TLS validation when MQTT is disabled

* Simplify MQTT TLS validation regression coverage

---------

Co-authored-by: Peter Kay <dougle.peter.kay@googlemail.com>
2026-11-12 10:04:09 -06:00
Josh HawkinsandGitHub a655ddcb2e Pin debug replay to main recording stream (#24952)
* pin debug replay to one recording stream

Debug replay stream-copied the merged VOD playlist into one mp4. When main had gaps that sub filled, the clip switched streams mid-file, and a camera with an H.265 main and an H.264 sub produced an mp4 labeled H.264 that also held HEVC packets. The replay camera's decoder then failed and the watchdog killed ffmpeg. Replay now follows the clip download rule: it pins the playlist to main and only falls back to sub when the range has no main recordings.

* choose the replay stream with the vod coverage resolver

Stream selection counted a main row that only touched the range, while the pinned playlist served nothing for it, so replay failed instead of using the sub footage. Selection now uses resolve_coverage, the same resolver the pinned vod and export use.
2026-11-12 08:33:33 -07:00
Josh HawkinsandGitHub 806ee66562 Fix sub label filter for non-ascii names (#24953)
* fix sub label filter for non-ascii names

The sub_labels filter lowercased the requested name in Python but the stored value with SQLite's LOWER(), which only folds ASCII. Cyrillic and other non-ASCII names never matched, so filtering by a face name returned no events. Both sides are now lowered in SQLite, which keeps ASCII matching case-insensitive and non-ASCII matching exact.

* cover events/search in sub label filter test
2026-11-12 08:33:04 -07:00
Hosted WeblateandJosh Hawkins 42f5a7560b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 21836c7444 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
e70eba2fb8 Translated using Weblate (Serbian (Latin script))
Currently translated at 28.7% (25 of 87 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 2.0% (1 of 49 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 21.8% (19 of 87 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 6.6% (4 of 60 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 97.9% (142 of 145 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 5.0% (3 of 60 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 44.1% (64 of 145 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 56.1% (791 of 1409 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 2.2% (2 of 87 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (68 of 68 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 5.2% (26 of 500 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 23.1% (326 of 1409 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 17.9% (252 of 1404 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (775 of 775 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (522 of 522 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (37 of 37 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (266 of 266 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 0.1% (2 of 1404 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 100.0% (775 of 775 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 80.6% (421 of 522 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 52.2% (139 of 266 strings)

Translated using Weblate (Serbian (Latin script))

Currently translated at 5.0% (25 of 500 strings)

Co-authored-by: Branislav Maricic <b.n.maricic@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-groups/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sr_Latn/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Groups
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-11-12 08:23:49 -06:00
e0b5a6c965 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (64 of 64 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (775 of 775 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (522 of 522 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (282 of 282 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1409 of 1409 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (68 of 68 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (266 of 266 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 93.9% (728 of 775 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 97.5% (509 of 522 strings)

Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 91.7% (711 of 775 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 94.2% (492 of 522 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (37 of 37 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.2% (264 of 266 strings)

Co-authored-by: GuoQing Liu <zhaisoul@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/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/config-validation/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/zh_Hans/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-11-12 08:23:49 -06:00
6733a757e8 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 96.8% (62 of 64 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 81.8% (54 of 66 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 88.4% (23 of 26 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 89.6% (695 of 775 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 90.4% (472 of 522 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 64.1% (181 of 282 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 89.8% (1261 of 1404 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (49 of 49 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 77.5% (100 of 129 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 98.8% (86 of 87 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 98.5% (67 of 68 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 59.4% (22 of 37 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 89.1% (107 of 120 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 90.2% (240 of 266 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (500 of 500 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 96.8% (62 of 64 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 88.4% (23 of 26 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 89.6% (695 of 775 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 90.4% (472 of 522 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 89.7% (1260 of 1404 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 98.8% (86 of 87 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 98.5% (67 of 68 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 90.2% (240 of 266 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 蘭蘭露 <flandretw@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/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/config-groups/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/zh_Hant/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Groups
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 94e700fbac Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 5f59f7c53c Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 0e2da4e24a Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins dade7a6b3e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 329430760e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 2fbb66e475 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 5019984a33 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
db58f9f5cc Translated using Weblate (French)
Currently translated at 27.2% (211 of 775 strings)

Translated using Weblate (French)

Currently translated at 61.8% (323 of 522 strings)

Translated using Weblate (French)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (French)

Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (French)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (French)

Currently translated at 100.0% (266 of 266 strings)

Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: tiphergane <tiphergane@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins f83b8b57db Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 146d61017e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 86c9b4bf74 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins ed1a515c1b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins c013f6dcb8 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins bb1736bb4d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
68988780cd Translated using Weblate (Hindi)
Currently translated at 29.0% (145 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Thomas Maslanky <terminallychill@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/hi/
Translation: Frigate NVR/audio
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins c148063e76 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 37ca717f63 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins b6d16fb6f3 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins e45b7de79b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins a1f8f95497 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
cabbfd5d73 Translated using Weblate (Catalan)
Currently translated at 100.0% (1409 of 1409 strings)

Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Eduardo Pastor Fernández <edugaga@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 20a3bd7ac5 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins e287833e8d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins e150c5d1dd Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 60c4e37303 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
3ac35fe777 Translated using Weblate (Romanian)
Currently translated at 100.0% (1409 of 1409 strings)

Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 8b0325aefb Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
c57b48e977 Translated using Weblate (Estonian)
Currently translated at 100.0% (266 of 266 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/et/
Translation: Frigate NVR/common
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 74a2aac866 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 2af6dc34da Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Hosted WeblateandJosh Hawkins 9eb336a3d8 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
bb0e2c0d5e Translated using Weblate (Turkish)
Currently translated at 100.0% (1409 of 1409 strings)

Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Murat Odabasi <modabas@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/tr/
Translation: Frigate NVR/views-settings
2026-11-12 08:23:49 -06:00
Josh HawkinsandGitHub 06d1365056 Report ownership scan progress (#24940)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* report ownership scan progress

The ownership scan printed nothing until it finished, which took 30+ minutes on large recordings volumes and looked stuck. It now logs a running count of checked and mismatched entries every 30 seconds.

* check the scan clock on every entry
2026-11-11 08:52:58 -07:00
Josh HawkinsandGitHub 8a88cefc9a Autotracking tweaks (#24935)
* fix autotracking movement time prediction and recorded move times

Movement time prediction multiplied the fitted intercept by the saved zero-step calibration time instead of 1, so predictions didn't match the regression. In absolute zoom mode, a pan/tilt followed by a zoom recorded the zoom's timestamps against the pan/tilt distance, because _zoom_absolute resets the PTZ start and stop times. The pan/tilt times are now captured before the zoom runs.

* predict velocity and zoom size for objects approaching the autotracked camera

The velocity check required both box corners to move in the same direction, but the corners of an approaching object diverge, so its velocity was rejected and zeroed and the camera aimed where the object had been. That check is removed, and the existing magnitude, delta, and variance checks still reject noise. The zoom size prediction fit area against epoch time with no intercept, which returned roughly the mean area instead of a forecast. It now scales the target box by the growth the corner velocities predict over the movement time.

* keep predicted target box positive and add regression tests

A shrinking object's predicted size could reach zero, so the relative zoom ratio divided by zero and sent a NaN zoom to the camera. The predicted size is now floored at 1 px. Tests also cover the movement time intercept and the pan/tilt timestamps that absolute zoom overwrites.
2026-11-11 08:38:21 -07:00
Josh HawkinsandGitHub fba967c74b fix annotation offset seeking in tracking details (#24936)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
The VOD manifest used to find the keyframe-snapped clip start was built from the live annotation offset while the playing clip used the offset from when the event opened. Each offset step fetched a manifest for a different clip, so the seek mapping drifted: the video jumped forward about one GOP every second of offset and flickered while each new manifest loaded. The manifest now uses the same stable source offset as the playlist
2026-11-10 07:57:21 -06:00
Josh HawkinsandGitHub 33c55649a4 report custom go2rtc and ffmpeg builds in analytics (#24928)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
Users can swap in their own go2rtc binary at /config/go2rtc or point ffmpeg.path at a custom build, and either can explain stream behavior that looks like a Frigate regression. The install section now reports a custom_go2rtc boolean and an ffmpeg enum (default, bundled, custom) for the configured global path, so no version strings or path text leave the install.
2026-11-09 14:05:16 -07:00
Josh HawkinsandGitHub c88434dc29 Write migrated config atomically (#24926)
* write migrated config atomically

Config migration rewrote config.yml in place with open("w") after each step, so nginx's get_nginx_settings.py, which starts alongside frigate, could read an empty file, get None from yaml.load, and crash on config.get. Each step now writes to a temp file with the original mode and os.replace's it into place.

* keep config owner and handle file bind mounts in migration writes

os.replace failed with EBUSY when config.yml was a file bind mount, as in the documented --validate-config command, and the replaced file lost its original owner when Frigate ran as root. The write now copies in place on EBUSY and restores the original owner when permitted. Tests cover a failed dump leaving the original config and the bind-mount fallback.

* follow config symlinks in migration writes
2026-11-09 13:56:34 -07:00
Josh HawkinsandGitHub e7256d58a1 strip nginx user directive when not running as root (#24925)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
2026-11-09 12:41:19 -06:00
b2a7e992c9 Request FP16 TensorRT engine for ONNX detector with device Tensorrt (#24923)
* Request FP16 TensorRT engine for ONNX detector with device Tensorrt

get_ort_providers only enables trt_fp16_enable when requires_fp16 is
passed, and the ONNX detector never passed it, so device: Tensorrt
always built an FP32 engine. USE_FP16=False still disables FP16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Make TensorRT FP16 opt-in via USE_FP16 only

Drop the unused requires_fp16 parameter from get_ort_providers and decide
trt_fp16_enable from the USE_FP16 environment variable alone, defaulting
to false. Revert the ONNX detector change and document USE_FP16=true for
device: Tensorrt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop the TensorRT docs line

The stock image ships only the TensorRT runtime, not the builder
libraries needed to create engines, so the instructions were misleading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:46:25 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
11f4dd194c Update google-genai requirement (#24920)
Updates the requirements on [google-genai](https://github.com/googleapis/python-genai) to permit the latest version.

Updates `google-genai` to 3.0.0
- [Release notes](https://github.com/googleapis/python-genai/releases)
- [Changelog](https://github.com/googleapis/python-genai/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-genai/compare/v2.34.0...v3.0.0)

---
updated-dependencies:
- dependency-name: google-genai
  dependency-version: 3.0.0
  dependency-type: direct:production
  dependency-group: pydantic-genai
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-09 08:25:39 -06:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0e7da1bee4 Bump the web-minor-patch group in /web with 5 updates (#24922)
Bumps the web-minor-patch group in /web with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [framer-motion](https://github.com/motiondivision/motion) | `14.9.1` | `14.10.0` |
| [i18next](https://github.com/i18next/i18next) | `26.7.1` | `26.7.2` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.69.0` | `1.70.0` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.1.12` | `17.1.13` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.64.1` | `1.65.0` |


Updates `framer-motion` from 14.9.1 to 14.10.0
- [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/motiondivision/motion/compare/v14.9.1...v14.10.0)

Updates `i18next` from 26.7.1 to 26.7.2
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.7.1...v26.7.2)

Updates `lucide-react` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.70.0/packages/lucide-react)

Updates `react-i18next` from 17.1.12 to 17.1.13
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.1.12...v17.1.13)

Updates `@playwright/test` from 1.64.1 to 1.65.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.64.1...v1.65.0)

---
updated-dependencies:
- dependency-name: framer-motion
  dependency-version: 14.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: i18next
  dependency-version: 26.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web-minor-patch
- dependency-name: lucide-react
  dependency-version: 1.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: react-i18next
  dependency-version: 17.1.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web-minor-patch
- dependency-name: "@playwright/test"
  dependency-version: 1.65.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-09 08:25:23 -06:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
14870f5237 Bump ruff in /docker/main in the pip-main-minor-patch group (#24921)
Bumps the pip-main-minor-patch group in /docker/main with 1 update: [ruff](https://github.com/astral-sh/ruff).


Updates `ruff` from 0.17.3 to 0.17.4
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.17.3...0.17.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.17.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: pip-main-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-09 08:25:17 -06:00
Josh HawkinsandGitHub ca851a9238 Miscellaneous Fixes (#24914)
* strip intel media driver on install

The manually built iHD_drv_video.so was installed with full debug info, making it 513 MB in the image instead of 41 MB stripped. Installing with install/strip removes about 240 MB from the compressed amd64 image.

* remove rjsf 6.11.0 dependabot hold

The hold covered the GenAI form refilling the saved model after a provider switch. That was a GenAIModelWidget bug that rjsf 6.11 exposed, and it was fixed with the 6.11.1 bump in #24901, so no rjsf version needs holding.

* strip intel media driver after install

install/strip skipped iHD_drv_video.so because media-driver installs it with install(PROGRAMS), not as a target. Strip it directly instead.

* bump dev docs version in header

* attest published images with github build provenance

Published images carried unsigned BuildKit provenance, so nothing tied an image digest to the commit it was built from. Each published image (the default multi-arch build and the six variants) now gets an actions/attest provenance attestation pushed to GHCR, verifiable with gh attestation verify. Release tags are skopeo copies with the same digest, so they're covered too. Storage records are off because they require an org-owned repo.
2026-11-09 06:44:48 -07:00
81cf7be89a Add on-demand Frigate+ API key validation (#24918)
* Add on-demand Frigate+ API key validation

The Frigate+ settings page reported the API key as validated whenever it
matched the expected format, without ever checking it with the server.

- Clarify the status text to say the key matches the expected format
- Add PlusApi.validate_key(), which requests a token from Frigate+ and
  reports the key as valid, invalid, unreachable, or not set; server
  errors and timeouts are not reported as an invalid key
- Add an admin-only POST /plus/validate endpoint and regenerate the API spec
- Add a "Validate with Frigate+" button that shows the result inline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4iDiZE2b3tVFVYyZrfZde

* Validate Frigate+ key asynchronously and keep cached token on bad replies

- Use aiohttp for the validation request so the endpoint awaits it
  instead of blocking a worker thread
- Only replace the cached token when the reply has a string accessToken
  and numeric expires; otherwise report unreachable so an unexpected
  response can't break later Frigate+ requests
- Test against a local aiohttp server, covering timeouts, connection
  failures, non-JSON replies and unusable token shapes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4iDiZE2b3tVFVYyZrfZde

* Honor proxy settings when validating the Frigate+ key

aiohttp ignores HTTP(S)_PROXY unless trust_env is set, so validation
reported Frigate+ as unreachable on installs that need a proxy while
other Frigate+ requests still worked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4iDiZE2b3tVFVYyZrfZde

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 06:30:35 -07:00
145 changed files with 9211 additions and 1476 deletions
+2
View File
@@ -4,6 +4,8 @@ inputs:
GITHUB_TOKEN: GITHUB_TOKEN:
required: true required: true
outputs: outputs:
image-repo:
value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}
image-name: image-name:
value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ steps.create-short-sha.outputs.SHORT_SHA }} value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ steps.create-short-sha.outputs.SHORT_SHA }}
cache-name: cache-name:
-3
View File
@@ -136,9 +136,6 @@ updates:
# Later versions need Node 22 and extract false keys # Later versions need Node 22 and extract false keys
- dependency-name: "i18next-cli" - dependency-name: "i18next-cli"
versions: [">1.5.11"] versions: [">1.5.11"]
# 6.11.0 refills the saved model in the GenAI settings form
- dependency-name: "@rjsf/*"
versions: ["6.11.0"]
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/docs" directory: "/docs"
+86
View File
@@ -423,6 +423,11 @@ jobs:
run: docker rm -f frigate || true run: docker rm -f frigate || true
arm64_build: arm64_build:
runs-on: ubuntu-22.04-arm runs-on: ubuntu-22.04-arm
permissions:
contents: read
packages: write
attestations: write
id-token: write
name: ARM Build name: ARM Build
steps: steps:
- name: Check out code - name: Check out code
@@ -435,6 +440,7 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push arm64 standard build - name: Build and push arm64 standard build
id: standard-arm64
uses: docker/build-push-action@v7 uses: docker/build-push-action@v7
with: with:
context: . context: .
@@ -445,6 +451,13 @@ jobs:
tags: | tags: |
${{ steps.setup.outputs.image-name }}-standard-arm64 ${{ steps.setup.outputs.image-name }}-standard-arm64
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64 cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
- name: Attest arm64 standard build
uses: actions/attest@v4
with:
subject-name: ${{ steps.setup.outputs.image-repo }}
subject-digest: ${{ steps.standard-arm64.outputs.digest }}
push-to-registry: true
create-storage-record: false
- name: Build and push RPi build - name: Build and push RPi build
uses: docker/bake-action@v7 uses: docker/bake-action@v7
with: with:
@@ -458,6 +471,11 @@ jobs:
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max
jetson_jp6_build: jetson_jp6_build:
runs-on: ubuntu-22.04-arm runs-on: ubuntu-22.04-arm
permissions:
contents: read
packages: write
attestations: write
id-token: write
name: Jetson Jetpack 6 name: Jetson Jetpack 6
steps: steps:
- name: Check out code - name: Check out code
@@ -470,6 +488,7 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push TensorRT (Jetson, Jetpack 6) - name: Build and push TensorRT (Jetson, Jetpack 6)
id: tensorrt-jp6
env: env:
ARCH: arm64 ARCH: arm64
BASE_IMAGE: nvcr.io/nvidia/tensorrt:23.12-py3-igpu BASE_IMAGE: nvcr.io/nvidia/tensorrt:23.12-py3-igpu
@@ -485,8 +504,20 @@ jobs:
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt-jp6 tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt-jp6
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6 *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6,mode=max
- name: Attest TensorRT (Jetson, Jetpack 6)
uses: actions/attest@v4
with:
subject-name: ${{ steps.setup.outputs.image-repo }}
subject-digest: ${{ fromJSON(steps.tensorrt-jp6.outputs.metadata)['tensorrt']['containerimage.digest'] }}
push-to-registry: true
create-storage-record: false
amd64_extra_builds: amd64_extra_builds:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
attestations: write
id-token: write
name: AMD64 Extra Build name: AMD64 Extra Build
needs: needs:
- amd64_build - amd64_build
@@ -501,6 +532,7 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push TensorRT (x86 GPU) - name: Build and push TensorRT (x86 GPU)
id: tensorrt
env: env:
COMPUTE_LEVEL: "50 60 70 80 90" COMPUTE_LEVEL: "50 60 70 80 90"
uses: docker/bake-action@v7 uses: docker/bake-action@v7
@@ -513,7 +545,15 @@ jobs:
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt,mode=max
- name: Attest TensorRT (x86 GPU)
uses: actions/attest@v4
with:
subject-name: ${{ steps.setup.outputs.image-repo }}
subject-digest: ${{ fromJSON(steps.tensorrt.outputs.metadata)['tensorrt']['containerimage.digest'] }}
push-to-registry: true
create-storage-record: false
- name: AMD/ROCm general build - name: AMD/ROCm general build
id: rocm
env: env:
HSA_OVERRIDE: 0 HSA_OVERRIDE: 0
uses: docker/bake-action@v7 uses: docker/bake-action@v7
@@ -526,8 +566,20 @@ jobs:
rocm.tags=${{ steps.setup.outputs.image-name }}-rocm rocm.tags=${{ steps.setup.outputs.image-name }}-rocm
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm,mode=max
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm
- name: Attest AMD/ROCm general build
uses: actions/attest@v4
with:
subject-name: ${{ steps.setup.outputs.image-repo }}
subject-digest: ${{ fromJSON(steps.rocm.outputs.metadata)['rocm']['containerimage.digest'] }}
push-to-registry: true
create-storage-record: false
arm64_extra_builds: arm64_extra_builds:
runs-on: ubuntu-22.04-arm runs-on: ubuntu-22.04-arm
permissions:
contents: read
packages: write
attestations: write
id-token: write
name: ARM Extra Build name: ARM Extra Build
needs: needs:
- arm64_build - arm64_build
@@ -542,6 +594,7 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Rockchip build - name: Build and push Rockchip build
id: rk
uses: docker/bake-action@v7 uses: docker/bake-action@v7
with: with:
source: . source: .
@@ -551,8 +604,20 @@ jobs:
set: | set: |
rk.tags=${{ steps.setup.outputs.image-name }}-rk rk.tags=${{ steps.setup.outputs.image-name }}-rk
*.cache-from=type=gha *.cache-from=type=gha
- name: Attest Rockchip build
uses: actions/attest@v4
with:
subject-name: ${{ steps.setup.outputs.image-repo }}
subject-digest: ${{ fromJSON(steps.rk.outputs.metadata)['rk']['containerimage.digest'] }}
push-to-registry: true
create-storage-record: false
synaptics_build: synaptics_build:
runs-on: ubuntu-22.04-arm runs-on: ubuntu-22.04-arm
permissions:
contents: read
packages: write
attestations: write
id-token: write
name: Synaptics Build name: Synaptics Build
needs: needs:
- arm64_build - arm64_build
@@ -567,6 +632,7 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Synaptics build - name: Build and push Synaptics build
id: synaptics
uses: docker/bake-action@v7 uses: docker/bake-action@v7
with: with:
source: . source: .
@@ -576,10 +642,22 @@ jobs:
set: | set: |
synaptics.tags=${{ steps.setup.outputs.image-name }}-synaptics synaptics.tags=${{ steps.setup.outputs.image-name }}-synaptics
*.cache-from=type=gha *.cache-from=type=gha
- name: Attest Synaptics build
uses: actions/attest@v4
with:
subject-name: ${{ steps.setup.outputs.image-repo }}
subject-digest: ${{ fromJSON(steps.synaptics.outputs.metadata)['synaptics']['containerimage.digest'] }}
push-to-registry: true
create-storage-record: false
# The majority of users running arm64 are rpi users, so the rpi # The majority of users running arm64 are rpi users, so the rpi
# build should be the primary arm64 image # build should be the primary arm64 image
assemble_default_build: assemble_default_build:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
attestations: write
id-token: write
name: Assemble and push default build name: Assemble and push default build
needs: needs:
- amd64_build - amd64_build
@@ -598,8 +676,16 @@ jobs:
- name: Create short sha - name: Create short sha
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
- uses: int128/docker-manifest-create-action@v2 - uses: int128/docker-manifest-create-action@v2
id: manifest
with: with:
tags: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }} tags: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}
sources: | sources: |
ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-amd64 ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-amd64
ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-rpi ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-rpi
- name: Attest default build
uses: actions/attest@v4
with:
subject-name: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}
subject-digest: ${{ steps.manifest.outputs.digest }}
push-to-registry: true
create-storage-record: false
+1
View File
@@ -46,3 +46,4 @@ make -C /tmp/media-driver/build -j"$(nproc)"
# Install driver to rootfs for COPY --from # Install driver to rootfs for COPY --from
make -C /tmp/media-driver/build install DESTDIR=/rootfs make -C /tmp/media-driver/build install DESTDIR=/rootfs
strip --strip-unneeded /rootfs/usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
+1 -1
View File
@@ -1,4 +1,4 @@
ruff == 0.17.3 ruff == 0.17.4
# types # types
types-peewee == 4.0.* types-peewee == 4.0.*
+1 -1
View File
@@ -47,7 +47,7 @@ onnxruntime == 1.31.*
# Embeddings # Embeddings
transformers == 5.22.* transformers == 5.22.*
# Generative AI # Generative AI
google-genai == 2.34.* google-genai == 3.0.*
ollama == 0.6.* ollama == 0.6.*
openai == 3.46.* openai == 3.46.*
# push notifications # push notifications
@@ -80,6 +80,11 @@ cp -r /usr/local/nginx/conf/. /tmp/nginx/conf/
set_worker_processes set_worker_processes
# A non-root master ignores `user` and warns on every start
if [[ "$runs_as_root" -eq 0 ]]; then
sed -i '/^user root;$/d' /tmp/nginx/conf/nginx.conf
fi
# TLS certs: user-mounted certs at /etc/letsencrypt/live/frigate (documented # TLS certs: user-mounted certs at /etc/letsencrypt/live/frigate (documented
# contract) always win; otherwise fall back to a self-signed cert persisted in # contract) always win; otherwise fall back to a self-signed cert persisted in
# /config/tls, which stays writable under a read-only root filesystem. # /config/tls, which stays writable under a read-only root filesystem.
+14 -1
View File
@@ -105,6 +105,8 @@ if [[ -n "$sentinel" ]]; then
mismatch_expr+=(-a ! -path "$sentinel") mismatch_expr+=(-a ! -path "$sentinel")
fi fi
exec 3>&1
for path in "$@"; do for path in "$@"; do
# An absent root is an incomplete sweep, not a finished one: /media/frigate # An absent root is an incomplete sweep, not a finished one: /media/frigate
# is not in the image, so a boot before the volume is mounted would # is not in the image, so a boot before the volume is mounted would
@@ -121,7 +123,18 @@ for path in "$@"; do
# stale mount. Tolerate it rather than aborting under errexit, but never # stale mount. Tolerate it rather than aborting under errexit, but never
# read a failed scan as "nothing to do": that would record the sweep as # read a failed scan as "nothing to do": that would record the sweep as
# complete without having looked. # complete without having looked.
if ! count=$(find "$path" -name lost+found -prune -o "${mismatch_expr[@]}" -printf '.' 2>/dev/null | wc -c); then # The walk prints nothing until it ends, so report every 30s through fd 3
# (stdout) while the count itself comes back through the substitution.
if ! count=$(find "$path" -name lost+found -prune -o "(" "${mismatch_expr[@]}" ")" -printf 'm\n' -o -printf '.\n' 2>/dev/null \
| awk -v path="$path" '
BEGIN { next_report = systime() + 30 }
$0 == "m" { mismatched++ }
systime() >= next_report {
printf "[INFO] fix-ownership: still scanning %s, %d entries checked, %d need changes\n", path, NR, mismatched > "/dev/fd/3"
fflush("/dev/fd/3")
next_report = systime() + 30
}
END { print mismatched + 0 }'); then
swept_clean=0 swept_clean=0
echo "[WARN] fix-ownership: could not scan ${path}; will retry on next boot" echo "[WARN] fix-ownership: could not scan ${path}; will retry on next boot"
continue continue
@@ -2,7 +2,7 @@
# file, all other path directives follow --prefix and must stay absolute. # file, all other path directives follow --prefix and must stay absolute.
daemon off; daemon off;
# ignored by a non-root master; keeps workers root under FRIGATE_RUN_AS_ROOT # keeps workers root under FRIGATE_RUN_AS_ROOT; the run script strips it otherwise
user root; user root;
worker_processes auto; worker_processes auto;
+2 -1
View File
@@ -71,13 +71,14 @@ Both the script and the boot sweep report progress, so you can tell a slow sweep
``` ```
[INFO] fix-ownership: scanning /media/frigate for ownership mismatches; this may take a while on large filesystems [INFO] fix-ownership: scanning /media/frigate for ownership mismatches; this may take a while on large filesystems
[INFO] fix-ownership: still scanning /media/frigate, 2481000 entries checked, 2390112 need changes
[WARN] fix-ownership: adjusting ownership of 4823941 entries under /media/frigate [WARN] fix-ownership: adjusting ownership of 4823941 entries under /media/frigate
[INFO] fix-ownership: /media/frigate 5% (241197/4823941 entries) [INFO] fix-ownership: /media/frigate 5% (241197/4823941 entries)
[INFO] fix-ownership: /media/frigate 10% (482394/4823941 entries) [INFO] fix-ownership: /media/frigate 10% (482394/4823941 entries)
[INFO] fix-ownership: finished /media/frigate in 12m 4s [INFO] fix-ownership: finished /media/frigate in 12m 4s
``` ```
The scan has no percentage because the total isn't known until it finishes. Watch the boot sweep with `docker logs -f frigate`. The scan has no percentage because the total isn't known until it finishes, so it reports a running count every 30 seconds instead. Watch the boot sweep with `docker logs -f frigate`.
Once the volumes are aligned, start Frigate normally. A file at `/config/.permissions_version` records what was done, so later boots skip the sweep unless you change `PUID`/`PGID`. Once the volumes are aligned, start Frigate normally. A file at `/config/.permissions_version` records what was done, so later boots skip the sweep unless you change `PUID`/`PGID`.
+11 -10
View File
@@ -413,17 +413,18 @@ Avoid removing audio from only one of the two streams (for example with `-an`).
As a general rule, features that read recordings prefer the main stream and fall back to the sub stream for time ranges where the main recordings have expired. Analytics features use only the main stream. As a general rule, features that read recordings prefer the main stream and fall back to the sub stream for time ranges where the main recordings have expired. Analytics features use only the main stream.
| Feature | Stream used | | Feature | Stream used |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Recording playback (History and Review) | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected manually | | Recording playback (History and Review) | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected manually |
| Tracking details and Explore clip playback | Main, falling back to sub where the main recordings have expired | | Tracking details and Explore clip playback | Main, falling back to sub where the main recordings have expired |
| Exports | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected in the export dialog | | Exports | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected in the export dialog |
| Clip downloads | Main; sub is used when no main recordings remain in the range (streams are never mixed in one file) | | Clip downloads | Main; sub is used when no main recordings remain in the range (streams are never mixed in one file) |
| Frames grabbed from a recording in History (download snapshot, submit frame to Frigate+) | Main preferred, sub fallback | | Debug replay | Main; sub is used when no main recordings remain in the range (streams are never mixed in one file) |
| Audio extraction (e.g., transcription) | Main preferred, sub fallback | | Frames grabbed from a recording in History (download snapshot, submit frame to Frigate+) | Main preferred, sub fallback |
| Motion search | Main only | | Audio extraction (e.g., transcription) | Main preferred, sub fallback |
| Review timeline motion data | Main only | | Motion search | Main only |
| Storage usage statistics | Both streams counted, and listed separately per camera | | Review timeline motion data | Main only |
| Storage usage statistics | Both streams counted, and listed separately per camera |
This table covers only features that read recordings from disk. Tracked object snapshots and thumbnails (the images shown in Explore and sent with notifications, and the images submitted to Frigate+ from a tracked object) are captured live from the `detect` stream as the object is tracked, never from recordings, so sub stream recording does not affect them. This table covers only features that read recordings from disk. Tracked object snapshots and thumbnails (the images shown in Explore and sent with notifications, and the images submitted to Frigate+ from a tracked object) are captured live from the `detect` stream as the object is tracked, never from recordings, so sub stream recording does not affect them.
+1 -1
View File
@@ -4,7 +4,7 @@ import type { Config, PluginConfig } from "@docusaurus/types";
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs"; import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
// Bump when a new stable release ships // Bump when a new stable release ships
const STABLE_VERSION = "0.18"; const STABLE_VERSION = "0.19";
const config: Config = { const config: Config = {
title: "Frigate", title: "Frigate",
+23 -1
View File
@@ -683,6 +683,15 @@
"title": "FeaturesSection", "title": "FeaturesSection",
"type": "object" "type": "object"
}, },
"FfmpegSource": {
"enum": [
"default",
"bundled",
"custom"
],
"title": "FfmpegSource",
"type": "string"
},
"FpsBucket": { "FpsBucket": {
"enum": [ "enum": [
"le_5", "le_5",
@@ -1017,6 +1026,17 @@
"description": "CPU architecture", "description": "CPU architecture",
"x-public": true "x-public": true
}, },
"custom_go2rtc": {
"description": "Whether an executable /config/go2rtc build exists",
"title": "Custom Go2Rtc",
"type": "boolean",
"x-public": true
},
"ffmpeg": {
"$ref": "#/$defs/FfmpegSource",
"description": "Configured global ffmpeg.path: the default build, another bundled version, or a custom path",
"x-public": true
},
"image_variant": { "image_variant": {
"$ref": "#/$defs/ImageVariant", "$ref": "#/$defs/ImageVariant",
"description": "Published image the install runs", "description": "Published image the install runs",
@@ -1054,7 +1074,9 @@
"install_type", "install_type",
"arch", "arch",
"kernel", "kernel",
"run_as_root" "run_as_root",
"custom_go2rtc",
"ffmpeg"
], ],
"title": "InstallSection", "title": "InstallSection",
"type": "object" "type": "object"
+19
View File
@@ -3148,6 +3148,25 @@ paths:
- frigateUserAuth: [] - frigateUserAuth: []
x-required-role: any x-required-role: any
description: '**Access:** Any authenticated user.' description: '**Access:** Any authenticated user.'
/plus/validate:
post:
tags:
- App
summary: Validate Plus Key
description: |-
**Access:** Admin role required.
Validate the configured Frigate+ API key with the Frigate+ server.
operationId: validate_plus_key_plus_validate_post
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
security:
- frigateAdminAuth: []
x-required-role: admin
/recognized_license_plates: /recognized_license_plates:
get: get:
tags: tags:
+22 -1
View File
@@ -6,7 +6,15 @@ import re
from frigate.analytics.collectors.common import closed from frigate.analytics.collectors.common import closed
from frigate.analytics.context import ReportContext from frigate.analytics.context import ReportContext
from frigate.analytics.schema import Arch, ImageVariant, InstallSection, InstallType from frigate.analytics.schema import (
Arch,
FfmpegSource,
ImageVariant,
InstallSection,
InstallType,
)
from frigate.const import CONFIG_DIR, DEFAULT_FFMPEG_VERSION, INCLUDED_FFMPEG_VERSIONS
from frigate.util.config import is_valid_ffmpeg_path
from frigate.version import VERSION from frigate.version import VERSION
KERNEL_PATTERN = re.compile(r"^(\d{1,3})\.(\d{1,3})") KERNEL_PATTERN = re.compile(r"^(\d{1,3})\.(\d{1,3})")
@@ -54,6 +62,17 @@ def kernel(release: str) -> str:
return f"{match.group(1)}.{match.group(2)}" if match else "unknown" return f"{match.group(1)}.{match.group(2)}" if match else "unknown"
def ffmpeg_source(path: str) -> FfmpegSource:
"""Classify the configured ffmpeg.path, applying resolve_ffmpeg_path's alias fallbacks."""
if path.startswith("/") and is_valid_ffmpeg_path(path):
return FfmpegSource.custom
if path != DEFAULT_FFMPEG_VERSION and path in INCLUDED_FFMPEG_VERSIONS:
return FfmpegSource.bundled
return FfmpegSource.default
def collect(ctx: ReportContext) -> InstallSection: def collect(ctx: ReportContext) -> InstallSection:
"""Describe the Frigate version, image, install method, and platform.""" """Describe the Frigate version, image, install method, and platform."""
return InstallSection( return InstallSection(
@@ -63,4 +82,6 @@ def collect(ctx: ReportContext) -> InstallSection:
arch=arch(platform.machine()), arch=arch(platform.machine()),
kernel=kernel(platform.release()), kernel=kernel(platform.release()),
run_as_root=os.geteuid() == 0, run_as_root=os.geteuid() == 0,
custom_go2rtc=os.access(os.path.join(CONFIG_DIR, "go2rtc"), os.X_OK),
ffmpeg=ffmpeg_source(ctx.config.ffmpeg.path),
) )
+10
View File
@@ -47,6 +47,12 @@ class Arch(StrEnum):
other = "other" other = "other"
class FfmpegSource(StrEnum):
default = "default"
bundled = "bundled"
custom = "custom"
class GpuVendor(StrEnum): class GpuVendor(StrEnum):
intel = "intel" intel = "intel"
amd = "amd" amd = "amd"
@@ -201,6 +207,10 @@ class InstallSection(AnalyticsModel):
"Host kernel as major.minor", pattern=r"^(\d{1,3}\.\d{1,3}|unknown)$" "Host kernel as major.minor", pattern=r"^(\d{1,3}\.\d{1,3}|unknown)$"
) )
run_as_root: bool = metric("Whether the main process runs as root") run_as_root: bool = metric("Whether the main process runs as root")
custom_go2rtc: bool = metric("Whether an executable /config/go2rtc build exists")
ffmpeg: FfmpegSource = metric(
"Configured global ffmpeg.path: the default build, another bundled version, or a custom path"
)
class GpuInfo(AnalyticsModel): class GpuInfo(AnalyticsModel):
+11 -1
View File
@@ -54,7 +54,7 @@ from frigate.jobs.media_sync import (
start_media_sync_job, start_media_sync_job,
) )
from frigate.models import Event, Timeline from frigate.models import Event, Timeline
from frigate.plus import load_plus_model_info from frigate.plus import PlusKeyStatus, load_plus_model_info
from frigate.stats.prometheus import get_metrics, update_metrics from frigate.stats.prometheus import get_metrics, update_metrics
from frigate.types import JobStatusTypesEnum from frigate.types import JobStatusTypesEnum
from frigate.util.builtin import ( from frigate.util.builtin import (
@@ -1675,6 +1675,16 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
return JSONResponse(content=validModels) return JSONResponse(content=validModels)
@router.post("/plus/validate", dependencies=[Depends(require_role(["admin"]))])
async def validate_plus_key(request: Request):
"""Validate the configured Frigate+ API key with the Frigate+ server."""
status = await request.app.frigate_config.plus_api.validate_key()
return JSONResponse(
content={"success": status == PlusKeyStatus.valid, "status": status.value}
)
@router.get( @router.get(
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())] "/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
) )
+14 -22
View File
@@ -199,18 +199,14 @@ def events(
sub_label_clauses.append(Event.sub_label.is_null()) sub_label_clauses.append(Event.sub_label.is_null())
for label in filtered_sub_labels: for label in filtered_sub_labels:
lowered = label.lower() # SQLite LOWER() only folds ASCII, so lower both sides in SQL to
sub_label_clauses.append( # keep non-ASCII names matching exactly
fn.LOWER(Event.sub_label.cast("text")) == lowered stored = fn.LOWER(Event.sub_label.cast("text"))
) # include exact matches (case-insensitive) sub_label_clauses.append(stored == fn.LOWER(label))
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII) # include this label when part of a list
sub_label_clauses.append( sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*" sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
)
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
)
sub_label_clause = reduce(operator.or_, sub_label_clauses) sub_label_clause = reduce(operator.or_, sub_label_clauses)
clauses.append(sub_label_clause) clauses.append(sub_label_clause)
@@ -633,18 +629,14 @@ def events_search(
sub_label_clauses.append(Event.sub_label.is_null()) sub_label_clauses.append(Event.sub_label.is_null())
for label in filtered_sub_labels: for label in filtered_sub_labels:
lowered = label.lower() # SQLite LOWER() only folds ASCII, so lower both sides in SQL to
sub_label_clauses.append( # keep non-ASCII names matching exactly
fn.LOWER(Event.sub_label.cast("text")) == lowered stored = fn.LOWER(Event.sub_label.cast("text"))
) # include exact matches (case-insensitive) sub_label_clauses.append(stored == fn.LOWER(label))
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII) # include this label when part of a list
sub_label_clauses.append( sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*" sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
)
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
)
event_filters.append(reduce(operator.or_, sub_label_clauses)) event_filters.append(reduce(operator.or_, sub_label_clauses))
+10
View File
@@ -84,3 +84,13 @@ class MqttConfig(FrigateBaseModel):
if (self.user is None) != (self.password is None): if (self.user is None) != (self.password is None):
raise ValueError("Password must be provided with username.") raise ValueError("Password must be provided with username.")
return self return self
@model_validator(mode="after")
def tls_insecure_requires_tls(self) -> Self:
"""Reject TLS verification settings without TLS when MQTT is enabled."""
if self.enabled and self.tls_insecure is not None and self.tls_ca_certs is None:
raise ValueError(
"tls_insecure requires tls_ca_certs. Remove tls_insecure for plaintext "
"MQTT, or set tls_ca_certs to a CA certificate path to enable TLS."
)
return self
@@ -68,6 +68,9 @@ class ReviewDescriptionProcessor(PostProcessorApi):
self.review_desc_dps = EventsPerSecond() self.review_desc_dps = EventsPerSecond()
self.review_desc_dps.start() self.review_desc_dps.start()
# one review's ffmpeg extraction at a time, as on the maintainer loop
self._frame_extraction_lock = threading.Lock()
def calculate_frame_count( def calculate_frame_count(
self, self,
camera: str, camera: str,
@@ -179,53 +182,72 @@ class ReviewDescriptionProcessor(PostProcessorApi):
): ):
return return
image_source = camera_config.review.genai.image_source threading.Thread(
frame_mode = camera_config.review.genai.frame_mode target=self._describe_finished_review,
name=f"review_description_{id}",
daemon=True,
args=(
camera_config,
final_data,
camera_config.review.genai.image_source,
),
).start()
if image_source == ImageSourceEnum.recordings: def _describe_finished_review(
buffer_extension = get_recording_buffer_extension( self,
final_data["end_time"] - final_data["start_time"] camera_config: CameraConfig,
) final_data: dict[str, Any],
final_data["start_time"] -= buffer_extension image_source: ImageSourceEnum,
final_data["end_time"] += buffer_extension preview_fallback: bool = True,
named_objects: list[tuple[str, str]] | None = None,
) -> None:
"""Gather frames for a finished review item and start its description.
Runs off the maintainer loop since recording frames shell out to ffmpeg
once per frame.
"""
camera = final_data["camera"]
id = final_data["id"]
genai_config = camera_config.review.genai
frames: list[tuple[bytes, float]] = []
if image_source == ImageSourceEnum.recordings:
buffer_extension = get_recording_buffer_extension(
final_data["end_time"] - final_data["start_time"]
)
with self._frame_extraction_lock:
frames = self.get_recording_frames( frames = self.get_recording_frames(
camera, camera,
final_data["start_time"], final_data["start_time"] - buffer_extension,
final_data["end_time"], final_data["end_time"] + buffer_extension,
height=480, # Use 480p for good balance between quality and token usage height=480, # Use 480p for good balance between quality and token usage
frame_mode=frame_mode, frame_mode=genai_config.frame_mode,
) )
if not frames: if frames:
# Fallback to preview frames if no recordings available if genai_config.debug_save_thumbnails:
logger.warning(
f"No recording frames found for {camera}, falling back to preview frames"
)
frames = self.get_preview_frames_as_bytes(
camera,
final_data["start_time"],
final_data["end_time"],
final_data["thumb_path"],
id,
camera_config.review.genai.debug_save_thumbnails,
frame_mode,
)
elif camera_config.review.genai.debug_save_thumbnails:
self.save_debug_recording_frames(id, frames) self.save_debug_recording_frames(id, frames)
elif not preview_fallback:
logger.error("No recording frames are available for review item %s", id)
return
else: else:
# Use preview frames logger.warning(
frames = self.get_preview_frames_as_bytes( "No recording frames found for %s, falling back to preview frames",
camera, camera,
final_data["start_time"],
final_data["end_time"],
final_data["thumb_path"],
id,
camera_config.review.genai.debug_save_thumbnails,
frame_mode,
) )
self.start_analysis(camera_config, final_data, frames) if not frames:
frames = self.get_preview_frames_as_bytes(
camera,
final_data["start_time"],
final_data["end_time"],
final_data["thumb_path"],
id,
genai_config.debug_save_thumbnails,
genai_config.frame_mode,
)
self.start_analysis(camera_config, final_data, frames, named_objects)
def handle_request(self, topic: str, request_data: dict[str, Any]) -> str | None: def handle_request(self, topic: str, request_data: dict[str, Any]) -> str | None:
if topic == EmbeddingsRequestEnum.regenerate_review_description.value: if topic == EmbeddingsRequestEnum.regenerate_review_description.value:
@@ -419,31 +441,12 @@ class ReviewDescriptionProcessor(PostProcessorApi):
logger.error("Review item %s has not ended yet", review_id) logger.error("Review item %s has not ended yet", review_id)
return return
buffer_extension = get_recording_buffer_extension( self._describe_finished_review(
final_data["end_time"] - final_data["start_time"]
)
frames = self.get_recording_frames(
str(review.camera),
final_data["start_time"] - buffer_extension,
final_data["end_time"] + buffer_extension,
height=480,
frame_mode=camera_config.review.genai.frame_mode,
)
if not frames:
logger.error(
"No recording frames are available for review item %s", review_id
)
return
if camera_config.review.genai.debug_save_thumbnails:
self.save_debug_recording_frames(review_id, frames)
self.start_analysis(
camera_config, camera_config,
final_data, final_data,
frames, ImageSourceEnum.recordings,
get_named_objects( preview_fallback=False,
named_objects=get_named_objects(
final_data["data"].get("detections") or [], final_data["data"].get("detections") or [],
self.config.all_attributes, self.config.all_attributes,
), ),
+29 -29
View File
@@ -16,18 +16,21 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Optional, cast from typing import TYPE_CHECKING, Any, Optional, cast
from peewee import ModelSelect
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.config.camera.updater import CameraConfigUpdatePublisher from frigate.config.camera.updater import CameraConfigUpdatePublisher
from frigate.const import REPLAY_CAMERA_PREFIX, REPLAY_DIR from frigate.const import (
REPLAY_CAMERA_PREFIX,
REPLAY_DIR,
STREAM_TYPE_MAIN,
STREAM_TYPE_SUB,
)
from frigate.jobs.export import JobStatePublisher from frigate.jobs.export import JobStatePublisher
from frigate.jobs.job import Job from frigate.jobs.job import Job
from frigate.jobs.manager import job_is_running, set_current_job from frigate.jobs.manager import job_is_running, set_current_job
from frigate.models import Export, Recordings from frigate.models import Export
from frigate.types import JobStatusTypesEnum from frigate.types import JobStatusTypesEnum
from frigate.util.ffmpeg import run_ffmpeg_with_progress from frigate.util.ffmpeg import run_ffmpeg_with_progress
from frigate.util.recording_coverage import recording_overlap from frigate.util.recording_coverage import resolve_coverage
if TYPE_CHECKING: if TYPE_CHECKING:
from frigate.debug_replay import DebugReplayManager from frigate.debug_replay import DebugReplayManager
@@ -94,24 +97,6 @@ class DebugReplayJob(Job):
} }
def query_recordings(source_camera: str, start_ts: float, end_ts: float) -> ModelSelect:
"""Return the Recordings query for the time range.
Module-level so tests can patch it without instantiating a runner.
"""
query = (
Recordings.select(
Recordings.path,
Recordings.start_time,
Recordings.end_time,
)
.where(recording_overlap(start_ts, end_ts))
.where(Recordings.camera == source_camera)
.order_by(Recordings.start_time.asc())
)
return cast(ModelSelect, query)
class NoRecordingsError(ValueError): class NoRecordingsError(ValueError):
"""Raised when no recordings exist in the requested time range.""" """Raised when no recordings exist in the requested time range."""
@@ -156,7 +141,8 @@ class RecordingDebugReplaySource(DebugReplaySource):
Feeds ffmpeg the internal VOD endpoint so segments with mismatched Feeds ffmpeg the internal VOD endpoint so segments with mismatched
SPS/PPS (e.g. across day/night transitions) stitch cleanly via HLS SPS/PPS (e.g. across day/night transitions) stitch cleanly via HLS
discontinuities. discontinuities. The playlist is pinned to one stream because the
stream-copied clip cannot change codec or resolution mid-file.
""" """
def __init__( def __init__(
@@ -170,6 +156,7 @@ class RecordingDebugReplaySource(DebugReplaySource):
self._start_ts = start_ts self._start_ts = start_ts
self._end_ts = end_ts self._end_ts = end_ts
self._internal_port = internal_port self._internal_port = internal_port
self._stream_type = STREAM_TYPE_MAIN
@property @property
def source_camera(self) -> str: def source_camera(self) -> str:
@@ -187,15 +174,28 @@ class RecordingDebugReplaySource(DebugReplaySource):
if self._end_ts <= self._start_ts: if self._end_ts <= self._start_ts:
raise ValueError("End time must be after start time") raise ValueError("End time must be after start time")
if not query_recordings(self._camera, self._start_ts, self._end_ts).count(): # the VOD resolver decides what the pinned playlist serves, so it
raise NoRecordingsError( # also decides the stream; a row merely touching the range has no
f"No recordings found for camera '{self._camera}' in the specified time range" # interval and must not win
) intervals = resolve_coverage(self._camera, self._start_ts, self._end_ts)
if any(interval.main for interval in intervals):
self._stream_type = STREAM_TYPE_MAIN
return
if any(interval.sub for interval in intervals):
self._stream_type = STREAM_TYPE_SUB
return
raise NoRecordingsError(
f"No recordings found for camera '{self._camera}' in the specified time range"
)
def ffmpeg_input_args(self, working_dir: str) -> list[str]: def ffmpeg_input_args(self, working_dir: str) -> list[str]:
playlist_url = ( playlist_url = (
f"http://127.0.0.1:{self._internal_port}/vod/{self._camera}" f"http://127.0.0.1:{self._internal_port}/vod/{self._camera}"
f"/start/{self._start_ts}/end/{self._end_ts}/index.m3u8" f"/{self._stream_type}/start/{self._start_ts}/end/{self._end_ts}"
"/index.m3u8"
) )
return [ return [
"-protocol_whitelist", "-protocol_whitelist",
+81 -1
View File
@@ -3,9 +3,11 @@ import json
import logging import logging
import os import os
import re import re
from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, TypeGuard
import aiohttp
import cv2 import cv2
import requests import requests
from numpy import ndarray from numpy import ndarray
@@ -17,6 +19,17 @@ from frigate.const import MODEL_CACHE_DIR, PLUS_API_HOST, PLUS_ENV_VAR
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
PLUS_VALIDATE_TIMEOUT = 10
class PlusKeyStatus(str, Enum):
"""Result of validating a Frigate+ API key with the server."""
valid = "valid"
invalid = "invalid"
unreachable = "unreachable"
not_set = "not_set"
def add_hailo_alias(model_info: dict[str, Any]) -> dict[str, Any]: def add_hailo_alias(model_info: dict[str, Any]) -> dict[str, Any]:
"""Name the hailo detector by its current key as well as its old one. """Name the hailo detector by its current key as well as its old one.
@@ -72,6 +85,19 @@ def get_jpg_bytes(image: ndarray, max_dim: int, quality: int) -> bytes:
return jpg_bytes if isinstance(jpg_bytes, bytes) else b"" return jpg_bytes if isinstance(jpg_bytes, bytes) else b""
def _is_usable_token(token_data: Any) -> TypeGuard[dict[str, Any]]:
"""Whether a Frigate+ token response has a usable token and expiry."""
if not isinstance(token_data, dict):
return False
expires = token_data.get("expires")
return (
isinstance(token_data.get("accessToken"), str)
and isinstance(expires, (int, float))
and not isinstance(expires, bool)
)
class PlusApi: class PlusApi:
def __init__(self) -> None: def __init__(self) -> None:
self.host = PLUS_API_HOST self.host = PLUS_API_HOST
@@ -153,6 +179,60 @@ class PlusApi:
def is_active(self) -> bool: def is_active(self) -> bool:
return self._is_active return self._is_active
async def validate_key(self) -> PlusKeyStatus:
"""Check the configured API key against the Frigate+ server.
The key format is checked at startup, but only the server can tell
whether the key is actually valid. This requests a fresh token so the
result reflects the key's current state rather than a cached token.
Returns:
The key's status as reported by the Frigate+ server
"""
if self.key is None:
return PlusKeyStatus.not_set
key_id, _, secret = self.key.partition(":")
# Use a single request with a timeout instead of the retrying session,
# since this is user initiated and should fail fast when unreachable.
# trust_env honors proxy settings, matching the other Frigate+ requests.
try:
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=PLUS_VALIDATE_TIMEOUT),
trust_env=True,
) as session:
async with session.get(
f"{self.host}/v1/auth/token",
auth=aiohttp.BasicAuth(key_id, secret),
) as r:
status = r.status
token_data = await r.json(content_type=None) if r.ok else None
except (aiohttp.ClientError, TimeoutError) as err:
logger.warning("Unable to reach Frigate+ to validate API key: %s", err)
return PlusKeyStatus.unreachable
except ValueError:
logger.warning("Invalid response validating Frigate+ API key")
return PlusKeyStatus.unreachable
if status in (401, 403):
return PlusKeyStatus.invalid
if status >= 400:
logger.warning(
"Unexpected response validating Frigate+ API key: %s", status
)
return PlusKeyStatus.unreachable
# Keep the cached token unless the reply is a usable one, so an
# unexpected response can't break later Frigate+ requests
if not _is_usable_token(token_data):
logger.warning("Invalid response validating Frigate+ API key")
return PlusKeyStatus.unreachable
self._token_data = token_data
return PlusKeyStatus.valid
def upload_image(self, image: ndarray, camera: str) -> str: def upload_image(self, image: ndarray, camera: str) -> str:
r = self._get("image/signed_urls") r = self._get("image/signed_urls")
if not r.ok: if not r.ok:
+40 -51
View File
@@ -585,26 +585,36 @@ class PtzAutoTracker(threading.Thread):
def _predict_movement_time(self, camera, pan, tilt): def _predict_movement_time(self, camera, pan, tilt):
combined_movement = abs(pan) + abs(tilt) combined_movement = abs(pan) + abs(tilt)
input_data = np.array([self.intercept[camera], combined_movement]) input_data = np.array([1, combined_movement])
return np.dot(self.move_coefficients[camera], input_data) return np.dot(self.move_coefficients[camera], input_data)
def _predict_area_after_time(self, camera, time):
return np.dot(
self.tracked_object_metrics[camera]["area_coefficients"],
[self.tracked_object_history[camera][-1]["frame_time"] + time],
)
def _predict_target_box(self, camera, predicted_time): def _predict_target_box(self, camera, predicted_time):
target_box = self.tracked_object_metrics[camera]["target_box"] tom = self.tracked_object_metrics[camera]
target_box = tom["target_box"]
if not predicted_time: if not predicted_time:
return target_box return target_box
frame_shape = self.config.cameras[camera].frame_shape camera_config = self.config.cameras[camera]
left, top, right, bottom = self.tracked_object_history[camera][-1]["box"]
size = max(right - left, bottom - top)
return target_box + self._predict_area_after_time(camera, predicted_time) / ( if size <= 0:
frame_shape[0] * frame_shape[1] return target_box
# the corner velocities diverge as the object approaches the camera
frames = camera_config.detect.fps * predicted_time
velocity = tom["velocity"]
predicted_size = max(
right - left + frames * (velocity[2] - velocity[0]),
bottom - top + frames * (velocity[3] - velocity[1]),
1,
)
# target_box is the squared size raised to the zoom factor
return target_box * (predicted_size / size) ** (
2 * camera_config.onvif.autotracking.zoom_factor
) )
def _calculate_tracked_object_metrics(self, camera, obj): def _calculate_tracked_object_metrics(self, camera, obj):
@@ -660,26 +670,6 @@ class PtzAutoTracker(threading.Thread):
filtered_areas = remove_outliers(areas) if len(areas) > 3 else areas filtered_areas = remove_outliers(areas) if len(areas) > 3 else areas
# Filter entries that are not touching the frame edge
filtered_areas_not_touching_edge = [
entry
for entry in filtered_areas
if self._touching_frame_edges(camera, entry["box"]) == 0
]
# Calculate regression for area change predictions
if len(filtered_areas_not_touching_edge):
X = np.array(
[item["frame_time"] for item in filtered_areas_not_touching_edge]
)
y = np.array([item["area"] for item in filtered_areas_not_touching_edge])
tom["area_coefficients"] = np.linalg.lstsq(X.reshape(-1, 1), y, rcond=None)[
0
]
else:
tom["area_coefficients"] = np.array([0])
weights = np.arange(1, len(filtered_areas) + 1) weights = np.arange(1, len(filtered_areas) + 1)
weighted_area = np.average( weighted_area = np.average(
[item["area"] for item in filtered_areas], weights=weights [item["area"] for item in filtered_areas], weights=weights
@@ -744,6 +734,8 @@ class PtzAutoTracker(threading.Thread):
continue continue
else: else:
pan_tilt_times = None
if ( if (
camera_config.onvif.autotracking.zooming camera_config.onvif.autotracking.zooming
== ZoomingModeEnum.relative == ZoomingModeEnum.relative
@@ -755,17 +747,28 @@ class PtzAutoTracker(threading.Thread):
await self._wait_until_stopped(camera, metrics) await self._wait_until_stopped(camera, metrics)
# _zoom_absolute resets the metrics timestamps
pan_tilt_times = (
metrics.start_time.value,
metrics.stop_time.value,
)
if zoom > 0 and metrics.zoom_level.value != zoom: if zoom > 0 and metrics.zoom_level.value != zoom:
await self.onvif._zoom_absolute(camera, zoom, 1) await self.onvif._zoom_absolute(camera, zoom, 1)
await self._wait_until_stopped(camera, metrics) await self._wait_until_stopped(camera, metrics)
start_time, stop_time = pan_tilt_times or (
metrics.start_time.value,
metrics.stop_time.value,
)
if camera_config.onvif.autotracking.movement_weights: if camera_config.onvif.autotracking.movement_weights:
logger.debug( logger.debug(
f"{camera}: Predicted movement time: {self._predict_movement_time(camera, pan, tilt)}" f"{camera}: Predicted movement time: {self._predict_movement_time(camera, pan, tilt)}"
) )
logger.debug( logger.debug(
f"{camera}: Actual movement time: {metrics.stop_time.value - metrics.start_time.value}" f"{camera}: Actual movement time: {stop_time - start_time}"
) )
# save metrics for better estimate calculations # save metrics for better estimate calculations
@@ -781,8 +784,8 @@ class PtzAutoTracker(threading.Thread):
{ {
"pan": pan, "pan": pan,
"tilt": tilt, "tilt": tilt,
"start_timestamp": metrics.start_time.value, "start_timestamp": start_time,
"end_timestamp": metrics.stop_time.value, "end_timestamp": stop_time,
} }
) )
@@ -845,7 +848,6 @@ class PtzAutoTracker(threading.Thread):
# Thresholds # Thresholds
x_mags_thresh = camera_width / camera_fps / 2 x_mags_thresh = camera_width / camera_fps / 2
y_mags_thresh = camera_height / camera_fps / 2 y_mags_thresh = camera_height / camera_fps / 2
dir_thresh = 0.93
delta_thresh = 20 delta_thresh = 20
var_thresh = 10 var_thresh = 10
@@ -863,24 +865,12 @@ class PtzAutoTracker(threading.Thread):
stdev_list = np.std(velocities, axis=0) stdev_list = np.std(velocities, axis=0)
high_variances = np.any(stdev_list > var_thresh) high_variances = np.any(stdev_list > var_thresh)
# Check direction difference # the corners of an object moving toward or away from the camera move
# in different directions, so the delta bounds how far they may differ
velocities = np.round(velocities) velocities = np.round(velocities)
invalid_dirs = False
if np.all(np.linalg.norm(velocities, axis=1)):
cosine_sim = np.dot(velocities[0], velocities[1]) / (
np.linalg.norm(velocities[0]) * np.linalg.norm(velocities[1])
)
dir_thresh = 0.6 if np.all(delta < delta_thresh / 2) else dir_thresh
invalid_dirs = cosine_sim < dir_thresh
# Combine # Combine
invalid = ( invalid = invalid_x_mags or invalid_y_mags or invalid_delta or high_variances
invalid_x_mags
or invalid_y_mags
or invalid_dirs
or invalid_delta
or high_variances
)
if invalid: if invalid:
logger.debug( logger.debug(
@@ -891,7 +881,6 @@ class PtzAutoTracker(threading.Thread):
for var_name, is_invalid in [ for var_name, is_invalid in [
("invalid_x_mags", invalid_x_mags), ("invalid_x_mags", invalid_x_mags),
("invalid_y_mags", invalid_y_mags), ("invalid_y_mags", invalid_y_mags),
("invalid_dirs", invalid_dirs),
("invalid_delta", invalid_delta), ("invalid_delta", invalid_delta),
("high_variances", high_variances), ("high_variances", high_variances),
] ]
+34
View File
@@ -485,6 +485,40 @@ class TestHttpApp(BaseTestHttp):
assert sub_labels assert sub_labels
assert sub_labels == [sub_label] assert sub_labels == [sub_label]
def test_get_event_list_filter_sub_labels(self):
sub_labels = {
"1.cyrillic": "Кузнецова Ира",
"2.joined": "Ира, Bob",
"3.ascii": "Alice",
"4.none": None,
}
mock_embeddings = Mock()
mock_embeddings.search_thumbnail.return_value = [(id, 0.1) for id in sub_labels]
self.app.frigate_config.semantic_search.enabled = True
self.app.embeddings = mock_embeddings
with AuthTestClient(self.app) as client:
for id, sub_label in sub_labels.items():
super().insert_mock_event(id)
Event.update(sub_label=sub_label).where(Event.id == id).execute()
search_params = {"search_type": "similarity", "event_id": "1.cyrillic"}
for path, params in (("/events", {}), ("/events/search", search_params)):
def ids(query: str) -> set[str]:
events = client.get(
path, params={**params, "sub_labels": query}
).json()
return {event["id"] for event in events}
assert ids("Кузнецова Ира") == {"1.cyrillic"}
assert ids("Ира") == {"2.joined"}
assert ids("bob") == {"2.joined"}
assert ids("ALICE") == {"3.ascii"}
assert ids("None") == {"4.none"}
#################################################################################################################### ####################################################################################################################
################################### GET /metrics Endpoint ######################################################### ################################### GET /metrics Endpoint #########################################################
#################################################################################################################### ####################################################################################################################
+51
View File
@@ -0,0 +1,51 @@
"""Tests for the Frigate+ API endpoints."""
import unittest
from unittest.mock import patch
from frigate.models import Event
from frigate.plus import PlusKeyStatus
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
class TestHttpPlusValidate(BaseTestHttp):
def setUp(self):
super().setUp([Event])
def _validate(self, status: PlusKeyStatus) -> dict:
app = self.create_app()
with patch.object(
app.frigate_config.plus_api, "validate_key", return_value=status
):
with AuthTestClient(app) as client:
response = client.post("/plus/validate")
self.assertEqual(response.status_code, 200)
return response.json()
def test_a_valid_key(self):
self.assertEqual(
self._validate(PlusKeyStatus.valid), {"success": True, "status": "valid"}
)
def test_an_invalid_key(self):
self.assertEqual(
self._validate(PlusKeyStatus.invalid),
{"success": False, "status": "invalid"},
)
def test_viewers_cannot_validate(self):
app = self.create_app()
with AuthTestClient(app) as client:
response = client.post(
"/plus/validate",
headers={"remote-user": "viewer", "remote-role": "viewer"},
)
self.assertEqual(response.status_code, 403)
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -12,6 +12,7 @@ from frigate.analytics.collectors.common import closed, histogram, rate
from frigate.analytics.schema import ( from frigate.analytics.schema import (
Arch, Arch,
DecodeFamily, DecodeFamily,
FfmpegSource,
GpuVendor, GpuVendor,
HardwareKey, HardwareKey,
ImageVariant, ImageVariant,
@@ -56,6 +57,36 @@ class TestInstallCollector(unittest.TestCase):
self.assertEqual(section.arch, Arch.aarch64) self.assertEqual(section.arch, Arch.aarch64)
self.assertEqual(section.kernel, "6.8") self.assertEqual(section.kernel, "6.8")
self.assertFalse(section.run_as_root) self.assertFalse(section.run_as_root)
self.assertEqual(section.ffmpeg, FfmpegSource.default)
def test_ffmpeg_source_follows_path_resolution(self):
with (
patch.object(install, "DEFAULT_FFMPEG_VERSION", "8.0"),
patch.object(install, "INCLUDED_FFMPEG_VERSIONS", ["8.0", "7.0"]),
):
self.assertEqual(install.ffmpeg_source("default"), FfmpegSource.default)
self.assertEqual(install.ffmpeg_source("8.0"), FfmpegSource.default)
self.assertEqual(install.ffmpeg_source("4.4"), FfmpegSource.default)
self.assertEqual(install.ffmpeg_source("7.0"), FfmpegSource.bundled)
self.assertEqual(
install.ffmpeg_source("/config/custom-ffmpeg"), FfmpegSource.custom
)
self.assertEqual(
install.ffmpeg_source("/config/a b; rm"), FfmpegSource.default
)
def test_custom_go2rtc_needs_an_executable_binary(self):
with tempfile.TemporaryDirectory() as config_dir:
binary = os.path.join(config_dir, "go2rtc")
with patch.object(install, "CONFIG_DIR", config_dir):
self.assertFalse(install.collect(make_context()).custom_go2rtc)
open(binary, "w").close()
self.assertFalse(install.collect(make_context()).custom_go2rtc)
os.chmod(binary, 0o755)
self.assertTrue(install.collect(make_context()).custom_go2rtc)
def test_image_variant_is_dev_when_unset_and_other_when_unknown(self): def test_image_variant_is_dev_when_unset_and_other_when_unknown(self):
self.assertEqual(install.image_variant(None), ImageVariant.dev) self.assertEqual(install.image_variant(None), ImageVariant.dev)
+2
View File
@@ -25,6 +25,8 @@ INSTALL = InstallSection(
arch="x86_64", arch="x86_64",
kernel="6.8", kernel="6.8",
run_as_root=False, run_as_root=False,
custom_go2rtc=False,
ffmpeg="default",
) )
+37
View File
@@ -1,5 +1,6 @@
"""Tests for migrating detectors and model into the models list.""" """Tests for migrating detectors and model into the models list."""
import errno
import logging import logging
import os import os
import tempfile import tempfile
@@ -240,6 +241,42 @@ class TestMigrateConfigFile(unittest.TestCase):
with open(self.config_file) as f: with open(self.config_file) as f:
return YAML().load(f) return YAML().load(f)
def test_a_failed_write_leaves_the_original_config(self):
original = "mqtt:\n enabled: false\ncameras: {}\n"
with open(self.config_file, "w") as f:
f.write(original)
with (
patch.object(YAML, "dump", side_effect=RuntimeError),
self.assertRaises(RuntimeError),
):
migrate_frigate_config(self.config_file)
with open(self.config_file) as f:
self.assertEqual(f.read(), original)
def test_a_file_bind_mount_is_written_in_place(self):
with patch(
"frigate.util.config.os.replace",
side_effect=OSError(errno.EBUSY, "Device or resource busy"),
):
migrated = self._migrate("mqtt:\n enabled: false\ncameras: {}\n")
self.assertEqual(migrated["version"], CURRENT_CONFIG_VERSION)
self.assertFalse(os.path.exists(f"{self.config_file}.tmp"))
def test_a_symlinked_config_migrates_its_target(self):
target = os.path.join(self.temp_dir.name, "frigate.yml")
with open(target, "w") as f:
f.write("mqtt:\n enabled: false\ncameras: {}\n")
os.symlink(target, self.config_file)
migrate_frigate_config(self.config_file)
self.assertTrue(os.path.islink(self.config_file))
with open(target) as f:
self.assertEqual(YAML().load(f)["version"], CURRENT_CONFIG_VERSION)
def test_migrates_a_config_already_stamped_with_the_current_version(self): def test_migrates_a_config_already_stamped_with_the_current_version(self):
# 0.19 is unreleased, so a dev config can be current and still use # 0.19 is unreleased, so a dev config can be current and still use
# the pre-models keys # the pre-models keys
+41 -25
View File
@@ -18,6 +18,7 @@ from frigate.jobs.debug_replay import (
from frigate.jobs.export import JobStatePublisher from frigate.jobs.export import JobStatePublisher
from frigate.jobs.manager import _completed_jobs, _current_jobs from frigate.jobs.manager import _completed_jobs, _current_jobs
from frigate.types import JobStatusTypesEnum from frigate.types import JobStatusTypesEnum
from frigate.util.recording_coverage import CoverageInterval
def _reset_job_manager() -> None: def _reset_job_manager() -> None:
@@ -77,6 +78,31 @@ class TestDebugReplayJob(unittest.TestCase):
self.assertEqual(results["end_ts"], 200.0) self.assertEqual(results["end_ts"], 200.0)
class TestRecordingDebugReplaySource(unittest.TestCase):
def _source_url(self, intervals: list[CoverageInterval]) -> str:
source = RecordingDebugReplaySource(
source_camera="front", start_ts=100.0, end_ts=200.0, internal_port=5000
)
with patch(
"frigate.jobs.debug_replay.resolve_coverage", return_value=intervals
):
source.validate()
return source.ffmpeg_input_args("/tmp")[-1]
def test_pins_main_when_main_has_recordings(self) -> None:
url = self._source_url(
[
CoverageInterval(100.0, 150.0, None, MagicMock()),
CoverageInterval(150.0, 200.0, MagicMock(), MagicMock()),
]
)
self.assertIn("/vod/front/main/start/100.0/end/200.0/", url)
def test_falls_back_to_sub_without_main_recordings(self) -> None:
url = self._source_url([CoverageInterval(100.0, 200.0, None, MagicMock())])
self.assertIn("/vod/front/sub/start/100.0/end/200.0/", url)
class TestStartDebugReplayJob(unittest.TestCase): class TestStartDebugReplayJob(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
_reset_job_manager() _reset_job_manager()
@@ -87,9 +113,7 @@ class TestStartDebugReplayJob(unittest.TestCase):
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true" self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
self.publisher = MagicMock() self.publisher = MagicMock()
self.recordings_qs = MagicMock() self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
self.recordings_qs.count.return_value = 1
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
def tearDown(self) -> None: def tearDown(self) -> None:
runner = get_active_runner() runner = get_active_runner()
@@ -127,9 +151,7 @@ class TestStartDebugReplayJob(unittest.TestCase):
) )
def test_rejects_when_no_recordings(self) -> None: def test_rejects_when_no_recordings(self) -> None:
empty_qs = MagicMock() with patch("frigate.jobs.debug_replay.resolve_coverage", return_value=[]):
empty_qs.count.return_value = 0
with patch("frigate.jobs.debug_replay.query_recordings", return_value=empty_qs):
with self.assertRaises(NoRecordingsError): with self.assertRaises(NoRecordingsError):
start_debug_replay_job( start_debug_replay_job(
source=RecordingDebugReplaySource( source=RecordingDebugReplaySource(
@@ -152,8 +174,8 @@ class TestStartDebugReplayJob(unittest.TestCase):
with ( with (
patch( patch(
"frigate.jobs.debug_replay.query_recordings", "frigate.jobs.debug_replay.resolve_coverage",
return_value=self.recordings_qs, return_value=self.coverage,
), ),
patch( patch(
"frigate.jobs.debug_replay.run_ffmpeg_with_progress", "frigate.jobs.debug_replay.run_ffmpeg_with_progress",
@@ -192,8 +214,8 @@ class TestStartDebugReplayJob(unittest.TestCase):
with ( with (
patch( patch(
"frigate.jobs.debug_replay.query_recordings", "frigate.jobs.debug_replay.resolve_coverage",
return_value=self.recordings_qs, return_value=self.coverage,
), ),
patch( patch(
"frigate.jobs.debug_replay.run_ffmpeg_with_progress", "frigate.jobs.debug_replay.run_ffmpeg_with_progress",
@@ -242,9 +264,7 @@ class TestRunnerHappyPath(unittest.TestCase):
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true" self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
self.publisher = MagicMock() self.publisher = MagicMock()
self.recordings_qs = MagicMock() self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
self.recordings_qs.count.return_value = 1
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
def tearDown(self) -> None: def tearDown(self) -> None:
runner = get_active_runner() runner = get_active_runner()
@@ -272,8 +292,8 @@ class TestRunnerHappyPath(unittest.TestCase):
with ( with (
patch( patch(
"frigate.jobs.debug_replay.query_recordings", "frigate.jobs.debug_replay.resolve_coverage",
return_value=self.recordings_qs, return_value=self.coverage,
), ),
patch( patch(
"frigate.jobs.debug_replay.run_ffmpeg_with_progress", "frigate.jobs.debug_replay.run_ffmpeg_with_progress",
@@ -325,9 +345,7 @@ class TestRunnerFailurePath(unittest.TestCase):
self.frigate_config.cameras = {"front": MagicMock()} self.frigate_config.cameras = {"front": MagicMock()}
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true" self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
self.publisher = MagicMock() self.publisher = MagicMock()
self.recordings_qs = MagicMock() self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
self.recordings_qs.count.return_value = 1
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
def tearDown(self) -> None: def tearDown(self) -> None:
runner = get_active_runner() runner = get_active_runner()
@@ -350,8 +368,8 @@ class TestRunnerFailurePath(unittest.TestCase):
with ( with (
patch( patch(
"frigate.jobs.debug_replay.query_recordings", "frigate.jobs.debug_replay.resolve_coverage",
return_value=self.recordings_qs, return_value=self.coverage,
), ),
patch( patch(
"frigate.jobs.debug_replay.run_ffmpeg_with_progress", "frigate.jobs.debug_replay.run_ffmpeg_with_progress",
@@ -399,9 +417,7 @@ class TestRunnerCancellation(unittest.TestCase):
self.frigate_config.cameras = {"front": MagicMock()} self.frigate_config.cameras = {"front": MagicMock()}
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true" self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
self.publisher = MagicMock() self.publisher = MagicMock()
self.recordings_qs = MagicMock() self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
self.recordings_qs.count.return_value = 1
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
def tearDown(self) -> None: def tearDown(self) -> None:
runner = get_active_runner() runner = get_active_runner()
@@ -431,8 +447,8 @@ class TestRunnerCancellation(unittest.TestCase):
with ( with (
patch( patch(
"frigate.jobs.debug_replay.query_recordings", "frigate.jobs.debug_replay.resolve_coverage",
return_value=self.recordings_qs, return_value=self.coverage,
), ),
patch( patch(
"frigate.jobs.debug_replay.run_ffmpeg_with_progress", "frigate.jobs.debug_replay.run_ffmpeg_with_progress",
+58
View File
@@ -5,6 +5,7 @@ per-camera gating those processors apply once they exist.
""" """
import sys import sys
import threading
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@@ -19,6 +20,8 @@ for mod in _MOCK_MODULES:
if mod not in sys.modules: if mod not in sys.modules:
sys.modules[mod] = MagicMock() sys.modules[mod] = MagicMock()
from frigate.config.camera.review import ImageSourceEnum # noqa: E402
# imported from the maintainer to avoid tripping the circular import between # imported from the maintainer to avoid tripping the circular import between
# the maintainer and the processor modules # the maintainer and the processor modules
from frigate.embeddings.maintainer import ( # noqa: E402 from frigate.embeddings.maintainer import ( # noqa: E402
@@ -211,3 +214,58 @@ class TestObjectDescriptionCameraGating(unittest.TestCase):
mock_create_thumbnail.assert_called_once() mock_create_thumbnail.assert_called_once()
self.assertEqual(len(processor.tracked_events["1234.5-abcdef"]), 1) self.assertEqual(len(processor.tracked_events["1234.5-abcdef"]), 1)
class TestReviewDescriptionOffLoop(unittest.TestCase):
"""Frame extraction for a finished review must not block process_data."""
def test_recording_frames_extracted_off_the_caller_thread(self):
camera = MagicMock()
camera.review.genai.enabled = True
camera.review.genai.alerts = True
camera.review.genai.image_source = ImageSourceEnum.recordings
camera.review.genai.debug_save_thumbnails = False
config = MagicMock()
config.cameras = {"front": camera}
processor = ReviewDescriptionProcessor(
config, MagicMock(), MagicMock(), MagicMock()
)
release = threading.Event()
extracted = threading.Event()
analyzed = threading.Event()
def slow_frames(*args, **kwargs):
release.wait(5)
extracted.set()
return [(b"jpg", 1.0)]
with (
patch.object(processor, "get_recording_frames", side_effect=slow_frames),
patch.object(
processor, "start_analysis", side_effect=lambda *a, **k: analyzed.set()
) as start_analysis,
):
processor.process_data(
{
"type": "end",
"after": {
"id": "1234.5-abcdef",
"camera": "front",
"severity": "alert",
"start_time": 100.0,
"end_time": 110.0,
},
},
PostProcessDataEnum.review,
)
self.assertFalse(extracted.is_set())
release.set()
self.assertTrue(extracted.wait(5))
self.assertTrue(analyzed.wait(5))
start_analysis.assert_called_once()
final_data = start_analysis.call_args.args[1]
self.assertEqual(final_data["start_time"], 100.0)
self.assertEqual(final_data["end_time"], 110.0)
+24
View File
@@ -0,0 +1,24 @@
"""Tests for MQTT TLS configuration validation."""
import unittest
from contextlib import nullcontext
from pydantic import ValidationError
from frigate.config import MqttConfig
class TestMqttTls(unittest.TestCase):
def test_tls_insecure_requires_tls_only_when_enabled(self) -> None:
"""Require TLS for either explicit verification flag only if MQTT is enabled."""
for enabled in (True, False):
for insecure in (True, False):
expected = (
self.assertRaisesRegex(
ValidationError, "tls_insecure requires tls_ca_certs"
)
if enabled
else nullcontext()
)
with self.subTest(enabled=enabled, tls_insecure=insecure), expected:
MqttConfig(enabled=enabled, tls_insecure=insecure)
+138 -1
View File
@@ -1,12 +1,17 @@
"""Tests for Frigate+ model metadata.""" """Tests for Frigate+ model metadata."""
import asyncio
import json import json
import os import os
import tempfile import tempfile
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from frigate.plus import PlusApi, load_plus_model_info import aiohttp
from aiohttp import web
from aiohttp.test_utils import TestServer
from frigate.plus import PlusApi, PlusKeyStatus, load_plus_model_info
class TestHailoAlias(unittest.TestCase): class TestHailoAlias(unittest.TestCase):
@@ -75,5 +80,137 @@ class TestLoadPlusModelInfo(unittest.TestCase):
self.assertIsNone(load_plus_model_info("bad")) self.assertIsNone(load_plus_model_info("bad"))
class TestValidateKey(unittest.IsolatedAsyncioTestCase):
"""The key format is checked locally, but only the server knows if it is valid."""
CACHED_TOKEN = {"accessToken": "cached", "expires": 2000000000}
async def _serve(self, handler) -> str:
"""Serve the token endpoint locally and return its base url."""
app = web.Application()
app.router.add_get("/v1/auth/token", handler)
server = TestServer(app)
await server.start_server()
self.addAsyncCleanup(server.close)
return str(server.make_url("")).rstrip("/")
def _api(self, host: str, key: str | None = "id:secret") -> PlusApi:
api = PlusApi.__new__(PlusApi)
api.host = host
api.key = key
api._token_data = dict(self.CACHED_TOKEN)
return api
async def _validate(self, handler) -> tuple[PlusKeyStatus, PlusApi]:
api = self._api(await self._serve(handler))
return await api.validate_key(), api
def _replies(self, status: int = 200, body: object = None, text: str | None = None):
async def handler(request: web.Request) -> web.Response:
self.auth = request.headers.get("Authorization")
if text is not None:
return web.Response(status=status, text=text)
return web.json_response(body, status=status)
return handler
async def test_an_accepted_key_is_valid(self):
token = {"accessToken": "fresh", "expires": 2000000500.5}
status, api = await self._validate(self._replies(body=token))
self.assertEqual(status, PlusKeyStatus.valid)
self.assertEqual(api._token_data, token)
self.assertEqual(self.auth, aiohttp.BasicAuth("id", "secret").encode())
async def test_a_rejected_key_is_invalid(self):
for code in (401, 403):
status, api = await self._validate(self._replies(code, {"error": "no"}))
self.assertEqual(status, PlusKeyStatus.invalid)
self.assertEqual(api._token_data, self.CACHED_TOKEN)
async def test_a_server_error_is_not_reported_as_invalid(self):
status, _ = await self._validate(self._replies(500, {"error": "down"}))
self.assertEqual(status, PlusKeyStatus.unreachable)
async def test_unusable_replies_keep_the_cached_token(self):
# a successful reply without a usable token must not replace one that
# later Frigate+ requests depend on
for body in (
None,
[],
{},
{"accessToken": "t"},
{"expires": 2000000000},
{"accessToken": 1, "expires": 2000000000},
{"accessToken": "t", "expires": "soon"},
{"accessToken": "t", "expires": True},
):
with self.subTest(body=body):
status, api = await self._validate(self._replies(body=body))
self.assertEqual(status, PlusKeyStatus.unreachable)
self.assertEqual(api._token_data, self.CACHED_TOKEN)
async def test_a_non_json_reply_keeps_the_cached_token(self):
status, api = await self._validate(self._replies(text="<html>"))
self.assertEqual(status, PlusKeyStatus.unreachable)
self.assertEqual(api._token_data, self.CACHED_TOKEN)
async def test_a_connection_failure_is_unreachable(self):
# serve and stop, so the port refuses connections
server = TestServer(web.Application())
await server.start_server()
host = str(server.make_url("")).rstrip("/")
await server.close()
api = self._api(host)
self.assertEqual(await api.validate_key(), PlusKeyStatus.unreachable)
async def test_a_slow_server_times_out_as_unreachable(self):
async def slow(request: web.Request) -> web.Response:
await asyncio.sleep(5)
return web.json_response(self.CACHED_TOKEN)
with patch("frigate.plus.PLUS_VALIDATE_TIMEOUT", 0.1):
status, _ = await self._validate(slow)
self.assertEqual(status, PlusKeyStatus.unreachable)
async def test_the_request_goes_through_a_configured_proxy(self):
# installs that reach Frigate+ through a proxy must validate the same way
token = {"accessToken": "proxied", "expires": 2000000000}
proxied = []
async def proxy(request: web.Request) -> web.Response:
proxied.append(str(request.url))
return web.json_response(token)
proxy_url = await self._serve(proxy)
api = self._api("http://plus.invalid")
with patch.dict(
os.environ, {"HTTP_PROXY": proxy_url, "NO_PROXY": ""}, clear=False
):
status = await api.validate_key()
self.assertEqual(status, PlusKeyStatus.valid)
self.assertEqual(proxied, ["http://plus.invalid/v1/auth/token"])
async def test_no_key_is_not_sent_to_the_server(self):
requests_seen = []
async def handler(request: web.Request) -> web.Response:
requests_seen.append(request)
return web.json_response(self.CACHED_TOKEN)
api = self._api(await self._serve(handler), key=None)
self.assertEqual(await api.validate_key(), PlusKeyStatus.not_set)
self.assertEqual(requests_seen, [])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main(verbosity=2) unittest.main(verbosity=2)
+117 -2
View File
@@ -11,11 +11,14 @@ KeyError on the autotracker thread or silently keep the wrong state:
running its motion estimator for a camera that could not autotrack. running its motion estimator for a camera that could not autotrack.
""" """
import asyncio
import unittest import unittest
from unittest.mock import MagicMock from unittest.mock import AsyncMock, MagicMock
import numpy as np
from frigate.camera import PTZMetrics from frigate.camera import PTZMetrics
from frigate.config import FrigateConfig from frigate.config import FrigateConfig, ZoomingModeEnum
from frigate.config.camera.updater import CameraConfigUpdateEnum from frigate.config.camera.updater import CameraConfigUpdateEnum
from frigate.ptz.autotrack import PtzAutoTracker, calculate_max_target_box from frigate.ptz.autotrack import PtzAutoTracker, calculate_max_target_box
@@ -123,6 +126,118 @@ class TestAutotrackerDisable(unittest.TestCase):
self.assertIs(payload, autotracking) self.assertIs(payload, autotracking)
class TestAutotrackerPrediction(unittest.TestCase):
def test_movement_time_uses_the_fitted_intercept_unchanged(self) -> None:
tracker = _make_tracker()
tracker.intercept = {CAMERA: 0.5}
tracker.move_coefficients = {CAMERA: [0.3, 0.8]}
self.assertAlmostEqual(tracker._predict_movement_time(CAMERA, 0.25, 0.25), 0.7)
def test_shrinking_box_keeps_a_positive_target_box(self) -> None:
# 60 px shrinking 6 px/frame reaches zero after 2 s at 5 fps
target_box = self._target_box_for((0, 0, 60, 60), [3, 3, -3, -3], 2.0)
self.assertGreater(target_box, 0)
self.assertTrue(np.isfinite(target_box))
def test_approaching_object_keeps_its_velocity(self) -> None:
# the box grows, so the corners move in opposite directions
tracker = _make_tracker()
obj = MagicMock()
obj.obj_data = {"estimate_velocity": np.array([[-3.0, -4.0], [3.0, 1.0]])}
valid, velocity = tracker._get_valid_velocity(CAMERA, obj)
self.assertTrue(valid)
self.assertEqual(list(velocity), [-3, -4, 3, 1])
def test_corners_that_disagree_too_much_are_rejected(self) -> None:
tracker = _make_tracker()
obj = MagicMock()
obj.obj_data = {"estimate_velocity": np.array([[-15.0, 0.0], [15.0, 0.0]])}
valid, velocity = tracker._get_valid_velocity(CAMERA, obj)
self.assertFalse(valid)
self.assertFalse(np.any(velocity))
def _target_box(self, velocity: list[float], predicted_time: float) -> float:
return self._target_box_for((900, 400, 1000, 600), velocity, predicted_time)
def _target_box_for(
self,
box: tuple[int, int, int, int],
velocity: list[float],
predicted_time: float,
) -> float:
tracker = _make_tracker()
tracker.tracked_object_history = {CAMERA: [{"box": box}]}
tracker.tracked_object_metrics = {
CAMERA: {"target_box": 0.2, "velocity": np.array(velocity)}
}
return tracker._predict_target_box(CAMERA, predicted_time)
def test_target_box_grows_with_an_approaching_object(self) -> None:
# the 200px height gains 4px per frame at 5 fps for 1 second
zoom_factor = 0.3
self.assertAlmostEqual(
self._target_box([-1, -2, 1, 2], 1.0),
0.2 * (220 / 200) ** (2 * zoom_factor),
)
def test_target_box_is_unchanged_without_growth_or_time(self) -> None:
self.assertAlmostEqual(self._target_box([5, 0, 5, 0], 1.0), 0.2)
self.assertEqual(self._target_box([-1, -2, 1, 2], 0), 0.2)
class TestAutotrackerMoveQueue(unittest.IsolatedAsyncioTestCase):
async def test_absolute_zoom_does_not_overwrite_the_pan_tilt_sample(self) -> None:
tracker = _make_tracker()
autotracking = tracker.config.cameras[CAMERA].onvif.autotracking
autotracking.zooming = ZoomingModeEnum.absolute
autotracking.calibrate_on_startup = True
metrics = tracker.ptz_metrics[CAMERA]
tracker.stop_event = MagicMock()
tracker.stop_event.is_set.side_effect = [False, True]
tracker.move_queues = {CAMERA: asyncio.Queue()}
tracker.move_queue_locks = {CAMERA: asyncio.Lock()}
tracker.intercept = {CAMERA: 0.5}
tracker.move_metrics = {CAMERA: []}
tracker._wait_until_stopped = AsyncMock()
tracker._calculate_move_coefficients = MagicMock()
async def move_relative(*_args) -> None:
metrics.start_time.value = 100.0
metrics.stop_time.value = 102.0
async def zoom_absolute(*_args) -> None:
metrics.start_time.value = 103.0
metrics.stop_time.value = 0
tracker.onvif._move_relative = AsyncMock(side_effect=move_relative)
tracker.onvif._zoom_absolute = AsyncMock(side_effect=zoom_absolute)
tracker.move_queues[CAMERA].put_nowait((50.0, 0.5, 0.5, 0.5))
await tracker._process_move_queue(CAMERA)
tracker.onvif._zoom_absolute.assert_awaited_once()
self.assertEqual(
tracker.move_metrics[CAMERA],
[
{
"pan": 0.5,
"tilt": 0.5,
"start_timestamp": 100.0,
"end_timestamp": 102.0,
}
],
)
class TestMaxTargetBox(unittest.TestCase): class TestMaxTargetBox(unittest.TestCase):
def test_follows_zoom_factor(self) -> None: def test_follows_zoom_factor(self) -> None:
self.assertAlmostEqual(calculate_max_target_box(0.5), 0.6**2) self.assertAlmostEqual(calculate_max_target_box(0.5), 0.6**2)
+30
View File
@@ -321,6 +321,36 @@ class TestLighterANE(unittest.TestCase):
ane.assert_not_called() ane.assert_not_called()
class TestTensorRTProvider(unittest.TestCase):
def _trt_options(self, env):
with (
patch.object(
model_util.ort,
"get_available_providers",
return_value=["TensorrtExecutionProvider", "CPUExecutionProvider"],
),
patch.object(model_util.os, "makedirs"),
patch.dict(os.environ, env, clear=False),
):
if "USE_FP16" not in env:
os.environ.pop("USE_FP16", None)
providers, options = model_util.get_ort_providers(device="Tensorrt")
return options[providers.index("TensorrtExecutionProvider")]
def test_fp16_is_disabled_by_default(self):
self.assertFalse(self._trt_options({})["trt_fp16_enable"])
def test_use_fp16_enables_fp16(self):
for value in ("true", "True", "TRUE"):
with self.subTest(value=value):
options = self._trt_options({"USE_FP16": value})
self.assertTrue(options["trt_fp16_enable"])
def test_use_fp16_false_keeps_fp16_disabled(self):
self.assertFalse(self._trt_options({"USE_FP16": "False"})["trt_fp16_enable"])
class TestMIGraphX(unittest.TestCase): class TestMIGraphX(unittest.TestCase):
"""AMD's MIGraphX plugin provider, selected through its devices in the ROCm image.""" """AMD's MIGraphX plugin provider, selected through its devices in the ROCm image."""
+7 -2
View File
@@ -9,6 +9,7 @@ import queue
import re import re
import shlex import shlex
import struct import struct
import threading
import time import time
import urllib.parse import urllib.parse
from collections import deque from collections import deque
@@ -42,6 +43,7 @@ class EventsPerSecond:
self._max_events = max_events self._max_events = max_events
self._last_n_seconds = last_n_seconds self._last_n_seconds = last_n_seconds
self._timestamps: deque[float] = deque(maxlen=max_events) self._timestamps: deque[float] = deque(maxlen=max_events)
self._lock = threading.Lock()
def start(self) -> None: def start(self) -> None:
self._start = time.monotonic() self._start = time.monotonic()
@@ -71,8 +73,11 @@ class EventsPerSecond:
# remove aged out timestamps # remove aged out timestamps
def expire_timestamps(self, now: float) -> None: def expire_timestamps(self, now: float) -> None:
threshold = now - self._last_n_seconds threshold = now - self._last_n_seconds
while self._timestamps and self._timestamps[0] < threshold:
self._timestamps.popleft() # update() and eps() can run on different threads
with self._lock:
while self._timestamps and self._timestamps[0] < threshold:
self._timestamps.popleft()
class InferenceSpeed: class InferenceSpeed:
+33 -18
View File
@@ -1,6 +1,8 @@
"""configuration utils.""" """configuration utils."""
import asyncio import asyncio
import contextlib
import errno
import ipaddress import ipaddress
import logging import logging
import os import os
@@ -212,6 +214,28 @@ def find_config_file() -> str:
return config_path return config_path
def _write_config(config_file: str, yaml: YAML, config: dict[str, Any]) -> None:
config_file = os.path.realpath(config_file)
st = os.stat(config_file)
tmp_file = f"{config_file}.tmp"
with open(tmp_file, "w") as f:
yaml.dump(config, f)
os.chmod(tmp_file, st.st_mode)
with contextlib.suppress(PermissionError):
os.chown(tmp_file, st.st_uid, st.st_gid)
try:
os.replace(tmp_file, config_file)
except OSError as e:
if e.errno != errno.EBUSY:
raise
# a file bind mount can't be renamed over, so write it in place
shutil.copyfile(tmp_file, config_file)
os.remove(tmp_file)
def migrate_frigate_config(config_file: str): def migrate_frigate_config(config_file: str):
"""handle migrating the frigate config.""" """handle migrating the frigate config."""
logger.info("Checking if frigate config needs migration...") logger.info("Checking if frigate config needs migration...")
@@ -259,8 +283,7 @@ def migrate_frigate_config(config_file: str):
if previous_version < "0.14": if previous_version < "0.14":
logger.info(f"Migrating frigate config from {previous_version} to 0.14...") logger.info(f"Migrating frigate config from {previous_version} to 0.14...")
new_config = migrate_014(new_config) new_config = migrate_014(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.14" previous_version = "0.14"
logger.info("Migrating export file names...") logger.info("Migrating export file names...")
@@ -277,56 +300,48 @@ def migrate_frigate_config(config_file: str):
if previous_version < "0.15-0": if previous_version < "0.15-0":
logger.info(f"Migrating frigate config from {previous_version} to 0.15-0...") logger.info(f"Migrating frigate config from {previous_version} to 0.15-0...")
new_config = migrate_015_0(new_config) new_config = migrate_015_0(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.15-0" previous_version = "0.15-0"
if previous_version < "0.15-1": if previous_version < "0.15-1":
logger.info(f"Migrating frigate config from {previous_version} to 0.15-1...") logger.info(f"Migrating frigate config from {previous_version} to 0.15-1...")
new_config = migrate_015_1(new_config) new_config = migrate_015_1(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.15-1" previous_version = "0.15-1"
if previous_version < "0.16-0": if previous_version < "0.16-0":
logger.info(f"Migrating frigate config from {previous_version} to 0.16-0...") logger.info(f"Migrating frigate config from {previous_version} to 0.16-0...")
new_config = migrate_016_0(new_config) new_config = migrate_016_0(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.16-0" previous_version = "0.16-0"
if previous_version < "0.17-0": if previous_version < "0.17-0":
logger.info(f"Migrating frigate config from {previous_version} to 0.17-0...") logger.info(f"Migrating frigate config from {previous_version} to 0.17-0...")
new_config = migrate_017_0(new_config) new_config = migrate_017_0(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.17-0" previous_version = "0.17-0"
if previous_version < "0.18-0": if previous_version < "0.18-0":
logger.info(f"Migrating frigate config from {previous_version} to 0.18-0...") logger.info(f"Migrating frigate config from {previous_version} to 0.18-0...")
new_config = migrate_018_0(new_config) new_config = migrate_018_0(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.18-0" previous_version = "0.18-0"
if previous_version < "0.19-0": if previous_version < "0.19-0":
logger.info(f"Migrating frigate config from {previous_version} to 0.19-0...") logger.info(f"Migrating frigate config from {previous_version} to 0.19-0...")
new_config = migrate_019_0(new_config) new_config = migrate_019_0(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
previous_version = "0.19-0" previous_version = "0.19-0"
if needs_models: if needs_models:
logger.info("Migrating frigate detectors and model to models...") logger.info("Migrating frigate detectors and model to models...")
new_config = migrate_models(new_config) new_config = migrate_models(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
if needs_detector_rename: if needs_detector_rename:
logger.info("Migrating renamed frigate detectors...") logger.info("Migrating renamed frigate detectors...")
new_config = rename_hailo_detector(new_config) new_config = rename_hailo_detector(new_config)
with open(config_file, "w") as f: _write_config(config_file, yaml, new_config)
yaml.dump(new_config, f)
logger.info("Finished frigate config migration...") logger.info("Finished frigate config migration...")
+2 -3
View File
@@ -318,7 +318,6 @@ def post_process_yolox(
def get_ort_providers( def get_ort_providers(
force_cpu: bool = False, force_cpu: bool = False,
device: str | None = "AUTO", device: str | None = "AUTO",
requires_fp16: bool = False,
) -> tuple[list[str], list[dict[str, Any]]]: ) -> tuple[list[str], list[dict[str, Any]]]:
if force_cpu: if force_cpu:
return ( return (
@@ -357,8 +356,8 @@ def get_ort_providers(
options.append( options.append(
{ {
"device_id": device_id, "device_id": device_id,
"trt_fp16_enable": requires_fp16 "trt_fp16_enable": os.environ.get("USE_FP16", "False").lower()
and os.environ.get("USE_FP16", "True") != "False", == "true",
"trt_timing_cache_enable": True, "trt_timing_cache_enable": True,
"trt_engine_cache_enable": True, "trt_engine_cache_enable": True,
"trt_timing_cache_path": os.path.join( "trt_timing_cache_path": os.path.join(
+32 -32
View File
@@ -45,15 +45,15 @@
"copy-to-clipboard": "^4.0.2", "copy-to-clipboard": "^4.0.2",
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"framer-motion": "^14.9.1", "framer-motion": "^14.10.0",
"hls.js": "^1.7.4", "hls.js": "^1.7.4",
"i18next": "^26.7.1", "i18next": "^26.7.2",
"i18next-http-backend": "^4.0.10", "i18next-http-backend": "^4.0.10",
"idb-keyval": "^6.3.0", "idb-keyval": "^6.3.0",
"js-yaml": "^5.5.0", "js-yaml": "^5.5.0",
"konva": "^10.9.1", "konva": "^10.9.1",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"lucide-react": "^1.69.0", "lucide-react": "^1.70.0",
"monaco-yaml": "^5.5.1", "monaco-yaml": "^5.5.1",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"nosleep.js": "^0.12.0", "nosleep.js": "^0.12.0",
@@ -65,7 +65,7 @@
"react-dropzone": "^20.1.4", "react-dropzone": "^20.1.4",
"react-grid-layout": "^2.3.0", "react-grid-layout": "^2.3.0",
"react-hook-form": "^7.91.0", "react-hook-form": "^7.91.0",
"react-i18next": "^17.1.12", "react-i18next": "^17.1.13",
"react-icons": "^5.7.0", "react-icons": "^5.7.0",
"react-konva": "^19.3.2", "react-konva": "^19.3.2",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -87,7 +87,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@playwright/test": "^1.64.1", "@playwright/test": "^1.65.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.27", "@types/lodash": "^4.17.27",
"@types/node": "^26.6.6", "@types/node": "^26.6.6",
@@ -1714,13 +1714,13 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.64.1", "version": "1.65.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.65.0.tgz",
"integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", "integrity": "sha512-iGURLGuKDLakSEl7Pgfv8h7f+XLT+V861Wfhz0zlEl5nyFuKUopdkFBPKqTllTUp06aOwPuH8iuUEb3yp3MpbQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.64.1" "playwright": "1.65.0"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -5596,12 +5596,12 @@
} }
}, },
"node_modules/framer-motion": { "node_modules/framer-motion": {
"version": "14.9.1", "version": "14.10.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.9.1.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.10.0.tgz",
"integrity": "sha512-WDWNT2ixHdmpvGg02BZWI/SWwRYzPiMUf4aIkTBgrxxD8RYFdZO3fCM4pnLMo6j8mUpj0+BuJRHQBChI4v3Anw==", "integrity": "sha512-e8MjIet+IJKohYxBj8TAk9Mg5cD0HiOX87qde1YTPL67gI2ao074O6yBtQnIcWCk+bTX7XX4litepufLiYPrLg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"motion-dom": "14.9.1", "motion-dom": "14.10.0",
"motion-utils": "14.7.0", "motion-utils": "14.7.0",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
@@ -6009,9 +6009,9 @@
} }
}, },
"node_modules/i18next": { "node_modules/i18next": {
"version": "26.7.1", "version": "26.7.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.7.1.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.7.2.tgz",
"integrity": "sha512-cX+3RRVczel/V2kCMTACckSGSpqgtWYEA6beXNYVms7dOQiHojdPYHD26lT4IKimNeoGWrLvtPgjftbRvmZJVw==", "integrity": "sha512-RsOY3BeaCDdkk52NeJSFAXELQ2rQXBO/tOub6L8ue2ICPfR/FRDWDOfEDg4xMPd1SqO5goVYQ6i72qWEUB/PPw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -6996,9 +6996,9 @@
} }
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "1.69.0", "version": "1.70.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.69.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.70.0.tgz",
"integrity": "sha512-UTFECm4yEgkgMN0kfLuxWcmlk+4u02Jrqehgk3ZXRx/Ud68OpP7hjkp67bvkBHmIpMaZCIveF1QDYeupO7XmYQ==", "integrity": "sha512-gCTPPN1MlQUBtUgdsUv+0CAUj+X8hifRUiRBw4E5v7lvDIOIpx0EZezdGQMSimS8pKA6esvullJub4GYr0mCHA==",
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"@types/react": "*", "@types/react": "*",
@@ -8073,9 +8073,9 @@
} }
}, },
"node_modules/motion-dom": { "node_modules/motion-dom": {
"version": "14.9.1", "version": "14.10.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.9.1.tgz", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.10.0.tgz",
"integrity": "sha512-2eCCGXH2Yv09lM8GHK3TdfChrcvHxzVY19LD9VraoHEaQl5+PuGnTNnmJaBAR+5MdxUVdsLFXygFJjR9RwRSxw==", "integrity": "sha512-k0LwJZ1R5xCvqrScszO4ywuZP52lRcT3QO0IUovgczom9ZHb0BaRLOX6M2O40q7TrSLvzX/fIHHmDm8TUMt3fg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"motion-utils": "14.7.0" "motion-utils": "14.7.0"
@@ -8537,13 +8537,13 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.64.1", "version": "1.65.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.65.0.tgz",
"integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", "integrity": "sha512-F3AMWcR44qMBt4kq+6Kb6mX7bjGXshktKFOPULd7reED6ZJWevjbNRqysP7q+AATE9kdPEZBibex1BYsvpVoeA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.64.1" "playwright-core": "1.65.0"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -8553,9 +8553,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.64.1", "version": "1.65.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.65.0.tgz",
"integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", "integrity": "sha512-vNSKgH6YqtN7cdT/L334zo0a+YwkrpicyrHJlWWPIm/Q+Nn2SWInil01oT0mbu96U4GgoRtCYQD9CzJQgju+lA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@@ -9077,9 +9077,9 @@
} }
}, },
"node_modules/react-i18next": { "node_modules/react-i18next": {
"version": "17.1.12", "version": "17.1.13",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.1.12.tgz", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.1.13.tgz",
"integrity": "sha512-Gf8riCKF/EmqQ8I1g0N5pJOLvJCkAXGAEkzOuRNHc/sBGL3dfE3WZ7+sDLrUo/kw/xdug3g48jo7M+s/SKf0HQ==", "integrity": "sha512-ZxhOSlyKSKPtbUNPLwYMmE3w3bhoc0jaZJZ6bc7wGOT6cLbrbs//6m01HxCLtFdSjHvFpwFwoPhxxzIlHSs9cw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.29.7", "@babel/runtime": "^7.29.7",
+5 -5
View File
@@ -57,15 +57,15 @@
"copy-to-clipboard": "^4.0.2", "copy-to-clipboard": "^4.0.2",
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"framer-motion": "^14.9.1", "framer-motion": "^14.10.0",
"hls.js": "^1.7.4", "hls.js": "^1.7.4",
"i18next": "^26.7.1", "i18next": "^26.7.2",
"i18next-http-backend": "^4.0.10", "i18next-http-backend": "^4.0.10",
"idb-keyval": "^6.3.0", "idb-keyval": "^6.3.0",
"js-yaml": "^5.5.0", "js-yaml": "^5.5.0",
"konva": "^10.9.1", "konva": "^10.9.1",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"lucide-react": "^1.69.0", "lucide-react": "^1.70.0",
"monaco-yaml": "^5.5.1", "monaco-yaml": "^5.5.1",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"nosleep.js": "^0.12.0", "nosleep.js": "^0.12.0",
@@ -77,7 +77,7 @@
"react-dropzone": "^20.1.4", "react-dropzone": "^20.1.4",
"react-grid-layout": "^2.3.0", "react-grid-layout": "^2.3.0",
"react-hook-form": "^7.91.0", "react-hook-form": "^7.91.0",
"react-i18next": "^17.1.12", "react-i18next": "^17.1.13",
"react-icons": "^5.7.0", "react-icons": "^5.7.0",
"react-konva": "^19.3.2", "react-konva": "^19.3.2",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -99,7 +99,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@playwright/test": "^1.64.1", "@playwright/test": "^1.65.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.27", "@types/lodash": "^4.17.27",
"@types/node": "^26.6.6", "@types/node": "^26.6.6",
@@ -1289,8 +1289,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Ключ API Frigate+", "title": "Ключ API Frigate+",
"validated": "Ключ API Frigate+ выяўлены і правераны",
"notValidated": "Ключ API Frigate+ не выяўлены або не правераны",
"desc": "Ключ API Frigate+ уключае інтэграцыю з сэрвісам Frigate+.", "desc": "Ключ API Frigate+ уключае інтэграцыю з сэрвісам Frigate+.",
"plusLink": "Падрабязней пра Frigate+" "plusLink": "Падрабязней пра Frigate+"
}, },
@@ -1086,8 +1086,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Frigate+ API ključ", "title": "Frigate+ API ključ",
"validated": "Frigate+ API ključ je detektovan i validiran",
"notValidated": "Frigate+ API ključ nije detektovan ili nije validiran",
"desc": "Frigate+ API ključ omogućava integraciju sa uslugom Frigate+.", "desc": "Frigate+ API ključ omogućava integraciju sa uslugom Frigate+.",
"plusLink": "Pročitajte više o Frigate+" "plusLink": "Pročitajte više o Frigate+"
}, },
+8 -3
View File
@@ -714,9 +714,14 @@
"apiKey": { "apiKey": {
"plusLink": "Llegeix més sobre Frigate+", "plusLink": "Llegeix més sobre Frigate+",
"title": "Clau API de Frigate+", "title": "Clau API de Frigate+",
"validated": "La clau API de Frigate+ ha estat detectada i validada", "desc": "La clau API de Frigate+ habilita la integració amb el servei Frigate+.",
"notValidated": "La clau API de Frigate+ no ha estat detectada o no ha estat validada", "formatValid": "S'ha detectat la clau API Frigate+ i coincideix amb el format esperat",
"desc": "La clau API de Frigate+ habilita la integració amb el servei Frigate+." "formatInvalid": "La clau API Frigate+ no es detecta o no coincideix amb el format esperat",
"validate": "Valida amb Frigate+",
"validating": "S'està validant…",
"serverValid": "Frigate+ ha acceptat aquesta clau API",
"serverInvalid": "Frigate+ ha rebutjat aquesta clau API",
"serverUnreachable": "No s'ha pogut arribar a Frigate+ per validar aquesta clau API"
}, },
"unsavedChanges": "Canvis dels paràmetres de Frigate+ sense desar", "unsavedChanges": "Canvis dels paràmetres de Frigate+ sense desar",
"title": "Paràmetres de Frigate+", "title": "Paràmetres de Frigate+",
@@ -58,9 +58,7 @@
"cleanCopyWarning": "Některé kamery mají snímky zakázané" "cleanCopyWarning": "Některé kamery mají snímky zakázané"
}, },
"apiKey": { "apiKey": {
"notValidated": "API klíč Frigate+ nebyl rozpoznán nebo nebyl ověřen",
"plusLink": "Přečtěte si více o Frigate+", "plusLink": "Přečtěte si více o Frigate+",
"validated": "API klíč Frigate+ byl rozpoznán a ověřen",
"desc": "API klíč Frigate+ umožňuje integraci se službou Frigate+.", "desc": "API klíč Frigate+ umožňuje integraci se službou Frigate+.",
"title": "Frigate+ API Klíč" "title": "Frigate+ API Klíč"
}, },
@@ -639,8 +639,6 @@
"apiKey": { "apiKey": {
"title": "Frigate+ API Key", "title": "Frigate+ API Key",
"desc": "Der Frigate+ API Key aktiviert die Integration des Frigate+ Dienstes.", "desc": "Der Frigate+ API Key aktiviert die Integration des Frigate+ Dienstes.",
"validated": "Frigate+ API Key erkannt und validiert",
"notValidated": "Frigate+ API Key nicht erkannt und validiert",
"plusLink": "Lese mehr zu Frigate+" "plusLink": "Lese mehr zu Frigate+"
}, },
"snapshotConfig": { "snapshotConfig": {
+7 -2
View File
@@ -1285,8 +1285,13 @@
}, },
"apiKey": { "apiKey": {
"title": "Frigate+ API Key", "title": "Frigate+ API Key",
"validated": "Frigate+ API key is detected and validated", "formatValid": "Frigate+ API key is detected and matches the expected format",
"notValidated": "Frigate+ API key is not detected or not validated", "formatInvalid": "Frigate+ API key is not detected or does not match the expected format",
"validate": "Validate with Frigate+",
"validating": "Validating…",
"serverValid": "Frigate+ accepted this API key",
"serverInvalid": "Frigate+ rejected this API key",
"serverUnreachable": "Unable to reach Frigate+ to validate this API key",
"desc": "The Frigate+ API key enables integration with the Frigate+ service.", "desc": "The Frigate+ API key enables integration with the Frigate+ service.",
"plusLink": "Read more about Frigate+" "plusLink": "Read more about Frigate+"
}, },
+1 -3
View File
@@ -621,10 +621,8 @@
"title": "Configuración de Frigate+", "title": "Configuración de Frigate+",
"apiKey": { "apiKey": {
"title": "Clave API de Frigate+", "title": "Clave API de Frigate+",
"notValidated": "La clave API de Frigate+ no ha sido detectada o no ha sido validada",
"plusLink": "Lee más sobre Frigate+", "plusLink": "Lee más sobre Frigate+",
"desc": "La clave API de Frigate+ permite la integración con el servicio Frigate+.", "desc": "La clave API de Frigate+ permite la integración con el servicio Frigate+."
"validated": "La clave API de Frigate+ ha sido detectada y validada"
}, },
"snapshotConfig": { "snapshotConfig": {
"title": "Configuración de instantáneas", "title": "Configuración de instantáneas",
+2 -1
View File
@@ -76,7 +76,8 @@
"24hour": "dd. MMM yyyy, HH:mm" "24hour": "dd. MMM yyyy, HH:mm"
}, },
"formattedTimestampMonthDay": "dd. MMM", "formattedTimestampMonthDay": "dd. MMM",
"never": "Mitte kunagi" "never": "Mitte kunagi",
"custom": "Kohanadatud…"
}, },
"menu": { "menu": {
"user": { "user": {
+1 -3
View File
@@ -1143,11 +1143,9 @@
}, },
"frigatePlus": { "frigatePlus": {
"apiKey": { "apiKey": {
"notValidated": "کلید API ‏Frigate+ شناسایی نشده یا معتبرسازی نشده است",
"desc": "کلید API ‏Frigate+ امکان یکپارچه‌سازی با سرویس Frigate+ را فراهم می‌کند.", "desc": "کلید API ‏Frigate+ امکان یکپارچه‌سازی با سرویس Frigate+ را فراهم می‌کند.",
"plusLink": "دربارهٔ Frigate+ بیشتر بخوانید", "plusLink": "دربارهٔ Frigate+ بیشتر بخوانید",
"title": "کلید API فرigate+", "title": "کلید API فرigate+"
"validated": "کلید API فرigate+ شناسایی و تأیید شد"
}, },
"snapshotConfig": { "snapshotConfig": {
"title": "پیکربندی عکس فوری", "title": "پیکربندی عکس فوری",
+2 -1
View File
@@ -82,7 +82,8 @@
"inProgress": "En cours", "inProgress": "En cours",
"invalidStartTime": "Heure de début invalide", "invalidStartTime": "Heure de début invalide",
"invalidEndTime": "Heure de fin invalide", "invalidEndTime": "Heure de fin invalide",
"never": "Jamais" "never": "Jamais",
"custom": "Personnalisé…"
}, },
"button": { "button": {
"apply": "Appliquer", "apply": "Appliquer",
+2 -2
View File
@@ -7,11 +7,11 @@
"label": "Supprimer le groupe de caméras", "label": "Supprimer le groupe de caméras",
"confirm": { "confirm": {
"title": "Confirmez la suppression", "title": "Confirmez la suppression",
"desc": "Êtes-vous sûr de vouloir supprimer le groupe de caméras <em>{{name}}</em> ?" "desc": "Êtes-vous sûr de vouloir supprimer le groupe de caméras <em>{{name}}</em>?"
} }
}, },
"name": { "name": {
"placeholder": "Saisissez un nom.", "placeholder": "Saisissez un nom…",
"label": "Nom", "label": "Nom",
"errorMessage": { "errorMessage": {
"mustLeastCharacters": "Le nom du groupe de caméras doit comporter au moins 2 caractères.", "mustLeastCharacters": "Le nom du groupe de caméras doit comporter au moins 2 caractères.",
+5 -2
View File
@@ -87,7 +87,8 @@
"placeholder": "Sélectionner un dossier", "placeholder": "Sélectionner un dossier",
"newCaseOption": "Créer un nouveau cas", "newCaseOption": "Créer un nouveau cas",
"newCaseNamePlaceholder": "Nouveau nom de cas", "newCaseNamePlaceholder": "Nouveau nom de cas",
"newCaseDescriptionPlaceholder": "Description de cas" "newCaseDescriptionPlaceholder": "Description de cas",
"nameRequired": "Nom du dossier requis"
}, },
"queueing": "Mise en file d'attente de l'export...", "queueing": "Mise en file d'attente de l'export...",
"tabs": { "tabs": {
@@ -116,7 +117,9 @@
"selectWithActivity": "Caméras avec des objets suivis", "selectWithActivity": "Caméras avec des objets suivis",
"selectGroup": "Sélectionner un groupe", "selectGroup": "Sélectionner un groupe",
"noMatchingCameras": "Aucune caméra ne correspond à votre recherche", "noMatchingCameras": "Aucune caméra ne correspond à votre recherche",
"selectedCount": "{{selected}} / {{total}} sélectionnées" "selectedCount": "{{selected}} / {{total}} sélectionnées",
"noCamerasSelected": "Sélectionner au moins une caméra",
"caseNameDefault": "Export Caméra multiple - {{date}}"
}, },
"multi": { "multi": {
"title_one": "Export {{count}} revue", "title_one": "Export {{count}} revue",
+2 -2
View File
@@ -21,7 +21,7 @@
"title": "Toutes les dates", "title": "Toutes les dates",
"short": "Dates" "short": "Dates"
}, },
"selectPreset": "Sélectionnez un préréglage." "selectPreset": "Sélectionnez un préréglage…"
}, },
"more": "Plus de filtres", "more": "Plus de filtres",
"reset": { "reset": {
@@ -105,7 +105,7 @@
"allLogs": "Tous les journaux" "allLogs": "Tous les journaux"
}, },
"recognizedLicensePlates": { "recognizedLicensePlates": {
"placeholder": "Tapez pour rechercher des plaques d'immatriculation.", "placeholder": "Tapez pour rechercher des plaques d'immatriculation…",
"noLicensePlatesFound": "Aucune plaque d'immatriculation trouvée", "noLicensePlatesFound": "Aucune plaque d'immatriculation trouvée",
"loading": "Chargement des plaques d'immatriculation reconnues…", "loading": "Chargement des plaques d'immatriculation reconnues…",
"title": "Plaques d'immatriculation reconnues", "title": "Plaques d'immatriculation reconnues",
+66 -7
View File
@@ -48,7 +48,7 @@
}, },
"labelmap": { "labelmap": {
"label": "Customisation Audio", "label": "Customisation Audio",
"description": "Remplacements ou entrées de remappage à fusionner avec la table standard des libellés audio." "description": "Remplacements ou réassignations à fusionner aux étiquettes audio standard."
} }
}, },
"audio_transcription": { "audio_transcription": {
@@ -78,8 +78,8 @@
"description": "Emplacement numérique contrôlant l'ordre de la caméra dans la disposition en vue à vol d'oiseau." "description": "Emplacement numérique contrôlant l'ordre de la caméra dans la disposition en vue à vol d'oiseau."
}, },
"modes": { "modes": {
"label": "Types d'activité", "label": "Types d'événements",
"description": "Types d’activité qui incluent des caméras dans Birdseye." "description": "Types d'activité qui incluent les caméras dans Birdseye."
} }
}, },
"detect": { "detect": {
@@ -143,7 +143,7 @@
}, },
"scene": { "scene": {
"label": "Détecter la scène", "label": "Détecter la scène",
"description": "L'environnement filmé par cette caméra, utilisé pour déterminer lequel des modèles configurés doit être exécuté sur celle-ci. Les caméras laissées sur « default » utilisent le modèle configuré avec une scène définie sur « default »." "description": "L'environnement filmé par cette caméra, utilisé pour choisir le modèle configuré à exécuter. Les caméras laissées sur « default » exécutent le modèle configuré avec la scène « default »."
} }
}, },
"face_recognition": { "face_recognition": {
@@ -240,6 +240,30 @@
"quality": { "quality": {
"label": "Qualité de la diffusion en direct", "label": "Qualité de la diffusion en direct",
"description": "Qualité de l'encodage pour le flux jsmpeg (1 étant la plus élevée, 31 la plus faible)." "description": "Qualité de l'encodage pour le flux jsmpeg (1 étant la plus élevée, 31 la plus faible)."
},
"transcode": {
"label": "Flux transcodés",
"description": "Flux en direct transcodé en qualité inférieur à la demande de go2rtc.",
"enabled": {
"label": "Activé les flux transcodés",
"description": "ajouter un flux en direct de qualité inférieur que go2rtc transcode en temps réel pendant que quelqu'un regarde."
},
"source": {
"label": "Flux source",
"description": "flux à décoder par go2rtc. Par défaut le premier flux en direct."
},
"qualities": {
"label": "Qualités",
"description": "Un flux transcodé est ajouté par qualité.",
"height": {
"label": "Taille",
"description": "Taille de sortie en pixels; le forma respect le ratio de la source."
},
"bitrate": {
"label": "Bitrate",
"description": "Cible et bitrate maximum de la vidéo en kbps."
}
}
} }
}, },
"lpr": { "lpr": {
@@ -517,6 +541,41 @@
"enabled_in_config": { "enabled_in_config": {
"label": "État original de l'enregistrement", "label": "État original de l'enregistrement",
"description": "Indique si l'enregistrement était activé dans la configuration statique originale." "description": "Indique si l'enregistrement était activé dans la configuration statique originale."
},
"sub": {
"label": "Enregistrement flux secondaire",
"description": "Paramètres pour enregistrer un second flux, de qualité moindre.",
"enabled": {
"label": "Activer l'enregistrement des flux secondaires",
"description": "Activer l'enregistrement d'un flux secondaire, de qualité moindre pour une qualité adaptative et une rétention étendue."
},
"continuous": {
"label": "Rétention continue des flux secondaires",
"description": "Nombre de jour de rétention des enregistrements des flux secondaire sans prise en compte d'objet traqué ou de mouvement.",
"days": {
"label": "Jour de rétention",
"description": "Combien de jour conserver les enregistrements."
}
},
"motion": {
"label": "Rétention des flux secondaires liés au mouvement",
"description": "Nombre de jours de conservation des enregistrements des flux secondaires déclenchés par un mouvement.",
"days": {
"label": "Jours de rétention",
"description": "Combien de jours conserver les enregistrements."
}
},
"alerts": {
"label": "Alerte rétention flux secondaires",
"description": "Paramètres de conservation pour les flux secondaires liés aux alertes.",
"days": {
"label": "Jours de rétention",
"description": "Nombre de jour de conservation des enregistrements lié à la détection d'événements."
},
"mode": {
"label": "Mode de rétention"
}
}
} }
}, },
"review": { "review": {
@@ -534,7 +593,7 @@
}, },
"required_zones": { "required_zones": {
"label": "Zones requises", "label": "Zones requises",
"description": "Zones qu'un objet doit entrer pour être considéré comme une alerte. (Laisser vide afin d'autoriser n'importe quelle zone.)" "description": "Zones dans lesquelles un objet doit entrer pour être considéré comme une alarme; laisser vide pour définir l'entièreté de la zone"
}, },
"enabled_in_config": { "enabled_in_config": {
"label": "État des alertes originales", "label": "État des alertes originales",
@@ -544,13 +603,13 @@
"label": "Temps de coupure des alertes", "label": "Temps de coupure des alertes",
"description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper." "description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper."
}, },
"description": "Paramètres pour lesquels les objets suivis génèrent des alertes et comment ces alertes sont conservées." "description": "Paramètres définissant quels objets suivis génèrent des alertes et comment ces alertes sont conservées."
}, },
"detections": { "detections": {
"label": "Configuration des détections", "label": "Configuration des détections",
"description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.", "description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.",
"enabled": { "enabled": {
"label": "Activer les détections", "label": "Activation des détections",
"description": "Activer ou désactiver les événements de détection pour cette caméra." "description": "Activer ou désactiver les événements de détection pour cette caméra."
}, },
"labels": { "labels": {
+68 -7
View File
@@ -88,8 +88,8 @@
"label": "Qualité d'encodage" "label": "Qualité d'encodage"
}, },
"modes": { "modes": {
"label": "Types d'activité", "label": "Types d'événements",
"description": "Types d’activité qui incluent des caméras dans Birdseye." "description": "Types d'activité qui incluent les caméras dans Birdseye."
} }
}, },
"detect": { "detect": {
@@ -98,7 +98,7 @@
}, },
"scene": { "scene": {
"label": "Détecter la scène", "label": "Détecter la scène",
"description": "L'environnement filmé par cette caméra, utilisé pour déterminer lequel des modèles configurés doit être exécuté sur celle-ci. Les caméras laissées sur « default » utilisent le modèle configuré avec une scène définie sur « default »." "description": "L'environnement filmé par cette caméra, utilisé pour choisir le modèle configuré à exécuter. Les caméras laissées sur « default » exécutent le modèle configuré avec la scène « default »."
} }
}, },
"motion": { "motion": {
@@ -222,7 +222,7 @@
}, },
"labelmap": { "labelmap": {
"label": "Customisation Audio", "label": "Customisation Audio",
"description": "Remplacements ou entrées de remappage à fusionner avec la table standard des libellés audio." "description": "Remplacements ou réassignations à fusionner aux étiquettes audio standard."
} }
}, },
"ffmpeg": { "ffmpeg": {
@@ -341,6 +341,41 @@
"enabled_in_config": { "enabled_in_config": {
"label": "État original de l'enregistrement", "label": "État original de l'enregistrement",
"description": "Indique si l'enregistrement était activé dans la configuration statique originale." "description": "Indique si l'enregistrement était activé dans la configuration statique originale."
},
"sub": {
"label": "Enregistrement flux secondaire",
"description": "Paramètres pour enregistrer un second flux, de qualité moindre.",
"enabled": {
"label": "Activer l'enregistrement des flux secondaires",
"description": "Activer l'enregistrement d'un flux secondaire, de qualité moindre pour une qualité adaptative et une rétention étendue."
},
"continuous": {
"label": "Rétention continue des flux secondaires",
"description": "Nombre de jour de rétention des enregistrements des flux secondaire sans prise en compte d'objet traqué ou de mouvement.",
"days": {
"label": "Jour de rétention",
"description": "Combien de jour conserver les enregistrements."
}
},
"motion": {
"label": "Rétention des flux secondaires liés au mouvement",
"description": "Nombre de jours de conservation des enregistrements des flux secondaires déclenchés par un mouvement.",
"days": {
"label": "Jours de rétention",
"description": "Combien de jours conserver les enregistrements."
}
},
"alerts": {
"label": "Alerte rétention flux secondaires",
"description": "Paramètres de conservation pour les flux secondaires liés aux alertes.",
"days": {
"label": "Jours de rétention",
"description": "Nombre de jour de conservation des enregistrements lié à la détection d'événements."
},
"mode": {
"label": "Mode de rétention"
}
}
} }
}, },
"review": { "review": {
@@ -356,7 +391,7 @@
}, },
"required_zones": { "required_zones": {
"label": "Zones requises", "label": "Zones requises",
"description": "Zones qu'un objet doit entrer pour être considéré comme une alerte. (Laisser vide afin d'autoriser n'importe quelle zone.)" "description": "Zones dans lesquelles un objet doit entrer pour être considéré comme une alarme; laisser vide pour définir l'entièreté de la zone"
}, },
"enabled_in_config": { "enabled_in_config": {
"label": "État des alertes originales", "label": "État des alertes originales",
@@ -366,13 +401,13 @@
"label": "Temps de coupure des alertes", "label": "Temps de coupure des alertes",
"description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper." "description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper."
}, },
"description": "Paramètres pour lesquels les objets suivis génèrent des alertes et comment ces alertes sont conservées." "description": "Paramètres définissant quels objets suivis génèrent des alertes et comment ces alertes sont conservées."
}, },
"detections": { "detections": {
"label": "Configuration des détections", "label": "Configuration des détections",
"description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.", "description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.",
"enabled": { "enabled": {
"label": "Activer les détections" "label": "Activation des détections"
}, },
"labels": { "labels": {
"label": "Étiquettes de détection", "label": "Étiquettes de détection",
@@ -415,5 +450,31 @@
"description": "Une liste de préoccupations ou de notes supplémentaires que la GenAI devrait prendre en compte lors de l'évaluation de l'activité sur cette caméra." "description": "Une liste de préoccupations ou de notes supplémentaires que la GenAI devrait prendre en compte lors de l'évaluation de l'activité sur cette caméra."
} }
} }
},
"live": {
"transcode": {
"label": "Flux transcodés",
"description": "Flux en direct transcodé en qualité inférieur à la demande de go2rtc.",
"enabled": {
"label": "Activé les flux transcodés",
"description": "ajouter un flux en direct de qualité inférieur que go2rtc transcode en temps réel pendant que quelqu'un regarde."
},
"source": {
"label": "Flux source",
"description": "flux à décoder par go2rtc. Par défaut le premier flux en direct."
},
"qualities": {
"label": "Qualités",
"description": "Un flux transcodé est ajouté par qualité.",
"height": {
"label": "Taille",
"description": "Taille de sortie en pixels; le forma respect le ratio de la source."
},
"bitrate": {
"label": "Bitrate",
"description": "Cible et bitrate maximum de la vidéo en kbps."
}
}
}
} }
} }
@@ -209,9 +209,7 @@
}, },
"frigatePlus": { "frigatePlus": {
"apiKey": { "apiKey": {
"notValidated": "La clé API Frigate+ n'est pas détectée ou n'est pas validée.",
"title": "Clé API Frigate+", "title": "Clé API Frigate+",
"validated": "La clé API Frigate+ est détectée et validée",
"desc": "La clé API Frigate+ permet l'intégration avec le service Frigate+.", "desc": "La clé API Frigate+ permet l'intégration avec le service Frigate+.",
"plusLink": "En savoir plus sur Frigate+" "plusLink": "En savoir plus sur Frigate+"
}, },
@@ -573,8 +573,6 @@
"title": "הגדרות +Frigate", "title": "הגדרות +Frigate",
"apiKey": { "apiKey": {
"title": "Frigate+ API מפתח", "title": "Frigate+ API מפתח",
"validated": "מפתח ה-API של Frigate+ זוהה ואושר",
"notValidated": "מפתח ה-API של Frigate+ לא זוהה או לא אומת",
"desc": "מפתח ה-API של Frigate+ מאפשר אינטגרציה עם שירות Frigate+.", "desc": "מפתח ה-API של Frigate+ מאפשר אינטגרציה עם שירות Frigate+.",
"plusLink": "קרא עוד על Frigate+" "plusLink": "קרא עוד על Frigate+"
}, },
+3 -1
View File
@@ -141,5 +141,7 @@
"aircraft": "विमान", "aircraft": "विमान",
"bicycle": "साइकिल", "bicycle": "साइकिल",
"bellow": "गर्जना करना", "bellow": "गर्जना करना",
"motorcycle": "मोटरसाइकिल" "motorcycle": "मोटरसाइकिल",
"snicker": "खिलखिलाना",
"choir": "गायक-वृन्द"
} }
@@ -897,8 +897,6 @@
"title": "Frigate+ postavke", "title": "Frigate+ postavke",
"apiKey": { "apiKey": {
"title": "Frigate+ API ključ", "title": "Frigate+ API ključ",
"validated": "Frigate+ API ključ je detektiran i validiran",
"notValidated": "Frigate+ API ključ nije detektiran ili nije validiran",
"desc": "Frigate+ API ključ omogućuje integraciju s Frigate+ uslugom.", "desc": "Frigate+ API ključ omogućuje integraciju s Frigate+ uslugom.",
"plusLink": "Pročitajte više o Frigate+" "plusLink": "Pročitajte više o Frigate+"
}, },
@@ -246,8 +246,6 @@
"apiKey": { "apiKey": {
"plusLink": "Tudjon meg többet a Frigate+-ról", "plusLink": "Tudjon meg többet a Frigate+-ról",
"title": "Frigate+ API kulcs", "title": "Frigate+ API kulcs",
"validated": "Frigate+ API kulcs észlelve és ellenőrizve",
"notValidated": "Frigate+ API kulcs nem található vagy nem ellenőrzött",
"desc": "A Frigate+ API kulcs lehetővé teszi az integrációt a Frigate+ szolgáltatással." "desc": "A Frigate+ API kulcs lehetővé teszi az integrációt a Frigate+ szolgáltatással."
}, },
"snapshotConfig": { "snapshotConfig": {
@@ -1310,8 +1310,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Kunci API Frigate+", "title": "Kunci API Frigate+",
"validated": "Kunci API Frigate+ terdeteksi dan tervalidasi",
"notValidated": "Kunci API Frigate+ tidak terdeteksi atau tidak tervalidasi",
"desc": "Kunci API Frigate+ memungkinkan integrasi dengan layanan Frigate+.", "desc": "Kunci API Frigate+ memungkinkan integrasi dengan layanan Frigate+.",
"plusLink": "Baca lebih lanjut tentang Frigate+" "plusLink": "Baca lebih lanjut tentang Frigate+"
}, },
@@ -29,8 +29,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Chiave API Frigate+", "title": "Chiave API Frigate+",
"validated": "La chiave API Frigate+ è stata rilevata e convalidata",
"notValidated": "La chiave API Frigate+ non è stata rilevata o non è stata convalidata",
"desc": "La chiave API Frigate+ consente l'integrazione con il servizio Frigate+.", "desc": "La chiave API Frigate+ consente l'integrazione con il servizio Frigate+.",
"plusLink": "Scopri di più su Frigate+" "plusLink": "Scopri di più su Frigate+"
}, },
@@ -734,8 +734,6 @@
"title": "Frigate+ 設定", "title": "Frigate+ 設定",
"apiKey": { "apiKey": {
"title": "Frigate+ API キー", "title": "Frigate+ API キー",
"validated": "Frigate+ API キーが検出され、検証されました",
"notValidated": "Frigate+ API キーが検出されないか、検証されていません",
"desc": "Frigate+ API キーは Frigate+ サービスとの統合を有効にします。", "desc": "Frigate+ API キーは Frigate+ サービスとの統合を有効にします。",
"plusLink": "Frigate+ の詳細を読む" "plusLink": "Frigate+ の詳細を読む"
}, },
@@ -1193,8 +1193,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Frigate+ API Key", "title": "Frigate+ API Key",
"validated": "Frigate+ API key ត្រូវបានរកឃើញ និងផ្ទៀងផ្ទាត់",
"notValidated": "Frigate+ API key មិនត្រូវបានរកឃើញ ឬមិនបានផ្ទៀងផ្ទាត់",
"desc": "Frigate+ API key អនុញ្ញាតឱ្យមានការរួមបញ្ចូលជាមួយសេវាកម្ម Frigate+។", "desc": "Frigate+ API key អនុញ្ញាតឱ្យមានការរួមបញ្ចូលជាមួយសេវាកម្ម Frigate+។",
"plusLink": "អានបន្ថែមអំពី Frigate+" "plusLink": "អានបន្ថែមអំពី Frigate+"
}, },
@@ -1373,8 +1373,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Frigate+ API 키", "title": "Frigate+ API 키",
"validated": "Frigate+ API 키가 확인 및 검증되었습니다",
"notValidated": "Frigate+ API 키가 감지되지 않았거나 유효하지 않습니다",
"desc": "Frigate+ API 키를 통해 Frigate+ 서비스와의 연동을 활성화합니다.", "desc": "Frigate+ API 키를 통해 Frigate+ 서비스와의 연동을 활성화합니다.",
"plusLink": "Frigate+ 자세히 알아보기" "plusLink": "Frigate+ 자세히 알아보기"
}, },
@@ -657,8 +657,6 @@
"title": "Frigate+ Nustatymai", "title": "Frigate+ Nustatymai",
"apiKey": { "apiKey": {
"title": "Frigate+ API raktas", "title": "Frigate+ API raktas",
"validated": "Frigate+ API raktas aptiktas ir patvirtintas",
"notValidated": "Frigate+ API raktas neaptiktas ir nepatvirtintas",
"desc": "Frigate+ API raktas įgalina integraciją su Frigate+ paslauga.", "desc": "Frigate+ API raktas įgalina integraciją su Frigate+ paslauga.",
"plusLink": "Skaityti daugiau apie Frigate+" "plusLink": "Skaityti daugiau apie Frigate+"
}, },
@@ -616,9 +616,7 @@
}, },
"frigatePlus": { "frigatePlus": {
"apiKey": { "apiKey": {
"notValidated": "Frigate+ API-nøkkel er ikke detektert eller validert",
"title": "Frigate+ API-nøkkel", "title": "Frigate+ API-nøkkel",
"validated": "Frigate+ API-nøkkel er detektert og validert",
"desc": "Frigate+ API-nøkkelen muliggjør integrasjon med Frigate+ tjenesten.", "desc": "Frigate+ API-nøkkelen muliggjør integrasjon med Frigate+ tjenesten.",
"plusLink": "Les mer om Frigate+" "plusLink": "Les mer om Frigate+"
}, },
+1 -3
View File
@@ -618,9 +618,7 @@
"apiKey": { "apiKey": {
"title": "Frigate+ API-sleutel", "title": "Frigate+ API-sleutel",
"plusLink": "Lees meer over Frigate+", "plusLink": "Lees meer over Frigate+",
"validated": "Frigate+ API-sleutel is gedetecteerd en gevalideerd", "desc": "Met de Frigate+ API-sleutel is integratie met de Frigate+ service mogelijk."
"desc": "Met de Frigate+ API-sleutel is integratie met de Frigate+ service mogelijk.",
"notValidated": "Frigate+ API-sleutel wordt niet gedetecteerd of niet gevalideerd"
}, },
"snapshotConfig": { "snapshotConfig": {
"title": "Snapshot-configuratie", "title": "Snapshot-configuratie",
@@ -621,9 +621,7 @@
"title": "Ustawienia Frigate+", "title": "Ustawienia Frigate+",
"apiKey": { "apiKey": {
"title": "Klucz API Frigate+", "title": "Klucz API Frigate+",
"validated": "Klucz API Frigate+ został wykryty i zweryfikowany",
"plusLink": "Dowiedz się więcej o Frigate+", "plusLink": "Dowiedz się więcej o Frigate+",
"notValidated": "Klucz API Frigate+ nie został wykryty lub nie został zweryfikowany",
"desc": "Klucz API Frigate+ umożliwia integrację z usługą Frigate+." "desc": "Klucz API Frigate+ umożliwia integrację z usługą Frigate+."
}, },
"snapshotConfig": { "snapshotConfig": {
@@ -719,8 +719,6 @@
"title": "Configurações do Frigate+", "title": "Configurações do Frigate+",
"apiKey": { "apiKey": {
"title": "Chave de API do Frigate+", "title": "Chave de API do Frigate+",
"validated": "A chave de API do Frigate+ detectada e validada",
"notValidated": "Chave de API do Frigate+ não detectada ou não validada",
"desc": "A chave de API do Frigate+ habilita a integração com o serviço do Frigate+.", "desc": "A chave de API do Frigate+ habilita a integração com o serviço do Frigate+.",
"plusLink": "Leia mais sobre o Frigate+" "plusLink": "Leia mais sobre o Frigate+"
}, },
@@ -173,8 +173,6 @@
}, },
"title": "Configurações Frigate+", "title": "Configurações Frigate+",
"apiKey": { "apiKey": {
"validated": "A chave da API do Frigate+ foi detectada e validada",
"notValidated": "A chave da API do Frigate+ não foi detectada ou não foi validada",
"desc": "A chave de API do Frigate+ permite a integração com o serviço Frigate+.", "desc": "A chave de API do Frigate+ permite a integração com o serviço Frigate+.",
"plusLink": "Saiba mais sobre o Frigate+", "plusLink": "Saiba mais sobre o Frigate+",
"title": "Chave de API do Frigate+" "title": "Chave de API do Frigate+"
+7 -2
View File
@@ -715,9 +715,14 @@
"apiKey": { "apiKey": {
"plusLink": "Citește mai multe despre Frigate+", "plusLink": "Citește mai multe despre Frigate+",
"desc": "Cheia API Frigate+ permite integrarea cu serviciul Frigate+.", "desc": "Cheia API Frigate+ permite integrarea cu serviciul Frigate+.",
"validated": "Cheia API Frigate+ a fost detectată și validată",
"title": "Cheie API Frigate+", "title": "Cheie API Frigate+",
"notValidated": "Cheia API Frigate+ nu este detectată sau validată" "formatValid": "Cheia API Frigate+ este detectată și corespunde formatului așteptat",
"formatInvalid": "Cheia API Frigate+ nu este detectată sau nu corespunde formatului așteptat",
"validate": "Validează cu Frigate+",
"validating": "Se validează…",
"serverValid": "Frigate+ a acceptat această cheie API",
"serverInvalid": "Frigate+ a respins această cheie API",
"serverUnreachable": "Nu s-a putut contacta Frigate+ pentru a valida această cheie API"
}, },
"snapshotConfig": { "snapshotConfig": {
"title": "Configurare snapshot-uri", "title": "Configurare snapshot-uri",
@@ -631,8 +631,6 @@
"title": "Настройки Frigate+", "title": "Настройки Frigate+",
"apiKey": { "apiKey": {
"title": "Ключ API Frigate+", "title": "Ключ API Frigate+",
"validated": "Ключ API Frigate+ найден и проверен",
"notValidated": "Ключ API Frigate+ не найден или не проверен",
"desc": "Ключ API Frigate+ включает интеграцию с сервисом Frigate+.", "desc": "Ключ API Frigate+ включает интеграцию с сервисом Frigate+.",
"plusLink": "Подробнее про Frigate+" "plusLink": "Подробнее про Frigate+"
}, },
@@ -883,8 +883,6 @@
"title": "Nastavenie Frigate+", "title": "Nastavenie Frigate+",
"apiKey": { "apiKey": {
"title": "Frigate + API kľúč", "title": "Frigate + API kľúč",
"validated": "Frigate + API kľúč je detekovaný a overený",
"notValidated": "Frigate + API kľúč nie je detekovaný alebo nie je overený",
"desc": "Frigate+ API kľúč umožňuje integráciu s Frigate+ služby.", "desc": "Frigate+ API kľúč umožňuje integráciu s Frigate+ služby.",
"plusLink": "Prečítajte si viac o Frigate+" "plusLink": "Prečítajte si viac o Frigate+"
}, },
@@ -1142,8 +1142,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Frigate+ API ključ", "title": "Frigate+ API ključ",
"validated": "API ključ je zaznan in potrjen",
"notValidated": "API ključ ni zaznan ali ni veljaven",
"desc": "API ključ omogoča integracijo s storitvijo Frigate+.", "desc": "API ključ omogoča integracijo s storitvijo Frigate+.",
"plusLink": "Preberi več o Frigate+" "plusLink": "Preberi več o Frigate+"
}, },
+368 -1
View File
@@ -1 +1,368 @@
{} {
"time": {
"untilForTime": "Do {{time}}",
"untilForRestart": "Dok se Frigate ne ponovo pokrene.",
"untilRestart": "Do ponovnog pokretanja",
"never": "Nikada",
"ago": "Pre {{timeAgo}}",
"justNow": "Upravo sada",
"today": "Danas",
"yesterday": "Juče",
"last7": "Poslednjih 7 dana",
"last14": "Poslednjih 14 dana",
"last30": "Poslednjih 30 dana",
"thisWeek": "Ove nedelje",
"lastWeek": "Prošle nedelje",
"thisMonth": "Ovog meseca",
"lastMonth": "Prošlog meseca",
"5minutes": "5 minuta",
"10minutes": "10 minuta",
"30minutes": "30 minuta",
"1hour": "1 sat",
"12hours": "12 sati",
"24hours": "24 sata",
"custom": "Prilagođeno...",
"pm": "popodne",
"am": "pre podne",
"yr": "{{time}} god.",
"year_one": "{{time}} godina",
"year_few": "{{time}} godine",
"year_other": "{{time}} godina",
"mo": "{{time}} mes.",
"month_one": "{{time}} mesec",
"month_few": "{{time}} meseca",
"month_other": "{{time}} meseci",
"d": "{{time}} d.",
"day_one": "{{time}} dan",
"day_few": "{{time}} dana",
"day_other": "{{time}} dana",
"h": "{{time}} č.",
"hour_one": "{{time}} sat",
"hour_few": "{{time}} sata",
"hour_other": "{{time}} sati",
"m": "{{time}} min.",
"minute_one": "{{time}} minut",
"minute_few": "{{time}} minuta",
"minute_other": "{{time}} minuta",
"s": "{{time}} s",
"second_one": "{{time}} sekunda",
"second_few": "{{time}} sekunde",
"second_other": "{{time}} sekundi",
"formattedTimestamp": {
"12hour": "d. MMM, h:mm:ss aaa",
"24hour": "d. MMM, HH:mm:ss"
},
"formattedTimestamp2": {
"12hour": "dd.MM. h:mm:ssa",
"24hour": "d. MMM, HH:mm:ss"
},
"formattedTimestampHourMinute": {
"12hour": "h:mm aaa",
"24hour": "HH:mm"
},
"formattedTimestampHourMinuteSecond": {
"12hour": "h:mm:ss aaa",
"24hour": "HH:mm:ss"
},
"formattedTimestampMonthDayHourMinute": {
"12hour": "d. MMM, h:mm aaa",
"24hour": "d. MMM, HH:mm"
},
"formattedTimestampMonthDayYear": {
"12hour": "d. MMM yyyy.",
"24hour": "d. MMM yyyy."
},
"formattedTimestampMonthDayYearHourMinute": {
"12hour": "d. MMM yyyy, h:mm aaa",
"24hour": "d. MMM yyyy, HH:mm"
},
"formattedTimestampMonthDay": "d. MMM",
"formattedTimestampFilename": {
"12hour": "dd-MM-yy-h-mm-ss-a",
"24hour": "dd-MM-yy-HH-mm-ss"
},
"inProgress": "U toku",
"invalidStartTime": "Nevažeće vreme početka",
"invalidEndTime": "Nevažeće vreme završetka"
},
"commandMenu": {
"action": {
"toggleTheme": "Prebaci na svetli ili tamni režim"
},
"camera": {
"review": "Pregled",
"live": "Uživo"
},
"section": {
"actions": "Akcije",
"settings": "Podešavanja",
"cameraGroups": "Grupe kamera",
"cameras": "Kamere",
"pages": "Stranice",
"recent": "Nedavno"
},
"hint": "{{key}} ili / za otvaranje, Enter za pokretanje",
"empty": "Nema rezultata",
"placeholder": "Pretražite stranice, kamere, podešavanja i akcije",
"description": "Pretražite stranice, kamere, podešavanja i akcije",
"title": "Komandni meni"
},
"credentialField": {
"savedPlaceholder": "Sačuvano — ostavite prazno da zadržite trenutno stanje"
},
"validation_errors": "Greške pri validaciji",
"unit": {
"speed": {
"kph": "km/h",
"mph": "mph"
},
"length": {
"feet": "stopa",
"meters": "metri"
},
"data": {
"kbps": "kB/s",
"mbps": "MB/s",
"gbps": "GB/s",
"kbph": "kB/sat",
"mbph": "MB/sat",
"gbph": "GB/sat"
}
},
"label": {
"back": "Idi nazad",
"hide": "Sakrij {{item}}",
"show": "Prikaži {{item}}",
"ID": "ID",
"none": "Ništa",
"all": "Sve",
"other": "Ostalo"
},
"list": {
"two": "{{0}} i {{1}}",
"many": "{{items}} i {{last}}",
"separatorWithSpace": ", "
},
"field": {
"optional": "Opciono",
"internalID": "Interni ID koji Frigate koristi u konfiguraciji i bazi podataka"
},
"button": {
"add": "Dodaj",
"apply": "Primeni",
"applying": "Primenjuje se…",
"reset": "Resetuj",
"undo": "Opozovi",
"done": "Završeno",
"enabled": "Omogućeno",
"enable": "Omogući",
"disabled": "Onemogućeno",
"disable": "Onemogući",
"save": "Sačuvaj",
"saving": "Čuvanje…",
"cancel": "Otkaži",
"close": "Zatvori",
"expand": "Proširi",
"collapse": "Skupi",
"clear": "Očisti",
"copy": "Kopiraj",
"copiedToClipboard": "Kopirano u privremenu memoriju",
"back": "Nazad",
"history": "Istorija",
"fullscreen": "Ceo ekran",
"exitFullscreen": "Izađi iz celog ekrana",
"pictureInPicture": "Slika u slici",
"twoWayTalk": "Dvosmerna komunikacija",
"cameraAudio": "Zvuk kamere",
"on": "Uključeno",
"off": "Isključeno",
"edit": "Izmeni",
"copyCoordinates": "Kopiraj koordinate",
"delete": "Obriši",
"yes": "Da",
"no": "Ne",
"download": "Preuzmi",
"info": "Informacije",
"suspended": "Suspendovano",
"unsuspended": "Aktiviraj ponovo",
"play": "Pusti",
"unselect": "Poništi izbor",
"export": "Izvezi",
"deleteNow": "Obriši odmah",
"next": "Sledeće",
"continue": "Nastavi",
"modified": "Izmenjeno",
"overridden": "Nadjačano",
"resetToGlobal": "Vrati na globalna podešavanja",
"resetToDefault": "Vrati na podrazumevano",
"saveAll": "Sačuvaj sve",
"savingAll": "Čuvanje svega…",
"undoAll": "Opozovi sve",
"retry": "Pokušaj ponovo",
"reload": "Osveži"
},
"menu": {
"system": "Sistem",
"systemMetrics": "Stanje i metrike",
"configuration": "Konfiguracija",
"systemLogs": "Dnevnik",
"profiles": "Profili",
"settings": "Podešavanja",
"configurationEditor": "Editor konfiguracije",
"languages": "Jezici",
"language": {
"en": "Engleski (English)",
"es": "Španski (Español)",
"zhCN": "Pojednostavljeni kineski (简体中文)",
"zhHant": "Tradicionalni kineski (繁體中文)",
"hi": "Hindi (हिन्दी)",
"fr": "Francuski (Français)",
"ar": "Arapski (العربية)",
"pt": "Portugalski (Português)",
"ptBR": "Brazilski portugalski (Português brasileiro)",
"ru": "Ruski (Русский)",
"de": "Nemački (Deutsch)",
"ja": "Japanski (日本語)",
"tr": "Turski (Türkçe)",
"it": "Italijanski (Italiano)",
"nl": "Holandski (Nederlands)",
"sv": "Švedski (Svenska)",
"cs": "Češki (Čeština)",
"nb": "Norveški bukmol (Norsk Bokmål)",
"ko": "Korejski (한국어)",
"vi": "Vijetnamski (Tiếng Việt)",
"fa": "Persijski (فارسی)",
"pl": "Poljski (Polski)",
"uk": "Ukrajinski (Українська)",
"be": "Beloruski (Беларуская)",
"he": "Hebrejski (עברית)",
"el": "Grčki (Ελληνικά)",
"ro": "Rumunski (Română)",
"hu": "Mađarski (Magyar)",
"fi": "Finski (Suomi)",
"da": "Danski (Dansk)",
"sk": "Slovački (Slovenčina)",
"yue": "Kantonski (粵語)",
"th": "Tajlandski (ไทย)",
"ca": "Katalonski (Català)",
"hr": "Hrvatski (Hrvatski)",
"bs": "Bosanski (Bosanski)",
"sr": "Srpski (Српски)",
"sl": "Slovenački (Slovenščina)",
"lt": "Litvanski (Lietuvių)",
"bg": "Bugarski (Български)",
"gl": "Galicijski (Galego)",
"id": "Indonežanski (Bahasa Indonesia)",
"ur": "Urdu (اردو)",
"withSystem": {
"label": "Koristi sistemska podešavanja za jezik"
}
},
"appearance": "Izgled",
"darkMode": {
"label": "Tamni režim",
"light": "Svetli",
"dark": "Tamni",
"withSystem": {
"label": "Koristi sistemska podešavanja za svetli ili tamni režim"
}
},
"withSystem": "Sistem",
"theme": {
"label": "Tema",
"blue": "Plava",
"green": "Zelena",
"nord": "Nord",
"red": "Crvena",
"highcontrast": "Visok kontrast",
"default": "Podrazumevano"
},
"help": "Pomoć",
"documentation": {
"title": "Dokumentacija",
"label": "Frigate dokumentacija"
},
"restart": "Ponovo pokreni Frigate",
"live": {
"title": "Uživo",
"allCameras": "Sve kamere",
"cameras": {
"title": "Kamere",
"count_one": "{{count}} kamera",
"count_few": "{{count}} kamere",
"count_other": "{{count}} kamera"
}
},
"review": "Pregled",
"explore": "Istraži",
"export": "Izvezi",
"actions": "Akcije",
"uiPlayground": "Korisničko okruženje za testiranje",
"features": "Funkcije",
"faceLibrary": "Biblioteka lica",
"classification": "Klasifikacija",
"chat": "Ćaskanje",
"user": {
"title": "Korisnik",
"account": "Nalog",
"current": "Trenutni korisnik: {{user}}",
"anonymous": "anoniman",
"logout": "Odjavi se",
"setPassword": "Postavi lozinku"
}
},
"toast": {
"copyUrlToClipboard": "URL je kopiran u privremenu memoriju.",
"save": {
"title": "Sačuvaj",
"error": {
"title": "Neuspešno čuvanje promena u konfiguraciji: {{errorMessage}}",
"noMessage": "Neuspešno čuvanje promena u konfiguraciji"
},
"success": "Promene u konfiguraciji su uspešno sačuvane."
}
},
"role": {
"title": "Uloga",
"admin": "Administrator",
"viewer": "Posmatrač",
"desc": "Administratori imaju potpun pristup svim funkcijama u Frigate korisničkom interfejsu. Gledaci su ograničeni na gledanje kamera, pregled stavki i istorijskih snimaka u korisničkom interfejsu."
},
"pagination": {
"label": "straničenje",
"previous": {
"title": "Prethodna",
"label": "Idi na prethodnu stranicu"
},
"next": {
"title": "Sledeća",
"label": "Idi na sledeću stranicu"
},
"more": "Više stranica"
},
"accessDenied": {
"documentTitle": "Pristup odbijen - Frigate",
"title": "Pristup odbijen",
"desc": "Nemate dozvolu za pregled ove stranice."
},
"notFound": {
"documentTitle": "Nije pronađeno - Frigate",
"title": "404",
"desc": "Stranica nije pronađena"
},
"error": {
"title": "Ova stranica je prestala sa radom",
"desc": "Došlo je do neočekivane greške na stranici. Osvežavanje obično rešava problem. Ako se greška ponavlja, kopirajte detalje i priložite ih diskusiji na GitHub-u.",
"staleTitle": "Potrebno je osvežavanje",
"staleDesc": "Deo aplikacije nije mogao da se učita, što obično znači da je Frigate ažuriran dok je ova kartica bila otvorena. Osvežite stranicu da biste prešli na novu verziju.",
"partial": "Deo aplikacije je prestao sa radom.",
"copyDetails": "Kopiraj detalje",
"copyFailed": "Neuspešno kopiranje detalja u privremenu memoriju"
},
"selectItem": "Izaberi {{item}}",
"readTheDocumentation": "Pročitaj dokumentaciju",
"information": {
"pixels": "{{area}} px"
},
"no_items": "Nema elemenata"
}
@@ -1 +1,90 @@
{} {
"group": {
"label": "Grupe kamera",
"add": "Dodaj grupu kamera",
"showAll": "Prikaži sve grupe kamera",
"showLess": "Prikaži manje",
"edit": "Izmeni grupu kamera",
"editGroups": "Izmeni grupe kamera",
"delete": {
"label": "Obriši grupu kamera",
"confirm": {
"title": "Potvrdite brisanje",
"desc": "Da li ste sigurni da želite da obrišete grupu kamera <em>{{name}}</em>?"
}
},
"name": {
"label": "Naziv",
"placeholder": "Unesite naziv…",
"errorMessage": {
"mustLeastCharacters": "Naziv grupe kamera mora imati najmanje 2 karaktera.",
"exists": "Naziv grupe kamera već postoji.",
"nameMustNotPeriod": "Naziv grupe kamera ne sme da sadrži tačku.",
"invalid": "Nevažeći naziv grupe kamera."
}
},
"cameras": {
"label": "Kamere",
"desc": "Izaberite kamere za ovu grupu."
},
"icon": "Ikona",
"success": "Grupa kamera ({{name}}) je sačuvana.",
"camera": {
"birdseye": "Birdseye",
"setting": {
"label": "Podešavanja strimovanja kamere",
"title": "Podešavanja strimovanja za kameru {{cameraName}}",
"desc": "Promenite opcije strimovanja uživo za kontrolnu tablu ove grupe kamera. <em>Ova podešavanja su specifična za uređaj/veb-pregledač.</em>",
"audioIsAvailable": "Zvuk je dostupan za ovaj strim",
"audioIsUnavailable": "Zvuk je nedostupan za ovaj strim",
"audio": {
"tips": {
"title": "Zvuk mora biti poslat sa vaše kamere i konfigurisan u go2rtc-u za ovaj strim."
}
},
"stream": "Strim",
"placeholder": "Izaberite strim",
"streamMethod": {
"label": "Metod strimovanja",
"placeholder": "Izaberite metod strimovanja",
"method": {
"noStreaming": {
"label": "Bez strimovanja",
"desc": "Slike sa kamere će se osvežavati samo jednom u minuti i neće biti strimovanja uživo."
},
"smartStreaming": {
"label": "Pametno strimovanje (preporučeno)",
"desc": "Pametno strimovanje će osvežavati sliku sa vaše kamere jednom u minuti kada nema uočljivih aktivnosti, kako bi se uštedeo protok podataka i resursi. Kada se detektuje aktivnost, prikaz se neprimetno prebacuje na strim uživo."
},
"continuousStreaming": {
"label": "Neprekidno strimovanje",
"desc": {
"title": "Slika sa kamere će uvek biti strim uživo kada je vidljiva na kontrolnoj tabli, čak i ako se ne detektuje nikakva aktivnost.",
"warning": "Neprekidno strimovanje može izazvati visoku potrošnju protoka podataka i probleme sa performansama. Koristite oprezno."
}
}
}
},
"compatibilityMode": {
"label": "Režim kompatibilnosti",
"desc": "Omogućite ovu opciju samo ako strim uživo sa vaše kamere prikazuje izobličenja boja (artefakte) i ima dijagonalnu liniju na desnoj strani slike."
}
}
}
},
"debug": {
"options": {
"label": "Podešavanja",
"title": "Opcije",
"showOptions": "Prikaži opcije",
"hideOptions": "Sakrij opcije"
},
"boundingBox": "Uokvireni prostor",
"timestamp": "Vremenska oznaka",
"zones": "Zone",
"mask": "Maska",
"motion": "Pokret",
"regions": "Regioni",
"paths": "Putanje"
}
}
@@ -1 +1,222 @@
{} {
"restart": {
"title": "Da li ste sigurni da želite da ponovo pokrenete Frigate?",
"description": "Ovo će nakratko zaustaviti Frigate dok se ponovo pokreće.",
"button": "Ponovo pokreni",
"restarting": {
"title": "Frigate se ponovo pokreće",
"content": "Ova stranica će se ponovo učitati za {{countdown}} sekundi.",
"button": "Osveži odmah"
}
},
"explore": {
"plus": {
"submitToPlus": {
"label": "Pošalji na Frigate+",
"desc": "Objekti na lokacijama koje želite da izbegnete nisu lažno pozitivni rezultati. Njihovo slanje kao lažno pozitivnih zbuniće model."
},
"review": {
"question": {
"label": "Potvrdite ovu oznaku za Frigate Plu",
"ask_a": "Da li je ovaj objekat <code>{{label}}</code>?",
"ask_an": "Da li je ovaj objekat <code>{{label}}</code>?",
"ask_full": "Da li je ovaj objekat <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
},
"state": {
"submitted": "Poslato"
},
"toast": {
"error": "Slanje na Frigate+ nije uspelo. Proverite mrežnu vezu i pokušajte ponovo."
}
}
},
"video": {
"viewInHistory": "Prikaži u istoriji"
}
},
"export": {
"name": {
"placeholder": "Imenujte izvezeni snimak"
},
"stream": {
"label": "Kvalitet",
"auto": "Automatski",
"main": "Izvorni",
"sub": "Nizak",
"autoDesc": "Koristi glavni strim, a u slučaju njegove nedostupnosti prebacuje se na sporedni strim nižeg kvaliteta.",
"mainDesc": "Izvozi isključivo glavni strim izvornog kvaliteta. Vremenski periodi u kojima glavni strim nije bio dostupan biće izostavljeni.",
"subDesc": "Izvozi isključivo sporedni strim nižeg kvaliteta. Vremenski periodi u kojima sporedni strim nije bio dostupan biće izostavljeni."
},
"case": {
"newCaseOption": "Kreiraj novi slučaj",
"newCaseNamePlaceholder": "Naziv novog slučaja",
"newCaseDescriptionPlaceholder": "Opis slučaja",
"label": "Slučaj",
"placeholder": "Izaberite slučaj",
"nameRequired": "Naziv slučaja je obavezan"
},
"select": "Izaberi",
"export": "Izvezi",
"queueing": "Dodavanje izvoza u red...",
"selectOrExport": "Izaberi ili izvezi",
"tabs": {
"export": "Jedna kamera",
"multiCamera": "Više kamera"
},
"multiCamera": {
"timeRange": "Vremenski opseg",
"selectFromTimeline": "Izaberite sa vremenske linije",
"cameraSelection": "Kamere",
"checkingActivity": "Provera aktivnosti kamera...",
"cameraSelectionHelp": "Kamere sa praćenim objektima u ovom vremenskom rasponu su unapred izabrane",
"searchOrSelectGroup": "Pretražite ili izaberite grupu kamera...",
"selectAll": "Izaberi sve kamere",
"clearSelection": "Poništi izbor",
"selectWithActivity": "Kamere sa praćenim objektima",
"selectGroup": "Izaberi grupu",
"noMatchingCameras": "Nema kamera koje odgovaraju pretrazi",
"selectedCount": "Izabrano: {{selected}} od {{total}}",
"noCameras": "Nema dostupnih kamera",
"noCamerasSelected": "Izaberite najmanje jednu kameru",
"detectionCount_one": "1 praćeni objekat",
"detectionCount_few": "{{count}} praćena objekata",
"detectionCount_other": "{{count}} praćenih objekata",
"nameLabel": "Naziv izvoza",
"caseNameDefault": "Izvoz sa više kamera - {{date}}",
"namePlaceholder": "Opcioni osnovni naziv za ove izvoze",
"queueingButton": "Dodavanje izvoza u red...",
"exportButton_one": "Izvezi 1 kameru",
"exportButton_few": "Izvezi {{count}} kamere",
"exportButton_other": "Izvezi {{count}} kamera"
},
"time": {
"fromTimeline": "Izaberite sa vremenske linije",
"lastHour_one": "Poslednji sat",
"lastHour_few": "Poslednjih {{count}} sata",
"lastHour_other": "Poslednjih {{count}} sati",
"custom": "Prilagođeno",
"start": {
"title": "Vreme početka",
"label": "Izaberite vreme početka"
},
"end": {
"title": "Vreme završetka",
"label": "Izaberite vreme završetka"
}
},
"multi": {
"title_one": "Izvezi 1 pregled",
"title_few": "Izvezi {{count}} pregleda",
"title_other": "Izvezi {{count}} pregleda",
"description": "Izvezite svaki izabrani pregled. Svi izvezeni snimci biće grupisani pod jednim slučajem.",
"descriptionNoCase": "Izvezite svaki izabrani pregled.",
"caseNamePlaceholder": "Izvoz pregleda - {{date}}",
"exportButton_one": "Izvezi 1 pregled",
"exportButton_few": "Izvezi {{count}} pregleda",
"exportButton_other": "Izvezi {{count}} pregleda",
"exportingButton": "Izvoženje...",
"toast": {
"started_one": "Pokrenut 1 izvoz.",
"started_few": "Pokrenuto {{count}} izvoza.",
"started_other": "okrenuto {{count}} izvoza.",
"partial": "Pokrenuto je {{successful}} od {{total}} izvoza. Neuspešno: {{failedItems}}",
"failed": "Pokretanje {{total}} izvoza nije uspelo. Neuspešno: {{failedItems}}"
}
},
"toast": {
"success": "Izvoz je uspešno pokrenut. Pogledajte datoteku na stranici sa izvezenim snimcima.",
"queued": "Izvoz je dodat u red. Pratite napredak na stranici sa izvezenim snimcima.",
"view": "Prikaži",
"batchSuccess_one": "Pokrenut 1 izvoz. Otvaranje slučaja...",
"batchSuccess_few": "Pokrenuto {{count}} izvoza. Otvaranje slučaja...",
"batchSuccess_other": "Pokrenuto {{count}} izvoza. Otvaranje slučaja...",
"batchPartial": "Pokrenuto je {{successful}} od {{total}} izvoza. Neuspešne kamere: {{failedCameras}}",
"batchFailed": "Pokretanje {{total}} izvoza nije uspelo. Neuspešne kamere: {{failedCameras}}",
"batchQueuedSuccess_one": "Dodat 1 izvoz u red.",
"batchQueuedSuccess_few": "Dodato {{count}} izvoza u red.",
"batchQueuedSuccess_other": "Dodato {{count}} izvoza u red.",
"batchQueuedPartial": "Dodato je {{successful}} od {{total}} izvoza u red. Neuspešne kamere: {{failedCameras}}",
"batchQueueFailed": "Dodavanje {{total}} izvoza u red nije uspelo. Neuspešne kamere: {{failedCameras}}",
"error": {
"failed": "Dodavanje izvoza u red nije uspelo: {{error}}",
"endTimeMustAfterStartTime": "Vreme završetka mora biti nakon vremena početka",
"noValidTimeSelected": "Nije izabran važeći vremenski opseg"
}
},
"fromTimeline": {
"saveExport": "Sačuvaj izvezeni snimak",
"queueingExport": "Dodavanje izvoza u red...",
"previewExport": "Pregledaj izvezeni snimak",
"useThisRange": "Koristi ovaj opseg"
}
},
"streaming": {
"label": "Strim",
"restreaming": {
"disabled": "Ponovno strimovanje nije omogućeno za ovu kameru.",
"desc": {
"title": "Podesite go2rtc za dodatne opcije prikaza uživo i zvuk za ovu kameru."
}
},
"showStats": {
"label": "Prikaži statistiku strima",
"desc": "Omogućite ovu opciju da biste prikazali statistiku strima kao sloj preko video-izvora kamere."
},
"debugView": "Prikaz za otklanjanje grešaka"
},
"search": {
"saveSearch": {
"label": "Sačuvaj pretragu",
"desc": "Unesite naziv za ovu sačuvanu pretragu.",
"placeholder": "Unesite naziv za svoju pretragu",
"overwrite": "{{searchName}} već postoji. Čuvanje će prepisati postojeću vrednost.",
"success": "Pretraga ({{searchName}}) je sačuvana.",
"button": {
"save": {
"label": "Sačuvaj ovu pretragu"
}
}
}
},
"recording": {
"shareTimestamp": {
"label": "Deli vremensku oznaku",
"title": "Deli vremensku oznaku",
"description": "Podelite URL sa vremenskom oznakom trenutne pozicije plejera ili izaberite prilagođenu vremensku oznaku. Napomena: ovo nije javni URL za deljenje i dostupan je samo korisnicima koji imaju pristup Frigate-u i ovoj kameri.",
"custom": "Prilagođena vremenska oznaka",
"button": "URL za deljenje vremenske oznake",
"shareTitle": "Frigate pregled vremenske oznake: {{camera}}"
},
"confirmDelete": {
"title": "Potvrdite brisanje",
"desc": {
"selected": "Da li ste sigurni da želite da obrišete sav snimljeni video povezan sa ovom stavkom pregleda?<br /><br />Držite taster <em>Shift</em> da biste ubuduće zaobišli ovaj prozor."
},
"toast": {
"success": "Video-snimci povezani sa izabranim stavkama pregleda su uspešno obrisani.",
"error": "Neuspešno brisanje: {{error}}"
}
},
"button": {
"export": "Izvezi",
"markAsReviewed": "Označi kao pregledano",
"markAsUnreviewed": "Označi kao nepregledano",
"deleteNow": "Obriši odmah",
"generateDescription": "Generiši opis"
},
"genaiDescription": {
"toast": {
"success": "Zatražen je GenAI opis za ovu stavku pregleda.",
"error": "Slanje zahteva za opis nije uspelo: {{error}}"
}
}
},
"imagePicker": {
"selectImage": "Izaberite sličicu praćenog objekta",
"unknownLabel": "Sačuvana slika pokretača",
"search": {
"placeholder": "Pretražite po oznaci ili podoznaci..."
},
"noImages": "Nema pronađenih sličica za ovu kameru"
}
}
@@ -1 +1,140 @@
{} {
"filter": "Filtriraj",
"classes": {
"label": "Klase",
"all": {
"title": "Sve klase"
},
"count_one": "{{count}} klasa",
"count_other": "{{count}} klase"
},
"labels": {
"label": "Oznake",
"all": {
"title": "Sve oznake",
"short": "Oznake"
},
"count_one": "{{count}} oznaka",
"count_other": "{{count}} oznake"
},
"zones": {
"label": "Zone",
"all": {
"title": "Sve zone",
"short": "Zone"
}
},
"dates": {
"selectPreset": "Izaberite unapred podešenu poziciju…",
"all": {
"title": "Svi datumi",
"short": "Datumi"
}
},
"more": "Više filtera",
"reset": {
"label": "Vrati filtere na podrazumevane vrednosti"
},
"timeRange": "Vremenski raspon",
"subLabels": {
"label": "Podoznake",
"all": "Sve podoznake"
},
"attributes": {
"label": "Atributi klasifikacije",
"all": "Svi atributi"
},
"score": "Rezultat",
"estimatedSpeed": "Procenjena brzina ({{unit}})",
"features": {
"label": "Funkcije",
"hasSnapshot": "Ima snimak",
"hasVideoClip": "Ima video-snimak",
"submittedToFrigatePlus": {
"label": "Poslato na Frigate+",
"tips": "Morate prvo filtrirati praćene objekte koji imaju snimak.<br /><br />Praćeni objekti bez snimka ne mogu se poslati na Frigate+."
}
},
"sort": {
"label": "Sortiranje",
"dateAsc": "Datum (Rastuće)",
"dateDesc": "Datum (Opadajuće)",
"scoreAsc": "Rezultat objekta (Rastuće)",
"scoreDesc": "Rezultat objekta (Opadajuće)",
"speedAsc": "Procenjena brzina (Rastuće)",
"speedDesc": "Procenjena brzina (Opadajuće)",
"relevance": "Relevantnost"
},
"cameras": {
"label": "Filter kamera",
"all": {
"title": "Sve kamere",
"short": "Kamere"
}
},
"review": {
"showReviewed": "Prikaži pregledano"
},
"motion": {
"showMotionOnly": "Prikaži samo kretanje"
},
"explore": {
"settings": {
"title": "Podešavanja",
"defaultView": {
"title": "Podrazumevani prikaz",
"desc": "Kada nijedan filter nije izabran, prikaži sažetak najnovijih praćenih objekata po oznaci ili prikaži nefiltriranu mrežu.",
"summary": "Sažetak",
"unfilteredGrid": "Nefiltrirana mreža"
},
"gridColumns": {
"title": "Kolone u mreži",
"desc": "Izaberite broj kolona u prikazu mreže."
},
"searchSource": {
"label": "Izvor pretrage",
"desc": "Izaberite da li želite da pretražujete sličice (umanjene prikaze) ili opise vaših praćenih objekata.",
"options": {
"thumbnailImage": "Sličica",
"description": "Opis"
}
}
},
"date": {
"selectDateBy": {
"label": "Izaberite datum za filtriranje"
}
}
},
"logSettings": {
"label": "Filtriraj nivo dnevnika",
"filterBySeverity": "Filtriraj dnevnik prema ozbiljnosti",
"loading": {
"title": "Učitavanje",
"desc": "Kada se panel dnevnika pomeri do samog dna, novi zapisi se automatski strimuju čim se dodaju."
},
"disableLogStreaming": "Onemogući strimovanje dnevnika",
"allLogs": "Ceo dnevnik"
},
"trackedObjectDelete": {
"title": "Potvrdite brisanje",
"desc": "Brisanjem ovih {{objectLength}} praćenih objekata uklanja se snimak, svi sačuvani vektorski prikazi i svi povezani unosi o životnom ciklusu objekta. Snimljeni materijal ovih praćenih objekata u prikazu istorije <em>NEĆE</em> biti obrisan.<br /><br />Da li ste sigurni da želite da nastavite?<br /><br />Držite taster <em>Shift</em> da biste ubuduće zaobišli ovaj prozor.",
"toast": {
"success": "Praćeni objekti su uspešno obrisani.",
"error": "Neuspešno brisanje praćenih objekata: {{errorMessage}}"
}
},
"zoneMask": {
"filterBy": "Filtriraj prema maski zone"
},
"recognizedLicensePlates": {
"title": "Prepoznate registarske tablice",
"loadFailed": "Učitavanje prepoznatih registarskih tablica nije uspelo.",
"loading": "Učitavanje prepoznatih registarskih tablica…",
"placeholder": "Unesite tekst za pretragu tablica…",
"noLicensePlatesFound": "Nema pronađenih registarskih tablica.",
"selectPlatesFromList": "Izaberite jednu ili više tablica sa liste.",
"selectAll": "Izaberi sve",
"clearAll": "Poništi sve"
}
}
@@ -1 +1,61 @@
{} {
"noRecordingsFoundForThisTime": "Nema pronađenih snimaka za ovaj vremenski period",
"noPreviewFound": "Pregled nije pronađen",
"noPreviewFoundFor": "Pregled nije pronađen za kameru {{cameraName}}",
"submitFrigatePlus": {
"title": "Pošalji ovaj kadar na Frigate+?",
"submit": "Pošalji",
"previewError": "Učitavanje predpregleda snimka nije uspelo. Snimak možda nije dostupan u ovom trenutku."
},
"livePlayerRequiredIOSVersion": "Za ovaj tip strima uživo potreban je iOS 17.1 ili noviji.",
"streamOffline": {
"title": "Strim je van mreže",
"desc": "Nijedan kadar nije primljen na strimu za detekciju <code>detect</code> kamere {{cameraName}}, proverite dnevnik grešaka"
},
"cameraOff": "Kamera je isključena",
"quality": {
"auto": "Automatski",
"autoLow": "Reprodukcija u niskom kvalitetu (ograničen protok podataka)",
"autoLowCodec": "Reprodukcija u niskom kvalitetu (izvorni kvalitet nije podržan u ovom veb-pregledaču)",
"autoLowSaveData": "Reprodukcija u niskom kvalitetu (ušteda podataka)",
"main": "Izvorni",
"sub": "Nizak",
"notSupportedBrowser": "Nije podržano u ovom veb-pregledaču",
"label": "Kvalitet",
"noAudio": "Nema zvuka",
"audioRate": "{{rate}} kHz audio",
"audioCodecRate": "{{codec}} {{rate}} kHz",
"noRecordings": "Nema snimaka u ovom vremenskom opsegu"
},
"stats": {
"streamType": {
"title": "Tip strima:",
"short": "Tip"
},
"bandwidth": {
"title": "Propusni opseg:",
"short": "Propusni opseg"
},
"totalFrames": "Ukupno kadrova:",
"droppedFrames": {
"title": "Odbačeni kadrovi:",
"short": {
"title": "Odbačeno",
"value": "{{droppedFrames}} kadrova"
}
},
"decodedFrames": "Dekodirani kadrovi:",
"droppedFrameRate": "Brzina odbacivanja kadrova:",
"stream": "Strim:",
"streamAuto": "{{stream}} (automatski)"
},
"toast": {
"success": {
"submittedFrigatePlus": "Kadar je uspešno poslat na Frigate+"
},
"error": {
"submitFrigatePlusFailed": "Slanje kadra na Frigate+ nije uspelo",
"playRecordingsFailed": "Reprodukcija snimaka nije uspela (greška {{code}}): {{message}}"
}
}
}
@@ -1 +1,40 @@
{} {
"minimum": "Mora biti najmanje {{limit}}",
"maximum": "Mora biti najviše {{limit}}",
"exclusiveMinimum": "Mora biti veće od {{limit}}",
"exclusiveMaximum": "Mora biti manje od {{limit}}",
"minLength": "Mora imati najmanje {{limit}} karakter(a)",
"maxLength": "Mora imati najviše {{limit}} karakter(a)",
"minItems": "Mora imati najmanje {{limit}} stavki",
"maxItems": "Mora imati najviše {{limit}} stavki",
"pattern": "Nevažeći format",
"required": "Ovo polje je obavezno",
"type": "Nevažeći tip vrednosti",
"enum": "Mora biti jedna od dozvoljenih vrednosti",
"const": "Vrednost se ne poklapa sa očekivanom konstantom",
"uniqueItems": "Sve stavke moraju biti jedinstvene",
"format": "Nevažeći format",
"additionalProperties": "Nepoznato svojstvo nije dozvoljeno",
"oneOf": "Mora se podudarati sa tačno jednom od dozvoljenih šema",
"anyOf": "Mora se podudarati sa najmanje jednom od dozvoljenih šema",
"proxy": {
"header_map": {
"roleHeaderRequired": "Zaglavlje uloge je obavezno kada su konfigurisana mapiranja uloga."
}
},
"ffmpeg": {
"inputs": {
"rolesUnique": "Svaka uloga može biti dodeljena samo jednom ulaznom strimu.",
"detectRequired": "Najmanje jednom ulaznom strimu mora biti dodeljena uloga „detect“.",
"hwaccelDetectOnly": "Samo ulazni strim sa ulogom „detect“ može definisati argumente za hardversko ubrzanje."
}
},
"detect": {
"dimensionMustBeEven": "Mora biti paran broj."
},
"models": {
"defaultRequired": "Jedan model mora koristiti podrazumevanu scenu. Bez nje, svaka kamera koja ne izabere scenu neće imati rezervni model na koji može da se osloni.",
"sceneDuplicate": "Svaki model mora koristiti različitu scenu.",
"sameModel": "Modeli za scenu {{scene}} i scenu {{other}} koriste istu datoteku modela. Pokretanje jednog modela pod više različitih scena samo deli posao detekcije na zasebne detektore, što je sporije nego pokretanje jednog modela sa svim hardverskim resursima navedenim pod njim."
}
}
@@ -1 +1,219 @@
{} {
"documentTitle": "Klasifikacioni modeli - Frigate",
"disabled": "Onemogućeno",
"details": {
"scoreInfo": "Rezultat predstavlja prosečnu pouzdanost klasifikacije kroz sve detekcije ovog objekta.",
"none": "Ništa",
"unknown": "Nepoznato"
},
"button": {
"deleteClassificationAttempts": "Obriši klasifikacione slike",
"renameCategory": "Preimenuj klasu",
"deleteCategory": "Obriši klasu",
"deleteImages": "Obriši slike",
"trainModel": "Treniraj model",
"addClassification": "Dodaj klasifikaciju",
"deleteModels": "Obriši modele",
"editModel": "Izmeni model"
},
"tooltip": {
"trainingInProgress": "Model se trenutno trenira",
"noNewImages": "Nema novih slika za treniranje. Prvo klasifikujte više slika u skupu podataka.",
"noChanges": "Nema izmena u skupu podataka od poslednjeg treniranja.",
"modelNotReady": "Model nije spreman za treniranje"
},
"toast": {
"success": {
"deletedModel_one": "Uspešno je obrisan {{count}} model",
"deletedModel_few": "Uspešno je obrisano {{count}} modela",
"deletedModel_other": "Uspešno je obrisano {{count}} modela",
"categorizedImage": "Slika je uspešno klasifikovana",
"reclassifiedImage": "Slika je uspešno ponovo klasifikovana",
"trainedModel": "Model je uspešno treniran.",
"trainingModel": "Treniranje modela je uspešno pokrenuto.",
"updatedModel": "Konfiguracija modela je uspešno ažurirana",
"renamedCategory": "Klasa je uspešno preimenovana u {{name}}",
"deletedCategory_one": "Obrisan {{count}} klasa",
"deletedCategory_few": "Obrisano {{count}} klase",
"deletedCategory_other": "Obrisano {{count}} klasa",
"deletedImage_one": "Obrisana {{count}} slika",
"deletedImage_few": "Obrisano {{count}} slike",
"deletedImage_other": "Obrisano {{count}} slika"
},
"error": {
"deleteImageFailed": "Brisanje nije uspelo: {{errorMessage}}",
"deleteCategoryFailed": "Brisanje klase nije uspelo: {{errorMessage}}",
"deleteModelFailed": "Brisanje modela nije uspelo: {{errorMessage}}",
"categorizeFailed": "Kategorizacija slike nije uspela: {{errorMessage}}",
"trainingFailed": "Treniranje modela nije uspelo. Proverite Frigate dnevnike za detalje.",
"trainingFailedToStart": "Pokretanje treniranja modela nije uspelo: {{errorMessage}}",
"updateModelFailed": "Ažuriranje modela nije uspelo: {{errorMessage}}",
"renameCategoryFailed": "Preimenovanje klase nije uspelo: {{errorMessage}}",
"reclassifyFailed": "Ponovna klasifikacija slike nije uspela: {{errorMessage}}"
}
},
"deleteCategory": {
"title": "Obriši klasu",
"desc": "Da li ste sigurni da želite da obrišete klasu {{name}}? Ovo će trajno obrisati sve povezane slike i zahtevaće ponovno treniranje modela.",
"minClassesTitle": "Nije moguće obrisati klasu",
"minClassesDesc": "Klasifikacioni model mora imati najmanje 2 klase. Dodajte drugu klasu pre nego što obrišete ovu."
},
"deleteModel": {
"title": "Obriši klasifikacioni model",
"single": "Da li ste sigurni da želite da obrišete {{name}}? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti.",
"desc_one": "Da li ste sigurni da želite da obrišete {{count}} model? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti.",
"desc_few": "Da li ste sigurni da želite da obrišete {{count}} modela? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti.",
"desc_other": "Da li ste sigurni da želite da obrišete {{count}} modela? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti."
},
"edit": {
"title": "Izmeni klasifikacioni model",
"descriptionState": "Izmenite klase za ovaj model klasifikacije stanja. Izmene će zahtevati ponovno treniranje modela.",
"descriptionObject": "Izmenite tip objekta i tip klasifikacije za ovaj model klasifikacije objekata.",
"enabled": "Omogućeno",
"enabledDesc": "Pokrenite ovaj model. Kada je onemogućen, prestaje sa radom i više ne vrši klasifikaciju.",
"saveAttempts": "Pokušaji čuvanja",
"saveAttemptsDesc": "Broj slika pokušaja klasifikacije koje treba zadržati za korisnički interfejs nedavnih klasifikacija.",
"motion": "Pokreni pri kretanju",
"motionDesc": "Pokrenite klasifikaciju kada se detektuje kretanje unutar konfigurisanog isecanja kadra.",
"interval": "Interval",
"intervalDesc": "Broj sekundi između periodičnih pokretanja klasifikacije. Ostavite prazno da bi se pokretala samo pri kretanju.",
"intervalPlaceholder": "Bez intervala",
"stateClassesInfo": "Model je ažuriran. Ponovo trenirajte model kako bi izmene klasa stupile na snagu.",
"errors": {
"saveAttemptsInvalid": "Pokušaji čuvanja moraju biti ceo broj 0 ili veći",
"intervalInvalid": "Interval mora biti ceo broj veći od 0"
}
},
"deleteDatasetImages": {
"title": "Obriši slike iz skupa podataka",
"desc_one": "Da li ste sigurni da želite da obrišete {{count}} sliku iz skupa podataka {{dataset}}? Ova radnja se ne može poništiti i zahtevaće ponovno treniranje modela.",
"desc_few": "Da li ste sigurni da želite da obrišete {{count}} slike iz skupa podataka {{dataset}}? Ova radnja se ne može poništiti i zahtevaće ponovno treniranje modela.",
"desc_other": "Da li ste sigurni da želite da obrišete {{count}} slika iz skupa podataka {{dataset}}? Ova radnja se ne može poništiti i zahtevaće ponovno treniranje modela."
},
"deleteTrainImages": {
"title": "Obriši slike za trening",
"desc_one": "Da li ste sigurni da želite da obrišete {{count}} sliku? Ova radnja se ne može poništiti.",
"desc_few": "Da li ste sigurni da želite da obrišete {{count}} slike? Ova radnja se ne može poništiti.",
"desc_other": "Da li ste sigurni da želite da obrišete {{count}} slika? Ova radnja se ne može poništiti."
},
"renameCategory": {
"title": "Preimenuj klasu",
"desc": "Unesite novi naziv za {{name}}. Biće potrebno da ponovo trenirate model kako bi izmena naziva stupila na snagu."
},
"description": {
"invalidName": "Neispravan naziv. Nazivi mogu sadržati samo slova, brojeve, razmake, apostrofe, donje crte i crtice."
},
"train": {
"title": "Nedavne klasifikacije",
"titleShort": "Nedavno",
"aria": "Izaberite nedavne klasifikacije"
},
"categories": "Klase",
"createCategory": {
"new": "Kreiraj novu klasu"
},
"categorizeImageAs": "Klasifikuj sliku kao:",
"categorizeImage": "Klasifikuj sliku",
"reclassifyImageAs": "Ponovo klasifikuj sliku kao:",
"reclassifyImage": "Ponovo klasifikuj sliku",
"menu": {
"objects": "Objekti",
"states": "Stanja"
},
"noModels": {
"object": {
"title": "Nema modela za klasifikaciju objekata",
"description": "Kreirajte prilagođeni model za klasifikaciju detektovanih objekata.",
"buttonText": "Kreiraj model objekata"
},
"state": {
"title": "Nema modela za klasifikaciju stanja",
"description": "Kreirajte prilagođeni model za praćenje i klasifikaciju promena stanja u specifičnim zonama kamera.",
"buttonText": "Kreiraj model stanja"
}
},
"wizard": {
"title": "Kreiraj novu klasifikaciju",
"steps": {
"nameAndDefine": "Naziv i definisanje",
"stateArea": "Zona stanja",
"chooseExamples": "Izaberi primere"
},
"step1": {
"description": "Modeli stanja prate fiksne zone kamera radi uočavanja promena (npr. otvorena/zatvorena vrata). Modeli objekata dodaju klasifikacije detektovanim objektima (npr. poznate životinje, dostavljači, itd.).",
"name": "Naziv",
"namePlaceholder": "Unesite naziv modela...",
"type": "Tip",
"typeState": "Stanje",
"typeObject": "Objekat",
"objectLabel": "Oznaka objekta",
"objectLabelPlaceholder": "Izaberite tip objekta...",
"classificationType": "Tip klasifikacije",
"classificationTypeTip": "Saznajte više o tipovima klasifikacije",
"classificationTypeDesc": "Podoznake dodaju dodatni tekst oznaci objekta (npr. 'Osoba: UPS'). Atributi predstavljaju metapodatke pretražive kroz sistem koji se čuvaju odvojeno unutar metapodataka objekta.",
"classificationSubLabel": "Podoznaka",
"classificationAttribute": "Atribut",
"classes": "Klase",
"states": "Stanja",
"classesTip": "Saznajte više o klasama",
"classesStateDesc": "Definišite različita stanja u kojima se zona vaše kamere može nalaziti. Na primer: 'otvoreno' i 'zatvoreno' za garažna vrata.",
"classesObjectDesc": "Definišite različite kategorije u koje se detektovani objekti mogu klasifikovati. Na primer: 'dostavljač', 'stanar' (resident), 'stranac' za klasifikaciju osoba.",
"classPlaceholder": "Unesite naziv klase...",
"errors": {
"nameRequired": "Naziv modela je obavezan",
"nameLength": "Naziv modela mora imati 64 karaktera ili manje",
"nameOnlyNumbers": "Naziv modela ne može sadržati samo brojeve",
"classRequired": "Najmanje 1 klasa je obavezna",
"classesUnique": "Nazivi klasa moraju biti jedinstveni",
"noneNotAllowed": "Klasa 'none' nije dozvoljena",
"stateRequiresTwoClasses": "Modeli stanja zahtevaju najmanje 2 klase",
"objectLabelRequired": "Molimo izaberite oznaku objekta",
"objectTypeRequired": "Molimo izaberite tip klasifikacije"
}
},
"step2": {
"description": "Izaberite kamere i definišite zonu za praćenje za svaku kameru. Model će klasifikovati stanje ovih zona.",
"cameras": "Kamere",
"selectCamera": "Izaberite kameru",
"noCameras": "Kliknite na + da dodate kamere",
"selectCameraPrompt": "Izaberite kameru sa liste da biste definisali njenu zonu praćenja"
},
"step3": {
"selectImagesPrompt": "Izaberite sve slike sa: {{className}}",
"selectImagesDescription": "Kliknite na slike da ih izaberete. Kliknite na Nastavi kada završite sa ovom klasom.",
"allImagesRequired_one": "Molimo klasifikujte sve slike. Preostala je još {{count}} slika.",
"allImagesRequired_few": "Molimo klasifikujte sve slike. Preostala je još {{count}} slike.",
"allImagesRequired_other": "Molimo klasifikujte sve slike. Preostala je još {{count}} slika.",
"generating": {
"title": "Generisanje uzoraka slika",
"description": "Frigate preuzima reprezentativne slike iz vaših snimaka. Ovo može potrajati..."
},
"training": {
"title": "Treniranje modela",
"description": "Vaš model se trenira u pozadini. Zatvorite ovaj prozor, a vaš model će početi sa radom čim se treniranje završi."
},
"retryGenerate": "Pokušaj ponovo generisanje",
"noImages": "Nema generisanih uzoraka slika",
"classifying": "Klasifikacija i treniranje...",
"trainingStarted": "Treniranje je uspešno pokrenuto",
"modelCreated": "Model je uspešno kreiran. Koristite pregled „Nedavne klasifikacije“ da dodate slike za stanja koja nedostaju, a zatim trenirajte model.",
"errors": {
"noCameras": "Nema konfigurisanih kamera",
"noObjectLabel": "Nije izabrana oznaka objekta",
"generateFailed": "Generisanje primera nije uspelo: {{error}}",
"generationFailed": "Generisanje nije uspelo. Molimo pokušajte ponovo.",
"classifyFailed": "Klasifikacija slika nije uspela: {{error}}"
},
"generateSuccess": "Uzorci slika su uspešno generisani",
"refreshExamples": "Generiši nove primere",
"refreshConfirm": {
"title": "Želite li da generišete nove primere?",
"description": "Ovo će generisati novi skup slika i poništiti sve izbore, uključujući i sve prethodne klase. Biće potrebno da ponovo izaberete primere za sve klase."
},
"missingStatesWarning": {
"title": "Nedostaju primeri klasa",
"description": "Nemaju sve klase primere. Pokušajte da generišete nove primere kako biste pronašli klasu koja nedostaju, ili nastavite i kasnije koristite pregled „Nedavne klasifikacije“ da dodate slike."
}
}
}
}
+267 -1
View File
@@ -1 +1,267 @@
{} {
"documentTitle": "Istraži - Frigate",
"generativeAI": "Generativni AI",
"exploreMore": "Istražite više objekata tipa {{label}}",
"exploreIsUnavailable": {
"title": "Opcija „Istraži“ je nedostupna",
"embeddingsReindexing": {
"context": "Opcija „Istraži“ se može koristiti nakon što se završi ponovno indeksiranje ugnježdenih podataka praćenih objekata.",
"startingUp": "Pokretanje…",
"estimatedTime": "Procenjeno preostalo vreme:",
"finishingShortly": "Uskoro se završava",
"step": {
"thumbnailsEmbedded": "Sličice su ugnježdeni: ",
"descriptionsEmbedded": "Opisi su ugnježdeni: ",
"trackedObjectsProcessed": "Praćeni objekti su obrađeni: "
}
},
"downloadingModels": {
"context": "Frigate preuzima neophodne modele ugnježdenih podataka kako bi podržao funkciju semantičke pretrage. Ovo može potrajati nekoliko minuta u zavisnosti od brzine vaše mrežne veze.",
"setup": {
"visionModel": "Vizuelni model",
"visionModelFeatureExtractor": "Ekstraktor funkcija vizuelnog modela",
"textModel": "Tekstualni model",
"textTokenizer": "Tokenizator teksta"
},
"tips": {
"context": "Možda ćete želeti da ponovo indeksirate ugnježdene podatke (embeddings) vaših praćenih objekata kada se modeli preuzmu."
},
"error": "Došlo je do greške. Proverite Frigate dnevnike."
}
},
"trackedObjectDetails": "Detalji o praćenom objektu",
"type": {
"details": "detalji",
"snapshot": "snimak",
"thumbnail": "sličica",
"video": "video",
"tracking_details": "detalji praćenja"
},
"trackingDetails": {
"title": "Detalji praćenja",
"noImageFound": "Nije pronađena slika za ovu vremensku oznaku.",
"createObjectMask": "Kreiraj masku objekta",
"adjustAnnotationSettings": "Prilagodite podešavanja označavanja",
"scrollViewTips": "Kliknite da biste videli značajne trenutke u životnom ciklusu ovog objekta.",
"autoTrackingTips": "Pozicije okvira za detekciju biće neprecizne za kamere sa automatskim praćenjem.",
"count": "{{first}} od {{second}}",
"trackedPoint": "Praćena tačka",
"lifecycleItemDesc": {
"visible": "detektovano {{label}}",
"entered_zone": "{{label}} je ušao u {{zones}}",
"active": "{{label}} je postao aktivan",
"stationary": "{{label}} je postao stacionaran",
"attribute": {
"faceOrLicense_plate": "detektovan atribut {{attribute}} za {{label}}",
"other": "{{label}} je prepoznat kao {{attribute}}"
},
"gone": "{{label}} je otišao",
"heard": "registrovan zvuk {{label}}",
"external": "detektovan {{label}}",
"header": {
"zones": "Zone",
"ratio": "Odnos",
"area": "Površina",
"score": "Rezultat",
"computedScore": "Izračunati rezultat",
"topScore": "Najbolji rezultat",
"toggleAdvancedScores": "Uključi/isključi napredne rezultate"
}
},
"annotationSettings": {
"title": "Podešavanja označavanja",
"showAllZones": {
"title": "Prikaži sve zone",
"desc": "Uvek prikaži zone na kadrovima gde su objekti ušli u zonu."
},
"offset": {
"label": "Pomeraj označavanja",
"desc": "Ovi podaci dolaze sa strima za detekciju vaše kamere, ali se prikazuju preko slika sa strima za snimanje. Malo je verovatno da su ova dva strima savršeno sinhronizovana. Kao rezultat toga, okvir za detekciju i video snimak se neće savršeno poklapati. Možete koristiti ovo podešavanje da pomerite anotacije unapred ili unazad u vremenu kako biste ih bolje uskladili sa snimljenim materijalom.",
"millisecondsToOffset": "Milisekunde za koje se pomera anotacija detekcije. <em>Podrazumevano: 0</em>",
"tips": "Smanjite vrednost ako je reprodukcija videa ispred okvira i tačaka putanje, a povećajte vrednost ako reprodukcija videa kasni za njima. Ova vrednost može biti negativna.",
"toast": {
"success": "Pomeraj označavanja za {{camera}} je sačuvan u konfiguracionoj datoteci."
}
}
},
"carousel": {
"previous": "Prethodni slajd",
"next": "Sledeći slajd"
}
},
"details": {
"item": {
"title": "Detalji stavke pregleda",
"desc": "Detalji stavke pregleda",
"button": {
"share": "Podeli ovu stavku pregleda",
"viewInExplore": "Prikaži u opciji „Istraži“"
},
"tips": {
"mismatch_one": "{{count}} nedostupan objekat je detektovan i uključen u ovu stavku pregleda. Ti objekti ili nisu ispunili uslove za upozorenje ili detekciju, ili su već očišćeni/obrisani.",
"mismatch_few": "{{count}} nedostupana objekta su detektovana i uključena u ovu stavku pregleda. Ti objekti ili nisu ispunili uslove za upozorenje ili detekciju, ili su već očišćeni/obrisani.",
"mismatch_other": "{{count}} nedostupanih objekata je detektovano i uključeno u ovu stavku pregleda. Ti objekti ili nisu ispunili uslove za upozorenje ili detekciju, ili su već očišćeni/obrisani.",
"hasMissingObjects": "Prilagodite vašu konfiguraciju ako želite da Frigate čuva praćene objekte za sledeće oznake: <em>{{objects}}</em>"
},
"toast": {
"success": {
"regenerate": "Zahtevan je novi opis od provajdera {{provider}}. U zavisnosti od brzine vašeg provajdera, ponovno generisanje opisa može potrajati.",
"updatedSublabel": "Podoznaka je uspešno ažurirana.",
"updatedLPR": "Registarska tablica je uspešno ažurirana.",
"updatedAttributes": "Atributi su uspešno ažurirani.",
"audioTranscription": "Uspešno je zatražena transkripcija zvuka. U zavisnosti od brzine vašeg Frigate servera, transkripcija može potrajati."
},
"error": {
"regenerate": "Neuspelo pozivanje provajdera {{provider}} za novi opis: {{errorMessage}}",
"updatedSublabelFailed": "Neuspelo ažuriranje podoznake: {{errorMessage}}",
"updatedLPRFailed": "Neuspelo ažuriranje registarske tablice: {{errorMessage}}",
"updatedAttributesFailed": "Neuspelo ažuriranje atributa: {{errorMessage}}",
"audioTranscription": "Neuspeli zahtev za transkripciju zvuka: {{errorMessage}}"
}
}
},
"label": "Oznaka",
"editSubLabel": {
"title": "Izmeni podoznaku",
"desc": "Unesite novu podoznaku za: {{label}}",
"descNoLabel": "Unesite novu podoznaku za ovaj praćeni objekat"
},
"editLPR": {
"title": "Izmeni registarsku tablicu",
"desc": "Unesite novu vrednost registarske tablice za: {{label}}",
"descNoLabel": "Unesite novu vrednost registarske tablice za ovaj praćeni objekat"
},
"editAttributes": {
"title": "Izmeni atribute",
"desc": "Izaberite atribute klasifikacije za: {{label}}"
},
"snapshotScore": {
"label": "Rezultat snimka"
},
"topScore": {
"label": "Najviši rezultat",
"info": "Najviši rezultat predstavlja najveću srednju vrednost (medijanu) rezultata za praćeni objekat, pa se može razlikovati od rezultata prikazanog na sličici u rezultatima pretrage."
},
"score": {
"label": "Rezultat"
},
"recognizedLicensePlate": "Prepoznata registarska tablica",
"attributes": "Atributi klasifikacije",
"estimatedSpeed": "Procenjena brzina",
"objects": "Objekti",
"camera": "Kamera",
"zones": "Zone",
"timestamp": "Vremenska oznaka",
"button": {
"findSimilar": "Pronađi slično",
"regenerate": {
"title": "Ponovo generiši",
"label": "Ponovo generiši opis praćenog objekta"
}
},
"description": {
"label": "Opis",
"placeholder": "Opis praćenog objekta",
"aiTips": "Frigate neće zatražiti opis od vašeg provajdera generativne veštačke inteligencije dok se životni ciklus praćenog objekta ne završi."
},
"expandRegenerationMenu": "Proširi meni za ponovno generisanje",
"regenerateFromSnapshot": "Ponovo generiši iz snimka",
"regenerateFromThumbnails": "Ponovo generiši iz sličica",
"tips": {
"descriptionSaved": "Opis je uspešno sačuvan",
"saveDescriptionFailed": "Neuspelo ažuriranje opisa: {{errorMessage}}"
},
"title": {
"label": "Naslov"
},
"scoreInfo": "Informacije o rezultatu"
},
"itemMenu": {
"downloadVideo": {
"label": "Preuzmi video",
"aria": "Preuzmi video"
},
"downloadSnapshot": {
"label": "Preuzmi snimak",
"aria": "Preuzmi snimak"
},
"downloadCleanSnapshot": {
"label": "Preuzmi čist snimak",
"aria": "Preuzmi čist snimak"
},
"viewTrackingDetails": {
"label": "Prikaži detalje praćenja",
"aria": "Prikaži detalje praćenja"
},
"findSimilar": {
"label": "Pronađi slično",
"aria": "Pronađi slične praćene objekte"
},
"addTrigger": {
"label": "Dodaj okidač",
"aria": "Dodaj okidač za ovaj praćeni objekat"
},
"audioTranscription": {
"label": "Transkribuj",
"aria": "Zatraži transkripciju zvuka"
},
"submitToPlus": {
"label": "Pošalji u Frigate+",
"aria": "Pošalji u Frigate Plus"
},
"viewInHistory": {
"label": "Prikaži u istoriji",
"aria": "Prikaži u istoriji"
},
"deleteTrackedObject": {
"label": "Obriši ovaj praćeni objekat"
},
"showObjectDetails": {
"label": "Prikaži putanju objekta"
},
"hideObjectDetails": {
"label": "Sakrij putanju objekta"
},
"debugReplay": {
"label": "Ponovna reprodukcija za otklanjanje grešaka",
"aria": "Prikaži ovaj praćeni objekat u prikazu ponovne reprodukcije za otklanjanje grešaka"
},
"more": {
"aria": "Više"
}
},
"dialog": {
"confirmDelete": {
"title": "Potvrdi brisanje",
"desc": "Brisanjem ovog praćenog objekta uklanjaju se snimak, svi sačuvani vektorski prikazi i svi povezani unosi detalja praćenja. Snimljeni video-zapis ovog praćenog objekta u prikazu Istorija <em>NEĆE</em> biti obrisan.<br/><br/>Da li ste sigurni da želite da nastavite?"
},
"toast": {
"error": "Greška pri brisanju ovog praćenog objekta: {{errorMessage}}"
}
},
"noTrackedObjects": "Nije pronađen nijedan praćeni objekat",
"fetchingTrackedObjectsFailed": "Greška pri preuzimanju praćenih objekata: {{errorMessage}}",
"trackedObjectsCount_one": "{{count}} praćeni objekat ",
"trackedObjectsCount_few": "{{count}} praćena objekta ",
"trackedObjectsCount_other": "{{count}} praćenih objekata ",
"searchResult": {
"tooltip": "Podudaranje: {{type}} ({{confidence}}%",
"previousTrackedObject": "Prethodni praćeni objekat",
"nextTrackedObject": "Sledeći praćeni objekat",
"deleteTrackedObject": {
"toast": {
"success": "Praćeni objekat je uspešno obrisan.",
"error": "Neuspelo brisanje praćenog objekta: {{errorMessage}}"
}
}
},
"aiAnalysis": {
"title": "AI analiza"
},
"concerns": {
"label": "Upozorenja"
},
"objectLifecycle": {
"noImageFound": "Nije pronađena slika za ovaj praćeni objekat."
}
}
+41 -1
View File
@@ -1 +1,41 @@
{} {
"selected_one": "Izabrano: {{count}}",
"selected_other": "Izabrano: {{count}}",
"documentTitle": "Izvoz - Frigate",
"search": "Pretraga",
"noExports": "Nije pronađen nijedan izvoz",
"headings": {
"cases": "Slučajevi",
"uncategorizedExports": "Nekategorizovani izvozi"
},
"deleteExport": {
"label": "Obriši izvoz",
"desc": "Da li ste sigurni da želite da obrišete izvoz {{exportName}}?"
},
"editExport": {
"title": "Preimenuj izvoz",
"desc": "Unesite novi naziv za ovaj izvoz.",
"saveExport": "Sačuvaj izvoz"
},
"tooltip": {
"shareExport": "Podeli izvoz",
"downloadVideo": "Preuzmi video",
"editName": "Izmeni naziv",
"deleteExport": "Obriši izvoz",
"assignToCase": "Dodaj u slučaj",
"removeFromCase": "Ukloni iz slučaja"
},
"toolbar": {
"newCase": "Novi slučaj",
"addExport": "Dodaj izvoz",
"editCase": "Izmeni slučaj",
"deleteCase": "Obriši slučaj"
},
"toast": {
"error": {
"renameExportFailed": "Neuspelo preimenovanje izvoza: {{errorMessage}}",
"assignCaseFailed": "Neuspelo ažuriranje dodele slučaju: {{errorMessage}}",
"caseSaveFailed": "Neuspelo čuvanje slučaja: {{errorMessage}}"
}
}
}
@@ -1 +1,12 @@
{} {
"details": {
"unknown": "Nepoznato",
"timestamp": "Vremenska oznaka"
},
"description": {
"invalidName": "Neispravan naziv. Nazivi mogu sadržati samo slova, brojeve, razmake, apostrofe, donje crte i crtice."
},
"train": {
"titleShort": "Nedavno"
}
}
+3 -1
View File
@@ -1 +1,3 @@
{} {
"search": "Pretraga"
}
File diff suppressed because it is too large Load Diff
+380 -1
View File
@@ -1 +1,380 @@
{} {
"documentTitle": {
"cameras": "Statistika kamera - Frigate",
"storage": "Statistika skladišta - Frigate",
"general": "Opšta statistika - Frigate",
"enrichments": "Statistika poboljšanja - Frigate",
"logs": {
"frigate": "Dnevnik Frigate sistema - Frigate",
"go2rtc": "Go2RTC dnevnik - Frigate",
"nginx": "Nginx dnevnik - Frigate",
"websocket": "Dnevnik poruka - Frigate"
},
"health": "Stanje sistema - Frigate"
},
"title": "Sistem",
"metrics": "Sistemska metrika",
"showAllTabs": "Prikaži sve kartice",
"showLessTabs": "Prikaži manje",
"health": {
"title": "Stanje",
"notices": {
"title": "Obaveštenja",
"empty": "Vaš Frigate sistem radi ispravno",
"acknowledge": "Uvaži",
"acknowledgeHint": "Sakrij dok se ne ponovi",
"mute": "Utišaj",
"muteHint": "Nikada više ne prikazuj ovo",
"unmute": "Uključi zvuk",
"showAgain": "Prikaži ponovo",
"openSettings": "Otvori podešavanja",
"openLink": "Otvori vezu / Otvori link",
"noMatches": "Nema obaveštenja koja odgovaraju filteru",
"hiddenTitle": "Skriveno",
"noneHidden": "Nema skrivenih obaveštenja",
"showAll": "Prikaži sve ponovo",
"showAllTitle": "Prikaži sva skrivena obaveštenja?",
"showAllDesc": "Svako potvrđeno i utišano obaveštenje se vraća na listu obaveštenja, uključujući provere konfiguracije i strimova.",
"occurred_one": "Dogodilo se {{time}}",
"occurred_other": "Poslednji put se dogodilo {{lastTime}} · {{count}} puta od {{time}}",
"acknowledgedAt": "Uvaženo {{time}}",
"mutedAt": "Utišano {{time}}",
"filter": {
"showHidden": "Prikaži skriveno",
"severity": "Ozbiljnost",
"error": "Greška",
"warning": "Upozorenje",
"info": "Informacija"
},
"kinds": {
"detector_stuck": "Detektor {{detector}} je ponovo pokrenut nakon što je prestao da odgovara",
"model_download_failed": "Preuzimanje datoteke {{file}} za model {{model}} nije uspelo: {{error}}",
"skipped_detections": "Sistem za detekciju nije uspevao da isprati brzinu i preskočio je {{pct}}% kadrova tokom perioda dužeg od jednog minuta",
"ffmpeg_high_cpu": "Zauzeće procesora od strane FFmpeg-a je visoko (prosečno {{cpu}}%)",
"detect_high_cpu": "Zauzeće procesora od strane sistema za detekciju je visoko (prosečno {{cpu}}%)",
"shm_too_low": "/dev/shm dodeljena memorija (trenutno {{total}} MB) bi trebalo da se poveća na najmanje {{min}} MB",
"object_processing_behind": "Obrada praćenih objekata nije uspevala da isprati brzinu, pa su kadrovi sa kamere odbačeni. Proverite iskorišćenost procesora i da li ograničenja procesora ili dodeljivanje jezgara na kontejneru ili virtuelnoj mašini ostavljaju premalo procesorske snage za Frigate",
"failed_login_one": "Neuspešan pokušaj prijave za korisnika {{user}}",
"failed_login_other": "Neuspešni pokušaji prijave za korisnika {{user}}",
"update_available": "Dostupna je verzija Frigate {{version}}",
"analytics_prompt": "Razmislite o deljenju anonimne statistike korišćenja kako biste pomogli programerima da unaprede Frigate"
},
"streamPrefix": "Strim {{index}}: {{message}}",
"streamPrefixRestream": "Strim {{index}} (putem go2rtc): {{message}}",
"streamProbeFailed": "Analiza strima {{index}} nije uspela: {{error}}",
"cameraProbeFailed": "Analiza strimova nije uspela: {{error}}"
},
"hardware": {
"title": "Hardver",
"objectDetection": "Detekcija objekata",
"hardwareAcceleration": "Hardversko ubrzanje",
"enrichments": {
"title": "Poboljšanja",
"semantic_search": "Semantička pretraga",
"face_recognition": "Prepoznavanje lica",
"lpr": "Prepoznavanje registarskih tablica",
"audio_transcription": "Audio transkripcija"
},
"cameraConnections": "Veze sa kamerama",
"allCamerasExcellent": "Sve kamere imaju odličnu vezu.",
"fps": "{{camera}} / {{expected}} fps",
"nothingConfigured": "Ništa nije konfigurisano",
"allCameras": "Sve kamere",
"probeUnavailable": "Hardverska detekcija je nedostupna",
"justStarted": "Frigate je upravo pokrenut",
"deviceNotFound": "Uređaj {{devices}} nije pronađen na ovom sistemu",
"detectorNotRunning": "Proces detektora nije pokrenut",
"inferenceVerySlow": "Obrada (inferencija) je veoma spora ({{speed}} ms)",
"inferenceSlow": "Obrada (inferencija) je spora ({{speed}} ms)",
"inferenceMs": "{{speed}} ms",
"customArgs": "Prilagođeni ffmpeg argumenti",
"customArgsNotVerified": "Prilagođeni ffmpeg argumenti, neverifikovani",
"hwaccelNotConfigured": "Hardversko dekodiranje se ne koristi, ali je {{family}} dostupan. Izaberite ga u ffmpeg podešavanjima",
"hwaccelHardwareMissing": "{{family}} je konfigurisan, ali ga hardverska analiza nije prijavila",
"unrecognizedDevice": "Nepoznat uređaj za nadjačavanje, neverifikovan",
"decoderUsage": "Dekoder: {{usage}}",
"remoteProvider": "Udaljeni provajder",
"acceleratorMissing": "Konfigurisano za {{device}}, ali nije pronađen kompatibilan akcelerator",
"modelNotRunYet": "Model još uvek nije pokrenut; uređaj se verifikuje nakon prve upotrebe",
"fellBackToCpu": "Konfigurisano za {{device}}, ali se izvršava na procesoru",
"cpuDespiteAccelerator": "Izvršava se na procesoru iako je akcelerator dostupan",
"probed": "Analizirano",
"probing": "Analiza u toku...",
"recheck": "Ponovo proveri hardver",
"cameraStreams": "Strimovi kamere",
"streamsNotChecked": "Još uvek nije provereno",
"streamsChecking": "Provera: {{done}} od {{total}}",
"streamsClean": "Nema problema sa strimom",
"checked": "Provereno",
"runAgain": "Pokreni ponovo",
"runStreamChecks": "Pokreni provere strimova",
"streamsChecked_one": "Proverena {{count}} kamera",
"streamsChecked_other": "Provereno {{count}} kamera",
"streamsFlagged_one": "{{count}} sa problemima, navedena pod Obaveštenja",
"streamsFlagged_other": "{{count}} sa problemima, navedene pod Obaveštenja"
}
},
"logs": {
"websocket": {
"label": "Poruke",
"pause": "Pauziraj",
"resume": "Nastavi",
"clear": "Očisti",
"filter": {
"all": "Sve teme",
"topics": "Teme",
"events": "Događaji",
"reviews": "Pregledi",
"classification": "Klasifikacija",
"face_recognition": "Prepoznavanje lica",
"lpr": "LPR",
"camera_activity": "Aktivnost kamere",
"system": "Sistem",
"camera": "Kamera",
"all_cameras": "Sve kamere",
"cameras_count_one": "{{count}} kamera",
"cameras_count_other": "{{count}} kamere"
},
"empty": "Još uvek nema zabeleženih poruka",
"count_one": "{{count}} poruka",
"count_other": "{{count}} poruke",
"expanded": {
"payload": "Sadržaj poruke"
}
},
"download": {
"label": "Preuzmi dnevnik"
},
"copy": {
"label": "Kopiraj u privremenu memoriju",
"success": "Dnevnik je kopiran u privremenu memoriju",
"error": "Neuspešno kopiranje dnevnika u privremenu memoriju"
},
"type": {
"label": "Tip",
"timestamp": "Vremenska oznaka",
"tag": "Oznaka",
"message": "Poruka"
},
"tips": "Dnevnik se strimuje sa servera",
"toast": {
"error": {
"fetchingLogsFailed": "Greška pri preuzimanju dnevnika: {{errorMessage}}",
"whileStreamingLogs": "Greška tokom strimovanja dnevnika: {{errorMessage}}"
}
}
},
"general": {
"title": "Opšte",
"detector": {
"title": "Detektori",
"inferenceSpeed": "Brzina obrade detektora",
"temperature": "Temperatura detektora",
"cpuUsage": "Zauzeće procesora od strane detektora",
"cpuUsageInformation": "Procesor koji se koristi za pripremu ulaznih i izlaznih podataka za modele detekcije i iz njih. Ova vrednost ne meri iskorišćenost same obrade, čak i ako se koristi grafički procesor ili akcelerator.",
"memoryUsage": "Zauzeće memorije od strane detektora"
},
"hardwareInfo": {
"title": "Informacije o hardveru",
"gpuUsage": "GPU zauzeće",
"gpuMemory": "GPU memorija",
"gpuEncoder": "GPU enkoder",
"gpuCompute": "GPU obrada / enkodiranje",
"gpuDecoder": "GPU dekoder",
"gpuTemperature": "GPU temperatura",
"gpuInfo": {
"vainfoOutput": {
"title": "Vainfo izlazni podaci",
"returnCode": "Izlazni kod: {{code}}",
"processOutput": "Izlazni podaci procesa:",
"processError": "Greška u procesu:"
},
"nvidiaSMIOutput": {
"title": "Nvidia SMI izlazni podaci",
"name": "Naziv: {{name}}",
"driver": "Drajver: {{driver}}",
"cudaComputerCapability": "CUDA računarska sposobnost: {{cuda_compute}}",
"vbios": "VBios informacije: {{vbios}}"
},
"closeInfo": {
"label": "Zatvori informacije o GPU"
},
"copyInfo": {
"label": "Kopiraj informacije o GPU"
},
"toast": {
"success": "Informacije o GPU su kopirane u privremenu memoriju"
}
},
"npuUsage": "NPU zauzeće",
"npuMemory": "NPU memorija",
"npuTemperature": "NPU temperatura"
},
"otherProcesses": {
"title": "Ostali procesi",
"series": {
"audio_detector": "audio detektor",
"embeddings": "vektorski prikazi",
"review_segment": "segment pregleda",
"recording": "snimanje",
"go2rtc": "go2rtc"
},
"processMemoryUsage": "Zauzeće memorije",
"processCpuUsage": "Zauzeće CPU"
}
},
"storage": {
"recordings": {
"title": "Snimci",
"tips": "Ova vrednost predstavlja ukupno skladište koje zauzimaju snimci u Frigate bazi podataka. Frigate ne prati zauzeće skladišta za sve datoteke na vašem disku.",
"earliestRecording": "Najraniji dostupni snimak:"
},
"overview": "Pregled",
"title": "Skladište",
"shm": {
"title": "Dodeljena SHM (deljena) memorija",
"warning": "Trenutna veličina SHM-a od {{total}} MB je previše mala. Povećajte je na najmanje {{min_shm}} MB.",
"frameLifetime": {
"title": "Vreme čuvanja kadra",
"description": "Svaka kamera ima {{frames}} mesta za kadrove u deljenoj memoriji. Pri najvećem broju kadrova u sekundi (frame rate) te kamere, svaki kadar je dostupan približno {{lifetime}} sekundi pre nego što bude prepisan."
}
},
"cameraStorage": {
"title": "Skladište kamera",
"camera": "Kamera",
"unusedStorageInformation": "Informacije o neiskorišćenom prostoru",
"storageUsed": "Skladište",
"percentageOfTotalUsed": "Procenat od ukupnog",
"bandwidth": "Propusni opseg",
"unused": {
"title": "Neiskorišćeno",
"tips": "Ova vrednost možda neće tačno prikazati slobodan prostor dostupan Frigate-u ako na vašem disku imate sačuvane druge datoteke pored Frigate snimaka. Frigate ne prati zauzeće skladišta izvan svojih snimaka."
},
"subStream": {
"information": "Informacije o skladištu sporednog strima",
"tips": "Kamere koje snimaju sporedni strim (sub stream) odvojeno prikazuju zauzeće za izvorni i nizak kvalitet, koristeći iste nazive kao i opcije kvaliteta pri reprodukciji snimaka. Niski kvalitet snimaka ostaje na disku sve dok ne istekne period čuvanja, tako da se ovaj prikaz može i dalje pojavljivati za kameru nakon što je snimanje sporednog strima isključeno."
}
}
},
"cameras": {
"title": "Kamere",
"overview": "Pregled",
"info": {
"aspectRatio": "Odnos stranica",
"cameraProbeInfo": "Informacije o analizi kamere {{camera}}",
"streamDataFromFFPROBE": "Podaci o strimu su dobijeni pomoću <code>ffprobe</code>.",
"fetching": "Preuzimanje podataka o kameri",
"stream": "Strim {{idx}}",
"video": "Video:",
"codec": "Kodek:",
"resolution": "Rezolucija:",
"fps": "FPS:",
"unknown": "Nepoznato",
"audio": "Audio:",
"error": "Greška: {{error}}",
"tips": {
"title": "Informacije o analizi kamere"
},
"keyframes": {
"title": "Analiza ključnih kadrova",
"analyzing": "Analiza ključnih kadrova je u toku... preostalo je {{seconds}} sekundi",
"stillAnalyzing": "Analiza ključnih kadrova je i dalje u toku...",
"recordStream": "Strim za snimanje:",
"keyframeCount": "Uočeni ključni kadrovi:",
"observedDuration": "Uočeno trajanje:",
"gap": "Razmak između ključnih kadrova (min / prosek / maks):",
"segmentLength": "Dužina segmenta snimanja:",
"ok": "Ključni kadrovi na svakih ~{{seconds}} s, što je dobro za snimanje i reprodukciju.",
"warningFixed": "Ključni kadrovi su ravnomerno raspoređeni ali su retki (na svakih ~{{seconds}} s). Snimanje i dalje radi, ali će reprodukcija uživo i premotavanje započinjati sporije. Podesite interval ključnih kadrova (I-frame / keyframe) na kameri tako da odgovara broju kadrova u sekundi.",
"warningVariable": "Razmak između ključnih kadrova je neujednačen (od {{minSeconds}} s do {{maxSeconds}} s), što obično znači da je uključen pametni kodek (H.264+/H.265+). Ovo se ne preporučuje.",
"errorFixed": "Ključni kadrovi na svakih ~{{seconds}} s su duži od dužine segmenta snimanja ({{segmentTime}} s), pa pojedini segmenti nemaju ključni kadar i neće moći da se reprodukuju. Skratite interval ključnih kadrova (I-frame / keyframe) na kameri tako da odgovara broju kadrova u sekundi (frame rate).",
"errorVariable": "Razmaci između ključnih kadrova dostižu ~{{seconds}} s, što je duže od dužine segmenta snimanja ({{segmentTime}} s). Pojedini segmenti neće imati ključni kadar, što prekida reprodukciju. Onemogućite pametni/+ kodek na kameri ili skratite interval ključnih kadrova.",
"unknown": "Nije moguće utvrditi razmak između ključnih kadrova.",
"recordDisabled": "Snimanje je onemogućeno za ovu kameru."
}
},
"framesAndDetections": "Kadrovi / Detekcije",
"noCameras": {
"title": "Nema pronađenih kamera"
},
"label": {
"camera": "kamera",
"detect": "detekcija",
"skipped": "preskočeno",
"ffmpeg": "FFmpeg",
"capture": "snimanje",
"overallFramesPerSecond": "ukupno kadrova u sekundi",
"overallDetectionsPerSecond": "ukupno detekcija u sekundi",
"overallSkippedDetectionsPerSecond": "ukupno preskočenih detekcija u sekundi",
"cameraFfmpeg": "{{camName}} FFmpeg",
"cameraCapture": "{{camName}} snimanje",
"cameraDetect": "{{camName}} detekcija",
"cameraGpu": "{{camName}} GPU",
"cameraFramesPerSecond": "{{camName}} kadrova u sekundi",
"cameraDetectionsPerSecond": "{{camName}} detekcija u sekundi",
"cameraSkippedDetectionsPerSecond": "{{camName}} preskočenih detekcija u sekundi"
},
"connectionQuality": {
"title": "Kvalitet veze",
"excellent": "Odličan",
"fair": "Zadovoljavajući",
"poor": "Loš",
"unusable": "Neupotrebljiv",
"fps": "FPS",
"expectedFps": "Očekivani FPS",
"reconnectsLastHour": "Ponovna povezivanja (poslednji sat)",
"stallsLastHour": "Zastoji (poslednji sat)"
},
"toast": {
"success": {
"copyToClipboard": "Podaci o analizi su kopirani u privremenu memoriju."
},
"error": {
"unableToProbeCamera": "Nije moguće analizirati kameru: {{errorMessage}}"
}
}
},
"lastRefreshed": "Poslednji put osveženo: ",
"stats": {
"cameraSkippedDetections": "Kamera {{camera}} preskače detekciju na {{pct}}% kadrova",
"healthy": "Sistem ispravn",
"systemNotices_one": "{{count}} sistemsko obaveštenje",
"systemNotices_other": "{{count}} sistemska obaveštenja",
"moreMessages_one": "+{{count}}",
"moreMessages_other": "+{{count}}",
"reindexingEmbeddings": "Ponovno indeksiranje vektorskih prikaza (završeno {{processed}}%)",
"reindexEmbeddingsFailed": "Ponovno indeksiranje vektorskih prikaza nije uspelo, proverite dnevnik",
"cameraIsOffline": "Kamera {{camera}} je van mreže",
"detectIsSlow": "Detekcija na {{detect}} je spora ({{speed}} ms)",
"detectIsVerySlow": "Detekcija na {{detect}} je veoma spora ({{speed}} ms)",
"retentionUnmet": "Snimci se brišu pre isteka perioda čuvanja kako bi se oslobodio prostor",
"debugReplayActive": "Sesija otklanjanja grešaka i ponovnog puštanja je aktivna"
},
"enrichments": {
"title": "Poboljšanja",
"infPerSecond": "Broj obrada u sekundi",
"averageInf": "Prosečno vreme obrade",
"embeddings": {
"image_embedding": "Vektorski prikaz slike",
"text_embedding": "Vektorski prikaz teksta",
"face_recognition": "Prepoznavanje lica",
"plate_recognition": "Prepoznavanje tablica",
"image_embedding_speed": "Brzina vektorskog prikaza slike",
"face_embedding_speed": "Brzina vektorskog prikaza lica",
"face_recognition_speed": "Brzina prepoznavanja lica",
"plate_recognition_speed": "Brzina prepoznavanja tablica",
"text_embedding_speed": "Brzina vektorskog prikaza teksta",
"yolov9_plate_detection_speed": "YOLOv9 brzina detekcije tablica",
"yolov9_plate_detection": "YOLOv9 detekcija tablica",
"review_description": "Opis stavke pregleda",
"review_description_speed": "Brzina opisivanja stavke pregleda",
"review_description_events_per_second": "Opis stavke pregleda",
"object_description": "Opis objekta",
"object_description_speed": "Brzina opisivanja objekta",
"object_description_events_per_second": "Opis objekta",
"classification": "{{name}} klasifikacija",
"classification_speed": "{{name}} brzina klasifikacije",
"classification_events_per_second": "{{name}} događaja klasifikacije u sekundi"
}
}
}
@@ -1154,8 +1154,6 @@
}, },
"apiKey": { "apiKey": {
"title": "Frigate+ API кључ", "title": "Frigate+ API кључ",
"validated": "Frigate+ API кључ је детектован и валидиран",
"notValidated": "Frigate+ API кључ није детектован или није валидиран",
"desc": "Frigate+ API кључ омогућава интеграцију са Frigate+ сервисом.", "desc": "Frigate+ API кључ омогућава интеграцију са Frigate+ сервисом.",
"plusLink": "Прочитајте више о Frigate+" "plusLink": "Прочитајте више о Frigate+"
}, },
+28 -1
View File
@@ -1 +1,28 @@
{} {
"bicycle": "Bicikl",
"car": "Automobil",
"motorcycle": "Motocikl",
"bus": "Autobus",
"train": "Voz",
"boat": "Čamac",
"bird": "Ptica",
"cat": "Mačka",
"dog": "Pas",
"horse": "Konj",
"sheep": "Ovca",
"skateboard": "Skejtbord",
"door": "Vrata",
"mouse": "Miš",
"keyboard": "Tastatura",
"sink": "Sudopera",
"blender": "Blender",
"clock": "Sat",
"scissors": "Makaze",
"hair_dryer": "Fen za kosu",
"toothbrush": "Četkica za zube",
"vehicle": "Vozilo",
"animal": "Životinja",
"bark": "Lavež",
"goat": "Koza",
"camera": "Kamera"
}
@@ -1 +1,16 @@
{} {
"form": {
"user": "Korisničko ime",
"password": "Lozinka",
"login": "Prijava",
"firstTimeLogin": "Pokušavate da se prijavite prvi put? Podaci za prijavu su ispisani u Frigate dnevniku.",
"errors": {
"usernameRequired": "Korisničko ime je obavezno",
"passwordRequired": "Lozinka je obavezna",
"rateLimit": "Prekoračeno je ograničenje broja pokušaja. Pokušajte ponovo kasnije.",
"loginFailed": "Prijava nije uspela",
"unknownError": "Nepoznata greška. Proverite dnevnik.",
"webUnknownError": "Nepoznata greška. Proverite dnevnik konzole."
}
}
}
@@ -1 +1,8 @@
{} {
"iconPicker": {
"selectIcon": "Izaberite ikonu",
"search": {
"placeholder": "Pretražite ikone…"
}
}
}
@@ -1 +1,10 @@
{} {
"button": {
"downloadVideo": {
"label": "Preuzmi video",
"toast": {
"success": "Preuzimanje video-snimka iz pregleda je počelo."
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+73 -1
View File
@@ -1 +1,73 @@
{} {
"audio": {
"global": {
"detection": "Globalna detekcija",
"sensitivity": "Globalna osetljivost"
},
"cameras": {
"detection": "Detekcija",
"sensitivity": "Osetljivost"
}
},
"timestamp_style": {
"global": {
"appearance": "Globalni izgled"
},
"cameras": {
"appearance": "Izgled"
}
},
"motion": {
"global": {
"sensitivity": "Globalna osetljivost",
"algorithm": "Globalni algoritam"
},
"cameras": {
"sensitivity": "Osetljivost",
"algorithm": "Algoritam"
}
},
"snapshots": {
"global": {
"display": "Globalni prikaz"
},
"cameras": {
"display": "Prikaz"
}
},
"detect": {
"global": {
"resolution": "Globalna rezolucija",
"tracking": "Globalno praćenje"
},
"cameras": {
"resolution": "Rezolucija",
"tracking": "Praćenje"
}
},
"objects": {
"global": {
"tracking": "Globalno praćenje",
"filtering": "Globalno filtriranje"
},
"cameras": {
"tracking": "Praćenje",
"filtering": "Filtriranje"
}
},
"record": {
"global": {
"retention": "Globalno čuvanje",
"events": "Globalni događaji"
},
"cameras": {
"retention": "Čuvanje",
"events": "Događaji"
}
},
"ffmpeg": {
"cameras": {
"cameraFfmpeg": "FFmpeg argumenti specifični za kameru"
}
}
}
+131 -1
View File
@@ -1 +1,131 @@
{} {
"person": "Osoba",
"bicycle": "Bicikl",
"car": "Automobil",
"motorcycle": "Motocikl",
"airplane": "Avion",
"bus": "Autobus",
"train": "Voz",
"boat": "Čamac",
"traffic_light": "Semafor",
"fire_hydrant": "Protivpožarni hidrant",
"street_sign": "Ulični znak",
"stop_sign": "Znak STOP",
"parking_meter": "Parking metar",
"bench": "Klupa",
"bird": "Ptica",
"cat": "Mačka",
"dog": "Pas",
"horse": "Konj",
"sheep": "Ovca",
"cow": "Krava",
"elephant": "Slon",
"bear": "Medved",
"zebra": "Zebra",
"giraffe": "Žirafa",
"hat": "Šešir",
"backpack": "Ranac",
"umbrella": "Kišobran",
"shoe": "Cipela",
"eye_glasses": "Naočare",
"handbag": "Torbica",
"tie": "Kravata",
"suitcase": "Kofer",
"frisbee": "Frizbi",
"skis": "Skije",
"snowboard": "Snoubord",
"sports_ball": "Sportska lopta",
"kite": "Zmaj",
"baseball_bat": "Bezbol palica",
"baseball_glove": "Bezbol rukavica",
"skateboard": "Skejtbord",
"surfboard": "Daska za surfovanje",
"tennis_racket": "Teniski reket",
"bottle": "Flaša",
"plate": "Tanjir",
"wine_glass": "Čaša za vino",
"cup": "Šolja",
"fork": "Viljuška",
"knife": "Nož",
"spoon": "Kašika",
"bowl": "Činija",
"banana": "Banana",
"apple": "Jabuka",
"sandwich": "Sendvič",
"orange": "Pomorandža",
"broccoli": "Brokoli",
"carrot": "Šargarepa",
"hot_dog": "Hot-dog",
"pizza": "Pica",
"donut": "Krofna",
"cake": "Kolač",
"chair": "Stolica",
"couch": "Kauč",
"potted_plant": "Saksijska biljka",
"bed": "Krevet",
"mirror": "Ogledalo",
"dining_table": "Trpezarijski sto",
"window": "Prozor",
"desk": "Radni sto",
"toilet": "Toalet",
"door": "Vrata",
"tv": "Televizor",
"laptop": "Laptop",
"mouse": "Miš",
"remote": "Daljinski upravljač",
"keyboard": "Tastatura",
"cell_phone": "Mobilni telefon",
"microwave": "Mikrotalasna",
"oven": "Rerna",
"toaster": "Toster",
"sink": "Sudopera",
"refrigerator": "Frižider",
"blender": "Blender",
"book": "Knjiga",
"clock": "Sat",
"vase": "Vaza",
"scissors": "Makaze",
"teddy_bear": "Plišani medvedić",
"hair_dryer": "Fen za kosu",
"toothbrush": "Četkica za zube",
"hair_brush": "Četka za kosu",
"vehicle": "Vozilo",
"squirrel": "Veverica",
"deer": "Jelen",
"animal": "Životinja",
"bark": "Lavež",
"fox": "Lisica",
"goat": "Koza",
"rabbit": "Zec",
"raccoon": "Rakun",
"robot_lawnmower": "Robotska kosilica",
"waste_bin": "Kanta za smeće",
"on_demand": "Na zahtev",
"face": "Lice",
"license_plate": "Registarska tablica",
"package": "Paket",
"bbq_grill": "Roštilj",
"amazon": "Amazon",
"usps": "USPS",
"ups": "UPS",
"fedex": "FedEx",
"dhl": "DHL",
"an_post": "An Post",
"purolator": "Purolator",
"postnl": "PostNL",
"nzpost": "NZPost",
"postnord": "PostNord",
"gls": "GLS",
"dpd": "DPD",
"canada_post": "Canada Post",
"royal_mail": "Royal Mail",
"school_bus": "Školski autobus",
"skunk": "Tvor",
"kangaroo": "Kengur",
"baby": "Beba",
"baby_stroller": "Dečja kolica",
"rickshaw": "Rikša",
"rodent": "Glodar",
"possum": "Oposum",
"garbage_truck": "Kamion za odvoženje smeća"
}
+88 -1
View File
@@ -1 +1,88 @@
{} {
"documentTitle": "Ćaskanje - Frigate",
"title": "Frigate ćaskanje",
"subtitle": "Vaš AI asistent za upravljanje kamerama i uvođenje u detalje",
"placeholder": "Pitajte bilo šta...",
"error": "Nešto je pošlo po zlu. Pokušajte ponovo.",
"processing": "Obrada...",
"toolsUsed": "Korišćeno: {{tools}}",
"showTools": "Prikaži alate ({{count}})",
"hideTools": "Sakrij alate",
"call": "Poziv",
"result": "Rezultat",
"arguments": "Argumenti:",
"response": "Odgovor:",
"attachment_chip_label": "{{label}} na kameri {{camera}}",
"attachment_chip_remove": "Ukloni prilog",
"open_in_explore": "Otvori u pretraživaču",
"attach_event_aria": "Priloži događaj {{eventId}}",
"attachment_picker_paste_label": "Ili nalepite ID događaja",
"attachment_picker_attach": "Priloži",
"attachment_picker_placeholder": "Priloži događaj",
"quick_reply_find_similar": "Pronađi slična uočavanja",
"quick_reply_tell_me_more": "Reci mi nešto više o ovome",
"quick_reply_when_else": "Kada je još viđeno?",
"quick_reply_find_similar_text": "Pronađi slična uočavanja za ovo.",
"quick_reply_tell_me_more_text": "Reci mi nešto više o ovome.",
"quick_reply_when_else_text": "Kada je još ovo viđeno?",
"anchor": "Referenca",
"similarity_score": "Sličnost",
"no_similar_objects_found": "Nisu pronađeni slični objekti.",
"semantic_search_required": "Semantička pretraga mora biti omogućena da bi se pronašli slični objekti.",
"send": "Pošalji",
"suggested_requests": "Pokušajte da pitate:",
"starting_requests": {
"show_recent_events": "Prikaži nedavne događaje",
"show_camera_status": "Prikaži status kamera",
"recap": "Šta se desilo u odsustvu?",
"watch_camera": "Prati kameru za aktivnosti"
},
"starting_requests_prompts": {
"show_recent_events": "Prikaži mi nedavne događaje iz poslednjeg sata",
"show_camera_status": "Kakav je trenutni status kamera?",
"recap": "Šta se desilo u odsustvu?",
"watch_camera": "Prati ulazna vrata i javi mi ako se neko pojavi"
},
"new_chat": "Novo ćaskanje",
"settings": {
"title": "Podešavanja ćaskanja",
"show_stats": {
"title": "Prikaži statistiku",
"desc": "Prikaži brzinu generisanja i veličinu konteksta za odgovore u ćaskanju.",
"while_generating": "Tokom generisanja",
"always": "Uvek"
},
"auto_scroll": {
"title": "Automatsko pomeranje ekrana",
"desc": "Prati nove poruke kako pristižu."
},
"always_allow": {
"title": "Uvek dozvoljene akcije",
"desc": "Akcije koje asistent može da pokrene bez prethodnog traženja dozvole.",
"none": "Nijedna",
"reset": "Resetuj"
}
},
"stats": {
"context": "{{tokens}} tokena",
"tokens_per_second": "{{rate}} t/s"
},
"reasoning": {
"active": "Razmišljanje…",
"show": "Prikaži tok razmišljanja",
"hide": "Sakrij tok razmišljanja"
},
"thinking": {
"toggle": "Prebaci prikaz razmišljanja"
},
"approval": {
"title": "Odobriti {{tool}}?",
"desc": "Ova akcija menja nešto u Frigate-u. Pregledajte detalje pre odobravanja.",
"approve": "Odobri",
"always_allow": "Uvek dozvoli",
"reject": "Odbij",
"approved": "Odobreno",
"rejected": "Odbijeno",
"placeholder": "Odobrite ili odbijte akciju na čekanju da biste nastavili"
}
}
@@ -1 +1,18 @@
{} {
"documentTitle": "Editor konfiguracije - Frigate",
"configEditor": "Editor konfiguracije",
"safeConfigEditor": "Editor konfiguracije (Bezbedan režim)",
"safeModeDescription": "Frigate je u bezbednom režimu zbog greške u validaciji konfiguracije.",
"copyConfig": "Kopiraj konfiguraciju",
"saveAndRestart": "Sačuvaj i ponovo pokreni",
"saveOnly": "Samo sačuvaj",
"confirm": "Izaći bez čuvanja?",
"toast": {
"success": {
"copyToClipboard": "Konfiguracija je kopirana u privremenu memoriju."
},
"error": {
"savingError": "Greška pri čuvanju konfiguracije"
}
}
}
+96 -1
View File
@@ -1 +1,96 @@
{} {
"alerts": "Upozorenja",
"detections": "Detekcije",
"motion": {
"label": "Kretanje",
"only": "Samo kretanje"
},
"allCameras": "Sve kamere",
"empty": {
"alert": "Nema upozorenja za pregled",
"detection": "Nema detekcija za pregled",
"motion": "Nisu pronađeni podaci o kretanju",
"recordingsDisabled": {
"title": "Snimanje mora biti omogućeno",
"description": "Stavke za pregled se mogu kreirati za kameru samo kada je snimanje omogućeno za tu kameru."
}
},
"timeline": {
"label": "Vremenska linija",
"aria": "Izaberite vremensku liniju"
},
"zoomIn": "Uveličaj",
"zoomOut": "Umanji",
"subOnlyQuality": "U ovom vremenskom opsegu dostupni su samo snimci nižeg kvaliteta",
"events": {
"label": "Događaji",
"aria": "Izaberite događaje",
"noFoundForTimePeriod": "Nema pronađenih događaja za ovaj vremenski period."
},
"detail": {
"label": "Detalj",
"noDataFound": "Nema detaljnih podataka za pregled",
"aria": "Uključi/isključi detaljan prikaz",
"trackedObject_one": "{{count}} objekat",
"trackedObject_other": "{{count}} objekata",
"noObjectDetailData": "Nema dostupnih detaljnih podataka o objektu.",
"settings": "Podešavanja detaljnog prikaza",
"alwaysExpandActive": {
"title": "Uvek proširi aktivno",
"desc": "Uvek proširi detalje o objektu za aktivnu stavku pregleda kada su dostupni."
}
},
"objectTrack": {
"trackedPoint": "Praćena tačka",
"clickToSeek": "Kliknite da premotate na ovo vreme"
},
"documentTitle": "Pregled - Frigate",
"recordings": {
"documentTitle": "Snimci - Frigate",
"invalidSharedLink": "Nije moguće otvoriti vezu ka snimku sa vremenskom oznakom zbog greške u parsiranju.",
"invalidSharedCamera": "Nije moguće otvoriti vezu ka snimku sa vremenskom oznakom zbog nepoznate ili neovlašćene kamere."
},
"calendarFilter": {
"last24Hours": "Poslednja 24 sata"
},
"markAsReviewed": "Označi kao pregledano",
"markTheseItemsAsReviewed": "Označi ove stavke kao pregledane",
"newReviewItems": {
"label": "Prikaži nove stavke za pregled",
"button": "Nove stavke za pregled"
},
"selected_one": "Izabrano: {{count}}",
"selected_other": "Izabrano: {{count}}",
"select_all": "Sve",
"camera": "Kamera",
"detected": "detektovano",
"normalActivity": "Normalno",
"needsReview": "Zahteva pregled",
"securityConcern": "Bezbednosni problem",
"motionSearch": {
"menuItem": "Pretraga kretanja",
"openMenu": "Opcije kamere"
},
"motionPreviews": {
"menuItem": "Prikaži preglede kretanja",
"title": "Pregledi kretanja: {{camera}}",
"mobileSettingsTitle": "Podešavanja pregleda kretanja",
"mobileSettingsDesc": "Prilagodite brzinu reprodukcije, zatamnjenje i isecanje, i izaberite datum za pregled video zapisa koji sadrže samo kretanje.",
"dim": "Zatamnjenje",
"dimAria": "Prilagodite intenzitet zatamnjenja",
"dimDesc": "Povećajte zatamnjenje da biste povećali vidljivost oblasti sa kretanjem.",
"speed": "Brzina",
"speedAria": "Izaberite brzinu reprodukcije pregleda",
"speedDesc": "Izaberite koliko brzo se reprodukuju video zapisi pregleda.",
"back": "Nazad",
"empty": "Nema dostupnih pregleda",
"noPreview": "Pregled nije dostupan",
"seekAria": "Premotajte plejer za kameru {{camera}} na {{time}}",
"filter": "Filter",
"filterDesc": "Izaberite oblasti da biste prikazali samo video zapise sa kretanjem u tim regionima.",
"filterClear": "Očisti",
"crop": "Iseci prema filteru",
"cropAria": "Uključi/isključi isecanje pregleda na filtrirane oblasti",
"cropDesc": "Zumirajte preglede na izabrane oblasti filtera umesto prikazivanja celog kadra."
}
}
@@ -1 +1,12 @@
{} {
"filter": "Filtriraj",
"export": "Izvezi",
"calendar": "Kalendar",
"filters": "Filteri",
"toast": {
"error": {
"noValidTimeSelected": "Nije izabran važeći vremenski raspon",
"endTimeMustAfterStartTime": "Vreme završetka mora biti nakon vremena početka"
}
}
}

Some files were not shown because too many files have changed in this diff Show More