Compare commits

...
Author SHA1 Message Date
Nicolas Mowen abf8a1875c Update api spec 2026-08-22 09:13:08 -06:00
Nicolas Mowen 172b404a93 Require camera access to delete review for camera 2026-08-22 09:12:27 -06:00
b1cdf1f76b Translated using Weblate (Norwegian Bokmål)
CI / AMD64 Build (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
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (1295 of 1295 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
fc319f4223 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (108 of 108 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/zh_Hans/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-replay
2026-08-21 14:34:47 -05:00
bf35e90bc8 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (129 of 129 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ryan He <koungho@gmail.com>
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/objects/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-settings/zh_Hant/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
07abbd2c0a Translated using Weblate (Khmer (Central))
Currently translated at 0.5% (1 of 185 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 98.7% (1279 of 1295 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 0.8% (2 of 239 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 1.3% (1 of 74 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 100.0% (10 of 10 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: SeyhaLite <sok123230@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/km/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/km/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-21 14:34:47 -05:00
694d162071 Translated using Weblate (Korean)
Currently translated at 90.6% (117 of 129 strings)

Translated using Weblate (Korean)

Currently translated at 85.2% (426 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: ydavelee <ydavelee.work@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ko/
Translation: Frigate NVR/audio
Translation: Frigate NVR/objects
2026-08-21 14:34:47 -05:00
b1d9676638 Translated using Weblate (Persian)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (500 of 500 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (45 of 45 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Persian)

Currently translated at 65.1% (71 of 109 strings)

Co-authored-by: Abdollah Ashjaa <abdollah.ashjaa@gmail.com>
Co-authored-by: Amir reza Irani ali poor <amir1376irani@yahoo.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: حمید ملک محمدی <hmmftg@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-groups/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fa/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fa/
Translation: Frigate NVR/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-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-21 14:34:47 -05:00
891a0df879 Translated using Weblate (Swedish)
Currently translated at 63.2% (506 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 62.2% (498 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 51.7% (670 of 1295 strings)

Translated using Weblate (Swedish)

Currently translated at 51.7% (670 of 1295 strings)

Translated using Weblate (Swedish)

Currently translated at 90.0% (54 of 60 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Swedish)

Currently translated at 46.8% (375 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 95.7% (454 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 37.8% (303 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 69.8% (331 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 36.7% (294 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 67.9% (322 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Swedish)

Currently translated at 33.7% (270 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 62.2% (295 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 26.1% (209 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 48.7% (231 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 13.3% (107 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 26.1% (124 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Swedish)

Currently translated at 99.8% (500 of 501 strings)

Translated using Weblate (Swedish)

Currently translated at 2.3% (19 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 5.6% (27 of 474 strings)

Co-authored-by: Fredrik B <fredrik@brannvall.nu>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kristian Johansson <knmjohansson@gmail.com>
Co-authored-by: Mats Lojander <mats@lojander.com>
Co-authored-by: Samuel Åkesson <samuel.akesson@bolmso.se>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sv/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
2d5845c770 Translated using Weblate (French)
Currently translated at 18.1% (145 of 800 strings)

Translated using Weblate (French)

Currently translated at 54.4% (258 of 474 strings)

Translated using Weblate (French)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (French)

Currently translated at 16.2% (130 of 800 strings)

Translated using Weblate (French)

Currently translated at 51.4% (244 of 474 strings)

Translated using Weblate (French)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (French)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jérémy MRPX <jeremy.marpaux@gmail.com>
Co-authored-by: Nathan Signouret <nathan.signouret@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/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/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
2026-08-21 14:34:47 -05:00
612a7cb871 Translated using Weblate (Spanish)
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (1295 of 1295 strings)

Co-authored-by: David Cambra <cambrafontan.david@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/es/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
101e5d0e98 Translated using Weblate (Nepali)
Currently translated at 4.8% (24 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Milan Thapa <hello@milanthapa.me>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ne/
Translation: Frigate NVR/audio
2026-08-21 14:34:47 -05:00
bcff29c35b Translated using Weblate (Dutch)
Currently translated at 75.8% (47 of 62 strings)

Translated using Weblate (Dutch)

Currently translated at 97.0% (776 of 800 strings)

Translated using Weblate (Dutch)

Currently translated at 84.8% (402 of 474 strings)

Translated using Weblate (Dutch)

Currently translated at 86.8% (1125 of 1295 strings)

Translated using Weblate (Dutch)

Currently translated at 83.5% (396 of 474 strings)

Translated using Weblate (Dutch)

Currently translated at 96.7% (774 of 800 strings)

Translated using Weblate (Dutch)

Currently translated at 83.1% (394 of 474 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Patrick <github@derr.eu>
Co-authored-by: Wim Timmer <wc.timmer@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
467404b410 Translated using Weblate (Arabic)
Currently translated at 38.5% (193 of 501 strings)

Translated using Weblate (Arabic)

Currently translated at 38.5% (193 of 501 strings)

Co-authored-by: Ahmed Marzouq <ahmed.marzouq.co@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Modar Soos <modarsoos@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ar/
Translation: Frigate NVR/audio
2026-08-21 14:34:47 -05:00
767597967e Translated using Weblate (Italian)
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Italian)

Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 94.0% (174 of 185 strings)

Translated using Weblate (Italian)

Currently translated at 99.6% (1291 of 1295 strings)

Translated using Weblate (Italian)

Currently translated at 98.5% (66 of 67 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (798 of 800 strings)

Translated using Weblate (Italian)

Currently translated at 92.9% (131 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Italian)

Currently translated at 85.5% (684 of 800 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Italian)

Currently translated at 74.1% (593 of 800 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (473 of 474 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Filippo-riccardo Franzin (filippo franzin) <filric01@gmail.com>
Co-authored-by: Gringo <ita.translations@tiscali.it>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nton <arlatalpa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/it/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-21 14:34:47 -05:00
57b8206e86 Translated using Weblate (Malay)
Currently translated at 9.7% (49 of 501 strings)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nazri Masnan <nazrimasnan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ms/
Translation: Frigate NVR/audio
2026-08-21 14:34:47 -05:00
86b828f52e Translated using Weblate (Polish)
Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Polish)

Currently translated at 17.7% (142 of 800 strings)

Translated using Weblate (Polish)

Currently translated at 17.7% (142 of 800 strings)

Translated using Weblate (Polish)

Currently translated at 46.6% (221 of 474 strings)

Translated using Weblate (Polish)

Currently translated at 46.6% (221 of 474 strings)

Co-authored-by: Artur <wy66m6xm@anonaddy.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J P <jpoloczek24@gmail.com>
Co-authored-by: Kamil Cybułka <kamil.cybulka@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pl/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/views-events
2026-08-21 14:34:47 -05:00
4b39edf983 Translated using Weblate (Hungarian)
Currently translated at 51.3% (56 of 109 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Hungarian)

Currently translated at 91.2% (457 of 501 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (239 of 239 strings)

Co-authored-by: Dávid Attila Balog <davidattilabalog@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/hu/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/hu/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/hu/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/hu/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
2026-08-21 14:34:47 -05:00
06f5229567 Translated using Weblate (Catalan)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (108 of 108 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/ca/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-replay
2026-08-21 14:34:47 -05:00
90a33f504c Translated using Weblate (Japanese)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (129 of 129 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: alpha <alphamob0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ja/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ja/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ja/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ja/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ja/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/ja/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ja/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
d0766aa3ee Translated using Weblate (Ukrainian)
Currently translated at 2.8% (23 of 800 strings)

Translated using Weblate (Ukrainian)

Currently translated at 6.9% (33 of 474 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.0% (108 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nikita Mikheiev <nikimihiki@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/uk/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
2026-08-21 14:34:47 -05:00
76a5e00bd5 Translated using Weblate (Romanian)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (108 of 108 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/ro/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-replay
2026-08-21 14:34:47 -05:00
b914f32cea Translated using Weblate (Russian)
Currently translated at 100.0% (45 of 45 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (798 of 800 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Russian)

Currently translated at 64.1% (43 of 67 strings)

Translated using Weblate (Russian)

Currently translated at 77.7% (84 of 108 strings)

Co-authored-by: Artem Vladimirov <artyomka71@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ru/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-21 14:34:47 -05:00
48acba8dab Translated using Weblate (Estonian)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (108 of 108 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/components-dialog/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/et/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-replay
2026-08-21 14:34:47 -05:00
c605295483 Translated using Weblate (Danish)
Currently translated at 4.8% (3 of 62 strings)

Translated using Weblate (Danish)

Currently translated at 0.1% (1 of 800 strings)

Translated using Weblate (Danish)

Currently translated at 85.1% (120 of 141 strings)

Translated using Weblate (Danish)

Currently translated at 1.6% (21 of 1295 strings)

Translated using Weblate (Danish)

Currently translated at 26.8% (18 of 67 strings)

Translated using Weblate (Danish)

Currently translated at 68.6% (344 of 501 strings)

Co-authored-by: Anders Fosgerau <afosgerau@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/da/
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
ad35bf49f7 Translated using Weblate (German)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (German)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (German)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (German)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (German)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (German)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (German)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (German)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (German)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Viktor Stier <viktor-stier@gmx.de>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
000bf4a03b Translated using Weblate (Portuguese (Brazil))
Currently translated at 47.2% (378 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 85.0% (403 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.8% (375 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 83.7% (397 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.5% (372 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 80.8% (383 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.3% (371 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 79.7% (378 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 44.3% (355 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 76.1% (361 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 44.2% (354 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 75.9% (360 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 43.2% (346 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.2% (352 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (108 of 108 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Klenner Martins Barros <klenne.al@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pt_BR/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
2026-08-21 14:34:47 -05:00
d2982bd144 Added translation using Weblate (Tamil)
Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
2026-08-21 14:34:47 -05:00
2cd53ccdfe Translated using Weblate (Lithuanian)
Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Lithuanian)

Currently translated at 0.7% (6 of 800 strings)

Translated using Weblate (Lithuanian)

Currently translated at 1.2% (6 of 474 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Lithuanian)

Currently translated at 42.4% (550 of 1295 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Lithuanian)

Currently translated at 58.1% (50 of 86 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (239 of 239 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: MaBeniu <runnerm@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/lt/
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/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-settings
2026-08-21 14:34:47 -05:00
2ba33e227c Translated using Weblate (Turkish)
Currently translated at 7.8% (63 of 800 strings)

Translated using Weblate (Turkish)

Currently translated at 14.9% (71 of 474 strings)

Translated using Weblate (Turkish)

Currently translated at 98.1% (106 of 108 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: furkan geldi <furkangeldi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/tr/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
2026-08-21 14:34:47 -05:00
Josh HawkinsandGitHub 036bae4ea9 Return a specific 404 when starting a debug replay with no recordings in range (#24024)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / Assemble and push default build (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
2026-08-18 10:08:52 -05:00
dtigheandGitHub 8384a8c5b3 Fix Gemini tool calling on 3.6+ by using documented function response role (#24013)
Gemini 3.6 and newer reject role="function" on the function response
Content with 400 INVALID_ARGUMENT, breaking any chat query that triggers
a tool call. The tool call itself succeeds; only the hand-back to the
model fails, and because the error surfaces mid-stream the request still
returns HTTP 200, so it is easy to miss.

Google's function calling documentation specifies role="user" for
returning function results:

    contents.append(response.candidates[0].content)
    contents.append(types.Content(role="user", parts=[function_response_part]))

https://ai.google.dev/gemini-api/docs/generate-content/function-calling

Verified with my local setup.
2026-08-18 08:16:47 -06:00
Josh HawkinsandGitHub 77fc2ce174 Miscellaneous fixes (0.18 beta) (#24016)
CI / AMD64 Build (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
* fix classification drawer closing instead of scrolling when list is long on mobile

* add qwen3.8 to genai docs

* add titles to more clearly separate model types
2026-08-18 07:01:22 -06:00
Josh HawkinsandGitHub 8425a76558 Miscellaneous fixes (0.18 beta) (#23993)
CI / AMD64 Build (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
* subscribe to add in webpush

* add docs for detector cpu usage

* rebuild notification camera access when a camera is added at runtime

* document how frigate shows CPU usage metrics

* add faq about version key in config
2026-08-16 12:39:28 -06:00
Josh HawkinsandGitHub 11f8786459 sanitize user-supplied path components (#23990)
CI / AMD64 Build (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
sanitize_filename leaves ".." intact and collapses variants like "..:" and "..*" to "..", so filesystem paths built from face names, classification model/category names, image ids, and trigger data could escape their base directory. Route every such site through new frigate/util/path.py helpers (safe_join, sanitize_path_component, sanitize_contained_path), which reject traversal and verify containment.

Worst case was DELETE /classification/{name}, which rmtree'd /media/frigate and /config while returning 200.

Important to note that all affected endpoints already require admin permission, so this sould be considered hardening rather than fixing exploitable code.
2026-08-13 21:59:46 -05:00
Josh HawkinsandGitHub 812e5308a3 fix notification suspend state lost on page reload (#23989)
<camera>/notifications/suspended arrives as a string over the live connection but as a number in the camera_activity snapshot, and the truthiness guard dropped the numeric 0, so a camera with notifications off rendered as active after a reload. Normalize to a string and derive isSuspended instead of storing it.
2026-08-13 16:51:44 -06:00
Josh HawkinsandGitHub fd98977506 Categorize manual events as alerts when their label is an alert label (#23981)
CI / AMD64 Build (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
* Categorize manual events as alerts when their label is an alert label

* tweak docs
2026-08-13 11:16:02 -06:00
LarosenandGitHub 6816050a46 fix(audio): correct sodeling typo to yodeling (#23946)
* fix(audio): correct sodeling typo to yodeling

Fixes a typo in audio-labelmap.txt where the yodeling class was
misspelled as "sodeling".

* fix(i18n): remove duplicate sodeling key in en audio.json

The en audio.json already contains a correct "yodeling" key. Remove
the duplicate/misspelled "sodeling" entry to avoid ambiguity.
2026-08-13 07:02:38 -05:00
Josh HawkinsandGitHub c70a0802b8 filter dedicated LPR plates before creating the event (#23977) 2026-08-13 05:44:31 -06:00
Josh HawkinsandGitHub aff9799451 Don't require a restart to enable GenAI descriptions (#23964)
CI / AMD64 Build (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
* create GenAI post processors when a camera enables GenAI at runtime

* fix types
2026-08-12 08:55:34 -05:00
Josh HawkinsandGitHub c75611b4df Multi-export UI fixes (#23959)
CI / AMD64 Build (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
* multi export fixes

* i18n

* new tests
2026-08-11 10:11:47 -06:00
Josh HawkinsandGitHub 0735a8ac75 Docs updates (#23947)
* misc docs updates

* add warning about proxies to 5000 for notifications
2026-08-10 15:54:41 -06:00
Josh HawkinsandGitHub 2599795ab0 add faq to notifications docs (#23939) 2026-08-08 11:12:13 -06:00
Josh HawkinsandGitHub 344efb6bc1 Miscellaneous fixes (0.18 beta) (#23934)
CI / AMD64 Build (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
* add host npu requirements to docs

* allow toggling live audio transcription via mqtt

* improve spacing consistency on mobile drawers

* fix clearing the region grid not surviving a restart
2026-08-08 07:20:09 -06:00
8e55da67b0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (1295 of 1295 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
62d90e8de8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (129 of 129 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/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/objects/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
599e0acad7 Translated using Weblate (Albanian)
Currently translated at 4.9% (25 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: checko dev <checkodev24@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sq/
Translation: Frigate NVR/audio
2026-08-08 06:09:12 -05:00
e5382db70e Translated using Weblate (Swedish)
Currently translated at 51.1% (662 of 1295 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Swedish)

Currently translated at 50.7% (657 of 1295 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mats Lojander <mats@lojander.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sv/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
4e13c3c9a0 Translated using Weblate (Dutch)
Currently translated at 95.4% (104 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Paul Bröerken <broerken@me.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translation: Frigate NVR/components-dialog
2026-08-08 06:09:12 -05:00
c62c31361f Translated using Weblate (Czech)
Currently translated at 86.2% (94 of 109 strings)

Translated using Weblate (Czech)

Currently translated at 32.5% (421 of 1295 strings)

Translated using Weblate (Czech)

Currently translated at 85.3% (93 of 109 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Czech)

Currently translated at 99.8% (500 of 501 strings)

Translated using Weblate (Czech)

Currently translated at 99.8% (500 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Matěj Kratochvíl <matejkratochvilbilina@gmail.com>
Co-authored-by: MiraCatsy <catsycatsymira@gmail.com>
Co-authored-by: romanslezar <roman.slezar@centrum.cz>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/cs/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
144513d3d6 Translated using Weblate (Catalan)
Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (129 of 129 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
Hosted WeblateJosh HawkinsYusuke, Hirota <hirota.yusuke@jp.fujitsu.com>
22c3dfa5a5 Translated using Weblate (Japanese)
Currently translated at 99.8% (799 of 800 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Yusuke, Hirota <hirota.yusuke@jp.fujitsu.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ja/
Translation: Frigate NVR/Config - Global
2026-08-08 06:09:12 -05:00
4e68c4723f Translated using Weblate (Romanian)
Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (129 of 129 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
dbce2d5a43 Translated using Weblate (Russian)
Currently translated at 85.1% (1103 of 1295 strings)

Translated using Weblate (Russian)

Currently translated at 76.1% (83 of 109 strings)

Translated using Weblate (Russian)

Currently translated at 71.7% (929 of 1295 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Russian)

Currently translated at 81.4% (386 of 474 strings)

Translated using Weblate (Russian)

Currently translated at 56.2% (728 of 1295 strings)

Translated using Weblate (Russian)

Currently translated at 65.3% (528 of 808 strings)

Translated using Weblate (Russian)

Currently translated at 50.4% (239 of 474 strings)

Co-authored-by: Artem Vladimirov <artyomka71@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ru/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
c00ea6a481 Translated using Weblate (Estonian)
Currently translated at 76.5% (111 of 145 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Estonian)

Currently translated at 28.3% (367 of 1295 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (129 of 129 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/objects/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/et/
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
a4c0aad206 Translated using Weblate (English)
Currently translated at 100.0% (1295 of 1295 strings)

Co-authored-by: Artem Vladimirov <artyomka71@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/en/
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
6aa2a010ce Translated using Weblate (Danish)
Currently translated at 68.0% (341 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Søren Niemann <niehans@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/da/
Translation: Frigate NVR/audio
2026-08-08 06:09:12 -05:00
5746f16472 Translated using Weblate (German)
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (German)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (German)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (German)

Currently translated at 99.9% (1294 of 1295 strings)

Translated using Weblate (German)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (German)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Michael Neuendorf <neuendorf@gonicus.de>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
24ab9460f5 Translated using Weblate (Telugu)
Currently translated at 3.8% (5 of 129 strings)

Translated using Weblate (Telugu)

Currently translated at 5.7% (29 of 501 strings)

Co-authored-by: Anil Surya Prakash <anilsuryaprakash@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/te/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/te/
Translation: Frigate NVR/audio
Translation: Frigate NVR/objects
2026-08-08 06:09:12 -05:00
9eb2c841a5 Translated using Weblate (Lithuanian)
Currently translated at 42.3% (548 of 1295 strings)

Translated using Weblate (Lithuanian)

Currently translated at 56.8% (62 of 109 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (239 of 239 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: MaBeniu <runnerm@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/lt/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-08-08 06:09:12 -05:00
300 changed files with 15461 additions and 1070 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ yell
sigh sigh
singing singing
choir choir
sodeling yodeling
chant chant
mantra mantra
child_singing child_singing
+1 -1
View File
@@ -256,7 +256,7 @@ The only field that is valid at the camera level is `enabled`.
#### Live transcription #### Live transcription
The single camera Live view in the Frigate UI supports live transcription of audio for streams defined with the `audio` role. Use the Enable/Disable Live Audio Transcription button/switch to toggle transcription processing. When speech is heard, the UI will display a black box over the top of the camera stream with text. The MQTT topic `frigate/<camera_name>/audio/transcription` will also be updated in real-time with transcribed text. The single camera Live view in the Frigate UI supports live transcription of audio for streams defined with the `audio` role. Use the Enable/Disable Live Audio Transcription button/switch to toggle transcription processing, or toggle it outside of the UI with the [`frigate/<camera_name>/audio_transcription/set`](/integrations/mqtt#frigatecamera_nameaudio_transcriptionset) MQTT topic or the HTTP API. When speech is heard, the UI will display a black box over the top of the camera stream with text. The MQTT topic `frigate/<camera_name>/audio/transcription` will also be updated in real-time with transcribed text.
Results can be error-prone due to a number of factors, including: Results can be error-prone due to a number of factors, including:
+25
View File
@@ -50,6 +50,31 @@ Connect each stream to get a live preview, an estimated bandwidth figure, and a
Other features, including [hardware acceleration](hardware_acceleration_video.md), [two way talk](/configuration/live#two-way-talk), and audio transcoding, is configured after the camera has been added. For camera model specific quirks, see the [camera specific](camera_specific.md) docs. Other features, including [hardware acceleration](hardware_acceleration_video.md), [two way talk](/configuration/live#two-way-talk), and audio transcoding, is configured after the camera has been added. For camera model specific quirks, see the [camera specific](camera_specific.md) docs.
## Deleting a camera
Click **Delete Camera** in <NavPath path="Settings > Global configuration > Camera management" />, choose the camera, and confirm. Deleting a camera requires the `admin` role and cannot be undone.
:::warning
Deleting a camera permanently removes its recordings, tracked objects, and configuration. If you only want to stop processing a camera, set its state to **Off** or **Disabled** in <NavPath path="Settings > Global configuration > Camera management" /> instead. See [camera state](/configuration/live#camera-state).
:::
Deleting a camera removes:
- The camera's section of your config file, along with its entries in any [role](authentication.md#user-roles) camera list. A custom role left with no cameras is removed as well.
- Every database record for the camera: tracked objects, review items, recordings, previews, timeline entries, the saved region grid, and [triggers](semantic_search.md#triggers).
- Every media file for the camera: recordings, snapshots, thumbnails, and preview clips.
[Exports](/usage/exports) are kept by default, so saved footage survives the deletion of the camera it came from. Turn on **Also delete exports for this camera** in the confirmation step to remove those too.
The camera's processes are stopped and the change takes effect immediately, so no restart is required. If the resulting config cannot be parsed, Frigate restores the previous config and reports an error instead of leaving Frigate in a broken state.
Two things are not cleaned up for you:
- **go2rtc streams.** Frigate makes a best effort to stop a running [go2rtc](go2rtc.md) stream named after the camera, but stream entries in your config file remain and are recreated on the next restart. Remove them in <NavPath path="Settings > System > go2rtc streams" /> or in your config file.
- **Camera groups.** A deleted camera stays listed in any [camera group](#setting-up-camera-groups) that referenced it. The group skips the missing camera, so this is harmless, but you can edit the group to drop the stale entry.
## Setting Up Camera Inputs ## Setting Up Camera Inputs
Several inputs can be configured for each camera and the role of each input can be mixed and matched based on your needs. This allows you to use a lower resolution stream for object detection, but create recordings from a higher resolution stream, or vice versa. Several inputs can be configured for each camera and the role of each input can be mixed and matched based on your needs. This allows you to use a lower resolution stream for object detection, but create recordings from a higher resolution stream, or vice versa.
+6 -2
View File
@@ -59,14 +59,18 @@ Running Generative AI models on CPU is not recommended, as high inference times
### Recommended Local Models ### Recommended Local Models
#### Vision models
You must use a vision-capable model with Frigate. The following models are recommended for local deployment of the `descriptions` and `chat` roles: You must use a vision-capable model with Frigate. The following models are recommended for local deployment of the `descriptions` and `chat` roles:
| Model | Notes | | Model | Notes |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qwen3-vl` | Strong visual and situational understanding, enhanced ability to identify smaller objects and interactions with object. | | `qwen3-vl` | Strong visual and situational understanding, enhanced ability to identify smaller objects and interactions with object. |
| `qwen3.6` | Strong situational understanding, but missing DeepStack from qwen3-vl leading to worse performance for identifying objects in people's hand and other small details. | | `qwen3.6`/`qwen3.8` | Strong situational understanding, but missing DeepStack from qwen3-vl leading to worse performance for identifying objects in people's hand and other small details. |
| `gemma4` | Strong situational understanding, sometimes resorts to more vague terms like 'interacts' instead of assigning a specific action. | | `gemma4` | Strong situational understanding, sometimes resorts to more vague terms like 'interacts' instead of assigning a specific action. |
#### Embedding models
The `embeddings` role needs a different kind of model. Text queries are matched against the stored image embeddings, so the model must be trained to place images and text into the same vector space. A chat or description model will still return vectors when asked, but those vectors are not trained for retrieval and text searches will return poor matches with no error to indicate why. The `embeddings` role needs a different kind of model. Text queries are matched against the stored image embeddings, so the model must be trained to place images and text into the same vector space. A chat or description model will still return vectors when asked, but those vectors are not trained for retrieval and text searches will return poor matches with no error to indicate why.
| Model | Notes | | Model | Notes |
+68 -2
View File
@@ -6,6 +6,7 @@ title: Notifications
import ConfigTabs from "@site/src/components/ConfigTabs"; import ConfigTabs from "@site/src/components/ConfigTabs";
import TabItem from "@theme/TabItem"; import TabItem from "@theme/TabItem";
import NavPath from "@site/src/components/NavPath"; import NavPath from "@site/src/components/NavPath";
import FaqItem from "@site/src/components/FaqItem";
# Notifications # Notifications
@@ -21,7 +22,7 @@ Push notifications require internet access from the Frigate server to the browse
In order to use notifications the following requirements must be met: In order to use notifications the following requirements must be met:
- Frigate must be accessed via a secure `https` connection ([see the authorization docs](/configuration/authentication)). - Frigate must be accessed via a secure `https` connection while signed in as a Frigate user ([see the authorization docs](/configuration/authentication)).
- A supported browser must be used. Currently Chrome, Firefox, and Safari are known to be supported. - A supported browser must be used. Currently Chrome, Firefox, and Safari are known to be supported.
- In order for notifications to be usable externally, Frigate must be accessible externally. - In order for notifications to be usable externally, Frigate must be accessible externally.
- For iOS devices, some users have also indicated that the Notifications switch needs to be enabled in iOS Settings --> Apps --> Safari --> Advanced --> Features. - For iOS devices, some users have also indicated that the Notifications switch needs to be enabled in iOS Settings --> Apps --> Safari --> Advanced --> Features.
@@ -85,7 +86,13 @@ cameras:
### Registration ### Registration
Once notifications are enabled, press the `Register for Notifications` button on all devices that you would like to receive notifications on. This will register the background worker. After this Frigate must be restarted and then notifications will begin to be sent. Once notifications are enabled, press the `Register This Device` button on all devices that you would like to receive notifications on. This will register the background worker. After this Frigate must be restarted and then notifications will begin to be sent.
:::warning
Each registration is attached to the Frigate user account you are signed in as, so you must register over a secure connection to the authenticated port (`8971`). Reverse proxies and tunnels should point at port `8971`.
:::
## Supported Notifications ## Supported Notifications
@@ -104,3 +111,62 @@ Different platforms handle notifications differently, some settings changes may
### Android ### Android
Most Android phones have battery optimization settings. To get reliable Notification delivery the browser (Chrome, Firefox) should have battery optimizations disabled. If Frigate is running as a PWA then the Frigate app should have battery optimizations disabled as well. Most Android phones have battery optimization settings. To get reliable Notification delivery the browser (Chrome, Firefox) should have battery optimizations disabled. If Frigate is running as a PWA then the Frigate app should have battery optimizations disabled as well.
## Notifications FAQ
<FaqItem id="how-do-i-debug-notifications-issues" question="How do I debug notifications issues?">
Push notifications involve Frigate, your browser, and your browser vendor's push service, so it helps to work from the server outward.
1. Enable debug logs for the push client by adding `frigate.comms.webpush: debug` to your `logger` configuration. Restart Frigate after this change.
```yaml
logger:
default: info
logs:
# highlight-next-line
frigate.comms.webpush: debug
```
These logs show exactly where a notification stopped, including:
- `Email must be provided for push notifications to be sent` means the global `email` field is empty and nothing will ever be sent.
- `Sending test notification` and `Sending push notification for <camera>, review ID <id>` mean Frigate handed the message off to the push service.
- `Skipping notification for <camera> - in global cooldown period` (or `camera-specific cooldown period`) means your [cooldown](#configuration) values suppressed it.
- `Notifications for <camera> are currently suspended` means notifications were suspended from <NavPath path="Settings > Notifications" /> or MQTT.
- `Notification endpoint expired for <user>, received 410` means that device's subscription is no longer valid and it must be re-registered.
- `Failed to send notification to <user> :: <status>` means the push service rejected the message. A `401` or `403` usually points at a VAPID or `email` problem, and a `5xx` is a problem on the push service's end.
- If you see no messages at all when an alert occurs, the notification was never queued. Confirm an actual **alert** was created (notifications are not sent for detections), and that notifications are enabled both globally and for that camera.
2. Verify the basics that most reports come down to:
- Frigate must be reached over `https` with a certificate your device trusts. Browsers silently refuse to register a service worker otherwise, and a self-signed certificate that is not installed as trusted on the device will fail.
- On iOS, notifications only work when Frigate has been installed to the Home Screen via **Share > Add to Home Screen** and opened from that icon. Safari and Chrome tabs cannot receive web push on iOS.
- Each device must be registered individually, and Frigate must be restarted after registering before anything can be sent, including test notifications.
- The Frigate server needs outbound internet access to the browser vendor's push service. See [Network Requirements](/frigate/network_requirements#push-notifications).
3. Test from the UI. Use the `Send a test notification` button in <NavPath path="Settings > Notifications" />. If the log shows `Sending test notification` but nothing arrives on the device, the problem is between the push service and your device rather than in Frigate.
4. Check the browser side on the device that is not receiving notifications:
- Confirm the site's notification permission is set to **Allow** in your browser or OS settings, and that a focus/do not disturb mode is not hiding them.
- In desktop browsers, open Developer Tools > Application > Service Workers and confirm `notifications-worker.js` is registered and activated. Unregistering it and registering the device again will rebuild a broken subscription.
- Check the browser console and your reverse proxy logs for failures loading `/notifications-worker.js` or errors on `/api/notifications/register`.
</FaqItem>
<FaqItem id="why-did-notifications-stop-arriving-after-working-for-a-while" question="Why did notifications stop arriving after working for a while?">
Push subscriptions are issued by the browser vendor and can be revoked, most often after a browser update, after clearing site data, or when a device has been offline for an extended period. When this happens the device still appears registered in Frigate, but the push service rejects the message. The debug logs will show `Notification endpoint expired` with a `404` or `410` status.
Unregister and re-register the affected device from <NavPath path="Settings > Notifications" />, then restart Frigate.
</FaqItem>
<FaqItem id="why-am-i-not-getting-notifications-for-one-specific-camera" question="Why am I not getting notifications for one specific camera?">
Work through these in order:
- Notifications are only sent for **alerts**. If the camera is producing detections instead, adjust the camera's `review > alerts > labels` so the objects you care about are classified as alerts.
- Confirm notifications are enabled for that camera in <NavPath path="Settings > Camera configuration > Notifications" />.
- Check the camera's `cooldown` value, and remember that the global cooldown applies across all cameras. A busy camera can consume the global cooldown and suppress a quieter one.
- If [authentication](/configuration/authentication) is enabled with roles, users only receive notifications for the cameras their role grants access to.
</FaqItem>
@@ -297,6 +297,14 @@ detectors:
::: :::
### Intel NPU host requirements {#intel-npu-requirements}
The NPU firmware is loaded by the host kernel and is not part of the Frigate image. Everything else the NPU needs is bundled in the container, so host NPU libraries should never be mounted in.
Frigate bundles a specific version of Intel's [linux-npu-driver](https://github.com/intel/linux-npu-driver/releases), and the host firmware must come from that release or a newer one. Firmware older than the bundled driver may fail with `MAPPED_INFERENCE_VERSION is NOT compatible with the ELF`, where `Expected` is the version the firmware supports and `received` is the version the bundled compiler produced. Distributions often package older firmware than the driver Frigate ships, so check the build date on the host with `sudo dmesg | grep -i vpu` and update it there if needed.
Intel NPUs cannot be used under Home Assistant OS, which does not include the NPU firmware.
### Configuration {#configuration-openvino} ### Configuration {#configuration-openvino}
<ModelConfigDropdown detectorTitle="OpenVINO" models={objectDetectorsModels.openvino.models} /> <ModelConfigDropdown detectorTitle="OpenVINO" models={objectDetectorsModels.openvino.models} />
+25
View File
@@ -121,6 +121,31 @@ cameras:
</TabItem> </TabItem>
</ConfigTabs> </ConfigTabs>
## Categorizing manual events
Events created with the [create manual event API](../integrations/api/create-event-events-camera-name-label-create-post.api.mdx) are categorized with the same label lists, using the label from the request path:
1. If alerts are enabled and the label is listed in `review -> alerts -> labels`, the review item is an alert.
2. Otherwise, if detections are enabled and the label is listed in `review -> detections -> labels`, the review item is a detection.
3. If the label is in neither list, the review item is an alert, or no review item is created if alerts are disabled.
This means manual events are alerts unless you explicitly list their label as a detection label. For example, to have PIR sensors create detections instead of alerts, post to `/api/events/front_door/pir_sensor/create` with the following config:
```yaml {5-7}
cameras:
front_door:
review:
detections:
labels:
- pir_sensor
```
:::note
Required zones do not apply to manual events, since they are created through the API rather than by the object tracker. Setting `review -> alerts -> labels` to an empty list also does not stop manual events from becoming alerts, as a label in neither list still falls back to an alert.
:::
## Restricting review items to specific zones ## Restricting review items to specific zones
By default a review item will be created if any `review -> alerts -> labels` and `review -> detections -> labels` are detected anywhere in the camera frame. You will likely want to configure review items to only be created when the object enters an area of interest, [see the zone docs for more information](./zones.md#restricting-alerts-and-detections-to-specific-zones) By default a review item will be created if any `review -> alerts -> labels` and `review -> detections -> labels` are detected anywhere in the camera frame. You will likely want to configure review items to only be created when the object enters an area of interest, [see the zone docs for more information](./zones.md#restricting-alerts-and-detections-to-specific-zones)
+25 -5
View File
@@ -292,7 +292,9 @@ Topic with the currently active profile name. Published value is the profile nam
### `frigate/notifications/set` ### `frigate/notifications/set`
Topic to turn notifications on and off. Expected values are `ON` and `OFF`. Topic to turn notifications on and off for all cameras. Expected values are `ON` and `OFF`.
Only available when notifications are enabled in the config. Not persisted across Frigate restarts.
### `frigate/notifications/state` ### `frigate/notifications/state`
@@ -308,6 +310,8 @@ Publishes the current health status of each role that is enabled (`audio`, `dete
- `offline`: Stream is offline and is being restarted - `offline`: Stream is offline and is being restarted
- `disabled`: Camera is currently turned off (either at runtime via the `enabled/set` topic, or persistently via the configuration file). See [Camera state](/configuration/live#camera-state) for the distinction. - `disabled`: Camera is currently turned off (either at runtime via the `enabled/set` topic, or persistently via the configuration file). See [Camera state](/configuration/live#camera-state) for the distinction.
These reflect the state of Frigate's process for that role, not the camera's reachability, so an unreachable camera alternates between `offline` and `online` as the watchdog restarts ffmpeg. Wait for the status to hold steady (for example with Home Assistant's `for:`) rather than acting on a single message.
### `frigate/<camera_name>/<object_name>` ### `frigate/<camera_name>/<object_name>`
Publishes the count of objects for the camera for use as a sensor in Home Assistant. Publishes the count of objects for the camera for use as a sensor in Home Assistant.
@@ -390,6 +394,18 @@ Topic to turn audio detection for a camera on and off. Expected values are `ON`
Topic with current state of audio detection for a camera. Published values are `ON` and `OFF`. Topic with current state of audio detection for a camera. Published values are `ON` and `OFF`.
### `frigate/<camera_name>/audio_transcription/set`
Topic to turn [live audio transcription](/configuration/audio_detectors#live-transcription) for a camera on and off. Expected values are `ON` and `OFF`. Transcribed text is published to `frigate/<camera_name>/audio/transcription`.
`ON` is ignored unless audio transcription is enabled in the config for the camera. Unlike the other camera toggles, this one is not persisted across Frigate restarts.
**NOTE:** Requires audio detection and transcription to be enabled
### `frigate/<camera_name>/audio_transcription/state`
Topic with current state of live audio transcription for a camera. Published values are `ON` and `OFF`.
### `frigate/<camera_name>/recordings/set` ### `frigate/<camera_name>/recordings/set`
Topic to turn recordings for a camera on and off. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)). Topic to turn recordings for a camera on and off. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)).
@@ -556,16 +572,20 @@ Topic with current state of the Birdseye mode for a camera. Published values are
### `frigate/<camera_name>/notifications/set` ### `frigate/<camera_name>/notifications/set`
Topic to turn notifications on and off. Expected values are `ON` and `OFF`. Topic to turn notifications for a camera on and off. Expected values are `ON` and `OFF`.
`ON` is ignored unless notifications are enabled in the config for the camera. This is not persisted across Frigate restarts. It is the same control the UI labels **Suspend until restart**.
### `frigate/<camera_name>/notifications/state` ### `frigate/<camera_name>/notifications/state`
Topic with current state of notifications. Published values are `ON` and `OFF`. Topic with current state of notifications. Published values are `ON` and `OFF`. This is the authoritative topic for whether a camera will notify.
### `frigate/<camera_name>/notifications/suspend` ### `frigate/<camera_name>/notifications/suspend`
Topic to suspend notifications for a certain number of minutes. Expected value is an integer. Topic to suspend notifications for a certain number of minutes. Expected value is an integer. Separate from `notifications/set`: it does not change `notifications/state`, and is ignored while notifications are off.
### `frigate/<camera_name>/notifications/suspended` ### `frigate/<camera_name>/notifications/suspended`
Topic with timestamp that notifications are suspended until. Published value is a UNIX timestamp, or 0 if notifications are not suspended. Topic with timestamp that notifications are suspended until. Published value is a UNIX timestamp, or 0 if there is no timed suspension.
`0` does not mean notifications are enabled: `notifications/set` `OFF` clears the timed suspension, so this publishes `0` while `notifications/state` is `OFF`.
+41 -1
View File
@@ -3,7 +3,31 @@ id: cpu
title: High CPU Usage title: High CPU Usage
--- ---
High CPU usage can impact Frigate's performance and responsiveness. This guide outlines the most effective configuration changes to help reduce CPU consumption and optimize resource usage. High CPU usage can impact Frigate's performance and responsiveness. This guide explains how to interpret the CPU values Frigate reports and outlines the most effective configuration changes to help reduce CPU consumption and optimize resource usage.
## Understanding Frigate's Reported CPU Usage
Frigate's CPU percentages often look much higher than what the host reports. Usually both numbers are correct and are simply measured against different denominators, so confirm you actually have a problem before tuning anything.
### Per-process values are relative to a single core
The values Frigate reports for FFmpeg, capture, detect, detector, and other processes follow the same convention as `top`: 100% means one CPU core is fully saturated, not that the whole system is saturated. A multithreaded process such as FFmpeg can legitimately report well over 100%.
Host and hypervisor tools instead report a percentage of the machine's total capacity across all cores. This includes `docker stats`, the `htop` summary, the Proxmox summary graph, the Unraid dashboard, Synology Resource Monitor, and Home Assistant's system monitor sensors. To reconcile the two:
```
host percentage ≈ (sum of Frigate's process percentages) / (number of cores)
```
On a 4 core system, an FFmpeg process reporting 100% is consuming one quarter of the machine, so the host will show roughly 25 to 30% once the remaining Frigate processes are included. That same 100% on a 16 core system is about 6%. Frigate's own warning thresholds use the per-core convention as well, so an FFmpeg process is flagged at 20% of a single core, not 20% of the system.
### Instantaneous samples and averages measure different things
Frigate collects stats every 15 seconds, and the `cpu` value covers only the interval since the previous collection. The `cpu_average` value in the stats API and MQTT payload is the average across the entire life of the process, and it is what the high CPU usage warnings are based on. Host dashboards generally plot data averaged over a longer window, so a single Frigate sample can show a peak that a host graph never displays. A process that has just started, such as FFmpeg after a camera reconnect, reports 0 until it has been sampled twice.
### The system-wide value depends on what the container can see
The system CPU value is read from `/proc/stat`. Under Docker that file belongs to the host, so the value covers the entire machine including workloads unrelated to Frigate, and it will not match `docker stats` for the Frigate container. Under an LXC container, lxcfs virtualizes `/proc/stat` and the value reflects only the cores assigned to the container. In a virtual machine, the guest sees only its assigned vCPUs while the hypervisor divides by every physical thread on the node, so guest and host percentages will not agree even when both are accurate.
## 1. Hardware Acceleration for Video Decoding ## 1. Hardware Acceleration for Video Decoding
@@ -72,3 +96,19 @@ The model you use significantly impacts detector performance. Frigate provides d
- Larger models (640x640): Slower inference, can sometimes have higher accuracy on very large objects that take up a majority of the frame. - Larger models (640x640): Slower inference, can sometimes have higher accuracy on very large objects that take up a majority of the frame.
For more detail on picking the right size, see [Choosing a model size](../configuration/object_detectors.md#choosing-a-model-size). For more detail on picking the right size, see [Choosing a model size](../configuration/object_detectors.md#choosing-a-model-size).
## 3. Reducing Detector CPU Usage
**Priority: High**
The **Detector CPU Usage** metric measures the CPU spent converting frames into the tensor format the model expects and post-processing the model's output. It does not include inference, so this value can be high even when you've configured a GPU, NPU, or Coral for object detection.
This metric scales with how many detections per second Frigate runs and how expensive each one is to prepare. Tuning [motion detection](../configuration/motion_detection) is usually the first recommendation to reduce the number of detections. Additionally, you can:
- **Lower `detect -> fps`.** 5 is the recommended value for nearly all cameras. Running at 10 doubles the frames eligible for detection and is one of the largest contributors to this metric.
- **Use a 320x320 model.** A 640x640 model has 4 times as many pixels to transpose, convert, and copy on every inference.
- **Prefer a model that takes integer input.** Models configured with `input_dtype: float` require each frame to be converted to float32 and normalized on the CPU first. Models taking `int` input, such as the tflite models used by the Edge TPU, skip that step.
- **Do not match the detect resolution to the model resolution.** The detect stream should match your camera's aspect ratio, for example `1280x720`, not the model's input size. Frigate crops and scales regions of motion itself, so an oversized detect stream only adds work.
- **Tune stationary object behavior.** Objects that never settle into a stationary state are re-detected continuously. Raising `detect -> stationary -> interval` reduces how often detection runs on objects that are already parked. See [stationary objects](../configuration/stationary_objects).
Adding [more detector instances](#multiple-detector-instances) spreads this work across more CPU cores, but does not reduce the total CPU used.
+16 -2
View File
@@ -65,9 +65,17 @@ This is because Frigate does not run in host mode so localhost points to the Fri
### How do I know if my camera is offline ### How do I know if my camera is offline
A camera being offline can be detected via MQTT or /api/stats, the camera_fps for any offline camera will be 0. Frigate publishes a per-role health status to [`frigate/<camera_name>/status/<role>`](/integrations/mqtt#frigatecamera_namestatusrole), where `<role>` is each enabled role on the camera (`detect`, `record`, and `audio`). The published value is one of:
Also, Home Assistant will mark any offline camera as being unavailable when the camera is offline. - `online`: Frigate's process for that role is running normally
- `offline`: the process is down and Frigate is restarting it
- `disabled`: the camera is turned off, either at runtime or in the configuration file
These reflect the state of Frigate's process for that role, not the camera's reachability, so an unreachable camera alternates between `offline` and `online` as the watchdog restarts ffmpeg. Wait for the status to hold steady (for example with Home Assistant's `for:`) rather than acting on a single message.
Because the status is per role, a camera whose substream is fine but whose recording stream has dropped will report `online` for `detect` and `offline` for `record`. The status is republished whenever it changes.
You can also detect an offline camera through `/api/stats`, where `camera_fps` will be 0.
### How can I view the Frigate log files without using the Web UI? ### How can I view the Frigate log files without using the Web UI?
@@ -125,6 +133,12 @@ cameras:
height: 720 height: 720
``` ```
### What is the `version` key in my config file?
`version` records the config format that your config was last migrated to. On startup Frigate compares it against the format the running version expects, and if it is older it copies your config to `/config/backup_config.yaml`, rewrites it to the new format, and updates `version` as the final step. A config with no `version` key is assumed to predate 0.14 and is migrated from there.
Frigate manages this key for you, so do not set or edit it. Raising it makes Frigate skip migrations your config still needs, and lowering it re-runs migrations against config that has already been converted. Either can leave you with a config that no longer validates.
### Why does Frigate keep creating new tracked objects for my parked car? ### Why does Frigate keep creating new tracked objects for my parked car?
Stationary tracking is designed to _prevent_ this: a parked car should remain a single tracked object rather than generating new ones. If you're repeatedly getting new tracked objects for the same car, it's likely that Frigate is losing the object and re-detecting it as a new one. Stationary tracking is designed to _prevent_ this: a parked car should remain a single tracked object rather than generating new ones. If you're repeatedly getting new tracked objects for the same car, it's likely that Frigate is losing the object and re-detecting it as a new one.
+6 -3
View File
@@ -2308,8 +2308,8 @@ paths:
$ref: '#/components/schemas/HTTPValidationError' $ref: '#/components/schemas/HTTPValidationError'
security: security:
- frigateUserAuth: [] - frigateUserAuth: []
x-required-role: any x-required-role: camera
description: '**Access:** Any authenticated user.' description: '**Access:** Authenticated user with access to the referenced camera.'
/review/summarize/start/{start_ts}/end/{end_ts}: /review/summarize/start/{start_ts}/end/{end_ts}:
post: post:
tags: tags:
@@ -5093,6 +5093,7 @@ paths:
NOTES: NOTES:
- Creating a manual event does not trigger an update to /events MQTT topic. - Creating a manual event does not trigger an update to /events MQTT topic.
- If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end. - If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end.
- The review item is an alert unless the label is listed in the camera's review -> detections -> labels config.
operationId: create_event_events__camera_name___label__create_post operationId: create_event_events__camera_name___label__create_post
parameters: parameters:
- name: camera_name - name: camera_name
@@ -7108,7 +7109,9 @@ paths:
schema: schema:
$ref: '#/components/schemas/DebugReplayStartResponse' $ref: '#/components/schemas/DebugReplayStartResponse'
'400': '400':
description: Invalid camera, time range, or no recordings description: Invalid camera or time range
'404':
description: No recordings in the requested time range
'409': '409':
description: A replay session is already active description: A replay session is already active
'422': '422':
+132 -63
View File
@@ -11,7 +11,6 @@ from typing import Any
import cv2 import cv2
from fastapi import APIRouter, Depends, Request, UploadFile from fastapi import APIRouter, Depends, Request, UploadFile
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pathvalidate import sanitize_filename
from peewee import DoesNotExist from peewee import DoesNotExist
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
@@ -43,12 +42,21 @@ from frigate.util.classification import (
write_training_metadata, write_training_metadata,
) )
from frigate.util.file import get_event_snapshot from frigate.util.file import get_event_snapshot
from frigate.util.path import safe_join, sanitize_path_component
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(tags=[Tags.classification]) router = APIRouter(tags=[Tags.classification])
def invalid_name_response(value: str) -> JSONResponse:
"""Response for a name that cannot be used as a path component."""
return JSONResponse(
content={"success": False, "message": f"Invalid name: {value}"},
status_code=400,
)
@router.get( @router.get(
"/faces", "/faces",
response_model=FacesResponse, response_model=FacesResponse,
@@ -98,9 +106,7 @@ def reclassify_face(request: Request, body: dict = None):
) )
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
training_file = os.path.join( training_file = safe_join(FACE_DIR, "train", json.get("training_file", ""))
FACE_DIR, f"train/{sanitize_filename(json.get('training_file', ''))}"
)
if not training_file or not os.path.isfile(training_file): if not training_file or not os.path.isfile(training_file):
return JSONResponse( return JSONResponse(
@@ -150,8 +156,10 @@ def train_face(request: Request, name: str, body: dict = None):
) )
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
training_file_name = sanitize_filename(json.get("training_file", "")) training_file_name = json.get("training_file", "")
training_file = os.path.join(FACE_DIR, f"train/{training_file_name}") training_file = (
safe_join(FACE_DIR, "train", training_file_name) if training_file_name else None
)
event_id = json.get("event_id") event_id = json.get("event_id")
if not training_file_name and not event_id: if not training_file_name and not event_id:
@@ -165,7 +173,9 @@ def train_face(request: Request, name: str, body: dict = None):
status_code=400, status_code=400,
) )
if training_file_name and not os.path.isfile(training_file): if training_file_name and (
training_file is None or not os.path.isfile(training_file)
):
return JSONResponse( return JSONResponse(
content=( content=(
{ {
@@ -176,9 +186,13 @@ def train_face(request: Request, name: str, body: dict = None):
status_code=404, status_code=404,
) )
sanitized_name = sanitize_filename(name) sanitized_name = sanitize_path_component(name)
new_file_folder = safe_join(FACE_DIR, name)
if sanitized_name is None or new_file_folder is None:
return invalid_name_response(name)
new_name = f"{sanitized_name}-{datetime.datetime.now().timestamp()}.webp" new_name = f"{sanitized_name}-{datetime.datetime.now().timestamp()}.webp"
new_file_folder = os.path.join(FACE_DIR, f"{sanitized_name}")
os.makedirs(new_file_folder, exist_ok=True) os.makedirs(new_file_folder, exist_ok=True)
@@ -261,9 +275,12 @@ async def create_face(request: Request, name: str):
content={"message": "Face recognition is not enabled.", "success": False}, content={"message": "Face recognition is not enabled.", "success": False},
) )
os.makedirs( face_folder = safe_join(FACE_DIR, name.replace(" ", "_"))
os.path.join(FACE_DIR, sanitize_filename(name.replace(" ", "_"))), exist_ok=True
) if face_folder is None:
return invalid_name_response(name)
os.makedirs(face_folder, exist_ok=True)
return JSONResponse( return JSONResponse(
status_code=200, status_code=200,
content={"success": False, "message": "Successfully created face folder."}, content={"success": False, "message": "Successfully created face folder."},
@@ -287,6 +304,9 @@ def register_face(request: Request, name: str, file: UploadFile):
content={"message": "Face recognition is not enabled.", "success": False}, content={"message": "Face recognition is not enabled.", "success": False},
) )
if sanitize_path_component(name) is None:
return invalid_name_response(name)
context: EmbeddingsContext = request.app.embeddings context: EmbeddingsContext = request.app.embeddings
result = None if context is None else context.register_face(name, file.file.read()) result = None if context is None else context.register_face(name, file.file.read())
@@ -356,8 +376,8 @@ def reclassify_face_image(request: Request, name: str, body: dict = None):
) )
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
image_id = sanitize_filename(json.get("id", "")) image_id = sanitize_path_component(json.get("id", ""))
new_name = sanitize_filename(json.get("new_name", "")) new_name = sanitize_path_component(json.get("new_name", ""))
if not image_id or not new_name: if not image_id or not new_name:
return JSONResponse( return JSONResponse(
@@ -381,7 +401,12 @@ def reclassify_face_image(request: Request, name: str, body: dict = None):
status_code=400, status_code=400,
) )
source_folder = os.path.join(FACE_DIR, sanitize_filename(name)) source_folder = safe_join(FACE_DIR, name)
target_folder = safe_join(FACE_DIR, new_name)
if source_folder is None or target_folder is None:
return invalid_name_response(name)
source_file = os.path.join(source_folder, image_id) source_file = os.path.join(source_folder, image_id)
if not os.path.isfile(source_file): if not os.path.isfile(source_file):
@@ -396,7 +421,6 @@ def reclassify_face_image(request: Request, name: str, body: dict = None):
) )
target_filename = f"{new_name}-{datetime.datetime.now().timestamp()}.webp" target_filename = f"{new_name}-{datetime.datetime.now().timestamp()}.webp"
target_folder = os.path.join(FACE_DIR, new_name)
os.makedirs(target_folder, exist_ok=True) os.makedirs(target_folder, exist_ok=True)
shutil.move(source_file, os.path.join(target_folder, target_filename)) shutil.move(source_file, os.path.join(target_folder, target_filename))
@@ -430,8 +454,19 @@ def deregister_faces(request: Request, name: str, body: DeleteFaceImagesBody):
content={"message": "Face recognition is not enabled.", "success": False}, content={"message": "Face recognition is not enabled.", "success": False},
) )
sanitized_name = sanitize_path_component(name)
if sanitized_name is None:
return invalid_name_response(name)
sanitized_ids = [
component
for component in map(sanitize_path_component, body.ids)
if component is not None
]
context: EmbeddingsContext = request.app.embeddings context: EmbeddingsContext = request.app.embeddings
context.delete_face_ids(name, map(lambda file: sanitize_filename(file), body.ids)) context.delete_face_ids(sanitized_name, sanitized_ids)
return JSONResponse( return JSONResponse(
content=({"success": True, "message": "Successfully deleted faces."}), content=({"success": True, "message": "Successfully deleted faces."}),
status_code=200, status_code=200,
@@ -642,7 +677,11 @@ def transcribe_audio(request: Request, body: AudioTranscriptionBody):
def get_classification_dataset(name: str): def get_classification_dataset(name: str):
dataset_dict: dict[str, list[str]] = {} dataset_dict: dict[str, list[str]] = {}
dataset_dir = os.path.join(CLIPS_DIR, sanitize_filename(name), "dataset") sanitized_name = sanitize_path_component(name)
dataset_dir = safe_join(CLIPS_DIR, name, "dataset")
if sanitized_name is None or dataset_dir is None:
return invalid_name_response(name)
if not os.path.exists(dataset_dir): if not os.path.exists(dataset_dir):
return JSONResponse( return JSONResponse(
@@ -664,8 +703,8 @@ def get_classification_dataset(name: str):
dataset_dict[category_name].append(file) dataset_dict[category_name].append(file)
# Get training metadata # Get training metadata
metadata = read_training_metadata(sanitize_filename(name)) metadata = read_training_metadata(sanitized_name)
current_image_count = get_dataset_image_count(sanitize_filename(name)) current_image_count = get_dataset_image_count(sanitized_name)
if metadata is None: if metadata is None:
training_metadata = { training_metadata = {
@@ -729,8 +768,8 @@ def get_custom_attributes(
if object_type is not None and object_type not in model_objects: if object_type is not None and object_type not in model_objects:
continue continue
dataset_dir = os.path.join(CLIPS_DIR, sanitize_filename(model_key), "dataset") dataset_dir = safe_join(CLIPS_DIR, model_key, "dataset")
if not os.path.exists(dataset_dir): if dataset_dir is None or not os.path.exists(dataset_dir):
continue continue
attributes = [] attributes = []
@@ -760,7 +799,10 @@ def get_custom_attributes(
The name must exist in the classification models. Returns a success message or an error if the name is invalid.""", The name must exist in the classification models. Returns a success message or an error if the name is invalid.""",
) )
def get_classification_images(name: str): def get_classification_images(name: str):
train_dir = os.path.join(CLIPS_DIR, sanitize_filename(name), "train") train_dir = safe_join(CLIPS_DIR, name, "train")
if train_dir is None:
return invalid_name_response(name)
if not os.path.exists(train_dir): if not os.path.exists(train_dir):
return JSONResponse(status_code=200, content=[]) return JSONResponse(status_code=200, content=[])
@@ -831,15 +873,17 @@ def delete_classification_dataset_images(
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
list_of_ids = json.get("ids", "") list_of_ids = json.get("ids", "")
folder = os.path.join( sanitized_name = sanitize_path_component(name)
CLIPS_DIR, sanitize_filename(name), "dataset", sanitize_filename(category) folder = safe_join(CLIPS_DIR, name, "dataset", category)
)
if sanitized_name is None or folder is None:
return invalid_name_response(name)
deleted_count = 0 deleted_count = 0
for id in list_of_ids: for id in list_of_ids:
file_path = os.path.join(folder, sanitize_filename(id)) file_path = safe_join(folder, id)
if os.path.isfile(file_path): if file_path and os.path.isfile(file_path):
os.unlink(file_path) os.unlink(file_path)
deleted_count += 1 deleted_count += 1
@@ -850,7 +894,6 @@ def delete_classification_dataset_images(
# This ensures the dataset is marked as changed after deletion # This ensures the dataset is marked as changed after deletion
# (even if the total count happens to be the same after adding and deleting) # (even if the total count happens to be the same after adding and deleting)
if deleted_count > 0: if deleted_count > 0:
sanitized_name = sanitize_filename(name)
metadata = read_training_metadata(sanitized_name) metadata = read_training_metadata(sanitized_name)
if metadata: if metadata:
last_count = metadata.get("last_training_image_count", 0) last_count = metadata.get("last_training_image_count", 0)
@@ -888,8 +931,8 @@ def reclassify_classification_image(
) )
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
image_id = sanitize_filename(json.get("id", "")) image_id = sanitize_path_component(json.get("id", ""))
new_category = sanitize_filename(json.get("new_category", "")) new_category = sanitize_path_component(json.get("new_category", ""))
if not image_id or not new_category: if not image_id or not new_category:
return JSONResponse( return JSONResponse(
@@ -913,10 +956,13 @@ def reclassify_classification_image(
status_code=400, status_code=400,
) )
sanitized_name = sanitize_filename(name) sanitized_name = sanitize_path_component(name)
source_folder = os.path.join( source_folder = safe_join(CLIPS_DIR, name, "dataset", category)
CLIPS_DIR, sanitized_name, "dataset", sanitize_filename(category) target_folder = safe_join(CLIPS_DIR, name, "dataset", new_category)
)
if sanitized_name is None or source_folder is None or target_folder is None:
return invalid_name_response(name)
source_file = os.path.join(source_folder, image_id) source_file = os.path.join(source_folder, image_id)
if not os.path.isfile(source_file): if not os.path.isfile(source_file):
@@ -933,7 +979,6 @@ def reclassify_classification_image(
random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
timestamp = datetime.datetime.now().timestamp() timestamp = datetime.datetime.now().timestamp()
new_name = f"{new_category}-{timestamp}-{random_id}.png" new_name = f"{new_category}-{timestamp}-{random_id}.png"
target_folder = os.path.join(CLIPS_DIR, sanitized_name, "dataset", new_category)
os.makedirs(target_folder, exist_ok=True) os.makedirs(target_folder, exist_ok=True)
@@ -983,7 +1028,7 @@ def rename_classification_category(
) )
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
new_category = sanitize_filename(json.get("new_category", "")) new_category = sanitize_path_component(json.get("new_category", ""))
if not new_category: if not new_category:
return JSONResponse( return JSONResponse(
@@ -996,12 +1041,12 @@ def rename_classification_category(
status_code=400, status_code=400,
) )
old_folder = os.path.join( sanitized_name = sanitize_path_component(name)
CLIPS_DIR, sanitize_filename(name), "dataset", sanitize_filename(old_category) old_folder = safe_join(CLIPS_DIR, name, "dataset", old_category)
) new_folder = safe_join(CLIPS_DIR, name, "dataset", new_category)
new_folder = os.path.join(
CLIPS_DIR, sanitize_filename(name), "dataset", new_category if sanitized_name is None or old_folder is None or new_folder is None:
) return invalid_name_response(name)
if not os.path.exists(old_folder): if not os.path.exists(old_folder):
return JSONResponse( return JSONResponse(
@@ -1030,7 +1075,6 @@ def rename_classification_category(
# Mark dataset as ready to train by resetting training metadata # Mark dataset as ready to train by resetting training metadata
# This ensures the dataset is marked as changed after renaming # This ensures the dataset is marked as changed after renaming
sanitized_name = sanitize_filename(name)
write_training_metadata(sanitized_name, 0) write_training_metadata(sanitized_name, 0)
return JSONResponse( return JSONResponse(
@@ -1078,13 +1122,20 @@ def categorize_classification_image(request: Request, name: str, body: dict = No
) )
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
category = sanitize_filename(json.get("category", "")) category = sanitize_path_component(json.get("category", ""))
training_file_name = sanitize_filename(json.get("training_file", "")) training_file_name = json.get("training_file", "")
training_file = os.path.join( training_file = (
CLIPS_DIR, sanitize_filename(name), "train", training_file_name safe_join(CLIPS_DIR, name, "train", training_file_name)
if training_file_name
else None
) )
if training_file_name and not os.path.isfile(training_file): if category is None:
return invalid_name_response(json.get("category", ""))
if training_file_name and (
training_file is None or not os.path.isfile(training_file)
):
return JSONResponse( return JSONResponse(
content=( content=(
{ {
@@ -1098,9 +1149,10 @@ def categorize_classification_image(request: Request, name: str, body: dict = No
random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
timestamp = datetime.datetime.now().timestamp() timestamp = datetime.datetime.now().timestamp()
new_name = f"{category}-{timestamp}-{random_id}.png" new_name = f"{category}-{timestamp}-{random_id}.png"
new_file_folder = os.path.join( new_file_folder = safe_join(CLIPS_DIR, name, "dataset", category)
CLIPS_DIR, sanitize_filename(name), "dataset", category
) if new_file_folder is None:
return invalid_name_response(name)
os.makedirs(new_file_folder, exist_ok=True) os.makedirs(new_file_folder, exist_ok=True)
@@ -1138,9 +1190,10 @@ def create_classification_category(request: Request, name: str, category: str):
status_code=404, status_code=404,
) )
category_folder = os.path.join( category_folder = safe_join(CLIPS_DIR, name, "dataset", category)
CLIPS_DIR, sanitize_filename(name), "dataset", sanitize_filename(category)
) if category_folder is None:
return invalid_name_response(category)
os.makedirs(category_folder, exist_ok=True) os.makedirs(category_folder, exist_ok=True)
@@ -1179,12 +1232,15 @@ def delete_classification_train_images(request: Request, name: str, body: dict =
json: dict[str, Any] = body or {} json: dict[str, Any] = body or {}
list_of_ids = json.get("ids", "") list_of_ids = json.get("ids", "")
folder = os.path.join(CLIPS_DIR, sanitize_filename(name), "train") folder = safe_join(CLIPS_DIR, name, "train")
if folder is None:
return invalid_name_response(name)
for id in list_of_ids: for id in list_of_ids:
file_path = os.path.join(folder, sanitize_filename(id)) file_path = safe_join(folder, id)
if os.path.isfile(file_path): if file_path and os.path.isfile(file_path):
os.unlink(file_path) os.unlink(file_path)
return JSONResponse( return JSONResponse(
@@ -1201,7 +1257,11 @@ def delete_classification_train_images(request: Request, name: str, body: dict =
) )
async def generate_state_examples(request: Request, body: GenerateStateExamplesBody): async def generate_state_examples(request: Request, body: GenerateStateExamplesBody):
"""Generate examples for state classification.""" """Generate examples for state classification."""
model_name = sanitize_filename(body.model_name) model_name = sanitize_path_component(body.model_name)
if model_name is None:
return invalid_name_response(body.model_name)
cameras_normalized = { cameras_normalized = {
camera_name: tuple(crop) camera_name: tuple(crop)
for camera_name, crop in body.cameras.items() for camera_name, crop in body.cameras.items()
@@ -1224,7 +1284,11 @@ async def generate_state_examples(request: Request, body: GenerateStateExamplesB
) )
async def generate_object_examples(request: Request, body: GenerateObjectExamplesBody): async def generate_object_examples(request: Request, body: GenerateObjectExamplesBody):
"""Generate examples for object classification.""" """Generate examples for object classification."""
model_name = sanitize_filename(body.model_name) model_name = sanitize_path_component(body.model_name)
if model_name is None:
return invalid_name_response(body.model_name)
collect_object_classification_examples(model_name, body.label) collect_object_classification_examples(model_name, body.label)
return JSONResponse( return JSONResponse(
@@ -1243,10 +1307,16 @@ async def generate_object_examples(request: Request, body: GenerateObjectExample
Returns a success message.""", Returns a success message.""",
) )
def delete_classification_model(request: Request, name: str): def delete_classification_model(request: Request, name: str):
sanitized_name = sanitize_filename(name) # This endpoint intentionally accepts models that are not in the config, so
# there is no allow list to fall back on. Both paths below are recursive
# deletes, so an unusable name has to be rejected outright.
data_dir = safe_join(CLIPS_DIR, name)
model_dir = safe_join(MODEL_CACHE_DIR, name)
if data_dir is None or model_dir is None:
return invalid_name_response(name)
# Delete the classification model's data directory in clips # Delete the classification model's data directory in clips
data_dir = os.path.join(CLIPS_DIR, sanitized_name)
if os.path.exists(data_dir): if os.path.exists(data_dir):
try: try:
shutil.rmtree(data_dir) shutil.rmtree(data_dir)
@@ -1255,7 +1325,6 @@ def delete_classification_model(request: Request, name: str):
logger.debug(f"Failed to delete data directory for {name}: {e}") logger.debug(f"Failed to delete data directory for {name}: {e}")
# Delete the classification model's files in model_cache # Delete the classification model's files in model_cache
model_dir = os.path.join(MODEL_CACHE_DIR, sanitized_name)
if os.path.exists(model_dir): if os.path.exists(model_dir):
try: try:
shutil.rmtree(model_dir) shutil.rmtree(model_dir)
+11 -1
View File
@@ -13,6 +13,7 @@ from frigate.api.auth import require_role
from frigate.api.defs.tags import Tags from frigate.api.defs.tags import Tags
from frigate.jobs.debug_replay import ( from frigate.jobs.debug_replay import (
ExportDebugReplaySource, ExportDebugReplaySource,
NoRecordingsError,
RecordingDebugReplaySource, RecordingDebugReplaySource,
start_debug_replay_job, start_debug_replay_job,
) )
@@ -74,7 +75,8 @@ class DebugReplayStopResponse(BaseModel):
response_model=DebugReplayStartResponse, response_model=DebugReplayStartResponse,
status_code=202, status_code=202,
responses={ responses={
400: {"description": "Invalid camera, time range, or no recordings"}, 400: {"description": "Invalid camera or time range"},
404: {"description": "No recordings in the requested time range"},
409: {"description": "A replay session is already active"}, 409: {"description": "A replay session is already active"},
}, },
dependencies=[Depends(require_role(["admin"]))], dependencies=[Depends(require_role(["admin"]))],
@@ -113,6 +115,14 @@ async def start_debug_replay(request: Request, body: DebugReplayStartBody):
}, },
status_code=409, status_code=409,
) )
except NoRecordingsError:
return JSONResponse(
content={
"success": False,
"message": "No recordings found in the selected time range",
},
status_code=404,
)
except ValueError: except ValueError:
logger.exception("Rejected debug replay start request") logger.exception("Rejected debug replay start request")
return JSONResponse( return JSONResponse(
+41 -36
View File
@@ -16,7 +16,6 @@ import numpy as np
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.params import Depends from fastapi.params import Depends
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pathvalidate import sanitize_filename
from peewee import JOIN, DoesNotExist, fn, operator from peewee import JOIN, DoesNotExist, fn, operator
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
@@ -56,11 +55,12 @@ from frigate.api.defs.response.generic_response import GenericResponse
from frigate.api.defs.tags import Tags from frigate.api.defs.tags import Tags
from frigate.comms.event_metadata_updater import EventMetadataTypeEnum from frigate.comms.event_metadata_updater import EventMetadataTypeEnum
from frigate.config.classification import ObjectClassificationType from frigate.config.classification import ObjectClassificationType
from frigate.const import CLIPS_DIR, TRIGGER_DIR from frigate.const import CLIPS_DIR
from frigate.embeddings import EmbeddingsContext from frigate.embeddings import EmbeddingsContext
from frigate.models import Event, ReviewSegment, Timeline, Trigger from frigate.models import Event, ReviewSegment, Timeline, Trigger
from frigate.track.object_processing import TrackedObject from frigate.track.object_processing import TrackedObject
from frigate.util.file import get_event_thumbnail_bytes, load_event_snapshot_image from frigate.util.file import get_event_thumbnail_bytes, load_event_snapshot_image
from frigate.util.path import get_trigger_thumbnail_path, safe_join
from frigate.util.time import get_dst_transitions, get_tz_modifiers from frigate.util.time import get_dst_transitions, get_tz_modifiers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1452,10 +1452,10 @@ async def set_attributes(
continue continue
# Get available labels from dataset directory # Get available labels from dataset directory
dataset_dir = os.path.join(CLIPS_DIR, sanitize_filename(model_key), "dataset") dataset_dir = safe_join(CLIPS_DIR, model_key, "dataset")
available_labels = set() available_labels = set()
if os.path.exists(dataset_dir): if dataset_dir and os.path.exists(dataset_dir):
for category_name in os.listdir(dataset_dir): for category_name in os.listdir(dataset_dir):
category_dir = os.path.join(dataset_dir, category_name) category_dir = os.path.join(dataset_dir, category_name)
if os.path.isdir(category_dir): if os.path.isdir(category_dir):
@@ -1748,6 +1748,7 @@ async def delete_events(request: Request, body: EventsDeleteBody):
NOTES: NOTES:
- Creating a manual event does not trigger an update to /events MQTT topic. - Creating a manual event does not trigger an update to /events MQTT topic.
- If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end. - If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end.
- The review item is an alert unless the label is listed in the camera's review -> detections -> labels config.
""", """,
) )
def create_event( def create_event(
@@ -1958,18 +1959,13 @@ def create_trigger_embedding(
if body.type == "thumbnail": if body.type == "thumbnail":
# Save image to the triggers directory # Save image to the triggers directory
try: try:
os.makedirs( webp_path = get_trigger_thumbnail_path(camera_name, body.data)
os.path.join(TRIGGER_DIR, sanitize_filename(camera_name)),
exist_ok=True, if webp_path is None:
) raise ValueError(f"Invalid trigger thumbnail path for {body.data}")
with open(
os.path.join( os.makedirs(os.path.dirname(webp_path), exist_ok=True)
TRIGGER_DIR, with open(webp_path, "wb") as f:
sanitize_filename(camera_name),
f"{sanitize_filename(body.data)}.webp",
),
"wb",
) as f:
f.write(thumbnail) f.write(thumbnail)
logger.debug( logger.debug(
f"Writing thumbnail for trigger with data {body.data} in {camera_name}." f"Writing thumbnail for trigger with data {body.data} in {camera_name}."
@@ -2041,9 +2037,15 @@ def update_trigger_embedding(
if body.type == "description": if body.type == "description":
embedding = context.generate_description_embedding(body.data) embedding = context.generate_description_embedding(body.data)
elif body.type == "thumbnail": elif body.type == "thumbnail":
webp_file = sanitize_filename(body.data) + ".webp" webp_path = get_trigger_thumbnail_path(camera_name, body.data)
webp_path = os.path.join(
TRIGGER_DIR, sanitize_filename(camera_name), webp_file if webp_path is None:
return JSONResponse(
content={
"success": False,
"message": f"Invalid data for {body.type} trigger",
},
status_code=400,
) )
try: try:
@@ -2101,13 +2103,14 @@ def update_trigger_embedding(
# Update existing trigger # Update existing trigger
if trigger.data != body.data: # Delete old thumbnail only if data changes if trigger.data != body.data: # Delete old thumbnail only if data changes
try: try:
os.remove( old_path = get_trigger_thumbnail_path(camera_name, trigger.data)
os.path.join(
TRIGGER_DIR, if old_path is None:
sanitize_filename(camera_name), raise ValueError(
f"{trigger.data}.webp", f"Invalid trigger thumbnail path for {trigger.data}"
)
) )
os.remove(old_path)
logger.debug( logger.debug(
f"Deleted thumbnail for trigger with data {trigger.data} in {camera_name}." f"Deleted thumbnail for trigger with data {trigger.data} in {camera_name}."
) )
@@ -2141,12 +2144,13 @@ def update_trigger_embedding(
if body.type == "thumbnail": if body.type == "thumbnail":
# Save image to the triggers directory # Save image to the triggers directory
try: try:
camera_path = os.path.join(TRIGGER_DIR, sanitize_filename(camera_name)) thumbnail_path = get_trigger_thumbnail_path(camera_name, body.data)
os.makedirs(camera_path, exist_ok=True)
with open( if thumbnail_path is None:
os.path.join(camera_path, f"{sanitize_filename(body.data)}.webp"), raise ValueError(f"Invalid trigger thumbnail path for {body.data}")
"wb",
) as f: os.makedirs(os.path.dirname(thumbnail_path), exist_ok=True)
with open(thumbnail_path, "wb") as f:
f.write(thumbnail) f.write(thumbnail)
logger.debug( logger.debug(
f"Writing thumbnail for trigger with data {body.data} in {camera_name}." f"Writing thumbnail for trigger with data {body.data} in {camera_name}."
@@ -2217,11 +2221,12 @@ def delete_trigger_embedding(
) )
try: try:
os.remove( thumbnail_path = get_trigger_thumbnail_path(camera_name, trigger.data)
os.path.join(
TRIGGER_DIR, sanitize_filename(camera_name), f"{trigger.data}.webp" if thumbnail_path is None:
) raise ValueError(f"Invalid trigger thumbnail path for {trigger.data}")
)
os.remove(thumbnail_path)
logger.debug( logger.debug(
f"Deleted thumbnail for trigger with data {trigger.data} in {camera_name}." f"Deleted thumbnail for trigger with data {trigger.data} in {camera_name}."
) )
+6 -11
View File
@@ -13,7 +13,7 @@ from pathlib import Path
import psutil import psutil
from fastapi import APIRouter, Depends, Query, Request from fastapi import APIRouter, Depends, Query, Request
from fastapi.responses import JSONResponse, StreamingResponse from fastapi.responses import JSONResponse, StreamingResponse
from pathvalidate import sanitize_filename, sanitize_filepath from pathvalidate import sanitize_filename
from peewee import DoesNotExist from peewee import DoesNotExist
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
@@ -72,6 +72,7 @@ from frigate.record.export import (
PlaybackSourceEnum, PlaybackSourceEnum,
validate_ffmpeg_args, validate_ffmpeg_args,
) )
from frigate.util.path import sanitize_contained_path
from frigate.util.time import is_current_hour from frigate.util.time import is_current_hour
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -129,18 +130,12 @@ def _validate_export_case(export_case_id: str | None) -> JSONResponse | None:
def _sanitize_existing_image( def _sanitize_existing_image(
image_path: str | None, image_path: str | None,
) -> tuple[str | None, JSONResponse | None]: ) -> tuple[str | None, JSONResponse | None]:
# sanitize_filepath normalizes "\" to "/" but leaves ".." intact, so a path if not image_path:
# like "clips\..\..\etc/passwd" passes the CLIPS_DIR prefix check yet still return None, None
# escapes the directory once resolved. A valid snapshot path never uses "..".
if image_path and ".." in image_path:
return None, JSONResponse(
content={"success": False, "message": "Invalid image path"},
status_code=400,
)
existing_image = sanitize_filepath(image_path) if image_path else None existing_image = sanitize_contained_path(image_path, CLIPS_DIR)
if existing_image and not existing_image.startswith(CLIPS_DIR): if existing_image is None:
return None, JSONResponse( return None, JSONResponse(
content={"success": False, "message": "Invalid image path"}, content={"success": False, "message": "Invalid image path"},
status_code=400, status_code=400,
+16 -1
View File
@@ -53,6 +53,7 @@ from frigate.util.file import (
) )
from frigate.util.image import get_image_from_recording, get_image_quality_params from frigate.util.image import get_image_from_recording, get_image_quality_params
from frigate.util.media import get_keyframe_before from frigate.util.media import get_keyframe_before
from frigate.util.object import create_empty_regions_grid
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1083,7 +1084,21 @@ def clear_region_grid(request: Request, camera_name: str):
status_code=404, status_code=404,
) )
Regions.delete().where(Regions.camera == camera_name).execute() # store an empty grid instead of deleting the row so the grid is
# rebuilt from newly tracked objects and not from all past history
region = {
Regions.camera: camera_name,
Regions.grid: create_empty_regions_grid(),
Regions.last_update: datetime.now().timestamp(),
}
(
Regions.insert(region)
.on_conflict(
conflict_target=[Regions.camera],
update=region,
)
.execute()
)
return JSONResponse( return JSONResponse(
content={"success": True, "message": "Region grid cleared"}, content={"success": True, "message": "Region grid cleared"},
) )
+3
View File
@@ -709,6 +709,7 @@ async def get_review(request: Request, review_id: str):
dependencies=[Depends(allow_any_authenticated())], dependencies=[Depends(allow_any_authenticated())],
) )
async def set_not_reviewed( async def set_not_reviewed(
request: Request,
review_id: str, review_id: str,
current_user: dict = Depends(get_current_user), current_user: dict = Depends(get_current_user),
): ):
@@ -727,6 +728,8 @@ async def set_not_reviewed(
status_code=404, status_code=404,
) )
await require_camera_access(review.camera, request=request)
try: try:
user_review = UserReviewStatus.get( user_review = UserReviewStatus.get(
UserReviewStatus.user_id == user_id, UserReviewStatus.user_id == user_id,
+2 -14
View File
@@ -103,22 +103,10 @@ class FrigateApp:
self.detection_shms: list[mp.shared_memory.SharedMemory] = [] self.detection_shms: list[mp.shared_memory.SharedMemory] = []
self.log_queue: Queue = mp.Queue() self.log_queue: Queue = mp.Queue()
self.camera_metrics: DictProxy = self.metrics_manager.dict() self.camera_metrics: DictProxy = self.metrics_manager.dict()
self.embeddings_metrics: DataProcessorMetrics | None = (
DataProcessorMetrics( self.embeddings_metrics = DataProcessorMetrics(
self.metrics_manager, list(config.classification.custom.keys()) self.metrics_manager, list(config.classification.custom.keys())
) )
if (
config.semantic_search.enabled
or any(
c.objects.genai.enabled or c.review.genai.enabled
for c in config.cameras.values()
)
or config.lpr.enabled
or config.face_recognition.enabled
or len(config.classification.custom) > 0
)
else None
)
self.ptz_metrics: dict[str, PTZMetrics] = {} self.ptz_metrics: dict[str, PTZMetrics] = {}
self.processes: dict[str, int] = {} self.processes: dict[str, int] = {}
self.embeddings: EmbeddingsContext | None = None self.embeddings: EmbeddingsContext | None = None
+6
View File
@@ -77,6 +77,11 @@ class MqttClient(Communicator):
"ON" if camera.audio.enabled_in_config else "OFF", "ON" if camera.audio.enabled_in_config else "OFF",
retain=True, retain=True,
) )
self.publish(
f"{camera_name}/audio_transcription/state",
"ON" if camera.audio_transcription.live_enabled else "OFF",
retain=True,
)
self.publish( self.publish(
f"{camera_name}/detect/state", f"{camera_name}/detect/state",
"ON" if camera.detect.enabled else "OFF", "ON" if camera.detect.enabled else "OFF",
@@ -258,6 +263,7 @@ class MqttClient(Communicator):
"snapshots", "snapshots",
"detect", "detect",
"audio", "audio",
"audio_transcription",
"motion", "motion",
"improve_contrast", "improve_contrast",
"ptz_autotracker", "ptz_autotracker",
+5 -1
View File
@@ -89,7 +89,9 @@ class WebPushClient(Communicator):
# notification and auth config updater # notification and auth config updater
self.global_config_subscriber = ConfigSubscriber("config/") self.global_config_subscriber = ConfigSubscriber("config/")
self.config_subscriber = CameraConfigUpdateSubscriber( self.config_subscriber = CameraConfigUpdateSubscriber(
self.config, self.config.cameras, [CameraConfigUpdateEnum.notifications] self.config,
self.config.cameras,
[CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.notifications],
) )
self._refresh_user_cameras() self._refresh_user_cameras()
@@ -213,6 +215,8 @@ class WebPushClient(Communicator):
self.suspended_cameras[camera] = 0 self.suspended_cameras[camera] = 0
self.last_camera_notification_time[camera] = 0 self.last_camera_notification_time[camera] = 0
self._refresh_user_cameras()
if topic == "reviews": if topic == "reviews":
decoded = json.loads(payload) decoded = json.loads(payload)
camera = decoded["before"]["camera"] camera = decoded["before"]["camera"]
@@ -1172,6 +1172,28 @@ class LicensePlateProcessingMixin:
return rep["plate"], rep["conf"], rep["char_confidences"], rep["area"] return rep["plate"], rep["conf"], rep["char_confidences"], rep["area"]
def _passes_plate_filters(self, camera: str, plate: str) -> bool:
"""Check a plate against the configured length and format filters."""
if len(plate) < self.lpr_config.min_plate_length:
logger.debug(
f"{camera}: Filtered out plate '{plate}' due to length ({len(plate)} < {self.lpr_config.min_plate_length})"
)
return False
if self.lpr_config.format:
try:
if not re.fullmatch(self.lpr_config.format, plate):
logger.debug(
f"{camera}: Filtered out plate '{plate}' due to format mismatch"
)
return False
except re.error:
logger.error(
f"{camera}: Invalid regex in LPR format configuration: {self.lpr_config.format}"
)
return True
def _generate_plate_event(self, camera: str, plate: str, plate_score: float) -> str: def _generate_plate_event(self, camera: str, plate: str, plate_score: float) -> str:
"""Generate a unique ID for a plate event based on camera and text.""" """Generate a unique ID for a plate event based on camera and text."""
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
@@ -1511,10 +1533,14 @@ class LicensePlateProcessingMixin:
plate_id = None plate_id = None
for existing_id, data in self.detected_license_plates.items(): for existing_id, data in self.detected_license_plates.items():
# entries from the object pipeline on this camera have no
# last_seen until they pass the filters below
last_seen = data.get("last_seen")
if ( if (
data["camera"] == camera data["camera"] == camera
and data["last_seen"] is not None and last_seen is not None
and current_time - data["last_seen"] and current_time - last_seen
<= self.config.cameras[camera].lpr.expire_time <= self.config.cameras[camera].lpr.expire_time
): ):
similarity = JaroWinkler.similarity(data["plate"], top_plate) similarity = JaroWinkler.similarity(data["plate"], top_plate)
@@ -1525,6 +1551,11 @@ class LicensePlateProcessingMixin:
) )
break break
if plate_id is None: if plate_id is None:
# the event id doubles as the cluster key, so a plate rejected
# after this point would leave an entry that never expires
if not self._passes_plate_filters(camera, top_plate):
return
plate_id = self._generate_plate_event(camera, top_plate, avg_confidence) plate_id = self._generate_plate_event(camera, top_plate, avg_confidence)
logger.debug( logger.debug(
f"{camera}: New plate event for dedicated LPR camera {plate_id}: {top_plate}" f"{camera}: New plate event for dedicated LPR camera {plate_id}: {top_plate}"
@@ -1569,27 +1600,12 @@ class LicensePlateProcessingMixin:
f"{camera}: Clustering changed top plate '{top_plate}' (conf: {avg_confidence:.3f}) to rep '{rep_plate}' (conf: {rep_conf:.3f})" f"{camera}: Clustering changed top plate '{top_plate}' (conf: {avg_confidence:.3f}) to rep '{rep_plate}' (conf: {rep_conf:.3f})"
) )
# Apply length and format filters to the clustered representative # filter the clustered representative rather than individual OCR
# rather than individual OCR readings, so noisy variants still # readings, so noisy variants still contribute to clustering even
# contribute to clustering even when they don't pass on their own. # when they don't pass on their own
if len(rep_plate) < self.lpr_config.min_plate_length: if not self._passes_plate_filters(camera, rep_plate):
logger.debug(
f"{camera}: Filtered out clustered plate '{rep_plate}' due to length ({len(rep_plate)} < {self.lpr_config.min_plate_length})"
)
return return
if self.lpr_config.format:
try:
if not re.fullmatch(self.lpr_config.format, rep_plate):
logger.debug(
f"{camera}: Filtered out clustered plate '{rep_plate}' due to format mismatch"
)
return
except re.error:
logger.error(
f"{camera}: Invalid regex in LPR format configuration: {self.lpr_config.format}"
)
# Update stored rep # Update stored rep
self.detected_license_plates[id].update( self.detected_license_plates[id].update(
{ {
@@ -63,8 +63,10 @@ class ObjectDescriptionProcessor(PostProcessorApi):
"""Handle an update to a frame for an object.""" """Handle an update to a frame for an object."""
camera_config = self.config.cameras[camera] camera_config = self.config.cameras[camera]
# no need to save our own thumbnails if genai is not enabled if not camera_config.objects.genai.enabled:
# or if the object has become stationary return
# no need to save our own thumbnails if the object has become stationary
if not data["stationary"]: if not data["stationary"]:
if data["id"] not in self.tracked_events: if data["id"] not in self.tracked_events:
self.tracked_events[data["id"]] = [] self.tracked_events[data["id"]] = []
+11 -2
View File
@@ -28,6 +28,7 @@ from frigate.data_processing.common.face.model import (
from frigate.types import TrackedObjectUpdateTypesEnum from frigate.types import TrackedObjectUpdateTypesEnum
from frigate.util.builtin import EventsPerSecond, InferenceSpeed from frigate.util.builtin import EventsPerSecond, InferenceSpeed
from frigate.util.image import area from frigate.util.image import area
from frigate.util.path import safe_join, sanitize_path_component
from ..types import DataProcessorMetrics from ..types import DataProcessorMetrics
from .api import RealTimeProcessorApi from .api import RealTimeProcessorApi
@@ -409,9 +410,17 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
) )
# write face to library # write face to library
folder = os.path.join(FACE_DIR, label) sanitized_label = sanitize_path_component(label)
folder = safe_join(FACE_DIR, label)
if sanitized_label is None or folder is None:
return {
"message": f"Invalid face name: {label}",
"success": False,
}
file = os.path.join( file = os.path.join(
folder, f"{label}_{datetime.datetime.now().timestamp()}.webp" folder, f"{sanitized_label}_{datetime.datetime.now().timestamp()}.webp"
) )
os.makedirs(folder, exist_ok=True) os.makedirs(folder, exist_ok=True)
+11 -5
View File
@@ -21,6 +21,7 @@ from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.models import Event from frigate.models import Event
from frigate.util.builtin import serialize from frigate.util.builtin import serialize
from frigate.util.classification import kickoff_model_training from frigate.util.classification import kickoff_model_training
from frigate.util.path import safe_join
from frigate.util.process import FrigateProcess from frigate.util.process import FrigateProcess
from .maintainer import EmbeddingMaintainer from .maintainer import EmbeddingMaintainer
@@ -33,7 +34,7 @@ class EmbeddingProcess(FrigateProcess):
def __init__( def __init__(
self, self,
config: FrigateConfig, config: FrigateConfig,
metrics: DataProcessorMetrics | None, metrics: DataProcessorMetrics,
stop_event: MpEvent, stop_event: MpEvent,
) -> None: ) -> None:
super().__init__( super().__init__(
@@ -234,11 +235,16 @@ class EmbeddingsContext:
) )
def delete_face_ids(self, face: str, ids: list[str]) -> None: def delete_face_ids(self, face: str, ids: list[str]) -> None:
folder = os.path.join(FACE_DIR, face) folder = safe_join(FACE_DIR, face)
for id in ids:
file_path = os.path.join(folder, id)
if os.path.isfile(file_path): if folder is None:
logger.warning("Not deleting faces for invalid name %s", face)
return
for id in ids:
file_path = safe_join(folder, id)
if file_path and os.path.isfile(file_path):
os.unlink(file_path) os.unlink(file_path)
if face != "train" and len(os.listdir(folder)) == 0: if face != "train" and len(os.listdir(folder)) == 0:
+62 -20
View File
@@ -78,6 +78,16 @@ logger = logging.getLogger(__name__)
MAX_THUMBNAILS = 10 MAX_THUMBNAILS = 10
GENAI_UPDATE_TOPICS = frozenset(
{
CameraConfigUpdateEnum.add.name,
CameraConfigUpdateEnum.objects.name,
CameraConfigUpdateEnum.object_genai.name,
CameraConfigUpdateEnum.review.name,
CameraConfigUpdateEnum.review_genai.name,
}
)
class EmbeddingMaintainer(threading.Thread): class EmbeddingMaintainer(threading.Thread):
"""Handle embedding queue and post event updates.""" """Handle embedding queue and post event updates."""
@@ -85,7 +95,7 @@ class EmbeddingMaintainer(threading.Thread):
def __init__( def __init__(
self, self,
config: FrigateConfig, config: FrigateConfig,
metrics: DataProcessorMetrics | None, metrics: DataProcessorMetrics,
stop_event: MpEvent, stop_event: MpEvent,
) -> None: ) -> None:
super().__init__(name="embeddings_maintainer") super().__init__(name="embeddings_maintainer")
@@ -220,16 +230,6 @@ class EmbeddingMaintainer(threading.Thread):
# post processors # post processors
self.post_processors: list[PostProcessorApi] = [] self.post_processors: list[PostProcessorApi] = []
if any(c.review.genai.enabled_in_config for c in self.config.cameras.values()):
self.post_processors.append(
ReviewDescriptionProcessor(
self.config,
self.requestor,
self.metrics,
self.genai_manager,
)
)
if self.config.lpr.enabled: if self.config.lpr.enabled:
self.post_processors.append( self.post_processors.append(
LicensePlatePostProcessor( LicensePlatePostProcessor(
@@ -252,9 +252,9 @@ class EmbeddingMaintainer(threading.Thread):
) )
) )
semantic_trigger_processor: SemanticTriggerProcessor | None = None self.semantic_trigger_processor: SemanticTriggerProcessor | None = None
if self.config.semantic_search.enabled: if self.config.semantic_search.enabled:
semantic_trigger_processor = SemanticTriggerProcessor( self.semantic_trigger_processor = SemanticTriggerProcessor(
db, db,
self.config, self.config,
self.requestor, self.requestor,
@@ -262,9 +262,49 @@ class EmbeddingMaintainer(threading.Thread):
metrics, metrics,
self.embeddings, self.embeddings,
) )
self.post_processors.append(semantic_trigger_processor) self.post_processors.append(self.semantic_trigger_processor)
if any(c.objects.genai.enabled_in_config for c in self.config.cameras.values()): self._sync_genai_processors()
self.stop_event = stop_event
# recordings data
self.recordings_available_through: dict[str, float] = {}
def _sync_genai_processors(self) -> None:
"""Create GenAI post processors for cameras that have GenAI enabled.
Called at startup and again after camera config updates so enabling
GenAI on the first camera does not require a restart. Processors are
never removed once created.
A profile can turn GenAI on without setting enabled_in_config, so both
flags are checked.
"""
cameras = self.config.cameras.values()
if any(
c.review.genai.enabled or c.review.genai.enabled_in_config for c in cameras
) and not any(
isinstance(p, ReviewDescriptionProcessor) for p in self.post_processors
):
logger.debug("Initializing review description processor")
self.post_processors.append(
ReviewDescriptionProcessor(
self.config,
self.requestor,
self.metrics,
self.genai_manager,
)
)
if any(
c.objects.genai.enabled or c.objects.genai.enabled_in_config
for c in cameras
) and not any(
isinstance(p, ObjectDescriptionProcessor) for p in self.post_processors
):
logger.debug("Initializing object description processor")
self.post_processors.append( self.post_processors.append(
ObjectDescriptionProcessor( ObjectDescriptionProcessor(
self.config, self.config,
@@ -272,19 +312,21 @@ class EmbeddingMaintainer(threading.Thread):
self.requestor, self.requestor,
self.metrics, self.metrics,
self.genai_manager, self.genai_manager,
semantic_trigger_processor, self.semantic_trigger_processor,
) )
) )
self.stop_event = stop_event def _check_camera_config_updates(self) -> None:
"""Apply camera config updates and register newly enabled processors."""
updated_topics = self.config_updater.check_for_updates()
# recordings data if updated_topics.keys() & GENAI_UPDATE_TOPICS:
self.recordings_available_through: dict[str, float] = {} self._sync_genai_processors()
def run(self) -> None: def run(self) -> None:
"""Maintain a SQLite-vec database for semantic search.""" """Maintain a SQLite-vec database for semantic search."""
while not self.stop_event.is_set(): while not self.stop_event.is_set():
self.config_updater.check_for_updates() self._check_camera_config_updates()
self._check_enrichment_config_updates() self._check_enrichment_config_updates()
self._process_requests() self._process_requests()
self._process_updates() self._process_updates()
+2 -2
View File
@@ -245,7 +245,7 @@ class GeminiClient(GenAIClient):
) )
gemini_messages.append( gemini_messages.append(
types.Content( types.Content(
role="function", role="user",
parts=[ parts=[
types.Part.from_function_response( types.Part.from_function_response(
name=msg.get("name") name=msg.get("name")
@@ -501,7 +501,7 @@ class GeminiClient(GenAIClient):
) )
gemini_messages.append( gemini_messages.append(
types.Content( types.Content(
role="function", role="user",
parts=[ parts=[
types.Part.from_function_response( types.Part.from_function_response(
name=msg.get("name") name=msg.get("name")
+5 -1
View File
@@ -115,6 +115,10 @@ def query_recordings(source_camera: str, start_ts: float, end_ts: float) -> Mode
return cast(ModelSelect, query) return cast(ModelSelect, query)
class NoRecordingsError(ValueError):
"""Raised when no recordings exist in the requested time range."""
class DebugReplaySource(ABC): class DebugReplaySource(ABC):
"""Abstract source for a debug replay session. """Abstract source for a debug replay session.
@@ -187,7 +191,7 @@ class RecordingDebugReplaySource(DebugReplaySource):
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(): if not query_recordings(self._camera, self._start_ts, self._end_ts).count():
raise ValueError( raise NoRecordingsError(
f"No recordings found for camera '{self._camera}' in the specified time range" f"No recordings found for camera '{self._camera}' in the specified time range"
) )
+43 -40
View File
@@ -392,6 +392,32 @@ class ReviewSegmentMaintainer(threading.Thread):
return self._publish_segment_end(segment, prev_data) return self._publish_segment_end(segment, prev_data)
return None return None
def get_manual_event_severity(self, camera: str, label: str) -> SeverityEnum | None:
"""Determine the review severity for a manual event label.
Alert labels take precedence over detection labels, matching how
tracked objects are categorized. Labels in neither list default to
alerts so manual events keep their historical severity.
"""
review_config = self.config.cameras[camera].review
# label contains 'label: sub_label', only the label is categorized
label = label.split(": ")[0]
if review_config.alerts.enabled and label in review_config.alerts.labels:
return SeverityEnum.alert
if (
review_config.detections.enabled
and review_config.detections.labels is not None
and label in review_config.detections.labels
):
return SeverityEnum.detection
if review_config.alerts.enabled:
return SeverityEnum.alert
return None
def update_existing_segment( def update_existing_segment(
self, self,
segment: PendingReviewSegment, segment: PendingReviewSegment,
@@ -734,24 +760,19 @@ class ReviewSegmentMaintainer(threading.Thread):
manual_info["label"] manual_info["label"]
) )
if topic == DetectionTypeEnum.api: if topic == DetectionTypeEnum.api:
# manual_info["label"] contains 'label: sub_label' severity = self.get_manual_event_severity(
# so split out the label without modifying manual_info camera, manual_info["label"]
det_labels = self.config.cameras[ )
camera
].review.detections.labels if severity == SeverityEnum.alert:
if (
self.config.cameras[camera].review.detections.enabled
and det_labels is not None
and manual_info["label"].split(": ")[0] in det_labels
):
current_segment.last_detection_time = manual_info[
"end_time"
]
elif self.config.cameras[camera].review.alerts.enabled:
current_segment.severity = SeverityEnum.alert current_segment.severity = SeverityEnum.alert
current_segment.last_alert_time = manual_info[ current_segment.last_alert_time = manual_info[
"end_time" "end_time"
] ]
elif severity == SeverityEnum.detection:
current_segment.last_detection_time = manual_info[
"end_time"
]
elif ( elif (
topic == DetectionTypeEnum.lpr topic == DetectionTypeEnum.lpr
and self.config.cameras[camera].review.detections.enabled and self.config.cameras[camera].review.detections.enabled
@@ -765,21 +786,12 @@ class ReviewSegmentMaintainer(threading.Thread):
current_segment.detections[manual_info["event_id"]] = ( current_segment.detections[manual_info["event_id"]] = (
manual_info["label"] manual_info["label"]
) )
if topic == DetectionTypeEnum.api:
if ( if (
topic == DetectionTypeEnum.api self.get_manual_event_severity(
and self.config.cameras[camera].review.alerts.enabled camera, manual_info["label"]
): )
# manual_info["label"] contains 'label: sub_label' == SeverityEnum.alert
# so split out the label without modifying manual_info
det_labels = self.config.cameras[
camera
].review.detections.labels
if (
not self.config.cameras[
camera
].review.detections.enabled
or det_labels is None
or manual_info["label"].split(": ")[0] not in det_labels
): ):
current_segment.severity = SeverityEnum.alert current_segment.severity = SeverityEnum.alert
elif ( elif (
@@ -853,18 +865,9 @@ class ReviewSegmentMaintainer(threading.Thread):
detections, detections,
) )
elif topic == DetectionTypeEnum.api: elif topic == DetectionTypeEnum.api:
severity = None severity = self.get_manual_event_severity(
# manual_info["label"] contains 'label: sub_label' camera, manual_info["label"]
# so split out the label without modifying manual_info )
det_labels = self.config.cameras[camera].review.detections.labels
if (
self.config.cameras[camera].review.detections.enabled
and det_labels is not None
and manual_info["label"].split(": ")[0] in det_labels
):
severity = SeverityEnum.detection
elif self.config.cameras[camera].review.alerts.enabled:
severity = SeverityEnum.alert
if severity: if severity:
api_segment = PendingReviewSegment( api_segment = PendingReviewSegment(
+1 -1
View File
@@ -62,7 +62,7 @@ def get_latest_version(config: FrigateConfig) -> str:
def stats_init( def stats_init(
config: FrigateConfig, config: FrigateConfig,
camera_metrics: DictProxy, camera_metrics: DictProxy,
embeddings_metrics: DataProcessorMetrics | None, embeddings_metrics: DataProcessorMetrics,
detectors: dict[str, ObjectDetectProcess], detectors: dict[str, ObjectDetectProcess],
processes: dict[str, int], processes: dict[str, int],
) -> StatsTrackingTypes: ) -> StatsTrackingTypes:
@@ -2,6 +2,7 @@
from unittest.mock import patch from unittest.mock import patch
from frigate.jobs.debug_replay import NoRecordingsError
from frigate.models import Event, Recordings, ReviewSegment from frigate.models import Event, Recordings, ReviewSegment
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
@@ -66,6 +67,32 @@ class TestDebugReplayAPI(BaseTestHttp):
# (CodeQL: information exposure through an exception). # (CodeQL: information exposure through an exception).
self.assertEqual(body["message"], "Invalid debug replay parameters") self.assertEqual(body["message"], "Invalid debug replay parameters")
def test_start_returns_404_when_no_recordings(self):
with patch(
"frigate.api.debug_replay.start_debug_replay_job",
side_effect=NoRecordingsError(
"No recordings found for camera 'front' in the specified time range"
),
):
with AuthTestClient(self.app) as client:
resp = client.post(
"/debug_replay/start",
json={
"camera": "front",
"start_time": 100,
"end_time": 200,
},
)
self.assertEqual(resp.status_code, 404)
body = resp.json()
self.assertFalse(body["success"])
# Message is hard-coded so we don't echo exception text back to clients
# (CodeQL: information exposure through an exception).
self.assertEqual(
body["message"], "No recordings found in the selected time range"
)
def test_start_returns_409_when_session_already_active(self): def test_start_returns_409_when_session_already_active(self):
with patch( with patch(
"frigate.api.debug_replay.start_debug_replay_job", "frigate.api.debug_replay.start_debug_replay_job",
@@ -166,6 +166,29 @@ class TestCameraAccessEventReview(BaseTestHttp):
resp = client.get("/review/rev1") resp = client.get("/review/rev1")
assert resp.status_code == 403 assert resp.status_code == 403
def test_review_not_reviewed_access(self):
super().insert_mock_review_segment("rev1", camera="front_door")
# Allowed
async def mock_require_allowed(camera: str, request: Request = None):
if camera == "front_door":
return
raise HTTPException(status_code=403, detail="Access denied")
with patch("frigate.api.review.require_camera_access", mock_require_allowed):
with AuthTestClient(self.app) as client:
resp = client.delete("/review/rev1/viewed")
assert resp.status_code == 200
# Disallowed
async def mock_require_disallowed(camera: str, request: Request = None):
raise HTTPException(status_code=403, detail="Access denied")
with patch("frigate.api.review.require_camera_access", mock_require_disallowed):
with AuthTestClient(self.app) as client:
resp = client.delete("/review/rev1/viewed")
assert resp.status_code == 403
def test_event_search_access(self): def test_event_search_access(self):
super().insert_mock_event("event1", camera="front_door") super().insert_mock_event("event1", camera="front_door")
super().insert_mock_event("event2", camera="back_door") super().insert_mock_event("event2", camera="back_door")
@@ -0,0 +1,73 @@
"""End to end checks that classification endpoints cannot escape their base dir."""
import os
import shutil
import tempfile
from unittest.mock import patch
from frigate.models import Event
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
# Percent encodings that survive nginx normalization. nginx collapses a bare
# ".." segment, but "..:" and friends are not relative segments to nginx while
# pathvalidate still reduces them to exactly "..".
TRAVERSAL_NAMES = ["..%3A", "..%2A", "..%3C", "..%7C", "..%20", ".."]
class TestHttpClassificationTraversal(BaseTestHttp):
def setUp(self):
super().setUp([Event])
self.app = super().create_app()
self.root = tempfile.mkdtemp()
self.clips = os.path.join(self.root, "clips")
self.model_cache = os.path.join(self.root, "model_cache")
os.makedirs(os.path.join(self.clips, "model1"))
os.makedirs(os.path.join(self.model_cache, "model1"))
os.makedirs(os.path.join(self.root, "recordings"))
# Sibling data that a "/.." escape from clips would reach.
self.canary = os.path.join(self.root, "recordings", "seg.mp4")
with open(self.canary, "w") as f:
f.write("recording")
clips_patch = patch("frigate.api.classification.CLIPS_DIR", self.clips)
cache_patch = patch(
"frigate.api.classification.MODEL_CACHE_DIR", self.model_cache
)
clips_patch.start()
cache_patch.start()
self.addCleanup(clips_patch.stop)
self.addCleanup(cache_patch.stop)
def tearDown(self):
shutil.rmtree(self.root, ignore_errors=True)
self.app.dependency_overrides.clear()
super().tearDown()
def test_delete_model_rejects_traversal_names(self):
client = AuthTestClient(self.app)
for name in TRAVERSAL_NAMES:
with self.subTest(name=name):
response = client.delete(f"/classification/{name}")
# Either the router never matches it or the handler rejects it,
# but the sibling directory must survive either way.
self.assertNotEqual(response.status_code, 200)
self.assertTrue(
os.path.exists(self.canary),
f"{name} deleted data outside the clips directory",
)
self.assertTrue(os.path.exists(os.path.join(self.root, "recordings")))
def test_delete_model_still_removes_its_own_directories(self):
client = AuthTestClient(self.app)
response = client.delete("/classification/model1")
self.assertEqual(response.status_code, 200)
self.assertFalse(os.path.exists(os.path.join(self.clips, "model1")))
self.assertFalse(os.path.exists(os.path.join(self.model_cache, "model1")))
self.assertTrue(os.path.exists(self.canary))
+2 -1
View File
@@ -9,6 +9,7 @@ from unittest.mock import MagicMock, patch
from frigate.debug_replay import DebugReplayManager from frigate.debug_replay import DebugReplayManager
from frigate.jobs.debug_replay import ( from frigate.jobs.debug_replay import (
DebugReplayJob, DebugReplayJob,
NoRecordingsError,
RecordingDebugReplaySource, RecordingDebugReplaySource,
cancel_debug_replay_job, cancel_debug_replay_job,
get_active_runner, get_active_runner,
@@ -129,7 +130,7 @@ class TestStartDebugReplayJob(unittest.TestCase):
empty_qs = MagicMock() empty_qs = MagicMock()
empty_qs.count.return_value = 0 empty_qs.count.return_value = 0
with patch("frigate.jobs.debug_replay.query_recordings", return_value=empty_qs): with patch("frigate.jobs.debug_replay.query_recordings", return_value=empty_qs):
with self.assertRaises(ValueError): with self.assertRaises(NoRecordingsError):
start_debug_replay_job( start_debug_replay_job(
source=RecordingDebugReplaySource( source=RecordingDebugReplaySource(
source_camera="front", source_camera="front",
+213
View File
@@ -0,0 +1,213 @@
"""Tests for GenAI enablement gating in the embeddings maintainer.
Covers creating post processors when GenAI is enabled at runtime, and the
per-camera gating those processors apply once they exist.
"""
import sys
import unittest
from unittest.mock import MagicMock, patch
# Mock TFLite before importing the maintainer
_MOCK_MODULES = [
"tflite_runtime",
"tflite_runtime.interpreter",
"ai_edge_litert",
"ai_edge_litert.interpreter",
]
for mod in _MOCK_MODULES:
if mod not in sys.modules:
sys.modules[mod] = MagicMock()
# imported from the maintainer to avoid tripping the circular import between
# the maintainer and the processor modules
from frigate.embeddings.maintainer import ( # noqa: E402
EmbeddingMaintainer,
ObjectDescriptionProcessor,
PostProcessDataEnum,
ReviewDescriptionProcessor,
)
class TestGenAIProcessorSync(unittest.TestCase):
"""Enabling GenAI on the first camera must not require a restart."""
def _make_maintainer(
self,
review: bool = False,
objects: bool = False,
review_in_config: bool | None = None,
objects_in_config: bool | None = None,
) -> EmbeddingMaintainer:
# Bypass the heavy __init__; only the attributes touched by
# _sync_genai_processors are needed for these tests.
maintainer = EmbeddingMaintainer.__new__(EmbeddingMaintainer)
maintainer.post_processors = []
maintainer.config = MagicMock()
maintainer.config.cameras = {
"front": self._make_camera(
review,
objects,
review if review_in_config is None else review_in_config,
objects if objects_in_config is None else objects_in_config,
)
}
maintainer.config_updater = MagicMock()
maintainer.embeddings = None
maintainer.requestor = MagicMock()
maintainer.metrics = MagicMock()
maintainer.genai_manager = MagicMock()
maintainer.semantic_trigger_processor = None
return maintainer
def _make_camera(
self,
review: bool,
objects: bool,
review_in_config: bool,
objects_in_config: bool,
) -> MagicMock:
camera = MagicMock()
camera.review.genai.enabled = review
camera.review.genai.enabled_in_config = review_in_config
camera.objects.genai.enabled = objects
camera.objects.genai.enabled_in_config = objects_in_config
return camera
def _processor_types(self, maintainer: EmbeddingMaintainer) -> list[type]:
return [type(p) for p in maintainer.post_processors]
def test_no_processors_when_genai_disabled(self):
"""A config with no GenAI cameras registers neither processor."""
maintainer = self._make_maintainer()
maintainer._sync_genai_processors()
self.assertEqual(maintainer.post_processors, [])
def test_review_processor_added_when_enabled_after_startup(self):
"""Enabling review GenAI on the first camera registers the processor."""
maintainer = self._make_maintainer()
maintainer._sync_genai_processors()
camera = maintainer.config.cameras["front"]
camera.review.genai.enabled = True
camera.review.genai.enabled_in_config = True
maintainer._sync_genai_processors()
self.assertEqual(
self._processor_types(maintainer), [ReviewDescriptionProcessor]
)
def test_object_processor_added_when_enabled_after_startup(self):
"""Enabling object GenAI on the first camera registers the processor."""
maintainer = self._make_maintainer()
maintainer._sync_genai_processors()
camera = maintainer.config.cameras["front"]
camera.objects.genai.enabled = True
camera.objects.genai.enabled_in_config = True
maintainer._sync_genai_processors()
self.assertEqual(
self._processor_types(maintainer), [ObjectDescriptionProcessor]
)
def test_processor_added_when_only_enabled_by_profile(self):
"""A profile enables GenAI without setting enabled_in_config."""
maintainer = self._make_maintainer(
review=True, objects=True, review_in_config=False, objects_in_config=False
)
maintainer._sync_genai_processors()
self.assertEqual(
self._processor_types(maintainer),
[ReviewDescriptionProcessor, ObjectDescriptionProcessor],
)
def test_processors_are_not_duplicated(self):
"""Repeated config updates must not register a second processor."""
maintainer = self._make_maintainer(review=True, objects=True)
maintainer._sync_genai_processors()
maintainer._sync_genai_processors()
self.assertEqual(
self._processor_types(maintainer),
[ReviewDescriptionProcessor, ObjectDescriptionProcessor],
)
def test_genai_topic_triggers_sync(self):
"""A camera config update on a GenAI topic registers the processor."""
maintainer = self._make_maintainer(review=True)
maintainer.config_updater.check_for_updates.return_value = {"review": ["front"]}
maintainer._check_camera_config_updates()
self.assertEqual(
self._processor_types(maintainer), [ReviewDescriptionProcessor]
)
def test_unrelated_topic_does_not_sync(self):
"""An unrelated camera config update must not register processors."""
maintainer = self._make_maintainer(review=True)
maintainer.config_updater.check_for_updates.return_value = {"motion": ["front"]}
maintainer._check_camera_config_updates()
self.assertEqual(maintainer.post_processors, [])
class TestObjectDescriptionCameraGating(unittest.TestCase):
"""One camera enabling object descriptions must not enlist the others."""
def _make_processor(self, enabled: bool) -> ObjectDescriptionProcessor:
config = MagicMock()
camera = MagicMock()
camera.objects.genai.enabled = enabled
camera.objects.genai.send_triggers.after_significant_updates = None
config.cameras = {"front": camera}
genai_manager = MagicMock()
genai_manager.description_client = MagicMock()
return ObjectDescriptionProcessor(
config, None, MagicMock(), MagicMock(), genai_manager, None
)
def _update(self, processor: ObjectDescriptionProcessor) -> None:
processor.process_data(
{
"camera": "front",
"data": {
"id": "1234.5-abcdef",
"box": (0, 0, 10, 10),
"stationary": False,
},
"state": "update",
"yuv_frame": MagicMock(),
},
PostProcessDataEnum.tracked_object,
)
@patch("frigate.data_processing.post.object_descriptions.create_thumbnail")
def test_disabled_camera_collects_no_thumbnails(self, mock_create_thumbnail):
"""A camera with object descriptions off does no thumbnail work."""
processor = self._make_processor(enabled=False)
self._update(processor)
mock_create_thumbnail.assert_not_called()
self.assertEqual(processor.tracked_events, {})
@patch("frigate.data_processing.post.object_descriptions.create_thumbnail")
def test_enabled_camera_collects_thumbnails(self, mock_create_thumbnail):
"""A camera with object descriptions on still collects thumbnails."""
mock_create_thumbnail.return_value = b"jpg"
processor = self._make_processor(enabled=True)
self._update(processor)
mock_create_thumbnail.assert_called_once()
self.assertEqual(len(processor.tracked_events["1234.5-abcdef"]), 1)
@@ -0,0 +1,154 @@
"""Tests for manual event severity categorization.
Regression coverage for manual events created via the events API being
categorized as detections when their label appears in both the alerts and
detections label lists. Alert labels must win, matching how tracked objects
are categorized, and labels in neither list must default to alerts so the
historical behavior of the API is preserved.
"""
import unittest
from frigate.config import FrigateConfig
from frigate.review.maintainer import ReviewSegmentMaintainer
from frigate.review.types import SeverityEnum
BASE_CONFIG = """
mqtt:
enabled: False
cameras:
front_door:
ffmpeg:
inputs:
- path: rtsp://10.0.0.1:554/video
roles:
- detect
detect:
width: 1920
height: 1080
fps: 5
%s
"""
class TestManualEventSeverity(unittest.TestCase):
def _make_maintainer(self, review_config: str = "") -> ReviewSegmentMaintainer:
"""Build a maintainer without invoking __init__ (avoids needing ZMQ
sockets, shared memory, and clip dirs). Only the config is read when
categorizing a manual event label."""
maintainer = ReviewSegmentMaintainer.__new__(ReviewSegmentMaintainer)
maintainer.config = FrigateConfig.parse_yaml(BASE_CONFIG % review_config)
return maintainer
def test_defaults_to_alert(self) -> None:
maintainer = self._make_maintainer()
self.assertEqual(
maintainer.get_manual_event_severity("front_door", "person"),
SeverityEnum.alert,
)
def test_unlisted_label_defaults_to_alert(self) -> None:
maintainer = self._make_maintainer(
"""
review:
detections:
labels:
- dog
"""
)
self.assertEqual(
maintainer.get_manual_event_severity("front_door", "pir_sensor"),
SeverityEnum.alert,
)
def test_detection_label_is_detection(self) -> None:
maintainer = self._make_maintainer(
"""
review:
alerts:
labels:
- person
detections:
labels:
- pir_sensor
"""
)
self.assertEqual(
maintainer.get_manual_event_severity("front_door", "pir_sensor"),
SeverityEnum.detection,
)
def test_alert_label_wins_over_detection_label(self) -> None:
maintainer = self._make_maintainer(
"""
review:
alerts:
labels:
- person
detections:
labels:
- person
- dog
"""
)
self.assertEqual(
maintainer.get_manual_event_severity("front_door", "person"),
SeverityEnum.alert,
)
def test_sub_label_is_stripped_before_categorizing(self) -> None:
maintainer = self._make_maintainer(
"""
review:
alerts:
labels:
- person
detections:
labels:
- person
"""
)
self.assertEqual(
maintainer.get_manual_event_severity("front_door", "person: Bob"),
SeverityEnum.alert,
)
def test_alert_label_is_detection_when_alerts_disabled(self) -> None:
maintainer = self._make_maintainer(
"""
review:
alerts:
enabled: False
labels:
- person
detections:
labels:
- person
"""
)
self.assertEqual(
maintainer.get_manual_event_severity("front_door", "person"),
SeverityEnum.detection,
)
def test_no_severity_when_alerts_disabled_and_label_not_a_detection(self) -> None:
maintainer = self._make_maintainer(
"""
review:
alerts:
enabled: False
detections:
labels:
- dog
"""
)
self.assertIsNone(
maintainer.get_manual_event_severity("front_door", "pir_sensor")
)
+197
View File
@@ -0,0 +1,197 @@
"""Tests for safe filesystem path construction."""
import os
import shutil
import tempfile
import unittest
from frigate.const import TRIGGER_DIR
from frigate.util.path import (
get_trigger_thumbnail_path,
is_contained_in,
safe_join,
sanitize_contained_path,
sanitize_path_component,
)
# Values that pathvalidate's sanitize_filename reduces to exactly "..", because
# it strips reserved characters but leaves relative markers intact. nginx only
# normalizes a bare ".." segment, so the decorated variants reach the app.
DOT_DOT_VARIANTS = ["..", "..:", "..*", "..?", '.."', "..<", "..>", "..|", ".. ", " .."]
class TestSanitizePathComponent(unittest.TestCase):
def test_rejects_dot_dot_variants(self):
for value in DOT_DOT_VARIANTS:
with self.subTest(value=value):
self.assertIsNone(sanitize_path_component(value))
def test_rejects_relative_markers_and_empty(self):
for value in [".", "", None, " ", "/", "//", "\\"]:
with self.subTest(value=value):
self.assertIsNone(sanitize_path_component(value))
def test_strips_separators(self):
component = sanitize_path_component("a/b/c")
self.assertIsNotNone(component)
self.assertNotIn("/", component)
def test_allows_ordinary_names(self):
for value in ["model1", "front-door", "My Model", "café", "a.b_c-1"]:
with self.subTest(value=value):
self.assertEqual(sanitize_path_component(value), value)
class TestSafeJoin(unittest.TestCase):
base = "/media/frigate/clips"
def test_rejects_dot_dot_variants(self):
for value in DOT_DOT_VARIANTS:
with self.subTest(value=value):
self.assertIsNone(safe_join(self.base, value))
def test_rejects_dot_dot_in_any_segment(self):
self.assertIsNone(safe_join(self.base, "model", "dataset", ".."))
self.assertIsNone(safe_join(self.base, "..", "dataset", ".."))
def test_result_stays_inside_base(self):
for value in ["model1", "a/../..", "....//", "..\\..", "%2e%2e"]:
with self.subTest(value=value):
joined = safe_join(self.base, value)
if joined is not None:
self.assertTrue(is_contained_in(joined, self.base))
def test_joins_multiple_segments(self):
self.assertEqual(
safe_join(self.base, "model1", "dataset", "none"),
"/media/frigate/clips/model1/dataset/none",
)
def test_rejects_empty_segment(self):
self.assertIsNone(safe_join(self.base, "model1", "", "none"))
class TestIsContainedIn(unittest.TestCase):
def test_rejects_sibling_sharing_a_name_prefix(self):
self.assertFalse(
is_contained_in("/media/frigate/clips_evil/x.webp", "/media/frigate/clips")
)
def test_accepts_base_itself_and_children(self):
self.assertTrue(is_contained_in("/media/frigate/clips", "/media/frigate/clips"))
self.assertTrue(
is_contained_in("/media/frigate/clips/a/b.webp", "/media/frigate/clips")
)
def test_rejects_parent(self):
self.assertFalse(is_contained_in("/media/frigate", "/media/frigate/clips"))
def test_handles_a_root_base(self):
# A prefix test would compare against "//" here and wrongly report that
# the root directory contains nothing.
self.assertTrue(is_contained_in("/child", "/"))
self.assertEqual(safe_join("/", "child"), "/child")
def test_rejects_uncomparable_paths(self):
self.assertFalse(is_contained_in("relative/x", "/media/frigate/clips"))
class TestSanitizeContainedPath(unittest.TestCase):
base = "/media/frigate/clips"
def test_rejects_dot_dot_anywhere(self):
for value in [
"/media/frigate/clips/../../etc/passwd",
"clips\\..\\..\\etc/passwd",
"/media/frigate/clips/a/../../../x",
]:
with self.subTest(value=value):
self.assertIsNone(sanitize_contained_path(value, self.base))
def test_rejects_sibling_sharing_a_name_prefix(self):
self.assertIsNone(
sanitize_contained_path("/media/frigate/clips_evil/x.webp", self.base)
)
def test_rejects_outside_base(self):
self.assertIsNone(sanitize_contained_path("/etc/passwd", self.base))
def test_rejects_empty(self):
self.assertIsNone(sanitize_contained_path("", self.base))
self.assertIsNone(sanitize_contained_path(None, self.base))
def test_keeps_a_valid_nested_path(self):
self.assertEqual(
sanitize_contained_path("/media/frigate/clips/a/b.webp", self.base),
"/media/frigate/clips/a/b.webp",
)
class TestTriggerThumbnailPath(unittest.TestCase):
def test_stays_inside_the_trigger_dir(self):
for camera, data in [
("cam", "../../../../etc/passwd"),
("cam", "../../../../config/config.yml"),
("cam", "normal-event-id"),
]:
with self.subTest(camera=camera, data=data):
path = get_trigger_thumbnail_path(camera, data)
self.assertIsNotNone(path)
self.assertTrue(is_contained_in(path, TRIGGER_DIR))
def test_rejects_traversal_camera_names(self):
for camera in DOT_DOT_VARIANTS:
with self.subTest(camera=camera):
self.assertIsNone(get_trigger_thumbnail_path(camera, "data"))
def test_builds_the_expected_path(self):
self.assertEqual(
get_trigger_thumbnail_path("front_door", "abc"),
os.path.join(TRIGGER_DIR, "front_door", "abc.webp"),
)
class TestRmtreeContainment(unittest.TestCase):
"""A recursive delete built through safe_join must not reach a parent.
shutil.rmtree on a path ending in ".." deletes the parent's contents before
failing on the final rmdir, so the guard has to run before the call.
"""
def setUp(self):
self.root = tempfile.mkdtemp()
self.clips = os.path.join(self.root, "clips")
os.makedirs(os.path.join(self.clips, "model1"))
os.makedirs(os.path.join(self.root, "recordings"))
with open(os.path.join(self.root, "recordings", "seg.mp4"), "w") as f:
f.write("recording")
def tearDown(self):
shutil.rmtree(self.root, ignore_errors=True)
def test_traversal_name_never_yields_a_path_to_delete(self):
for value in DOT_DOT_VARIANTS:
with self.subTest(value=value):
self.assertIsNone(safe_join(self.clips, value))
self.assertTrue(
os.path.exists(os.path.join(self.root, "recordings", "seg.mp4"))
)
def test_ordinary_name_still_deletes_its_own_directory(self):
target = safe_join(self.clips, "model1")
self.assertIsNotNone(target)
shutil.rmtree(target)
self.assertFalse(os.path.exists(os.path.join(self.clips, "model1")))
self.assertTrue(
os.path.exists(os.path.join(self.root, "recordings", "seg.mp4"))
)
if __name__ == "__main__":
unittest.main(verbosity=2)
+1 -1
View File
@@ -8,7 +8,7 @@ from frigate.object_detection.base import ObjectDetectProcess
class StatsTrackingTypes(TypedDict): class StatsTrackingTypes(TypedDict):
camera_metrics: dict[str, CameraMetrics] camera_metrics: dict[str, CameraMetrics]
embeddings_metrics: DataProcessorMetrics | None embeddings_metrics: DataProcessorMetrics
detectors: dict[str, ObjectDetectProcess] detectors: dict[str, ObjectDetectProcess]
started: int started: int
latest_frigate_version: str latest_frigate_version: str
+6 -6
View File
@@ -35,6 +35,11 @@ logger = logging.getLogger(__name__)
GRID_SIZE = 8 GRID_SIZE = 8
def create_empty_regions_grid() -> list[list[dict[str, Any]]]:
"""Create a region grid with no learned sizes."""
return [[{"sizes": []} for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
def get_camera_regions_grid( def get_camera_regions_grid(
name: str, name: str,
detect: DetectConfig, detect: DetectConfig,
@@ -47,12 +52,7 @@ def get_camera_regions_grid(
grid = regions.grid grid = regions.grid
last_update = regions.last_update last_update = regions.last_update
except DoesNotExist: except DoesNotExist:
grid = [] grid = create_empty_regions_grid()
for x in range(GRID_SIZE):
row = []
for y in range(GRID_SIZE):
row.append({"sizes": []})
grid.append(row)
last_update = 0 last_update = 0
# get events for timeline entries # get events for timeline entries
+134
View File
@@ -0,0 +1,134 @@
"""Helpers for building filesystem paths out of user supplied values."""
import os
from pathvalidate import ValidationError, sanitize_filename, sanitize_filepath
from frigate.const import TRIGGER_DIR
# Components that name a directory relative to its parent instead of a child.
# pathvalidate strips separators and reserved characters but leaves these
# intact, and it collapses values like "..:" down to "..", so they have to be
# rejected after sanitizing rather than before.
RELATIVE_COMPONENTS = {"", ".", ".."}
def sanitize_path_component(value: str | None) -> str | None:
"""Reduce a user supplied value to a single path component.
Args:
value: The untrusted value, such as a path parameter or body field
Returns:
A component that is safe to join onto a base directory, or None when
nothing usable remains so the caller can reject the request.
"""
if not value:
return None
try:
component = sanitize_filename(value)
except (ValidationError, ValueError):
return None
if component.strip() in RELATIVE_COMPONENTS:
return None
if os.sep in component or (os.altsep and os.altsep in component):
return None
return component
def is_contained_in(path: str, base: str) -> bool:
"""Check that a path sits inside a base directory.
Compares whole path components, so a sibling directory that merely shares a
name prefix with base is not treated as contained.
"""
resolved = os.path.normpath(path)
root = os.path.normpath(base)
try:
# commonpath compares components, and unlike a prefix test it stays
# correct for a base that already ends in a separator such as "/".
return os.path.commonpath([resolved, root]) == root
except ValueError:
# Raised when the paths cannot be compared, such as one relative and
# one absolute, or two different Windows drives.
return False
def safe_join(base: str, *parts: str | None) -> str | None:
"""Join user supplied parts beneath a trusted base directory.
Args:
base: Trusted base directory the result must stay inside of
parts: Untrusted values, each becoming one path component
Returns:
The joined path, or None if any part is unusable or the result would
land outside base.
"""
components: list[str] = []
for part in parts:
component = sanitize_path_component(part)
if component is None:
return None
components.append(component)
resolved = os.path.normpath(os.path.join(base, *components))
# normpath rather than realpath so symlinked media roots keep working; the
# per component checks above are what actually prevent traversal.
if not is_contained_in(resolved, base):
return None
return resolved
def sanitize_contained_path(path: str | None, base: str) -> str | None:
"""Validate a whole user supplied path that must already sit under base.
Unlike safe_join this keeps the directory structure the caller sent, so it
suits values that name an existing file rather than one component.
Args:
path: The untrusted path
base: Directory the path has to stay inside of
Returns:
The sanitized path, or None if it is unusable or escapes base.
"""
if not path:
return None
# sanitize_filepath normalizes "\" to "/" but leaves ".." intact, so a path
# like "clips\..\..\etc/passwd" would pass the containment check yet still
# escape once resolved. A valid path here never uses "..".
if ".." in path:
return None
sanitized = sanitize_filepath(path)
if not is_contained_in(sanitized, base):
return None
return sanitized
def get_trigger_thumbnail_path(camera_name: str, data: str) -> str | None:
"""Path of the thumbnail stored for a semantic search trigger.
Args:
camera_name: Camera the trigger belongs to
data: The trigger's data value, which is free-form text supplied by the
client and persisted verbatim
Returns:
The thumbnail path, or None if it cannot be built safely.
"""
return safe_join(TRIGGER_DIR, camera_name, f"{data}.webp")
+132 -10
View File
@@ -1,4 +1,4 @@
import { test, expect } from "../fixtures/frigate-test"; import { test, expect, type FrigateApp } from "../fixtures/frigate-test";
import { import {
expectBodyInteractive, expectBodyInteractive,
waitForBodyInteractive, waitForBodyInteractive,
@@ -575,7 +575,7 @@ test.describe("Multi-Review Export @high", () => {
await expect(dialog.getByText(/None/)).toBeVisible(); await expect(dialog.getByText(/None/)).toBeVisible();
}); });
test("starting an export posts the expected payload and navigates to the case", async ({ test("starting an export posts the expected payload and stays on the review page", async ({
frigateApp, frigateApp,
}) => { }) => {
test.skip(frigateApp.isMobile, "Desktop multi-select flow"); test.skip(frigateApp.isMobile, "Desktop multi-select flow");
@@ -673,9 +673,15 @@ test.describe("Multi-Review Export @high", () => {
"mex-review-002", "mex-review-002",
]); ]);
await expect(frigateApp.page).toHaveURL(/caseId=new-case-xyz/, { // Creating a case must not pull the user off the review they were
timeout: 5_000, // working through — the case is offered as a link on the toast instead.
}); const viewCase = frigateApp.page.getByRole("link", { name: /view/i });
await expect(viewCase).toBeVisible({ timeout: 5_000 });
await expect(viewCase).toHaveAttribute(
"href",
/export\?caseId=new-case-xyz$/,
);
await expect(frigateApp.page).toHaveURL(/\/review(\?|$)/);
}); });
test("mobile opens a drawer (not a dialog) for the multi-review export flow", async ({ test("mobile opens a drawer (not a dialog) for the multi-review export flow", async ({
@@ -834,13 +840,129 @@ test.describe("Multi-Review Export @high", () => {
expect(payload.new_case_description).toBeUndefined(); expect(payload.new_case_description).toBeUndefined();
expect(payload.items).toHaveLength(2); expect(payload.items).toHaveLength(2);
// Navigate should hit /export. useSearchEffect consumes the caseId // Attaching to a case leaves the user on the review page; the case is
// query param and strips it once the case is found in the cases list, // reachable from the toast action.
// so we assert on the path, not the query string. const viewCase = frigateApp.page.getByRole("link", { name: /view/i });
await expect(frigateApp.page).toHaveURL(/\/export(\?|$)/, { await expect(viewCase).toBeVisible({ timeout: 5_000 });
timeout: 5_000, await expect(viewCase).toHaveAttribute(
"href",
/export\?caseId=existing-case-abc$/,
);
await expect(frigateApp.page).toHaveURL(/\/review(\?|$)/);
}); });
}); });
test.describe("Multi-Camera Export from History @high", () => {
// The recording view seeds the multi-camera range around the playback
// position, so the deep link has to land close to the live edge for the
// seeded end to run past the end of the timeline.
const playbackTime = Math.floor(Date.now() / 1000) - 300;
async function openRecordingView(frigateApp: FrigateApp) {
// The recording view pulls these while the timeline renders; the preview
// server 500s on them, which the error collector would flag.
await frigateApp.page.route("**/api/*/recordings**", (route) =>
route.fulfill({ json: [] }),
);
await frigateApp.page.route("**/api/recordings/unavailable**", (route) =>
route.fulfill({ json: [] }),
);
await frigateApp.goto(`/review?timestamp=front_door_${playbackTime}`);
}
// Desktop opens the export form in a dialog from the Actions menu; mobile
// opens the same form inside the settings drawer.
async function openMultiCameraTab(frigateApp: FrigateApp) {
await openRecordingView(frigateApp);
if (frigateApp.isMobile) {
await frigateApp.page
.getByRole("button", { name: /filters/i })
.first()
.click({ timeout: 15_000 });
await frigateApp.page.getByRole("button", { name: /^export$/i }).click();
} else {
await frigateApp.page
.getByRole("button", { name: /actions/i })
.click({ timeout: 15_000 });
await frigateApp.page.getByRole("menuitem", { name: /export/i }).click();
}
const form = frigateApp.page.getByRole("dialog");
await expect(form).toBeVisible({ timeout: 5_000 });
await form.getByRole("tab", { name: /multi-camera/i }).click();
return form;
}
test("timeline selection renders both export handles on the timeline", async ({
frigateApp,
}) => {
await frigateApp.installDefaults();
const form = await openMultiCameraTab(frigateApp);
await form
.getByRole("button", { name: "Select from Timeline" })
.click({ timeout: 5_000 });
await expect(form).toBeHidden({ timeout: 5_000 });
// A range seeded past the end of the timeline has no segment to anchor
// to, which leaves the handle unpositioned at the top of the timeline
// with an empty label until it is dragged.
for (const handle of [".export-start", ".export-end"]) {
const locator = frigateApp.page.locator(handle);
await expect(locator).toHaveText(/\d{1,2}:\d{2}/, { timeout: 5_000 });
await expect(locator).not.toHaveAttribute("style", /top:\s*0px/);
}
});
test("the time range picker opens without a configured timezone", async ({
frigateApp,
}) => {
await frigateApp.installDefaults();
const form = await openMultiCameraTab(frigateApp);
// ui.timezone is null until the user sets one, which used to take the
// whole page down when the calendar worked out its disabled days
await form
.getByRole("button", { name: /^start time$/i })
.click({ timeout: 5_000 });
await expect(
frigateApp.page.getByRole("button", { name: /previous month/i }),
).toBeVisible({ timeout: 5_000 });
});
test("canceling timeline selection reopens the form with the case intact", async ({
frigateApp,
}) => {
await frigateApp.installDefaults();
const form = await openMultiCameraTab(frigateApp);
await form
.getByPlaceholder(/new case name/i)
.fill("Incident 7", { timeout: 5_000 });
await form
.getByPlaceholder(/case description/i)
.fill("Front gate follow-up");
await form.getByRole("button", { name: "Select from Timeline" }).click();
await expect(form).toBeHidden({ timeout: 5_000 });
await frigateApp.page.getByRole("button", { name: /cancel/i }).click();
await expect(form).toBeVisible({ timeout: 5_000 });
await expect(
form.getByRole("tab", { name: /multi-camera/i }),
).toHaveAttribute("aria-selected", "true");
await expect(form.getByPlaceholder(/new case name/i)).toHaveValue(
"Incident 7",
);
await expect(form.getByPlaceholder(/case description/i)).toHaveValue(
"Front gate follow-up",
);
});
}); });
test.describe("Export Page - Active Job Progress @medium", () => { test.describe("Export Page - Active Job Progress @medium", () => {
+40 -2
View File
@@ -38,7 +38,7 @@
"cough": "سُعَال", "cough": "سُعَال",
"throat_clearing": "تَنْحِيم", "throat_clearing": "تَنْحِيم",
"sneeze": "عُطَاس", "sneeze": "عُطَاس",
"sniff": "شَمَّ", "sniff": "كشف",
"run": "رَكْض", "run": "رَكْض",
"shuffle": "خَلْط", "shuffle": "خَلْط",
"footsteps": "خُطُوَات", "footsteps": "خُطُوَات",
@@ -161,5 +161,43 @@
"rumble": "الحلبة", "rumble": "الحلبة",
"skateboard": "لوح تزلج", "skateboard": "لوح تزلج",
"echo": "صدى الصوت", "echo": "صدى الصوت",
"noise": "ازعاج" "noise": "ازعاج",
"duck": "بطة",
"quack": "صوت البطة",
"goose": "وزة",
"honk": "نعيق الأوز",
"wild_animals": "حيوانات برية",
"roar": "زئير",
"chirp": "زقزقة",
"pigeon": "حمامة",
"crow": "غراب",
"roaring_cats": "قطط هائجة",
"squawk": "نعيق",
"coo": "هديل الحمام",
"mallet_percussion": "مطرقة إيقاعية",
"marimba": "ماريمبا",
"glockenspiel": "معزف الأجراس",
"vibraphone": "فيبرافون",
"steelpan": "طبل نحاسي",
"orchestra": "أوركسترا",
"brass_instrument": "آلة نحاسية",
"french_horn": "بوق فرنسي",
"trumpet": "بوق",
"trombone": "ترومبون",
"bowed_string_instrument": "آلة وترية مقوسة",
"string_section": "آلات وترية",
"violin": "كمان",
"pizzicato": "تقنية العزف بيزيكاتو",
"cello": "تشيلو",
"double_bass": "كمان كبير",
"wind_instrument": "آلة نفخية",
"flute": "فلوت",
"saxophone": "ساكسفون",
"clarinet": "كلارينيت",
"harp": "قيثارة",
"bell": "جرس",
"church_bell": "جرس الكنيسة",
"jingle_bell": "جرس جلجل",
"bicycle_bell": "جرس الدراجة",
"tuning_fork": "شوكة رنانة"
} }
+6 -6
View File
@@ -71,9 +71,9 @@
"batchSuccess_other": "S'han iniciat {{count}} exportacions. Obrint el cas ara.", "batchSuccess_other": "S'han iniciat {{count}} exportacions. Obrint el cas ara.",
"batchPartial": "S'han iniciat {{successful}} de {{total}} exportacions. Càmeres fallides: {{failedCameras}}", "batchPartial": "S'han iniciat {{successful}} de {{total}} exportacions. Càmeres fallides: {{failedCameras}}",
"batchFailed": "No s'han pogut iniciar {{total}} exportacions. Càmeres fallides: {{failedCameras}}", "batchFailed": "No s'han pogut iniciar {{total}} exportacions. Càmeres fallides: {{failedCameras}}",
"batchQueuedSuccess_one": "Exporta a la cua 1. Obrint el cas ara.", "batchQueuedSuccess_one": "Exporta a la cua 1.",
"batchQueuedSuccess_many": "{{count}} exportacions a la cua. Obrint el cas ara.", "batchQueuedSuccess_many": "{{count}} exportacions a la cua.",
"batchQueuedSuccess_other": "{{count}} exportacions a la cua. Obrint el cas ara.", "batchQueuedSuccess_other": "{{count}} exportacions a la cua.",
"batchQueuedPartial": "{{successful}} de {{total}} exportacions a la cua. Càmeres fallides: {{failedCameras}}", "batchQueuedPartial": "{{successful}} de {{total}} exportacions a la cua. Càmeres fallides: {{failedCameras}}",
"batchQueueFailed": "No s'han pogut posar a la cua {{total}} exportacions. Càmeres fallides: {{failedCameras}}" "batchQueueFailed": "No s'han pogut posar a la cua {{total}} exportacions. Càmeres fallides: {{failedCameras}}"
}, },
@@ -132,9 +132,9 @@
"exportButton_other": "Exporta {{count}} ressenyes", "exportButton_other": "Exporta {{count}} ressenyes",
"exportingButton": "S'està exportant...", "exportingButton": "S'està exportant...",
"toast": { "toast": {
"started_one": "S'ha iniciat l'exportació 1. Obrint el cas ara.", "started_one": "S'ha iniciat l'exportació 1.",
"started_many": "S'han iniciat {{count}} exportacions. Obrint el cas ara.", "started_many": "S'han iniciat {{count}} exportacions.",
"started_other": "S'han iniciat {{count}} exportacions. Obrint el cas ara.", "started_other": "S'han iniciat {{count}} exportacions.",
"startedNoCase_one": "S'ha iniciat l'exportació 1.", "startedNoCase_one": "S'ha iniciat l'exportació 1.",
"startedNoCase_many": "S'han iniciat {{count}} exportacions.", "startedNoCase_many": "S'han iniciat {{count}} exportacions.",
"startedNoCase_other": "S'han iniciat {{count}} exportacions.", "startedNoCase_other": "S'han iniciat {{count}} exportacions.",
+2 -2
View File
@@ -867,8 +867,8 @@
"description": "Ordre numèric utilitzat per ordenar la càmera a la interfície d'usuari (taulell de control i llistes per defecte); els nombres més grans apareixen més tard." "description": "Ordre numèric utilitzat per ordenar la càmera a la interfície d'usuari (taulell de control i llistes per defecte); els nombres més grans apareixen més tard."
}, },
"dashboard": { "dashboard": {
"label": "Mostra a l'interfície d'usuari", "label": "Mostra al tauler en directe",
"description": "Estableix si aquesta càmera és visible a tot arreu a la interfície d'usuari de la Frigate. Desactivar això requerirà editar manualment la configuració per tornar a veure aquesta càmera a la interfície d'usuari." "description": "Alterna si aquesta càmera és visible al tauler de control en directe de totes les càmeres per defecte. La càmera roman disponible a tot arreu a la interfície d'usuari, inclosos els grups i la configuració de la càmera."
}, },
"review": { "review": {
"label": "Mostra en la revisió", "label": "Mostra en la revisió",
+2 -2
View File
@@ -2338,8 +2338,8 @@
"description": "Ordre numèric utilitzat per ordenar la càmera a la interfície d'usuari (taulell de control i llistes per defecte); els nombres més grans apareixen més tard." "description": "Ordre numèric utilitzat per ordenar la càmera a la interfície d'usuari (taulell de control i llistes per defecte); els nombres més grans apareixen més tard."
}, },
"dashboard": { "dashboard": {
"label": "Mostra a la interfície", "label": "Mostra al tauler en directe",
"description": "Estableix si aquesta càmera és visible a tot arreu a la interfície d'usuari de Frigate. Desactivar això requerirà editar manualment la configuració per tornar a veure aquesta càmera a la interfície d'usuari." "description": "Alterna si aquesta càmera és visible al tauler de control en directe de totes les càmeres per defecte. La càmera roman disponible a tot arreu a la interfície d'usuari, inclosos els grups i la configuració de la càmera."
}, },
"review": { "review": {
"label": "Mostra en la revisió", "label": "Mostra en la revisió",
+3 -1
View File
@@ -126,5 +126,7 @@
"baby_stroller": "Cotxet", "baby_stroller": "Cotxet",
"rickshaw": "Ricksaw", "rickshaw": "Ricksaw",
"Rodent": "Rosegador", "Rodent": "Rosegador",
"rodent": "Rosegador" "rodent": "Rosegador",
"possum": "Possum",
"garbage_truck": "Camió de brossa"
} }
+5 -2
View File
@@ -76,7 +76,7 @@
"menuItem": "Visualitza les vistes prèvies del moviment", "menuItem": "Visualitza les vistes prèvies del moviment",
"title": "Vista prèvia del moviment: {{camera}}", "title": "Vista prèvia del moviment: {{camera}}",
"mobileSettingsTitle": "Configuració de la vista prèvia del moviment", "mobileSettingsTitle": "Configuració de la vista prèvia del moviment",
"mobileSettingsDesc": "Ajusteu la velocitat de reproducció i l'enfosquiment, i trieu una data per a revisar clips només en moviment.", "mobileSettingsDesc": "Ajusteu la velocitat de reproducció, l'enfosquiment i l'escapçament, i trieu una data per a revisar clips només en moviment.",
"dim": "Atenuar", "dim": "Atenuar",
"dimAria": "Ajusta la intensitat de l'enfosquiment", "dimAria": "Ajusta la intensitat de l'enfosquiment",
"dimDesc": "Incrementa l'enfosquiment per augmentar la visibilitat de l'àrea de moviment.", "dimDesc": "Incrementa l'enfosquiment per augmentar la visibilitat de l'àrea de moviment.",
@@ -89,6 +89,9 @@
"seekAria": "Cerca el reproductor {{camera}} a {{time}}", "seekAria": "Cerca el reproductor {{camera}} a {{time}}",
"filter": "Filtre", "filter": "Filtre",
"filterDesc": "Seleccioneu àrees per a mostrar només clips amb moviment en aquestes regions.", "filterDesc": "Seleccioneu àrees per a mostrar només clips amb moviment en aquestes regions.",
"filterClear": "Neteja" "filterClear": "Neteja",
"crop": "Escapça per filtrar",
"cropAria": "Commuta la vista prèvia d'escapçament a les àrees filtrades",
"cropDesc": "Amplia les vistes prèvies a les àrees de filtre seleccionades en lloc de mostrar el fotograma complet."
} }
} }
+2 -1
View File
@@ -52,7 +52,8 @@
"error": "No s'ha pogut iniciar la repetició de depuració: {{error}}", "error": "No s'ha pogut iniciar la repetició de depuració: {{error}}",
"alreadyActive": "Ja hi ha activada una sessió de reproducció", "alreadyActive": "Ja hi ha activada una sessió de reproducció",
"stopError": "No s'ha pogut aturar la repetició de depuració: {{error}}", "stopError": "No s'ha pogut aturar la repetició de depuració: {{error}}",
"goToReplay": "Ves a la repetició" "goToReplay": "Ves a la repetició",
"noRecordings": "No s'ha trobat cap enregistrament a l'interval de temps seleccionat"
} }
}, },
"description": "Reprodueix els enregistraments de la càmera per a la depuració. La llista d'objectes mostra un resum retardat en el temps dels objectes detectats i la pestanya Missatges mostra un flux de missatges interns de frigate a partir del metratge de reproducció." "description": "Reprodueix els enregistraments de la càmera per a la depuració. La llista d'objectes mostra un resum retardat en el temps dels objectes detectats i la pestanya Missatges mostra un flux de missatges interns de frigate a partir del metratge de reproducció."
+2 -2
View File
@@ -1330,7 +1330,7 @@
"webuiUrlHelp": "URL per a visitar la interfície d'usuari web de la càmera directament des de la vista de depuració. Deixeu-ho en blanc per desactivar l'enllaç.", "webuiUrlHelp": "URL per a visitar la interfície d'usuari web de la càmera directament des de la vista de depuració. Deixeu-ho en blanc per desactivar l'enllaç.",
"webuiUrlInvalid": "Ha de ser un URL vàlid (p. ex., https://example.com).", "webuiUrlInvalid": "Ha de ser un URL vàlid (p. ex., https://example.com).",
"dashboardLabel": "Mostra al tauler en directe", "dashboardLabel": "Mostra al tauler en directe",
"dashboardHelp": "Mostra aquesta càmera al Tauler en viu.", "dashboardHelp": "Mostra aquesta càmera al tauler de control en directe predeterminat de totes les càmeres. Es manté disponible a tot arreu, inclosos els grups de càmeres.",
"reviewLabel": "Mostra a la ressenya", "reviewLabel": "Mostra a la ressenya",
"reviewHelp": "Mostra aquesta càmera a Revisió, incloent el filtre de càmera, la revisió de moviment i la vista de l'historial." "reviewHelp": "Mostra aquesta càmera a Revisió, incloent el filtre de càmera, la revisió de moviment i la vista de l'historial."
}, },
@@ -1377,7 +1377,7 @@
"deleteCameraDialog": { "deleteCameraDialog": {
"title": "Suprimeix la càmera", "title": "Suprimeix la càmera",
"description": "Suprimir una càmera eliminarà permanentment tots els enregistraments, els objectes rastrejats i la configuració d'aquesta càmera. Qualsevol flux go2rtc associat amb aquesta càmera encara pot haver de ser eliminat manualment.", "description": "Suprimir una càmera eliminarà permanentment tots els enregistraments, els objectes rastrejats i la configuració d'aquesta càmera. Qualsevol flux go2rtc associat amb aquesta càmera encara pot haver de ser eliminat manualment.",
"selectPlaceholder": "Trieu la càmera...", "selectPlaceholder": "Trieu la càmera…",
"confirmTitle": "N'estàs segur?", "confirmTitle": "N'estàs segur?",
"confirmWarning": "Suprimir <strong>{{cameraName}}</strong> no es pot desfer.", "confirmWarning": "Suprimir <strong>{{cameraName}}</strong> no es pot desfer.",
"deleteExports": "Elimina també les exportacions d'aquesta càmera", "deleteExports": "Elimina també les exportacions d'aquesta càmera",
+56 -1
View File
@@ -443,5 +443,60 @@
"slap": "Plácnout", "slap": "Plácnout",
"smash": "Rozmlátit", "smash": "Rozmlátit",
"bouncing": "Odrážející", "bouncing": "Odrážející",
"scratch": "Škrábat" "scratch": "Škrábat",
"spray": "Sprej",
"pulse": "Pulz",
"inside": "Uvnitř",
"outside": "Venku",
"reverberation": "Dozvuk",
"echo": "Ozvěna",
"noise": "Hluk",
"mains_hum": "Síťový brum",
"distortion": "Zkreslení",
"sidetone": "Příposlech",
"cacophony": "Kakofonie",
"throbbing": "Pulzování",
"vibration": "Vibrace",
"sodeling": "Jódlování",
"shofar": "Šofar",
"slosh": "Šplouchání",
"gush": "Příval",
"whoosh": "Svištění",
"thump": "Tlumená rána",
"thunk": "Dutá rána",
"effects_unit": "Efektová jednotka",
"chorus_effect": "Chorus",
"whack": "Úder",
"breaking": "Rozbíjení",
"whip": "Švihnutí",
"flap": "Třepotání",
"scrape": "Drhnutí",
"rub": "Tření",
"roll": "Kutálení",
"crushing": "Drcení",
"crumpling": "Mačkání",
"tearing": "Trhání",
"beep": "Pípnutí",
"ping": "Ping",
"ding": "Cinknutí",
"clang": "Řinčení",
"squeal": "Skřípění",
"creak": "Vrzání",
"rustle": "Šustění",
"whir": "Hučení",
"clatter": "Rachocení",
"sizzle": "Prskání",
"clicking": "Klikání",
"clickety_clack": "Klapot",
"rumble": "Dunění",
"plop": "Žbluňknutí",
"hum": "Brum",
"zing": "Zvonivý tón",
"boing": "Pružinový zvuk",
"crunch": "Křupání",
"sine_wave": "Sinusový tón",
"harmonic": "Harmonický tón",
"chirp_tone": "Klouzavý tón",
"pump": "Pumpa",
"basketball_bounce": "Basketbalový odraz"
} }
+28 -4
View File
@@ -120,7 +120,19 @@
"deleteNow": "Smazat hned", "deleteNow": "Smazat hned",
"next": "Další", "next": "Další",
"export": "Exportovat", "export": "Exportovat",
"continue": "Pokračovat" "continue": "Pokračovat",
"add": "Přidat",
"applying": "Aplikuje se…",
"undo": "Vrátit",
"copiedToClipboard": "Zkopírováno do schránky",
"modified": "Upraveno",
"overridden": "Přepsáno",
"resetToGlobal": "Obnovit globální nastavení",
"resetToDefault": "Obnovit výchozí nastavení",
"saveAll": "Uložit vše",
"savingAll": "Ukládání…",
"undoAll": "Vrátit vše",
"retry": "Zkusit znovu"
}, },
"label": { "label": {
"back": "Jdi zpět", "back": "Jdi zpět",
@@ -213,7 +225,9 @@
"gl": "Galego (Galicijština)", "gl": "Galego (Galicijština)",
"id": "Bahasa Indonesia (Indonéština)", "id": "Bahasa Indonesia (Indonéština)",
"ur": "اردو (Urdština)", "ur": "اردو (Urdština)",
"hr": "Hrvatski (Chorvatština)" "hr": "Hrvatski (Chorvatština)",
"zhHant": "繁體中文 (Tradiční čínština)",
"bs": "Bosanski (Bosenština)"
}, },
"theme": { "theme": {
"highcontrast": "Vysoký kontrast", "highcontrast": "Vysoký kontrast",
@@ -251,7 +265,11 @@
"faceLibrary": "Knihovna Obličejů", "faceLibrary": "Knihovna Obličejů",
"configurationEditor": "Editor Konfigurace", "configurationEditor": "Editor Konfigurace",
"withSystem": "Systém", "withSystem": "Systém",
"classification": "Klasifikace" "classification": "Klasifikace",
"profiles": "Profily",
"actions": "Akce",
"features": "Funkce",
"chat": "Chat"
}, },
"pagination": { "pagination": {
"previous": { "previous": {
@@ -282,7 +300,8 @@
"error": { "error": {
"title": "Chyba při ukládání změn konfigurace: {{errorMessage}}", "title": "Chyba při ukládání změn konfigurace: {{errorMessage}}",
"noMessage": "Chyba při ukládání změn konfigurace" "noMessage": "Chyba při ukládání změn konfigurace"
} },
"success": "Změny konfigurace byly úspěšně uloženy."
} }
}, },
"role": { "role": {
@@ -303,5 +322,10 @@
}, },
"information": { "information": {
"pixels": "{{area}}px" "pixels": "{{area}}px"
},
"no_items": "Žádné položky",
"validation_errors": "Chyby ověření",
"credentialField": {
"savedPlaceholder": "Uloženo – ponechte prázdné pro zachování aktuální hodnoty"
} }
} }
+6 -2
View File
@@ -68,7 +68,10 @@
"desc": "Vyberte kamery pro tuto skupinu." "desc": "Vyberte kamery pro tuto skupinu."
}, },
"icon": "Ikona", "icon": "Ikona",
"success": "Skupina kamer {{name}} byla uložena." "success": "Skupina kamer {{name}} byla uložena.",
"showAll": "Zobrazit všechny skupiny kamer",
"showLess": "Zobrazit méně",
"editGroups": "Upravit skupiny kamer"
}, },
"debug": { "debug": {
"options": { "options": {
@@ -82,6 +85,7 @@
"regions": "Kraje", "regions": "Kraje",
"timestamp": "Časové razítko", "timestamp": "Časové razítko",
"boundingBox": "Ohraničení", "boundingBox": "Ohraničení",
"mask": "Maska" "mask": "Maska",
"paths": "Cesty"
} }
} }
+69 -2
View File
@@ -6,7 +6,8 @@
"title": "Frigate restartuje", "title": "Frigate restartuje",
"content": "Tato stránka bude obnovena za {{countdown}} sekund.", "content": "Tato stránka bude obnovena za {{countdown}} sekund.",
"button": "Vynutit opětovné načtení" "button": "Vynutit opětovné načtení"
} },
"description": "Frigate bude během restartu krátce nedostupný."
}, },
"explore": { "explore": {
"plus": { "plus": {
@@ -19,6 +20,9 @@
}, },
"state": { "state": {
"submitted": "Odesláno" "submitted": "Odesláno"
},
"toast": {
"error": "Odeslání do Frigate+ se nezdařilo. Zkontrolujte své síťové připojení a zkuste to znovu."
} }
}, },
"submitToPlus": { "submitToPlus": {
@@ -74,7 +78,11 @@
"endTimeMustAfterStartTime": "Čas konce musí být po čase začátku", "endTimeMustAfterStartTime": "Čas konce musí být po čase začátku",
"noVaildTimeSelected": "Není vybráno žádné platné časové období" "noVaildTimeSelected": "Není vybráno žádné platné časové období"
}, },
"view": "Zobrazení" "view": "Zobrazení",
"queued": "Export zařazen do fronty. Průběh zobrazíte na stránce Exporty.",
"batchSuccess_one": "Začal 1 export. Nyní se otevírá případ.",
"batchSuccess_few": "Začaly {{count}} exporty. Nyní se otevírá případ.",
"batchSuccess_other": "Začalo {{count}} exportů. Nyní se otevírá případ."
}, },
"fromTimeline": { "fromTimeline": {
"saveExport": "Uložit export", "saveExport": "Uložit export",
@@ -82,6 +90,65 @@
}, },
"name": { "name": {
"placeholder": "Jméno exportu" "placeholder": "Jméno exportu"
},
"case": {
"newCaseOption": "Vytvořit nový případ",
"newCaseNamePlaceholder": "Název nového případu",
"newCaseDescriptionPlaceholder": "Popis případu",
"label": "Případ",
"nonAdminHelp": "Pro tyto exporty bude vytvořen nový případ.",
"placeholder": "Vyberte případ"
},
"queueing": "Přidávání exportu do fronty…",
"tabs": {
"export": "Jedna kamera",
"multiCamera": "Vícekamerový"
},
"multiCamera": {
"timeRange": "Časový rozsah",
"selectFromTimeline": "Vybrat z časové osy",
"cameraSelection": "Kamery",
"cameraSelectionHelp": "Kamery se sledovanými objekty v tomto časovém rozsahu jsou předem vybrány",
"searchOrSelectGroup": "Vyhledejte nebo vyberte skupinu kamer…",
"selectAll": "Vybrat všechny kamery",
"clearSelection": "Zrušit výběr",
"selectWithActivity": "Kamery se sledovanými objekty",
"selectGroup": "Vybrat skupinu",
"noMatchingCameras": "Žádná kamera neodpovídá vašemu hledání",
"selectedCount": "{{selected}} / {{total}} vybráno",
"checkingActivity": "Kontrola aktivity kamer…",
"noCameras": "Nejsou k dispozici žádné kamery",
"detectionCount_one": "1 sledovaný objekt",
"detectionCount_few": "{{count}} sledované objekty",
"detectionCount_other": "{{count}} sledovaných objektů",
"nameLabel": "Název exportu",
"namePlaceholder": "Volitelný základní název pro tyto exporty",
"queueingButton": "Příprava exportů…",
"exportButton_one": "Exportovat 1 kameru",
"exportButton_few": "Exportovat {{count}} kamery",
"exportButton_other": "Exportovat {{count}} kamer"
},
"multi": {
"title_one": "Exportovat 1 kontrolu",
"title_few": "Exportovat {{count}} kontroly",
"title_other": "Exportovat {{count}} kontrol",
"description": "Exportujte každou vybranou kontrolu. Všechny exporty budou seskupeny do jednoho případu.",
"descriptionNoCase": "Exportujte každou vybranou kontrolu.",
"caseNamePlaceholder": "Export kontroly – {{date}}",
"exportButton_one": "Exportovat 1 kontrolu",
"exportButton_few": "Exportovat {{count}} kontroly",
"exportButton_other": "Exportovat {{count}} kontrol",
"exportingButton": "Exportování…",
"toast": {
"started_one": "Spuštěn 1 export. Otevírá se případ.",
"started_few": "Spuštěny {{count}} exporty. Otevírá se případ.",
"started_other": "Spuštěno {{count}} exportů. Otevírá se případ.",
"startedNoCase_one": "Spuštěn 1 export.",
"startedNoCase_few": "Spuštěny {{count}} exporty.",
"startedNoCase_other": "Spuštěno {{count}} exportů.",
"partial": "Spuštěno {{successful}} z {{total}} exportů. Selhalo: {{failedItems}}",
"failed": "Nepodařilo se spustit {{total}} exportů. Selhalo: {{failedItems}}"
}
} }
}, },
"streaming": { "streaming": {
+2 -1
View File
@@ -309,7 +309,8 @@
"triggers": "Spouštěče", "triggers": "Spouštěče",
"cameraManagement": "Správa", "cameraManagement": "Správa",
"cameraReview": "Kontrola", "cameraReview": "Kontrola",
"roles": "Role" "roles": "Role",
"profiles": "Profily"
}, },
"dialog": { "dialog": {
"unsavedChanges": { "unsavedChanges": {
+138 -1
View File
@@ -206,5 +206,142 @@
"whale_vocalization": "Hvallyde", "whale_vocalization": "Hvallyde",
"music": "Musik", "music": "Musik",
"musical_instrument": "Musikinstrument", "musical_instrument": "Musikinstrument",
"plucked_string_instrument": "Strengeinstrument" "plucked_string_instrument": "Strengeinstrument",
"zither": "Citar",
"ukulele": "Ukulele",
"piano": "Piano",
"electric_piano": "Elektrisk Piano",
"organ": "orgel",
"electronic_organ": "Elektrisk orgel",
"hammond_organ": "Hammond orgel",
"synthesizer": "Synthesizer",
"sampler": "Sampler",
"harpsichord": "Mundharmonika",
"percussion": "Percussion",
"drum_kit": "Trommesæt",
"drum_machine": "Trommemaskine",
"drum": "Tromme",
"timpani": "Pauker",
"tabla": "Tabla",
"cymbal": "Bækken",
"hi_hat": "Hi-Hat",
"wood_block": "Woodblock",
"maraca": "Maraca",
"gong": "Gong",
"tubular_bells": "Rørklokker",
"mallet_percussion": "Mallet percussion",
"glockenspiel": "Klokkespil",
"vibraphone": "Vibrafon",
"steelpan": "olietønde",
"orchestra": "Orkester",
"brass_instrument": "Messingblæser",
"french_horn": "Valdhorn",
"bowed_string_instrument": "Stryger",
"string_section": "Strygerdel",
"pizzicato": "Pizzicato",
"cello": "Cello",
"double_bass": "Kontrabas",
"wind_instrument": "Blæseinstrument",
"church_bell": "Kirkeklokke",
"jingle_bell": "Bjældeklang",
"bicycle_bell": "Ringeklokke",
"tuning_fork": "Stemmegaffel",
"chime": "Kimen",
"wind_chime": "Vindklokke",
"accordion": "Harmonika",
"theremin": "Theremin",
"singing_bowl": "Syngeskål",
"scratching": "Kradse",
"pop_music": "Popmusik",
"hip_hop_music": "Hip-Hopmusik",
"beatboxing": "Beatboxing",
"rock_music": "Rockmusik",
"heavy_metal": "Heavymetal",
"punk_rock": "Punkrock",
"grunge": "Grunge",
"progressive_rock": "Progressiv rock",
"rock_and_roll": "Rock and Roll",
"psychedelic_rock": "Psykedelisk rock",
"rhythm_and_blues": "Rhythm and Blues",
"soul_music": "Soulmusik",
"reggae": "Reggae",
"country": "Land",
"disco": "Disko",
"classical_music": "Klassisk musik",
"electronic_music": "Elektronisk musik",
"house_music": "Housemusik",
"drum_and_bass": "Drum and Bass",
"electronica": "Elektronisk musik",
"electronic_dance_music": "Elektronisk dancemusik",
"ambient_music": "Ambient musik",
"trance_music": "Trancemusik",
"music_of_latin_america": "Musik fra Latin Amerika",
"salsa_music": "Salsamusik",
"flamenco": "Flamenco",
"music_for_children": "Musik for børn",
"new-age_music": "New Age musik",
"vocal_music": "Vokalmusik",
"a_capella": "A Capella",
"music_of_africa": "Musik fra Afrika",
"afrobeat": "Afrobeat",
"christian_music": "Kristen musik",
"gospel_music": "Gospelmusik",
"music_of_asia": "Musik fra Asian",
"ska": "Ska",
"traditional_music": "Traditionel musik",
"independent_music": "Individual musik",
"background_music": "Baggrundsmusik",
"theme_music": "Temamusik",
"jingle": "Jingle",
"soundtrack_music": "Soundtrack musik",
"video_game_music": "Videospil musik",
"christmas_music": "Julemusik",
"dance_music": "Dance musik",
"wedding_music": "Bryllupsmusik",
"happy_music": "Glad musik",
"sad_music": "Trist musik",
"tender_music": "Kærlighedsmusik",
"exciting_music": "Spændende musik",
"angry_music": "Vred musik",
"scary_music": "Skræmmende musik",
"rustling_leaves": "Raslende blade",
"wind_noise": "Vindstøj",
"rain_on_surface": "Regn på en overflade",
"stream": "Strøm",
"ocean": "Verdenshav",
"steam": "Damp",
"gurgling": "Gurgle",
"crackle": "Knitren",
"motor_vehicle": "Motoriseret køretøj",
"toot": "Dytte",
"car_alarm": "Bilalarm",
"power_windows": "Elruder",
"skidding": "Udskridning",
"tire_squeal": "Dækhvin",
"car_passing_by": "Forbikørende bil",
"race_car": "Racerbil",
"truck": "Lastbil",
"air_brake": "Trykluftsbremse",
"air_horn": "Trykluftshorn",
"reversing_beeps": "Bakalarm",
"ice_cream_truck": "Isbil",
"emergency_vehicle": "Redningskøretøj",
"police_car": "Politibil",
"fire_engine": "Brandbil",
"traffic_noise": "Trafikstøj",
"rail_transport": "Godstogstransport",
"train_whistle": "Togfløjt",
"train_horn": "Toghorn",
"railroad_car": "Togvogn",
"train_wheels_squealing": "Skinneskrig",
"subway": "Undergrund",
"aircraft": "Fly",
"aircraft_engine": "Flymotor",
"jet_engine": "Jetmotor",
"propeller": "Probel",
"fixed-wing_aircraft": "Fastvingefly",
"engine": "Motor",
"light_engine": "Lille motor",
"swing_music": "Swing musik",
"bluegrass": "Bluegrass"
} }
+7 -1
View File
@@ -1 +1,7 @@
{} {
"camera_ui": {
"review": {
"label": "Vis i gennemgang"
}
}
}
@@ -170,7 +170,10 @@
"title": "Rediger kategoriseringsmodel", "title": "Rediger kategoriseringsmodel",
"descriptionState": "Rediger kategorierne for denne model til genstandstilstande. Ændringer kræver, at modellen trænes igen.", "descriptionState": "Rediger kategorierne for denne model til genstandstilstande. Ændringer kræver, at modellen trænes igen.",
"descriptionObject": "Rediger genstandstypen og kategoriseringstypen for denne genstandskategoriseringsmodel.", "descriptionObject": "Rediger genstandstypen og kategoriseringstypen for denne genstandskategoriseringsmodel.",
"stateClassesInfo": "Bemærk: Ændring af tilstandskategorier kræver, at modellen trænes igen med de opdaterede kategorier." "stateClassesInfo": "Bemærk: Ændring af tilstandskategorier kræver, at modellen trænes igen med de opdaterede kategorier.",
"enabled": "Aktiveret",
"enabledDesc": "Kør denne model. Ved deaktivering stopper den med at køre og klassificerer ikke længere",
"saveAttempts": "Gem forsøg"
}, },
"deleteDatasetImages": { "deleteDatasetImages": {
"title": "Slet billeder i datasættet", "title": "Slet billeder i datasættet",
+4 -1
View File
@@ -28,5 +28,8 @@
"detail": { "detail": {
"aria": "Skift til detaljevisning" "aria": "Skift til detaljevisning"
}, },
"timeline.aria": "Vælg tidslinje" "timeline.aria": "Vælg tidslinje",
"motionPreviews": {
"crop": "Beskær til filter"
}
} }
@@ -1 +1,9 @@
{} {
"metrics": {
"minutesSeconds": "{{minutes}}m {{seconds}}s",
"scanSummary": "{{segments}} segmentee · {{time}}"
},
"polygonControls": {
"moveMode": "Flyt"
}
}
+26
View File
@@ -16,5 +16,31 @@
"menu": { "menu": {
"ui": "Brugergrænseflade", "ui": "Brugergrænseflade",
"profiles": "Profiler" "profiles": "Profiler"
},
"configMessages": {
"model": {
"optimizedFor320": "Frigate er optimeret til en 320x320 model, der er det bedste valg for de fleste konfigurationer. En 640x640 model er langsommere og hjælper kun i særlige scenarier"
}
},
"configForm": {
"cameraInputs": {
"sourceMode": {
"manual": "Manuel input sti"
}
}
},
"cameraManagement": {
"clone": {
"footer": {
"restartNeeded": "Genstart er nødvendig for visse ændringer.",
"liveOnly": "Alle ændringer vil blive aktiveret umiddelbart uden genstart.",
"submit": "Klone",
"submitting": "Kloner…"
},
"toast": {
"success": "Indstillinger kopieret til {{cameraName}}",
"successWithRestart": "Indstillinger kopieret til {{cameraName}}. Genstart Frigate for at aktivere alle ændringer."
}
}
} }
} }
+10 -6
View File
@@ -30,6 +30,9 @@
"ask_a": "Ist dieses Objekt ein <code>{{label}}</code>?", "ask_a": "Ist dieses Objekt ein <code>{{label}}</code>?",
"ask_an": "Ist dieses Objekt ein <code>{{label}}</code>?", "ask_an": "Ist dieses Objekt ein <code>{{label}}</code>?",
"ask_full": "Ist dieses Objekt ein <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?" "ask_full": "Ist dieses Objekt ein <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
},
"toast": {
"error": "Übermittlung an Frigate+ fehlgeschlagen. Bitte überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut."
} }
}, },
"submitToPlus": { "submitToPlus": {
@@ -65,7 +68,8 @@
"error": { "error": {
"endTimeMustAfterStartTime": "Die Endzeit darf nicht vor der Startzeit liegen", "endTimeMustAfterStartTime": "Die Endzeit darf nicht vor der Startzeit liegen",
"failed": "Fehler beim Export in die Warteschlange: {{error}}", "failed": "Fehler beim Export in die Warteschlange: {{error}}",
"noVaildTimeSelected": "Kein gültiger Zeitraum ausgewählt" "noVaildTimeSelected": "Kein gültiger Zeitraum ausgewählt",
"noValidTimeSelected": "Kein gültiger Zeitraum ausgewählt"
}, },
"success": "Export erfolgreich gestartet. Die Datei befindet sich auf der Exportseite.", "success": "Export erfolgreich gestartet. Die Datei befindet sich auf der Exportseite.",
"view": "Ansicht", "view": "Ansicht",
@@ -74,8 +78,8 @@
"batchSuccess_other": "{{count}} Exports gestartet. Öffne den Fall jetzt.", "batchSuccess_other": "{{count}} Exports gestartet. Öffne den Fall jetzt.",
"batchPartial": "{{successful}} von {{total}} Exporten gestartet. Fehlgeschlagene Kameras: {{failedCameras}}", "batchPartial": "{{successful}} von {{total}} Exporten gestartet. Fehlgeschlagene Kameras: {{failedCameras}}",
"batchFailed": "Fehler beim Starten der {{total}} Exporte. Fehlgeschlagene Kameras: {{failedCameras}}", "batchFailed": "Fehler beim Starten der {{total}} Exporte. Fehlgeschlagene Kameras: {{failedCameras}}",
"batchQueuedSuccess_one": "1 Export in die Warteschlange gestellt. Fall wird jetzt geöffnet.", "batchQueuedSuccess_one": "1 Export in die Warteschlange gestellt.",
"batchQueuedSuccess_other": "{{count}} Exporte in der Warteschlange. Fall wird jetzt geöffnet.", "batchQueuedSuccess_other": "{{count}} Exporte in der Warteschlange.",
"batchQueuedPartial": "{{successful}} von {{total}} Exporten in die Warteschlange gestellt. Fehlerhafte Kameras: {{failedCameras}}", "batchQueuedPartial": "{{successful}} von {{total}} Exporten in die Warteschlange gestellt. Fehlerhafte Kameras: {{failedCameras}}",
"batchQueueFailed": "Fehler beim Einreihen von {{total}} Exporten in die Warteschlange. Fehlerhafte Kameras: {{failedCameras}}" "batchQueueFailed": "Fehler beim Einreihen von {{total}} Exporten in die Warteschlange. Fehlerhafte Kameras: {{failedCameras}}"
}, },
@@ -108,7 +112,7 @@
"noCameras": "Keine Kameras verfügbar", "noCameras": "Keine Kameras verfügbar",
"detectionCount_one": "1 verfolgtes Objekt", "detectionCount_one": "1 verfolgtes Objekt",
"detectionCount_other": "{{count}} verfolgte Objekte", "detectionCount_other": "{{count}} verfolgte Objekte",
"nameLabel": "Exportname", "nameLabel": "Name exportieren",
"namePlaceholder": "Optionaler Basisname für diese Exporte", "namePlaceholder": "Optionaler Basisname für diese Exporte",
"queueingButton": "Exporte werden in die Warteschlange gestellt...", "queueingButton": "Exporte werden in die Warteschlange gestellt...",
"exportButton_one": "Export 1 Kamera", "exportButton_one": "Export 1 Kamera",
@@ -131,8 +135,8 @@
"exportButton_other": "{{count}} Bewertung exportieren", "exportButton_other": "{{count}} Bewertung exportieren",
"exportingButton": "Exportieren...", "exportingButton": "Exportieren...",
"toast": { "toast": {
"started_one": "1 Export gestartet. Fall wird jetzt geöffnet.", "started_one": "1 Export gestartet.",
"started_other": "{{count}} Exporte gestartet. Fall wird jetzt geöffnet.", "started_other": "{{count}} Exporte gestartet.",
"startedNoCase_one": "1 Export gestartet.", "startedNoCase_one": "1 Export gestartet.",
"startedNoCase_other": "{{count}} Exports gestartet.", "startedNoCase_other": "{{count}} Exports gestartet.",
"partial": "{{successful}} von {{total}} Exporten gestartet. Fehlgeschlagen: {{failedItems}}", "partial": "{{successful}} von {{total}} Exporten gestartet. Fehlgeschlagen: {{failedItems}}",
+5 -2
View File
@@ -545,6 +545,9 @@
"max_concurrent": { "max_concurrent": {
"label": "Maximale Anzahl gleichzeitiger Exporte", "label": "Maximale Anzahl gleichzeitiger Exporte",
"description": "Maximale Anzahl der gleichzeitig zu verarbeitenden Exportaufträge." "description": "Maximale Anzahl der gleichzeitig zu verarbeitenden Exportaufträge."
},
"chapters": {
"label": "Kapitel-Metadaten, die in exportierte Aufzeichnungen eingebettet werden sollen"
} }
}, },
"preview": { "preview": {
@@ -768,8 +771,8 @@
"description": "Numerische Reihenfolge, nach der die Kamera in der Benutzeroberfläche sortiert wird (Standard-Dashboard und Listen); höhere Zahlen erscheinen später." "description": "Numerische Reihenfolge, nach der die Kamera in der Benutzeroberfläche sortiert wird (Standard-Dashboard und Listen); höhere Zahlen erscheinen später."
}, },
"dashboard": { "dashboard": {
"label": "In der Benutzeroberfläche anzeigen", "label": "Im Live-Dashboard anzeigen",
"description": "Schalte ein, ob diese Kamera überall in der Benutzeroberfläche von „Frigate“ sichtbar ist. Wenn du diese Option deaktivierst, musst du die Konfiguration manuell bearbeiten, um diese Kamera wieder in der Benutzeroberfläche anzuzeigen." "description": "Legt fest, ob diese Kamera im Standard-Live-Dashboard „Alle Kameras“ angezeigt wird. Die Kamera bleibt in allen anderen Bereichen der Benutzeroberfläche verfügbar, einschließlich Kameragruppen und Einstellungen."
}, },
"review": { "review": {
"label": "In der Überprüfung anzeigen", "label": "In der Überprüfung anzeigen",
+8 -5
View File
@@ -539,7 +539,7 @@
}, },
"network_bandwidth": { "network_bandwidth": {
"label": "Netzwerk Bandbreite", "label": "Netzwerk Bandbreite",
"description": "Aktivieren Sie die prozessbezogene Überwachung der Netzwerkbandbreite für Kamera-FFmpeg-Prozesse und Detektoren (erfordert entsprechende Funktionen)." "description": "Prozessbezogene Überwachung der Netzwerkbandbreite für Kamera-FFmpeg-Prozesse und Detektoren aktivieren (erfordert entsprechende Funktionen)."
}, },
"intel_gpu_device": { "intel_gpu_device": {
"label": "Intel GPU", "label": "Intel GPU",
@@ -1123,7 +1123,7 @@
}, },
"model_type": { "model_type": {
"label": "Typ des Objekterkennungsmodells", "label": "Typ des Objekterkennungsmodells",
"description": "Typ der Detektor-Modellarchitektur (ssd, yolox, yolonas), der von einigen Detektoren zur Optimierung verwendet wird." "description": "Typ der Detektor-Modellarchitektur (ssd, yolox, yolonas, yolo-generic, rfdetr, dfine), der von einigen Detektoren zur Optimierung verwendet wird."
} }
}, },
"model_path": { "model_path": {
@@ -1400,6 +1400,9 @@
"max_concurrent": { "max_concurrent": {
"label": "Maximale Anzahl gleichzeitiger Exporte", "label": "Maximale Anzahl gleichzeitiger Exporte",
"description": "Maximale Anzahl der gleichzeitig zu verarbeitenden Exportaufträge." "description": "Maximale Anzahl der gleichzeitig zu verarbeitenden Exportaufträge."
},
"chapters": {
"label": "Kapitel-Metadaten, die in exportierte Aufzeichnungen eingebettet werden sollen"
} }
}, },
"preview": { "preview": {
@@ -1729,7 +1732,7 @@
}, },
"model_type": { "model_type": {
"label": "Typ des Objekterkennungsmodells", "label": "Typ des Objekterkennungsmodells",
"description": "Typ der Detektor-Modellarchitektur (ssd, yolox, yolonas), der von einigen Detektoren zur Optimierung verwendet wird." "description": "Typ der Detektor-Modellarchitektur (ssd, yolox, yolonas, yolo-generic, rfdetr, dfine), der von einigen Detektoren zur Optimierung verwendet wird."
} }
}, },
"genai": { "genai": {
@@ -1932,8 +1935,8 @@
"description": "Numerische Reihenfolge, nach der die Kamera in der Benutzeroberfläche sortiert wird (Standard-Dashboard und Listen); höhere Zahlen erscheinen später." "description": "Numerische Reihenfolge, nach der die Kamera in der Benutzeroberfläche sortiert wird (Standard-Dashboard und Listen); höhere Zahlen erscheinen später."
}, },
"dashboard": { "dashboard": {
"label": "In der Benutzeroberfläche anzeigen", "label": "Im Live-Dashboard anzeigen",
"description": "Schalte ein, ob diese Kamera überall in der Benutzeroberfläche von „Frigate“ sichtbar ist. Wenn du diese Option deaktivierst, musst du die Konfiguration manuell bearbeiten, um diese Kamera wieder in der Benutzeroberfläche anzuzeigen." "description": "Legt fest, ob diese Kamera im Live-Dashboard ‚Alle Kameras‘ angezeigt wird. Die Kamera bleibt in allen anderen Bereichen der Benutzeroberfläche verfügbar, einschließlich Kamera-Gruppen und Einstellungen."
}, },
"review": { "review": {
"label": "In der Überprüfung anzeigen", "label": "In der Überprüfung anzeigen",
+3 -1
View File
@@ -125,5 +125,7 @@
"baby": "Baby", "baby": "Baby",
"baby_stroller": "Kinderwagen", "baby_stroller": "Kinderwagen",
"rickshaw": "Rikscha", "rickshaw": "Rikscha",
"rodent": "Nagetier" "rodent": "Nagetier",
"garbage_truck": "Müllwagen",
"possum": "Opossum"
} }
@@ -56,25 +56,38 @@
}, },
"deleteModel": { "deleteModel": {
"title": "Klassifizierungsmodell löschen", "title": "Klassifizierungsmodell löschen",
"single": "Möchten Sie {{name}} wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "single": "{{name}} wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
"desc_one": "Möchtest du {{count}} Modell wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "desc_one": "{{count}} Modell wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
"desc_other": "Möchtest du {{count}} Modelle wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden." "desc_other": "{{count}} Modelle wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
}, },
"edit": { "edit": {
"title": "Klassifikationsmodell bearbeiten", "title": "Klassifikationsmodell bearbeiten",
"descriptionState": "Bearbeite die Klassen für dieses Zustandsklassifikationsmodell. Änderungen erfordern ein erneutes Trainieren des Modells.", "descriptionState": "Bearbeite die Klassen für dieses Zustandsklassifikationsmodell. Änderungen erfordern ein erneutes Trainieren des Modells.",
"descriptionObject": "Bearbeite den Objekttyp und Klassifizierungstyp für dieses Objektklassifikationsmodell.", "descriptionObject": "Bearbeite den Objekttyp und Klassifizierungstyp für dieses Objektklassifikationsmodell.",
"stateClassesInfo": "Hinweis: Die Änderung der Statusklassen erfordert ein erneutes Trainieren des Modells mit den aktualisierten Klassen." "stateClassesInfo": "Modell aktualisiert. Trainieren Sie das Modell neu, damit die Klassenänderungen wirksam werden.",
"enabled": "Aktiviert",
"enabledDesc": "Dieses Modell ausführen. Bei Deaktivierung wird es nicht mehr ausgeführt und führt keine Klassifizierung mehr durch.",
"saveAttempts": "Versuche speichern",
"saveAttemptsDesc": "Anzahl der für die Übersicht der letzten Klassifizierungen zu speichernden Bilder.",
"motion": "Bei Bewegung ausführen",
"motionDesc": "Klassifizierung bei Bewegung im konfigurierten Bildausschnitt ausführen.",
"interval": "Intervall",
"intervalDesc": "Sekundenabstand zwischen regelmäßigen Klassifizierungsläufen. Leer lassen, um die Klassifizierung nur bei Bewegung auszuführen.",
"intervalPlaceholder": "Kein Intervall",
"errors": {
"saveAttemptsInvalid": "Die Anzahl der Speicherversuche muss eine ganze Zahl sein, die größer oder gleich 0 ist",
"intervalInvalid": "Intervall muss eine ganze Zahl sein, die größer als 0 ist"
}
}, },
"deleteDatasetImages": { "deleteDatasetImages": {
"title": "Datensatz Bilder löschen", "title": "Datensatz Bilder löschen",
"desc_one": "Bist du sicher, dass {{count}} Bild von {{dataset}} gelöscht werden sollen? Diese Aktion kann nicht rückgängig gemacht werden und erfordert ein erneutes Trainieren des Modells.", "desc_one": "Wirklich {{count}} Bild von {{dataset}} löschen? Diese Aktion kann nicht rückgängig gemacht werden und erfordert ein erneutes Trainieren des Modells.",
"desc_other": "Bist du sicher, dass {{count}} Bilder von {{dataset}} gelöscht werden sollen? Diese Aktion kann nicht rückgängig gemacht werden und erfordert ein erneutes Trainieren des Modells." "desc_other": "Wirklich {{count}} Bilder von {{dataset}} löschen? Diese Aktion kann nicht rückgängig gemacht werden und erfordert ein erneutes Trainieren des Modells."
}, },
"deleteTrainImages": { "deleteTrainImages": {
"title": "Trainingsbilder löschen", "title": "Trainingsbilder löschen",
"desc_one": "Bist du sicher, dass du {{count}} Bild löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "desc_one": "Wirklich {{count}} Bild löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"desc_other": "Bist du sicher, dass du {{count}} Bilder löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden." "desc_other": "Wirklich {{count}} Bilder löschen? Diese Aktion kann nicht rückgängig gemacht werden."
}, },
"renameCategory": { "renameCategory": {
"title": "Klasse umbenennen", "title": "Klasse umbenennen",
@@ -85,7 +98,7 @@
}, },
"train": { "train": {
"title": "Neue Klassifizierungen", "title": "Neue Klassifizierungen",
"titleShort": "frisch", "titleShort": "Neu",
"aria": "Neue Klassifizierungen auswählen" "aria": "Neue Klassifizierungen auswählen"
}, },
"categories": "Klassen", "categories": "Klassen",
@@ -195,5 +208,6 @@
}, },
"none": "Keiner", "none": "Keiner",
"reclassifyImageAs": "Bild neu klassifizieren als:", "reclassifyImageAs": "Bild neu klassifizieren als:",
"reclassifyImage": "Bild neu klassifizieren" "reclassifyImage": "Bild neu klassifizieren",
"disabled": "Deaktiviert"
} }
+5 -2
View File
@@ -76,7 +76,7 @@
"menuItem": "Bewegungsvorschau anzeigen", "menuItem": "Bewegungsvorschau anzeigen",
"title": "Bewegungsvorschau: {{camera}}", "title": "Bewegungsvorschau: {{camera}}",
"mobileSettingsTitle": "Einstellungen für die Bewegungsvorschau", "mobileSettingsTitle": "Einstellungen für die Bewegungsvorschau",
"mobileSettingsDesc": "Passen Sie die Wiedergabegeschwindigkeit und die Dimmung an und wählen Sie ein Datum aus, um Clips mit nur Bewegungen anzusehen.", "mobileSettingsDesc": "Passen Sie die Wiedergabegeschwindigkeit, die Dimmung und den Zuschnitt an und wählen Sie ein Datum aus, um Clips mit ausschließlich erkannten Bewegungen zu überprüfen.",
"dim": "düster", "dim": "düster",
"dimAria": "Dimmintensität einstellen", "dimAria": "Dimmintensität einstellen",
"dimDesc": "Erhöhen Sie die Dimmung, um die Sichtbarkeit des Bewegungsbereichs zu verbessern.", "dimDesc": "Erhöhen Sie die Dimmung, um die Sichtbarkeit des Bewegungsbereichs zu verbessern.",
@@ -89,6 +89,9 @@
"seekAria": "{{camera}} Player suchen bis {{time}}", "seekAria": "{{camera}} Player suchen bis {{time}}",
"filter": "Filter", "filter": "Filter",
"filterDesc": "Wählen Sie Bereiche aus, um nur Clips mit Bewegungen in diesen Regionen anzuzeigen.", "filterDesc": "Wählen Sie Bereiche aus, um nur Clips mit Bewegungen in diesen Regionen anzuzeigen.",
"filterClear": "Säubern" "filterClear": "Säubern",
"crop": "Auf Filterbereich zuschneiden",
"cropAria": "Vorschauen auf die gefilterten Bereiche zuschneiden ein- oder ausschalten",
"cropDesc": "Zeigt eine vergrößerte Vorschau der ausgewählten Filterbereiche an, statt das vollständige Bild zu zeigen."
} }
} }
+1 -1
View File
@@ -48,7 +48,7 @@
"title": "Neueste Erkennungen", "title": "Neueste Erkennungen",
"aria": "Wähle aktuelle Erkennungen", "aria": "Wähle aktuelle Erkennungen",
"empty": "Es gibt keine aktuellen Versuche zur Gesichtserkennung", "empty": "Es gibt keine aktuellen Versuche zur Gesichtserkennung",
"titleShort": "frisch", "titleShort": "Neu",
"emptyNoLibrary": { "emptyNoLibrary": {
"title": "Gesicht hinzufügen", "title": "Gesicht hinzufügen",
"description": "Sie müssen mindestens ein Gesicht zur Bibliothek hinzufügen, damit die Gesichtserkennung funktioniert." "description": "Sie müssen mindestens ein Gesicht zur Bibliothek hinzufügen, damit die Gesichtserkennung funktioniert."
+2 -1
View File
@@ -22,7 +22,8 @@
"error": "Fehler beim Starten der Debug-Wiedergabe: {{error}}", "error": "Fehler beim Starten der Debug-Wiedergabe: {{error}}",
"alreadyActive": "Eine Wiederholungssitzung ist bereits aktiv", "alreadyActive": "Eine Wiederholungssitzung ist bereits aktiv",
"stopError": "Die Wiedergabe der Debug-Daten konnte nicht beendet werden: {{error}}", "stopError": "Die Wiedergabe der Debug-Daten konnte nicht beendet werden: {{error}}",
"goToReplay": "Zur Aufzeichnung" "goToReplay": "Zur Aufzeichnung",
"noRecordings": "Keine Aufnahmen im ausgewählten Zeitraum gefunden"
} }
}, },
"page": { "page": {
+15 -7
View File
@@ -570,7 +570,7 @@
"openCameraWebUI": "Web-Benutzeroberfläche von {{camera}} öffnen", "openCameraWebUI": "Web-Benutzeroberfläche von {{camera}} öffnen",
"audio": { "audio": {
"title": "Audio", "title": "Audio",
"noAudioDetections": "Keine Audioerkennungen", "noAudioDetections": "Kein Audio-Ereignis erkannt",
"score": "Punktzahl", "score": "Punktzahl",
"currentRMS": "Aktueller Effektivwert", "currentRMS": "Aktueller Effektivwert",
"currentdbFS": "Aktuelle dbFS" "currentdbFS": "Aktuelle dbFS"
@@ -1383,7 +1383,7 @@
"webuiUrlHelp": "URL, um die Web-Benutzeroberfläche der Kamera direkt aus der Debug-Ansicht aufzurufen. Lassen Sie das Feld leer, um den Link zu deaktivieren.", "webuiUrlHelp": "URL, um die Web-Benutzeroberfläche der Kamera direkt aus der Debug-Ansicht aufzurufen. Lassen Sie das Feld leer, um den Link zu deaktivieren.",
"webuiUrlInvalid": "Es muss sich um eine gültige URL handeln (z. B. https://example.com).", "webuiUrlInvalid": "Es muss sich um eine gültige URL handeln (z. B. https://example.com).",
"dashboardLabel": "Im Live-Dashboard anzeigen", "dashboardLabel": "Im Live-Dashboard anzeigen",
"dashboardHelp": "Diese Kamera im Live-Dashboard anzeigen.", "dashboardHelp": "Diese Kamera im Standard Dashboard \"Alle Live Kameras\" anzeigen. Sie bleibt überall sonst verfügbar einschließlich Kameragruppen.",
"reviewLabel": "In der Überprüfung anzeigen", "reviewLabel": "In der Überprüfung anzeigen",
"reviewHelp": "Zeige diese Kamera in der Übersicht an, einschließlich des Kamerafilters, der Bewegungsübersicht und der Verlaufsansicht." "reviewHelp": "Zeige diese Kamera in der Übersicht an, einschließlich des Kamerafilters, der Bewegungsübersicht und der Verlaufsansicht."
}, },
@@ -1430,7 +1430,7 @@
"deleteCameraDialog": { "deleteCameraDialog": {
"title": "Kamera löschen", "title": "Kamera löschen",
"description": "Durch das Löschen einer Kamera werden alle Aufzeichnungen, erfassten Objekte und Konfigurationseinstellungen für diese Kamera endgültig entfernt. Alle mit dieser Kamera verbundenen go2rtc-Streams müssen möglicherweise noch manuell entfernt werden.", "description": "Durch das Löschen einer Kamera werden alle Aufzeichnungen, erfassten Objekte und Konfigurationseinstellungen für diese Kamera endgültig entfernt. Alle mit dieser Kamera verbundenen go2rtc-Streams müssen möglicherweise noch manuell entfernt werden.",
"selectPlaceholder": "Kamera auswählen...", "selectPlaceholder": "Kamera auswählen…",
"confirmTitle": "Bist du dir sicher?", "confirmTitle": "Bist du dir sicher?",
"confirmWarning": "Das Löschen von <strong>{{cameraName}}</strong> kann nicht rückgängig gemacht werden.", "confirmWarning": "Das Löschen von <strong>{{cameraName}}</strong> kann nicht rückgängig gemacht werden.",
"deleteExports": "Lösche auch die Exporte für diese Kamera", "deleteExports": "Lösche auch die Exporte für diese Kamera",
@@ -1608,8 +1608,8 @@
"overriddenInCameras": { "overriddenInCameras": {
"label_one": "In {{count}} Kamera überschrieben", "label_one": "In {{count}} Kamera überschrieben",
"label_other": "In {{count}} Kameras überschrieben", "label_other": "In {{count}} Kameras überschrieben",
"tooltip_one": "Die Kamera mit der Nummer {{count}} überschreibt die Werte in diesem Abschnitt. Klicken Sie hier, um Details anzuzeigen.", "tooltip_one": "Die Kamera mit der Nummer {{count}} überschreibt die Werte in diesem Abschnitt. Hier klicken, um Details anzuzeigen.",
"tooltip_other": "Die Kamera mit der Nummer {{count}} überschreibt die Werte in diesem Abschnitt. Klicken Sie hier, um Details anzuzeigen.", "tooltip_other": "{{count}} Kameras überschreiben die Werte in diesem Abschnitt. Hier klicken, um Details anzuzeigen.",
"heading_one": "Dieser globale Abschnitt enthält Felder, die in {{count}} Kamera überschrieben werden.", "heading_one": "Dieser globale Abschnitt enthält Felder, die in {{count}} Kamera überschrieben werden.",
"heading_other": "Dieser globale Abschnitt enthält Felder, die bei {{count}} Kameras überschrieben werden.", "heading_other": "Dieser globale Abschnitt enthält Felder, die bei {{count}} Kameras überschrieben werden.",
"othersField_one": "{{count}} andere", "othersField_one": "{{count}} andere",
@@ -1720,7 +1720,14 @@
"keyLabel": "Schlüssel", "keyLabel": "Schlüssel",
"valueLabel": "Wert", "valueLabel": "Wert",
"keyPlaceholder": "Neuer Schlüssel", "keyPlaceholder": "Neuer Schlüssel",
"remove": "Entfernen" "remove": "Entfernen",
"providerNameLabel": "Anbietername",
"providerNamePlaceholder": "z. B. openai",
"variableNameLabel": "Variablenname",
"variableNamePlaceholder": "z.B. MY_VARIABLE",
"loggerNameLabel": "Name des Loggers",
"loggerNamePlaceholder": "z.B. frigate.record",
"keyPatternError": "Verwende nur Buchstaben, Zahlen, Bindestriche und Unterstriche (keine Leerzeichen)"
}, },
"timezone": { "timezone": {
"defaultOption": "Zeitzone des Browsers verwenden" "defaultOption": "Zeitzone des Browsers verwenden"
@@ -2135,7 +2142,8 @@
"autotrackingNoZones": "Für die automatische Verfolgung ist mindestens eine Zone erforderlich. Definieren Sie unter „Masken / Zonen“ eine Zone für diese Kamera und legen Sie diese anschließend unten als erforderliche Zone fest." "autotrackingNoZones": "Für die automatische Verfolgung ist mindestens eine Zone erforderlich. Definieren Sie unter „Masken / Zonen“ eine Zone für diese Kamera und legen Sie diese anschließend unten als erforderliche Zone fest."
}, },
"ffmpeg": { "ffmpeg": {
"hwaccelManualNotRecommended": "Explizite Definitionen der Hardware-beschleunigungs Variablen sind nicht empfohlen. Wähle die Voreinstellung die zu deiner Hardware passt, außer wenn spezifische Anforderungen eine andere Konfiguration erfordern." "hwaccelManualNotRecommended": "Explizite Definitionen der Hardware-beschleunigungs Variablen sind nicht empfohlen. Wähle die Voreinstellung die zu deiner Hardware passt, außer wenn spezifische Anforderungen eine andere Konfiguration erfordern.",
"inputsMissingGo2rtcStream": "Ein Eingabefeld unten verweist auf einen go2rtc-Restream, der nicht mehr existiert. Wählen Sie einen vorhandenen Restream aus oder geben Sie die URL der Kamera manuell ein. Andernfalls kann keine Verbindung zu dieser Kamera hergestellt werden."
}, },
"model": { "model": {
"optimizedFor320": "Frigate ist für ein 320x320-Modell optimiert, was für die meisten Konfigurationen die beste Wahl ist. Ein 640x640-Modell ist langsamer und bietet nur in bestimmten Szenarien Vorteile.", "optimizedFor320": "Frigate ist für ein 320x320-Modell optimiert, was für die meisten Konfigurationen die beste Wahl ist. Ein 640x640-Modell ist langsamer und bietet nur in bestimmten Szenarien Vorteile.",
-1
View File
@@ -426,7 +426,6 @@
"radio": "Radio", "radio": "Radio",
"field_recording": "Field Recording", "field_recording": "Field Recording",
"scream": "Scream", "scream": "Scream",
"sodeling": "Sodeling",
"chird": "Chird", "chird": "Chird",
"change_ringing": "Change Ringing", "change_ringing": "Change Ringing",
"shofar": "Shofar", "shofar": "Shofar",
+4 -6
View File
@@ -100,10 +100,8 @@
"exportButton_other": "Export {{count}} reviews", "exportButton_other": "Export {{count}} reviews",
"exportingButton": "Exporting...", "exportingButton": "Exporting...",
"toast": { "toast": {
"started_one": "Started 1 export. Opening the case now.", "started_one": "Started 1 export.",
"started_other": "Started {{count}} exports. Opening the case now.", "started_other": "Started {{count}} exports.",
"startedNoCase_one": "Started 1 export.",
"startedNoCase_other": "Started {{count}} exports.",
"partial": "Started {{successful}} of {{total}} exports. Failed: {{failedItems}}", "partial": "Started {{successful}} of {{total}} exports. Failed: {{failedItems}}",
"failed": "Failed to start {{total}} exports. Failed: {{failedItems}}" "failed": "Failed to start {{total}} exports. Failed: {{failedItems}}"
} }
@@ -116,8 +114,8 @@
"batchSuccess_other": "Started {{count}} exports. Opening the case now.", "batchSuccess_other": "Started {{count}} exports. Opening the case now.",
"batchPartial": "Started {{successful}} of {{total}} exports. Failed cameras: {{failedCameras}}", "batchPartial": "Started {{successful}} of {{total}} exports. Failed cameras: {{failedCameras}}",
"batchFailed": "Failed to start {{total}} exports. Failed cameras: {{failedCameras}}", "batchFailed": "Failed to start {{total}} exports. Failed cameras: {{failedCameras}}",
"batchQueuedSuccess_one": "Queued 1 export. Opening the case now.", "batchQueuedSuccess_one": "Queued 1 export.",
"batchQueuedSuccess_other": "Queued {{count}} exports. Opening the case now.", "batchQueuedSuccess_other": "Queued {{count}} exports.",
"batchQueuedPartial": "Queued {{successful}} of {{total}} exports. Failed cameras: {{failedCameras}}", "batchQueuedPartial": "Queued {{successful}} of {{total}} exports. Failed cameras: {{failedCameras}}",
"batchQueueFailed": "Failed to queue {{total}} exports. Failed cameras: {{failedCameras}}", "batchQueueFailed": "Failed to queue {{total}} exports. Failed cameras: {{failedCameras}}",
"error": { "error": {
+1
View File
@@ -21,6 +21,7 @@
"toast": { "toast": {
"error": "Failed to start debug replay: {{error}}", "error": "Failed to start debug replay: {{error}}",
"alreadyActive": "A replay session is already active", "alreadyActive": "A replay session is already active",
"noRecordings": "No recordings found in the selected time range",
"stopError": "Failed to stop debug replay: {{error}}", "stopError": "Failed to stop debug replay: {{error}}",
"goToReplay": "Go to Replay" "goToReplay": "Go to Replay"
} }
+1 -2
View File
@@ -463,7 +463,7 @@
"deleteCameraDialog": { "deleteCameraDialog": {
"title": "Delete Camera", "title": "Delete Camera",
"description": "Deleting a camera will permanently remove all recordings, tracked objects, and configuration for that camera. Any go2rtc streams associated with this camera may still need to be manually removed.", "description": "Deleting a camera will permanently remove all recordings, tracked objects, and configuration for that camera. Any go2rtc streams associated with this camera may still need to be manually removed.",
"selectPlaceholder": "Choose camera...", "selectPlaceholder": "Choose camera…",
"confirmTitle": "Are you sure?", "confirmTitle": "Are you sure?",
"confirmWarning": "Deleting <strong>{{cameraName}}</strong> cannot be undone.", "confirmWarning": "Deleting <strong>{{cameraName}}</strong> cannot be undone.",
"deleteExports": "Also delete exports for this camera", "deleteExports": "Also delete exports for this camera",
@@ -613,7 +613,6 @@
} }
}, },
"footer": { "footer": {
"changeCount_zero": "No changes selected",
"changeCount_one": "{{count}} change will be applied", "changeCount_one": "{{count}} change will be applied",
"changeCount_other": "{{count}} changes will be applied", "changeCount_other": "{{count}} changes will be applied",
"restartNeeded": "Restart will be required for some changes.", "restartNeeded": "Restart will be required for some changes.",
+1 -1
View File
@@ -38,7 +38,7 @@
"ask_full": "¿Es este objeto un <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?" "ask_full": "¿Es este objeto un <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
}, },
"toast": { "toast": {
"error": "Error al enviarlo a Frigate+. Por favor, comprueba tu conexión a internet y prueba de nuevo." "error": "No se pudo enviar a Frigate+. Verifique su conexión de red e inténtelo de nuevo."
} }
} }
}, },
+5 -2
View File
@@ -583,6 +583,9 @@
"max_concurrent": { "max_concurrent": {
"label": "Exportaciones simultáneas máximas", "label": "Exportaciones simultáneas máximas",
"description": "Número máximo de trabajos de exportación que se procesarán al mismo tiempo." "description": "Número máximo de trabajos de exportación que se procesarán al mismo tiempo."
},
"chapters": {
"label": "Metadatos de capítulo que se incluirán en las grabaciones exportadas"
} }
}, },
"preview": { "preview": {
@@ -600,8 +603,8 @@
}, },
"ui": { "ui": {
"dashboard": { "dashboard": {
"description": "Alterna si esta cámara es visible en toda la interfaz de usuario de Frigate. Desactivar esta opción requerirá editar manualmente la configuración para volver a visualizar esta cámara en la interfaz.", "description": "Alterna si esta cámara será visible en el panel de control en directo predeterminado Todas las cámaras. La cámara seguirá estando disponible en el resto de la interfaz, incluidos los grupos de cámaras y la configuración.",
"label": "Mostrar en la interfaz" "label": "Mostrar en el panel de control en directo"
}, },
"label": "Interfaz de cámara", "label": "Interfaz de cámara",
"description": "Orden de visualización y visibilidad de esta cámara en la interfaz. El orden afecta al panel predeterminado. Para un control más granular, usa grupos de cámaras.", "description": "Orden de visualización y visibilidad de esta cámara en la interfaz. El orden afecta al panel predeterminado. Para un control más granular, usa grupos de cámaras.",
+7 -4
View File
@@ -471,7 +471,7 @@
}, },
"model_type": { "model_type": {
"label": "Tipo de modelo de detección de objetos", "label": "Tipo de modelo de detección de objetos",
"description": "Tipo de arquitectura del modelo detector (ssd, yolox, yolonas) usado por algunos detectores para optimización." "description": "Tipo de arquitectura del modelo del detector (ssd, yolox, yolonas, yolo-generic, rfdetr, dfine) utilizado por algunos detectores para su optimización."
} }
}, },
"model_path": { "model_path": {
@@ -984,6 +984,9 @@
"max_concurrent": { "max_concurrent": {
"label": "Exportaciones simultáneas máximas", "label": "Exportaciones simultáneas máximas",
"description": "Número máximo de trabajos de exportación que se procesarán al mismo tiempo." "description": "Número máximo de trabajos de exportación que se procesarán al mismo tiempo."
},
"chapters": {
"label": "Metadatos de capítulo que se incluirán en las grabaciones exportadas"
} }
}, },
"preview": { "preview": {
@@ -1002,8 +1005,8 @@
}, },
"camera_ui": { "camera_ui": {
"dashboard": { "dashboard": {
"description": "Alterna si esta cámara es visible en toda la interfaz de usuario de Frigate. Desactivar esta opción requerirá editar manualmente la configuración para volver a visualizar esta cámara en la interfaz.", "description": "Alterna si esta cámara será visible en el panel de control en directo predeterminado Todas las cámaras. La cámara seguirá estando disponible en el resto de la interfaz, incluidos los grupos de cámaras y la configuración.",
"label": "Mostrar en la interfaz" "label": "Mostrar en el panel de control en directo"
}, },
"label": "Interfaz de cámara", "label": "Interfaz de cámara",
"description": "Orden de visualización y visibilidad de esta cámara en la interfaz. El orden afecta al panel predeterminado. Para un control más granular, usa grupos de cámaras.", "description": "Orden de visualización y visibilidad de esta cámara en la interfaz. El orden afecta al panel predeterminado. Para un control más granular, usa grupos de cámaras.",
@@ -1549,7 +1552,7 @@
}, },
"model_type": { "model_type": {
"label": "Tipo de modelo de detección de objetos", "label": "Tipo de modelo de detección de objetos",
"description": "Tipo de arquitectura del modelo detector (ssd, yolox, yolonas) usado por algunos detectores para optimización." "description": "Tipo de arquitectura del modelo del detector (ssd, yolox, yolonas, yolo-generic, rfdetr, dfine) utilizada por algunos detectores para su optimización."
} }
}, },
"classification": { "classification": {
+3 -1
View File
@@ -126,5 +126,7 @@
"baby_stroller": "Cochecito de bebé", "baby_stroller": "Cochecito de bebé",
"rickshaw": "Rickshaw", "rickshaw": "Rickshaw",
"Rodent": "Roedor", "Rodent": "Roedor",
"rodent": "Roedor" "rodent": "Roedor",
"possum": "Zarigüeya",
"garbage_truck": "Camión de basura"
} }
@@ -57,7 +57,20 @@
"title": "Editar modelo de clasificación", "title": "Editar modelo de clasificación",
"descriptionState": "Edita las clases para este modelo de clasificación de estados. Los cambios requerirán un reentrenamiento de modelo.", "descriptionState": "Edita las clases para este modelo de clasificación de estados. Los cambios requerirán un reentrenamiento de modelo.",
"descriptionObject": "Edita el tipo de objeto y el tipo de clasificación para este modelo de clasificación de objetos.", "descriptionObject": "Edita el tipo de objeto y el tipo de clasificación para este modelo de clasificación de objetos.",
"stateClassesInfo": "Nota: El cambio de las clases de estado requiere reentrenar el modelo con las clases actualizadas." "stateClassesInfo": "Modelo actualizado. Vuelva a entrenar el modelo para que los cambios de clase surtan efecto.",
"enabled": "Activado",
"enabledDesc": "Ejecuta este modelo. Cuando se desactiva, deja de funcionar y ya no clasifica.",
"saveAttempts": "Guardar intentos",
"saveAttemptsDesc": "Número de imágenes de intentos de clasificación que se conservarán para la interfaz de usuario de clasificaciones recientes.",
"motion": "Ejecutar en movimiento",
"motionDesc": "Ejecutar la clasificación cuando se detecte movimiento dentro del cultivo configurado.",
"interval": "Intervalo",
"intervalDesc": "Segundos entre ciclos de clasificación periódica. Dejar en blanco para que se ejecute solo con detección de movimiento.",
"intervalPlaceholder": "Sin intervalo",
"errors": {
"saveAttemptsInvalid": "Los intentos de guardado deben ser un número entero de 0 o mayor",
"intervalInvalid": "El intervalo debe ser un número entero mayor que 0"
}
}, },
"tooltip": { "tooltip": {
"noChanges": "No se han realizado cambios en el conjunto de datos desde el último entrenamiento.", "noChanges": "No se han realizado cambios en el conjunto de datos desde el último entrenamiento.",
@@ -201,5 +214,6 @@
} }
}, },
"reclassifyImage": "Reclasificar imagen", "reclassifyImage": "Reclasificar imagen",
"reclassifyImageAs": "Reclasificar imagen como:" "reclassifyImageAs": "Reclasificar imagen como:",
"disabled": "Desactivado"
} }
+5 -2
View File
@@ -77,7 +77,7 @@
"menuItem": "Ver vistas previas de movimiento", "menuItem": "Ver vistas previas de movimiento",
"title": "Vistas previas de movimiento: {{camera}}", "title": "Vistas previas de movimiento: {{camera}}",
"mobileSettingsTitle": "Ajustes de vistas previas de movimiento", "mobileSettingsTitle": "Ajustes de vistas previas de movimiento",
"mobileSettingsDesc": "Ajusta la velocidad de reproducción y el atenuado, y elige una fecha para revisar clips solo de movimiento.", "mobileSettingsDesc": "Ajusta la velocidad de reproducción, el brillo y el recorte, y elige una fecha para revisar los clips que solo contienen movimiento.",
"dim": "Atenuar", "dim": "Atenuar",
"dimAria": "Ajustar intensidad de atenuado", "dimAria": "Ajustar intensidad de atenuado",
"dimDesc": "Aumenta el atenuado para mejorar la visibilidad de las áreas con movimiento.", "dimDesc": "Aumenta el atenuado para mejorar la visibilidad de las áreas con movimiento.",
@@ -90,6 +90,9 @@
"seekAria": "Mover el reproductor de {{camera}} a {{time}}", "seekAria": "Mover el reproductor de {{camera}} a {{time}}",
"filter": "Filtrar", "filter": "Filtrar",
"filterDesc": "Selecciona áreas para mostrar solo clips con movimiento en esas regiones.", "filterDesc": "Selecciona áreas para mostrar solo clips con movimiento en esas regiones.",
"filterClear": "Limpiar" "filterClear": "Limpiar",
"crop": "Recortar para filtrar",
"cropAria": "Alternar las vistas previas de recorte a las áreas filtradas",
"cropDesc": "La vista previa con zoom se centra en las áreas de filtro seleccionadas en lugar de mostrar el fotograma completo."
} }
} }
+1 -1
View File
@@ -302,7 +302,7 @@
}, },
"offset": { "offset": {
"label": "Desplazamiento de anotación", "label": "Desplazamiento de anotación",
"desc": "Estos datos provienen de la señal de detección de la cámara, pero se superponen a las imágenes de la señal de grabación. Es poco probable que ambas transmisiones estén perfectamente sincronizadas. Por lo tanto, el cuadro delimitador y el metraje no se alinearán perfectamente. Puede usar esta configuración para desplazar las anotaciones hacia adelante o hacia atrás en el tiempo para que se alineen mejor con el metraje grabado.", "desc": "Estos datos provienen de la señal de detección de tu cámara, pero se superponen a las imágenes de la señal de grabación. Es poco probable que ambas señales estén perfectamente sincronizadas. Por lo tanto, el cuadro delimitador y la grabación no coincidirán a la perfección. Puedes usar esta configuración para desplazar las anotaciones hacia adelante o hacia atrás en el tiempo y así alinearlas mejor con la grabación.",
"millisecondsToOffset": "Milisegundos para compensar la detección de anotaciones. <em>Predeterminado: 0</em>", "millisecondsToOffset": "Milisegundos para compensar la detección de anotaciones. <em>Predeterminado: 0</em>",
"tips": "Disminuya el valor si la reproducción de vídeo se produce antes de los cuadros y los puntos de ruta, y auméntelo si se produce después de ellos. Este valor puede ser negativo.", "tips": "Disminuya el valor si la reproducción de vídeo se produce antes de los cuadros y los puntos de ruta, y auméntelo si se produce después de ellos. Este valor puede ser negativo.",
"toast": { "toast": {
+17 -5
View File
@@ -691,7 +691,7 @@
"multiDeviceAdmin": "También puede obligar a todos los usuarios a volver a autenticarse inmediatamente rotando su secreto JWT." "multiDeviceAdmin": "También puede obligar a todos los usuarios a volver a autenticarse inmediatamente rotando su secreto JWT."
}, },
"createUser": { "createUser": {
"desc": "Añadir una nueva cuenta de usuario y especificar un rol para el acceso a áreas de la interfaz de usuario de Frigate.", "desc": "Agregue una nueva cuenta de usuario y especifique un rol para el acceso a las áreas de la interfaz de usuario de Frigate.",
"title": "Crear nuevo usuario", "title": "Crear nuevo usuario",
"usernameOnlyInclude": "El nombre de usuario solo puede incluir letras, números, . o _", "usernameOnlyInclude": "El nombre de usuario solo puede incluir letras, números, . o _",
"confirmPassword": "Por favor, confirma tu contraseña" "confirmPassword": "Por favor, confirma tu contraseña"
@@ -1308,7 +1308,7 @@
"webuiUrlHelp": "URL para acceder directamente a la interfaz web de la cámara desde la vista de depuración. Déjala en blanco para deshabilitar el enlace.", "webuiUrlHelp": "URL para acceder directamente a la interfaz web de la cámara desde la vista de depuración. Déjala en blanco para deshabilitar el enlace.",
"webuiUrlInvalid": "Debe ser una URL válida (p. ej., https://ejemplo.com).", "webuiUrlInvalid": "Debe ser una URL válida (p. ej., https://ejemplo.com).",
"dashboardLabel": "Mostrar en el panel En directo", "dashboardLabel": "Mostrar en el panel En directo",
"dashboardHelp": "Mostrar esta cámara en el panel en directo.", "dashboardHelp": "Esta cámara se muestra en el panel de control predeterminado \"Todas las cámaras\". Permanece disponible en todas partes, incluidos los grupos de cámaras.",
"reviewLabel": "Mostrar en Revisión", "reviewLabel": "Mostrar en Revisión",
"reviewHelp": "Mostrar esta cámara en Revisión, incluido el filtro de cámaras, la revisión de movimiento y la vista de historial." "reviewHelp": "Mostrar esta cámara en Revisión, incluido el filtro de cámaras, la revisión de movimiento y la vista de historial."
}, },
@@ -1354,7 +1354,7 @@
"deleteCameraDialog": { "deleteCameraDialog": {
"description": "Eliminar una cámara borrará permanentemente todas las grabaciones, los objetos rastreados y la configuración de esa cámara. Es posible que sea necesario eliminar manualmente cualquier transmisión go2rtc asociada a esta cámara.", "description": "Eliminar una cámara borrará permanentemente todas las grabaciones, los objetos rastreados y la configuración de esa cámara. Es posible que sea necesario eliminar manualmente cualquier transmisión go2rtc asociada a esta cámara.",
"title": "Eliminar cámara", "title": "Eliminar cámara",
"selectPlaceholder": "Elegir cámara...", "selectPlaceholder": "Elige una cámara…",
"confirmTitle": "¿Estás seguro?", "confirmTitle": "¿Estás seguro?",
"confirmWarning": "Eliminar <strong>{{cameraName}}</strong> no se puede deshacer.", "confirmWarning": "Eliminar <strong>{{cameraName}}</strong> no se puede deshacer.",
"deleteExports": "Eliminar también las exportaciones de esta cámara", "deleteExports": "Eliminar también las exportaciones de esta cámara",
@@ -1668,7 +1668,14 @@
"keyLabel": "Clave", "keyLabel": "Clave",
"valueLabel": "Valor", "valueLabel": "Valor",
"keyPlaceholder": "Nueva clave", "keyPlaceholder": "Nueva clave",
"remove": "Eliminar" "remove": "Eliminar",
"providerNameLabel": "Nombre del proveedor",
"providerNamePlaceholder": "p. ej., openai",
"variableNameLabel": "Nombre de la variable",
"variableNamePlaceholder": "p. ej., MI_VARIABLE",
"loggerNameLabel": "Nombre del registrador",
"keyPatternError": "Utilice únicamente letras, números, guiones y guiones bajos (sin espacios)",
"loggerNamePlaceholder": "p. ej., frigate.record"
}, },
"knownPlates": { "knownPlates": {
"namePlaceholder": "p. ej., Coche de mi mujer", "namePlaceholder": "p. ej., Coche de mi mujer",
@@ -2032,7 +2039,12 @@
"autotrackingNoZones": "El seguimiento automático requiere al menos una zona. Define una zona para esta cámara en Máscaras / Zonas y, a continuación, establécela como zona obligatoria a continuación." "autotrackingNoZones": "El seguimiento automático requiere al menos una zona. Define una zona para esta cámara en Máscaras / Zonas y, a continuación, establécela como zona obligatoria a continuación."
}, },
"ffmpeg": { "ffmpeg": {
"hwaccelManualNotRecommended": "No son remontados los argumentos de aceleración por hardware manual. A no ser que un requisito específico exista, seleccione la preselección que coincida con su hardware." "hwaccelManualNotRecommended": "No son remontados los argumentos de aceleración por hardware manual. A no ser que un requisito específico exista, seleccione la preselección que coincida con su hardware.",
"inputsMissingGo2rtcStream": "El campo de entrada que aparece a continuación apunta a una retransmisión de go2rtc que ya no existe. Seleccione una retransmisión existente o introduzca la URL de la cámara manualmente; de lo contrario, la cámara no podrá conectarse."
},
"model": {
"optimizedFor320": "Frigate está optimizado para un modelo de 320x320, que es la mejor opción para la mayoría de las configuraciones. Un modelo de 640x640 es más lento y solo resulta útil en situaciones específicas.",
"inputDimensionsNotDetectResolution": "El ancho y alto de entrada del modelo son las dimensiones de entrada del modelo de detección de objetos, no la resolución de detección de la cámara. Deben coincidir con las dimensiones del modelo que esté utilizando, normalmente un tamaño cuadrado como 320x320 o 640x640."
} }
}, },
"resetToDefaultDescription": "Esto restablecerá todos los ajustes de esta sección a sus valores predeterminados. Esta acción no se puede deshacer.", "resetToDefaultDescription": "Esto restablecerá todos los ajustes de esta sección a sus valores predeterminados. Esta acción no se puede deshacer.",
+4 -4
View File
@@ -83,8 +83,8 @@
"batchSuccess_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.", "batchSuccess_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.",
"batchPartial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestunud kaamerad: {{failedCameras}}", "batchPartial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestunud kaamerad: {{failedCameras}}",
"batchFailed": "{{total}} eksporti ebaõnnestus algatada. Ebaõnnestunud kaamerad: {{failedCameras}}", "batchFailed": "{{total}} eksporti ebaõnnestus algatada. Ebaõnnestunud kaamerad: {{failedCameras}}",
"batchQueuedSuccess_one": "1 eksport järjekorda lisatud. Avan juhtumi kohe.", "batchQueuedSuccess_one": "1 eksportimine on lisatud järjekorda.",
"batchQueuedSuccess_other": "{{count}} eksporti järjekorda lisatud. Avan juhtumi kohe.", "batchQueuedSuccess_other": "{{count}} eksportimist on lisatud järjekorda.",
"batchQueuedPartial": "{{successful}}/{{total}} ekspordist on järjekorda lisatud. Ebaõnnestunud kaamerad: {{failedCameras}}", "batchQueuedPartial": "{{successful}}/{{total}} ekspordist on järjekorda lisatud. Ebaõnnestunud kaamerad: {{failedCameras}}",
"batchQueueFailed": "{{total}} eksporti ebaõnnestus järjekorda lisada. Ebaõnnestunud kaamerad: {{failedCameras}}" "batchQueueFailed": "{{total}} eksporti ebaõnnestus järjekorda lisada. Ebaõnnestunud kaamerad: {{failedCameras}}"
}, },
@@ -139,8 +139,8 @@
"exportButton_other": "Ekspordi {{count}} ülevaadet", "exportButton_other": "Ekspordi {{count}} ülevaadet",
"exportingButton": "Ekspordin...", "exportingButton": "Ekspordin...",
"toast": { "toast": {
"started_one": "Alustasin 1 eksporti. Avan juhtumi kohe.", "started_one": "Alustasin ühte eksportimist.",
"started_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.", "started_other": "Alustasin {{count}} eksportimisega.",
"startedNoCase_one": "Alustasin 1 ekspordiga.", "startedNoCase_one": "Alustasin 1 ekspordiga.",
"startedNoCase_other": "Alustasin {{count}} ekspordiga.", "startedNoCase_other": "Alustasin {{count}} ekspordiga.",
"partial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestusid: {{failedItems}}", "partial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestusid: {{failedItems}}",
+3 -1
View File
@@ -126,5 +126,7 @@
"baby_stroller": "Lapsevanker", "baby_stroller": "Lapsevanker",
"rickshaw": "Rikša", "rickshaw": "Rikša",
"Rodent": "Näriline", "Rodent": "Näriline",
"rodent": "Näriline" "rodent": "Näriline",
"possum": "Opossum",
"garbage_truck": "Prügiauto"
} }
+5 -2
View File
@@ -74,7 +74,7 @@
"menuItem": "Vaata liikumiste eelvaateid", "menuItem": "Vaata liikumiste eelvaateid",
"title": "Liikumiste eelvaated: {{camera}}", "title": "Liikumiste eelvaated: {{camera}}",
"mobileSettingsTitle": "Liikumiste eelvaadete seadistused", "mobileSettingsTitle": "Liikumiste eelvaadete seadistused",
"mobileSettingsDesc": "Kohenda taasesituse kiirust ja heledust ning vali kuupäev, et vaadata läbi ainult liikumist kajastavaid klipid.", "mobileSettingsDesc": "Kohenda taasesituse kiirust, heledust ja kadreerimist ning vali kuupäev, et vaadata läbi ainult liikumist kajastavaid klipid.",
"dim": "Hämarus", "dim": "Hämarus",
"dimAria": "Muuda hämarust", "dimAria": "Muuda hämarust",
"dimDesc": "Kohenda hämarust parandamaks liikumisala nähtavust.", "dimDesc": "Kohenda hämarust parandamaks liikumisala nähtavust.",
@@ -87,6 +87,9 @@
"seekAria": "Keri „{{camera}}“ kaamera vaade ajatempli juurde: {{time}}", "seekAria": "Keri „{{camera}}“ kaamera vaade ajatempli juurde: {{time}}",
"filter": "Filtreeri", "filter": "Filtreeri",
"filterDesc": "Näitamaks ainult liikumisega klippe antud aladel, vali soovitud piirkonnad.", "filterDesc": "Näitamaks ainult liikumisega klippe antud aladel, vali soovitud piirkonnad.",
"filterClear": "Tühjenda" "filterClear": "Tühjenda",
"crop": "Kadreeri filtreerimiseks",
"cropAria": "Lülita filtreeritud alade kadreeritud eelvaated sisse/välja",
"cropDesc": "Täiskaadri kuvamise asemel suumi eelvaated valitud filtritega märgitud alani."
} }
} }
+3
View File
@@ -129,6 +129,9 @@
"error": { "error": {
"updatedLPRFailed": "Sõiduki numbrimärgi uuendamine ei õnnestunud: {{errorMessage}}" "updatedLPRFailed": "Sõiduki numbrimärgi uuendamine ei õnnestunud: {{errorMessage}}"
} }
},
"button": {
"share": "Jaga seda ülevaadatud objekti"
} }
}, },
"snapshotScore": { "snapshotScore": {
+2 -1
View File
@@ -19,7 +19,8 @@
"error": "Silumise taasesitus ebaõnnestus: {{error}}", "error": "Silumise taasesitus ebaõnnestus: {{error}}",
"alreadyActive": "Kordusseanss on juba aktiivne", "alreadyActive": "Kordusseanss on juba aktiivne",
"stopError": "Silumise taasesituse peatamine ebaõnnestus: {{error}}", "stopError": "Silumise taasesituse peatamine ebaõnnestus: {{error}}",
"goToReplay": "Mine kordusesse" "goToReplay": "Mine kordusesse",
"noRecordings": "Valitud ajavahemikust ei leidu ühtegi salvestust"
} }
}, },
"title": "Kordus veaotsinguks", "title": "Kordus veaotsinguks",
+1 -1
View File
@@ -538,7 +538,7 @@
"licensePlateRecognition": { "licensePlateRecognition": {
"title": "Sõidukite numbrimärkide tuvastus" "title": "Sõidukite numbrimärkide tuvastus"
}, },
"title": "Andmerikastuse seaded", "title": "Andmerikastuse seadistused",
"restart_required": "Taaskäivitamine on vajalik (Andmerikastuse seadeid on muudetud)", "restart_required": "Taaskäivitamine on vajalik (Andmerikastuse seadeid on muudetud)",
"toast": { "toast": {
"success": "Andmerikastamise seaded on salvestatud. Muudatuste rakendamiseks taaskäivitage Frigate.", "success": "Andmerikastamise seaded on salvestatud. Muudatuste rakendamiseks taaskäivitage Frigate.",
+1 -1
View File
@@ -413,7 +413,7 @@
"typewriter": "ماشین تحریر", "typewriter": "ماشین تحریر",
"computer_keyboard": "صفحه‌کلید رایانه", "computer_keyboard": "صفحه‌کلید رایانه",
"writing": "نوشتن", "writing": "نوشتن",
"alarm": "هشدار", "alarm": "زنگ هشدار",
"telephone": "تلفن", "telephone": "تلفن",
"siren": "آژیر", "siren": "آژیر",
"civil_defense_siren": "آژیر دفاع مدنی", "civil_defense_siren": "آژیر دفاع مدنی",
+6 -4
View File
@@ -134,7 +134,7 @@
"export": "خروجی گرفتن", "export": "خروجی گرفتن",
"next": "بعدی", "next": "بعدی",
"reset": "بازنشانی", "reset": "بازنشانی",
"enabled": "فعال", "enabled": "فعال شده",
"disable": "غیرفعال کردن", "disable": "غیرفعال کردن",
"save": "ذخیره", "save": "ذخیره",
"saving": "در حال ذخیره‌سازی…", "saving": "در حال ذخیره‌سازی…",
@@ -161,7 +161,8 @@
"resetToDefault": "بازنشانی به پیش‌فرض", "resetToDefault": "بازنشانی به پیش‌فرض",
"saveAll": "ذخیره همه", "saveAll": "ذخیره همه",
"savingAll": "ذخیره‌سازی همه…", "savingAll": "ذخیره‌سازی همه…",
"undoAll": "بازگردانی همه" "undoAll": "بازگردانی همه",
"overridden": "نادیده‌گرفته‌شده"
}, },
"menu": { "menu": {
"systemMetrics": "شاخص‌های سیستم", "systemMetrics": "شاخص‌های سیستم",
@@ -211,9 +212,10 @@
"label": "برای زبان از تنظیمات سامانه استفاده کنید" "label": "برای زبان از تنظیمات سامانه استفاده کنید"
}, },
"zhHant": "چینی سنتی", "zhHant": "چینی سنتی",
"hr": "کرواسیایی" "hr": "کرواسیایی",
"bs": "بوسنیایی"
}, },
"system": "سامانه", "system": "سیستم",
"systemLogs": "لاگ‌های سامانه", "systemLogs": "لاگ‌های سامانه",
"configurationEditor": "ویرایشگر پیکربندی", "configurationEditor": "ویرایشگر پیکربندی",
"languages": "زبان‌ها", "languages": "زبان‌ها",
+83 -6
View File
@@ -1,12 +1,13 @@
{ {
"restart": { "restart": {
"title": "آیا برای راه اندازی مجدد Frigate مطمئن هستید؟", "title": "آیا برای راه اندازی مجدد Frigate مطمئن هستید؟",
"button": "ری‌استارت", "button": "راه‌اندازی دوباره",
"restarting": { "restarting": {
"title": "فریگیت در حال ری‌استارت شدن", "title": "فریگیت در حال ری‌استارت شدن",
"content": "صفحه تا {{countdown}} ثانیه دیگر مجددا بارگزاری خواهد شد.", "content": "صفحه تا {{countdown}} ثانیه دیگر مجددا بارگزاری خواهد شد.",
"button": "بارگزاری مجدد هم اکنون اجرا شود" "button": "بارگزاری مجدد هم اکنون اجرا شود"
} },
"description": "این کار به‌طور موقت Frigate را هنگام راه‌اندازی مجدد متوقف می‌کند."
}, },
"explore": { "explore": {
"plus": { "plus": {
@@ -23,6 +24,9 @@
}, },
"state": { "state": {
"submitted": "ارسال شد" "submitted": "ارسال شد"
},
"toast": {
"error": "ارسال به Frigate+ انجام نشد. لطفاً اتصال شبکه خود را بررسی کرده و دوباره تلاش کنید."
} }
} }
}, },
@@ -49,21 +53,86 @@
"error": { "error": {
"endTimeMustAfterStartTime": "زمان پایان باید بعد از زمان شروع باشد", "endTimeMustAfterStartTime": "زمان پایان باید بعد از زمان شروع باشد",
"noVaildTimeSelected": "بازهٔ زمانی معتبر انتخاب نشده است", "noVaildTimeSelected": "بازهٔ زمانی معتبر انتخاب نشده است",
"failed": "شروع خروجی‌گیری ناموفق بود: {{error}}" "failed": "شروع خروجی‌گیری ناموفق بود: {{error}}",
"noValidTimeSelected": "بازهٔ زمانی معتبر انتخاب نشده است"
}, },
"success": "ساخت خروجی با موفقیت آغاز شد. فایل را در صفحه خروجی‌ها مشاهده کنید.", "success": "ساخت خروجی با موفقیت آغاز شد. فایل را در صفحه خروجی‌ها مشاهده کنید.",
"view": "مشاهده" "view": "مشاهده",
"queued": "خروجی در صف قرار گرفت. پیشرفت را در صفحهٔ خروجی‌ها مشاهده کنید.",
"batchSuccess_one": "۱ خروجی شروع شد. در حال باز کردن مورد.",
"batchSuccess_other": "{{count}} خروجی شروع شد. در حال باز کردن مورد.",
"batchPartial": "{{successful}} از {{total}} خروجی شروع شد. دوربین‌های ناموفق: {{failedCameras}}",
"batchFailed": "شروع {{total}} خروجی ناموفق بود. دوربین‌های ناموفق: {{failedCameras}}",
"batchQueuedSuccess_one": "۱ خروجی در صف قرار گرفت.",
"batchQueuedSuccess_other": "{{count}} خروجی در صف قرار گرفت.",
"batchQueuedPartial": "{{successful}} از {{total}} خروجی در صف قرار گرفت. دوربین‌های ناموفق: {{failedCameras}}",
"batchQueueFailed": "صف‌بندی {{total}} خروجی ناموفق بود. دوربین‌های ناموفق: {{failedCameras}}"
}, },
"fromTimeline": { "fromTimeline": {
"saveExport": "ذخیرهٔ خروجی", "saveExport": "ذخیرهٔ خروجی",
"previewExport": "پیش‌نمایش خروجی" "previewExport": "پیش‌نمایش خروجی",
"queueingExport": "در حال صف‌بندی خروجی...",
"useThisRange": "استفاده از این بازه"
}, },
"name": { "name": {
"placeholder": "برای خروجی نام بگذارید" "placeholder": "برای خروجی نام بگذارید"
}, },
"select": "انتخاب", "select": "انتخاب",
"export": "خروجی", "export": "خروجی",
"selectOrExport": "انتخاب یا خروجی" "selectOrExport": "انتخاب یا خروجی",
"multiCamera": {
"selectGroup": "انتخاب گروه",
"noMatchingCameras": "هیچ دوربینی با جستجوی شما مطابقت ندارد",
"checkingActivity": "در حال بررسی فعالیت دوربین...",
"noCameras": "هیچ دوربینی در دسترس نیست",
"detectionCount_one": "1 شیء ردیابی شده",
"detectionCount_other": "{{count}} شیء ردیابی شده",
"nameLabel": "نام خروجی",
"namePlaceholder": "نام پایه اختیاری برای این خروجی‌ها",
"queueingButton": "به صف کردن خروجی‌ها...",
"exportButton_one": "خروجی 1 دوربین",
"exportButton_other": "خروجی {{count}} دوربین",
"timeRange": "محدوده زمانی",
"selectFromTimeline": "انتخاب از تایم‌لاین",
"cameraSelection": "دوربین‌ها",
"cameraSelectionHelp": "دوربین‌هایی که در این بازه زمانی دارای اشیاء ردیابی‌شده هستند، از قبل انتخاب شده‌اند",
"searchOrSelectGroup": "جستجو یا انتخاب گروه دوربین...",
"selectAll": "انتخاب همه دوربین‌ها",
"clearSelection": "پاک کردن انتخاب‌ها",
"selectWithActivity": "دوربین‌های دارای اشیاء ردیابی‌شده",
"selectedCount": "{{selected}} / {{total}} انتخاب شده"
},
"multi": {
"title_one": "خروجی ۱ بازبینی",
"title_other": "خروجی {{count}} بازبینی",
"description": "از هر بازبینی انتخاب‌شده خروجی بگیرید. تمام خروجی‌ها تحت یک پرونده واحد گروه‌بندی خواهند شد.",
"descriptionNoCase": "از هر بازبینی انتخاب‌شده خروجی بگیرید.",
"caseNamePlaceholder": "بازبینی خروجی - {{date}}",
"exportButton_one": "خروجی 1 بازبینی",
"exportButton_other": "خروجی {{count}} بازبینی",
"exportingButton": "خروجی گرفتن...",
"toast": {
"started_one": "۱ خروجی شروع شد.",
"started_other": "{{count}} خروجی شروع شد.",
"startedNoCase_one": "۱ خروجی آغاز شد.",
"startedNoCase_other": "{{count}} خروجی آغاز شد.",
"partial": "{{successful}} از {{total}} خروجی آغاز شد. موارد ناموفق: {{failedItems}}",
"failed": "شروع {{total}} خروجی ناموفق بود. ناموفق: {{failedItems}}"
}
},
"case": {
"newCaseOption": "ایجاد پرونده جدید",
"newCaseNamePlaceholder": "نام پرونده جدید",
"newCaseDescriptionPlaceholder": "توضیحات مورد",
"label": "مورد",
"nonAdminHelp": "یک مورد جدید برای این خروجی‌ها ایجاد خواهد شد.",
"placeholder": "انتخاب یک مورد"
},
"queueing": "در حال قرار دادن خروجی در صف...",
"tabs": {
"export": "تک دوربین",
"multiCamera": "چند-دوربین"
}
}, },
"streaming": { "streaming": {
"label": "جریان", "label": "جریان",
@@ -109,6 +178,14 @@
"markAsReviewed": "علامت‌گذاری به‌عنوان بازبینی‌شده", "markAsReviewed": "علامت‌گذاری به‌عنوان بازبینی‌شده",
"markAsUnreviewed": "علامت‌گذاری به‌عنوان بازبینی‌نشده", "markAsUnreviewed": "علامت‌گذاری به‌عنوان بازبینی‌نشده",
"deleteNow": "حذف فوری" "deleteNow": "حذف فوری"
},
"shareTimestamp": {
"label": "اشتراک‌گذاری مهر زمانی",
"title": "اشتراک‌گذاری مهر زمانی",
"description": "یک نشانی اینترنتی با مهر زمانی از موقعیت فعلی پخش‌کننده به اشتراک بگذارید یا یک مهر زمانی سفارشی انتخاب کنید. توجه کنید که این یک لینک اشتراک‌گذاری عمومی نیست و فقط برای کاربرانی که به Frigate و این دوربین دسترسی دارند قابل دسترسی است.",
"custom": "مهر زمانی سفارشی",
"button": "اشتراک‌گذاری نشانی مهر زمانی",
"shareTitle": "مهر زمانی بازبینی فریگیت: {{camera}}"
} }
}, },
"imagePicker": { "imagePicker": {
+1 -1
View File
@@ -18,7 +18,7 @@
"count_other": "{{count}} برچسب‌ها" "count_other": "{{count}} برچسب‌ها"
}, },
"zones": { "zones": {
"label": "مناطق", "label": "نواحی",
"all": { "all": {
"title": "همهٔ ناحیه‌ها", "title": "همهٔ ناحیه‌ها",
"short": "ناحیه‌ها" "short": "ناحیه‌ها"
+30 -7
View File
@@ -33,7 +33,11 @@
}, },
"filters": { "filters": {
"label": "فیلترهای صوتی", "label": "فیلترهای صوتی",
"description": "تنظیمات فیلتر بر اساس نوع صدا مانند آستانه‌های اطمینان که برای کاهش تشخیص‌های مثبت کاذب استفاده می‌شوند." "description": "تنظیمات فیلتر بر اساس نوع صدا مانند آستانه‌های اطمینان که برای کاهش تشخیص‌های مثبت کاذب استفاده می‌شوند.",
"threshold": {
"label": "حداقل اطمینان صوتی",
"description": "حداقل آستانه اطمینان برای رویداد صوتی تا شمارش شود."
}
}, },
"enabled_in_config": { "enabled_in_config": {
"label": "وضعیت صوتی اصلی", "label": "وضعیت صوتی اصلی",
@@ -471,7 +475,7 @@
"description": "تنظیمات نگهداری برای ضبط رویدادهای تشخیص.", "description": "تنظیمات نگهداری برای ضبط رویدادهای تشخیص.",
"days": { "days": {
"label": "روزهای نگهداری", "label": "روزهای نگهداری",
"description": "تعداد روزهایی که لازم است سوابق رویدادهای شناسایی‌شده نگهداری EMSebi شوند ." "description": "تعداد روزهایی که ضبط‌های رویدادهای تشخیص باید نگهداری شوند."
}, },
"mode": { "mode": {
"label": "حالت نگهداری", "label": "حالت نگهداری",
@@ -485,6 +489,13 @@
"hwaccel_args": { "hwaccel_args": {
"label": "خروجی گرفتن از آرگومان‌های hwaccel", "label": "خروجی گرفتن از آرگومان‌های hwaccel",
"description": "آرگومان‌های شتاب سخت‌افزاری برای استفاده در عملیات صادرات/تبدیل کد." "description": "آرگومان‌های شتاب سخت‌افزاری برای استفاده در عملیات صادرات/تبدیل کد."
},
"max_concurrent": {
"label": "حداکثر خروجی‌های همزمان",
"description": "حداکثر تعداد کارهای خروجی که همزمان پردازش می‌شوند."
},
"chapters": {
"label": "فرادادهٔ فصل برای جاسازی در ضبط‌های خروجی‌گرفته‌شده"
} }
}, },
"preview": { "preview": {
@@ -672,8 +683,8 @@
} }
}, },
"quality": { "quality": {
"label": "کیفیت JPEG", "label": "کیفیت عکس فوری",
"description": "کیفیت کدگذاری JPEG برای عکس‌های ذخیره شده (0-100)." "description": "کیفیت کدگذاری برای عکس‌های فوری ذخیره‌شده (0-100)."
} }
}, },
"timestamp_style": { "timestamp_style": {
@@ -823,7 +834,7 @@
}, },
"timeout": { "timeout": {
"label": "مهلت بازگشت", "label": "مهلت بازگشت",
"description": "چند ثانیه صبر کن پس از از دست دادن ردیابی قبل از بازگرداندن دوربین به موقعیت از پیش تعیین شده ." "description": "پس از قطع ردیابی، این تعداد ثانیه صبر کنید قبل از بازگرداندن دوربین به موقعیت از پیش تعیین‌شده."
}, },
"movement_weights": { "movement_weights": {
"label": "وزنه‌های حرکتی", "label": "وزنه‌های حرکتی",
@@ -838,7 +849,11 @@
"label": "عدم تطابق زمانی را نادیده بگیرید", "label": "عدم تطابق زمانی را نادیده بگیرید",
"description": "برای ارتباط ONVIF، از تفاوت‌های همگام‌سازی زمانی بین دوربین و سرور Frigate صرف نظر کنید." "description": "برای ارتباط ONVIF، از تفاوت‌های همگام‌سازی زمانی بین دوربین و سرور Frigate صرف نظر کنید."
}, },
"label": "ONVIF پروتکل استاندارد انتقال تصویر ." "label": "ONVIF",
"profile": {
"label": "پروفایل ONVIF",
"description": "پروفایل رسانهٔ ONVIF خاص برای کنترل PTZ، مطابقت‌شده با توکن یا نام. اگر تنظیم نشود، اولین پروفایل با پیکربندی PTZ معتبر به‌طور خودکار انتخاب می‌شود."
}
}, },
"type": { "type": {
"label": "نوع دوربین", "label": "نوع دوربین",
@@ -854,6 +869,10 @@
"dashboard": { "dashboard": {
"label": "نمایش در رابط کاربری", "label": "نمایش در رابط کاربری",
"description": "مشخص کنید که آیا این دوربین در همه جای رابط کاربری Frigate قابل مشاهده باشد یا خیر. غیرفعال کردن این گزینه مستلزم ویرایش دستی پیکربندی برای مشاهده مجدد این دوربین در رابط کاربری است." "description": "مشخص کنید که آیا این دوربین در همه جای رابط کاربری Frigate قابل مشاهده باشد یا خیر. غیرفعال کردن این گزینه مستلزم ویرایش دستی پیکربندی برای مشاهده مجدد این دوربین در رابط کاربری است."
},
"review": {
"label": "نمایش در بازبینی",
"description": "مشخص کنید که آیا این دوربین در بازبینی قابل مشاهده باشد (صفحهٔ بازبینی و فیلتر دوربین آن، بازبینی حرکت، و نمای تاریخچه)."
} }
}, },
"webui_url": { "webui_url": {
@@ -861,7 +880,7 @@
"description": "آدرس اینترنتی برای بازدید مستقیم از دوربین از صفحه سیستم" "description": "آدرس اینترنتی برای بازدید مستقیم از دوربین از صفحه سیستم"
}, },
"zones": { "zones": {
"label": "مناطق", "label": "نواحی",
"description": "مناطق به شما امکان می‌دهند ناحیه خاصی از قاب را تعریف کنید تا بتوانید تعیین کنید که آیا یک شیء در یک ناحیه خاص قرار دارد یا خیر.", "description": "مناطق به شما امکان می‌دهند ناحیه خاصی از قاب را تعریف کنید تا بتوانید تعیین کنید که آیا یک شیء در یک ناحیه خاص قرار دارد یا خیر.",
"friendly_name": { "friendly_name": {
"label": "نام منطقه", "label": "نام منطقه",
@@ -937,5 +956,9 @@
"enabled_in_config": { "enabled_in_config": {
"label": "وضعیت دوربین اصلی", "label": "وضعیت دوربین اصلی",
"description": "وضعیت اولیه دوربین را پیگیری کنید." "description": "وضعیت اولیه دوربین را پیگیری کنید."
},
"profiles": {
"label": "پروفایل‌ها",
"description": "پروفایل‌های پیکربندی نام‌گذاری‌شده با لغوهای جزئی که در زمان اجرا قابل فعال‌سازی هستند."
} }
} }
File diff suppressed because it is too large Load Diff
+73 -1
View File
@@ -1 +1,73 @@
{} {
"audio": {
"global": {
"detection": "تشخیص سراسری",
"sensitivity": "حساسیت سراسری"
},
"cameras": {
"detection": "تشخیص",
"sensitivity": "حساسیت"
}
},
"timestamp_style": {
"global": {
"appearance": "ظاهر سراسری"
},
"cameras": {
"appearance": "ظاهر"
}
},
"motion": {
"global": {
"sensitivity": "حساسیت سراسری",
"algorithm": "الگوریتم سراسری"
},
"cameras": {
"sensitivity": "حساسیت",
"algorithm": "الگوریتم"
}
},
"snapshots": {
"global": {
"display": "نمایش سراسری"
},
"cameras": {
"display": "نمایش"
}
},
"detect": {
"global": {
"resolution": "وضوح سراسری",
"tracking": "ردیابی سراسری"
},
"cameras": {
"resolution": "وضوح",
"tracking": "ردیابی"
}
},
"objects": {
"global": {
"tracking": "ردیابی سراسری",
"filtering": "پالایش سراسری"
},
"cameras": {
"tracking": "ردیابی",
"filtering": "پالایش"
}
},
"record": {
"global": {
"retention": "نگهداری سراسری",
"events": "رویدادهای سراسری"
},
"cameras": {
"retention": "نگهداری",
"events": "رویدادها"
}
},
"ffmpeg": {
"cameras": {
"cameraFfmpeg": "آرگومان‌های FFmpeg اختصاصی دوربین"
}
}
}
+35 -1
View File
@@ -1 +1,35 @@
{} {
"minimum": "باید حداقل {{limit}} باشد",
"maximum": "باید حداکثر {{limit}} باشد",
"exclusiveMinimum": "باید بزرگ‌تر از {{limit}} باشد",
"exclusiveMaximum": "باید کم‌تر از {{limit}} باشد",
"minLength": "باید حداقل {{limit}} نویسه باشد",
"maxLength": "باید حداکثر {{limit}} نویسه باشد",
"minItems": "باید حداقل {{limit}} مورد داشته باشد",
"maxItems": "باید حداکثر {{limit}} مورد داشته باشد",
"pattern": "قالب نامعتبر",
"required": "این فیلد الزامی است",
"type": "نوع مقدار نامعتبر",
"enum": "باید یکی از مقادیر مجاز باشد",
"const": "مقدار با ثابت مورد انتظار مطابقت ندارد",
"uniqueItems": "همه موارد باید یکتا باشند",
"format": "قالب نامعتبر",
"additionalProperties": "ویژگی ناشناخته مجاز نیست",
"oneOf": "باید دقیقاً با یکی از طرح‌واره‌های مجاز مطابقت داشته باشد",
"anyOf": "باید با حداقل یکی از طرح‌واره‌های مجاز مطابقت داشته باشد",
"proxy": {
"header_map": {
"roleHeaderRequired": "هنگامی که نگاشت نقش‌ها پیکربندی شده است، هدر نقش الزامی است."
}
},
"ffmpeg": {
"inputs": {
"rolesUnique": "هر نقش فقط می‌تواند به یک جریان ورودی اختصاص یابد.",
"detectRequired": "حداقل یک جریان ورودی باید نقش «detect» داشته باشد.",
"hwaccelDetectOnly": "فقط جریان ورودی با نقش detect می‌تواند آرگومان‌های شتاب‌دهی سخت‌افزاری تعریف کند."
}
},
"detect": {
"dimensionMustBeEven": "باید یک عدد زوج باشد."
}
}
+12 -1
View File
@@ -116,5 +116,16 @@
"nzpost": "اِن‌زد پُست", "nzpost": "اِن‌زد پُست",
"gls": "جی‌اِل‌اِس", "gls": "جی‌اِل‌اِس",
"dpd": "دی‌پی‌دی", "dpd": "دی‌پی‌دی",
"surfboard": "تخته موج سواری" "surfboard": "تخته موج سواری",
"canada_post": "پست کانادا",
"royal_mail": "پست سلطنتی",
"school_bus": "اتوبوس مدرسه",
"skunk": "اسکانک",
"kangaroo": "کانگورو",
"baby": "نوزاد",
"baby_stroller": "کالسکه نوزاد",
"rickshaw": "ریکشا",
"rodent": "جونده",
"possum": "صاریغ",
"garbage_truck": "کامیون زباله"
} }
+72 -1
View File
@@ -1 +1,72 @@
{} {
"documentTitle": "گفتگو - فریگیت",
"title": "گفتگوی فریگیت",
"subtitle": "دستیار هوش مصنوعی شما برای مدیریت دوربین و دریافت بینش",
"placeholder": "هر چیزی بپرسید...",
"error": "خطایی رخ داد. لطفاً دوباره تلاش کنید.",
"processing": "در حال پردازش...",
"toolsUsed": "استفاده‌شده: {{tools}}",
"showTools": "نمایش ابزارها ({{count}})",
"hideTools": "پنهان کردن ابزارها",
"call": "فراخوانی",
"result": "نتیجه",
"arguments": "آرگومان‌ها:",
"response": "پاسخ:",
"attachment_chip_label": "{{label}} در {{camera}}",
"attachment_chip_remove": "حذف پیوست",
"open_in_explore": "باز کردن در کاوش",
"attach_event_aria": "پیوست رویداد {{eventId}}",
"attachment_picker_paste_label": "یا شناسه رویداد را جای‌گذاری کنید",
"attachment_picker_attach": "پیوست",
"attachment_picker_placeholder": "پیوست یک رویداد",
"quick_reply_find_similar": "یافتن مشاهدات مشابه",
"quick_reply_tell_me_more": "بیشتر درباره این به من بگو",
"quick_reply_when_else": "چه زمان‌های دیگری دیده شده؟",
"quick_reply_find_similar_text": "مشاهدات مشابه این را پیدا کن.",
"quick_reply_tell_me_more_text": "بیشتر درباره این یکی به من بگو.",
"quick_reply_when_else_text": "چه زمان‌های دیگری این دیده شده است؟",
"anchor": "ارجاع",
"similarity_score": "شباهت",
"no_similar_objects_found": "شیء مشابهی یافت نشد.",
"semantic_search_required": "برای یافتن اشیای مشابه، جست‌وجوی معنایی باید فعال باشد.",
"send": "ارسال",
"suggested_requests": "این موارد را امتحان کنید:",
"starting_requests": {
"show_recent_events": "نمایش رویدادهای اخیر",
"show_camera_status": "نمایش وضعیت دوربین",
"recap": "وقتی من نبودم چه اتفاقی افتاد؟",
"watch_camera": "تماشای دوربین برای فعالیت"
},
"starting_requests_prompts": {
"show_recent_events": "رویدادهای اخیر در یک ساعت گذشته را نشان بده",
"show_camera_status": "وضعیت فعلی دوربین‌های من چیست؟",
"recap": "وقتی من نبودم چه اتفاقی افتاد؟",
"watch_camera": "درب جلویی را تماشا کن و اگر کسی ظاهر شد به من اطلاع بده"
},
"new_chat": "گفتگوی جدید",
"settings": {
"title": "تنظیمات گفتگو",
"show_stats": {
"title": "نمایش آمار",
"desc": "نرخ تولید و اندازه زمینه برای پاسخ‌های گفتگو را نشان بده.",
"while_generating": "هنگام تولید",
"always": "همیشه"
},
"auto_scroll": {
"title": "پیمایش خودکار",
"desc": "پیام‌های جدید را هنگام رسیدن دنبال کن."
}
},
"stats": {
"context": "{{tokens}} توکن",
"tokens_per_second": "{{rate}} ت/ث"
},
"reasoning": {
"active": "در حال استدلال…",
"show": "نمایش استدلال",
"hide": "پنهان کردن استدلال"
},
"thinking": {
"toggle": "فعال/غیرفعال کردن تفکر"
}
}
@@ -11,17 +11,18 @@
}, },
"toast": { "toast": {
"success": { "success": {
"deletedCategory_one": "کلاس حذف شده", "deletedCategory_one": "{{count}} کلاس حذف شد",
"deletedCategory_other": "", "deletedCategory_other": "{{count}} کلاس حذف شدند",
"deletedImage_one": "عکس های حذف شده", "deletedImage_one": "{{count}} تصویر حذف شد",
"deletedImage_other": "", "deletedImage_other": "{{count}} تصویر حذف شدند",
"categorizedImage": "تصویر طبقه بندی شده", "categorizedImage": "تصویر طبقه بندی شده",
"trainedModel": "مدل آموزش دیده شده.", "trainedModel": "مدل آموزش دیده شده.",
"trainingModel": "آموزش دادن مدل با موفقیت شروع شد.", "trainingModel": "آموزش دادن مدل با موفقیت شروع شد.",
"deletedModel_one": "{{count}} مدل با موفقیت حذف شد", "deletedModel_one": "{{count}} مدل با موفقیت حذف شد",
"deletedModel_other": "{{count}} مدل با موفقیت حذف شدند", "deletedModel_other": "{{count}} مدل با موفقیت حذف شدند",
"updatedModel": "پیکربندی مدل با موفقیت به‌روزرسانی شد", "updatedModel": "پیکربندی مدل با موفقیت به‌روزرسانی شد",
"renamedCategory": "نام کلاس با موفقیت به {{name}} تغییر یافت" "renamedCategory": "نام کلاس با موفقیت به {{name}} تغییر یافت",
"reclassifiedImage": "طبقه‌بندی مجدد تصویر با موفقیت انجام شد"
}, },
"error": { "error": {
"deleteImageFailed": "حذف نشد: {{errorMessage}}", "deleteImageFailed": "حذف نشد: {{errorMessage}}",
@@ -31,7 +32,8 @@
"trainingFailed": "آموزش مدل ناموفق بود. برای جزئیات، گزارش‌های Frigate را بررسی کنید.", "trainingFailed": "آموزش مدل ناموفق بود. برای جزئیات، گزارش‌های Frigate را بررسی کنید.",
"trainingFailedToStart": "شروع آموزش مدل ناموفق بود: {{errorMessage}}", "trainingFailedToStart": "شروع آموزش مدل ناموفق بود: {{errorMessage}}",
"updateModelFailed": "به‌روزرسانی مدل ناموفق بود: {{errorMessage}}", "updateModelFailed": "به‌روزرسانی مدل ناموفق بود: {{errorMessage}}",
"renameCategoryFailed": "تغییر نام کلاس ناموفق بود: {{errorMessage}}" "renameCategoryFailed": "تغییر نام کلاس ناموفق بود: {{errorMessage}}",
"reclassifyFailed": "طبقه‌بندی مجدد تصویر ناموفق بود: {{errorMessage}}"
} }
}, },
"documentTitle": "دسته بندی مدل ها - فریگیت", "documentTitle": "دسته بندی مدل ها - فریگیت",
@@ -158,14 +160,32 @@
"noImages": "هیچ تصویر نمونه‌ای تولید نشد", "noImages": "هیچ تصویر نمونه‌ای تولید نشد",
"modelCreated": "مدل با موفقیت ایجاد شد. از نمای «طبقه‌بندی‌های اخیر» برای افزودن تصاویرِ وضعیت‌هایِ جاافتاده استفاده کنید، سپس مدل را آموزش دهید.", "modelCreated": "مدل با موفقیت ایجاد شد. از نمای «طبقه‌بندی‌های اخیر» برای افزودن تصاویرِ وضعیت‌هایِ جاافتاده استفاده کنید، سپس مدل را آموزش دهید.",
"generateSuccess": "تصاویر نمونه با موفقیت تولید شد", "generateSuccess": "تصاویر نمونه با موفقیت تولید شد",
"selectImagesPrompt": "همهٔ تصاویر با {{className}} را انتخاب کنید" "selectImagesPrompt": "همهٔ تصاویر با {{className}} را انتخاب کنید",
"refreshExamples": "تولید نمونه‌های جدید",
"refreshConfirm": {
"title": "تولید نمونه‌های جدید؟",
"description": "این کار یک مجموعهٔ جدید از تصاویر تولید می‌کند و همهٔ انتخاب‌ها را پاک می‌کند، از جمله کلاس‌های قبلی. باید نمونه‌ها را برای همهٔ کلاس‌ها دوباره انتخاب کنید."
}
} }
}, },
"edit": { "edit": {
"title": "ویرایش مدل طبقه‌بندی", "title": "ویرایش مدل طبقه‌بندی",
"descriptionState": "کلاس‌های این مدل طبقه‌بندی حالت را ویرایش کنید. اعمال تغییرات نیاز به بازآموزی مدل دارد.", "descriptionState": "کلاس‌های این مدل طبقه‌بندی حالت را ویرایش کنید. اعمال تغییرات نیاز به بازآموزی مدل دارد.",
"descriptionObject": "نوع شیء و نوع طبقه‌بندی را برای این مدل طبقه‌بندی شیء ویرایش کنید.", "descriptionObject": "نوع شیء و نوع طبقه‌بندی را برای این مدل طبقه‌بندی شیء ویرایش کنید.",
"stateClassesInfo": "توجه: تغییر کلاس‌های وضعیت نیازمند بازآموزی مدل با کلاس‌های به‌روزرسانی‌شده است." "stateClassesInfo": "توجه: تغییر کلاس‌های وضعیت نیازمند بازآموزی مدل با کلاس‌های به‌روزرسانی‌شده است.",
"enabled": "فعال شده",
"enabledDesc": "اجرای این مدل. هنگام غیرفعال بودن، اجرا متوقف می‌شود و دیگر طبقه‌بندی نمی‌کند.",
"saveAttempts": "تلاش‌های ذخیره",
"saveAttemptsDesc": "تعداد تصاویر تلاش طبقه‌بندی که برای رابط کاربری طبقه‌بندی‌های اخیر نگه‌داری می‌شوند.",
"motion": "اجرای هنگام حرکت",
"motionDesc": "اجرای طبقه‌بندی هنگام تشخیص حرکت در ناحیهٔ برش‌خوردهٔ پیکربندی‌شده.",
"interval": "بازه",
"intervalDesc": "ثانیه بین اجراهای دوره‌ای طبقه‌بندی. برای اجرای فقط هنگام حرکت، خالی بگذارید.",
"intervalPlaceholder": "بدون بازه",
"errors": {
"saveAttemptsInvalid": "تلاش‌های ذخیره باید یک عدد صحیح ۰ یا بزرگ‌تر باشد",
"intervalInvalid": "بازه باید یک عدد صحیح بزرگ‌تر از ۰ باشد"
}
}, },
"deleteDatasetImages": { "deleteDatasetImages": {
"title": "حذف تصاویر مجموعه‌داده", "title": "حذف تصاویر مجموعه‌داده",
@@ -185,5 +205,8 @@
"createCategory": { "createCategory": {
"new": "ایجاد کلاس جدید" "new": "ایجاد کلاس جدید"
}, },
"categorizeImageAs": "طبقه‌بندی تصویر به‌عنوان:" "categorizeImageAs": "طبقه‌بندی تصویر به‌عنوان:",
"disabled": "غیرفعال",
"reclassifyImageAs": "طبقه‌بندی مجدد تصویر به‌عنوان:",
"reclassifyImage": "طبقه‌بندی مجدد تصویر"
} }
+34 -4
View File
@@ -15,7 +15,9 @@
"description": "موارد بازبینی برای یک دوربین تنها درصورتی امکان ساخت دارند که ضبط‌ها برای آن دورین فعال باشد." "description": "موارد بازبینی برای یک دوربین تنها درصورتی امکان ساخت دارند که ضبط‌ها برای آن دورین فعال باشد."
} }
}, },
"timeline": "خط زمانی", "timeline": {
"label": "خط زمانی"
},
"timeline.aria": "انتخاب خط زمانی", "timeline.aria": "انتخاب خط زمانی",
"zoomIn": "بزرگ‌نمایی", "zoomIn": "بزرگ‌نمایی",
"zoomOut": "کوچک‌نمایی", "zoomOut": "کوچک‌نمایی",
@@ -25,7 +27,9 @@
"label": "رویدادها" "label": "رویدادها"
}, },
"recordings": { "recordings": {
"documentTitle": "ضبط‌ها - فریگیت" "documentTitle": "ضبط‌ها - فریگیت",
"invalidSharedLink": "باز کردن لینک ضبط با مهر زمانی به‌دلیل خطای تجزیه ممکن نیست.",
"invalidSharedCamera": "باز کردن لینک ضبط با مهر زمانی به‌دلیل دوربین ناشناخته یا غیرمجاز ممکن نیست."
}, },
"calendarFilter": { "calendarFilter": {
"last24Hours": "۲۴ ساعت گذشته" "last24Hours": "۲۴ ساعت گذشته"
@@ -55,11 +59,37 @@
}, },
"documentTitle": "بازبینی - Frigate", "documentTitle": "بازبینی - Frigate",
"selected_one": "{{count}} انتخاب شد", "selected_one": "{{count}} انتخاب شد",
"selected_other": "{{count}} انتخاب شدند", "selected_other": "{{count}} انتخاب شد",
"select_all": "همه", "select_all": "همه",
"camera": "دوربین", "camera": "دوربین",
"detected": "گزینه‌هاشناسایی شد", "detected": "گزینه‌هاشناسایی شد",
"normalActivity": "عادی", "normalActivity": "عادی",
"needsReview": "نیاز به بازبینی", "needsReview": "نیاز به بازبینی",
"securityConcern": "نگرانی امنیتی" "securityConcern": "نگرانی امنیتی",
"motionSearch": {
"menuItem": "جست‌وجوی حرکت",
"openMenu": "گزینه‌های دوربین"
},
"motionPreviews": {
"menuItem": "مشاهدهٔ پیش‌نمایش‌های حرکت",
"title": "پیش‌نمایش‌های حرکت: {{camera}}",
"mobileSettingsTitle": "تنظیمات پیش‌نمایش حرکت",
"mobileSettingsDesc": "سرعت پخش، تاری و برش را تنظیم کنید، و تاری را برای بازبینی کلیپ‌های فقط حرکتی انتخاب کنید.",
"dim": "تاری",
"dimAria": "تنظیم شدت تاری",
"dimDesc": "برای افزایش دیدمندی منطقهٔ حرکت، تاری را بیشتر کنید.",
"speed": "سرعت",
"speedAria": "انتخاب سرعت پخش پیش‌نمایش",
"speedDesc": "انتخاب کنید کلیپ‌های پیش‌نمایش با چه سرعتی پخش شوند.",
"back": "بازگشت",
"empty": "هیچ پیش‌نمایشی در دسترس نیست",
"noPreview": "پیش‌نمایش در دسترس نیست",
"seekAria": "پیمایش پخش‌کننده {{camera}} به {{time}}",
"filter": "فیلتر",
"filterDesc": "مناطق را انتخاب کنید تا فقط کلیپ‌هایی با حرکت در آن مناطق نشان داده شوند.",
"filterClear": "پاک کردن",
"crop": "برش به فیلتر",
"cropAria": "فعال/غیرفعال کردن برش پیش‌نمایش‌ها به مناطق فیلترشده",
"cropDesc": "بزرگ‌نمایی پیش‌نمایش‌ها به مناطق فیلتر انتخاب‌شده به‌جای نمایش کامل فریم."
}
} }
+19 -2
View File
@@ -85,7 +85,8 @@
"regenerateFromThumbnails": "بازسازی از تصاویر بندانگشتی", "regenerateFromThumbnails": "بازسازی از تصاویر بندانگشتی",
"title": { "title": {
"label": "عنوان" "label": "عنوان"
} },
"scoreInfo": "اطلاعات امتیاز"
}, },
"exploreIsUnavailable": { "exploreIsUnavailable": {
"title": "کاوش کردن در دسترس نیست", "title": "کاوش کردن در دسترس نیست",
@@ -136,7 +137,10 @@
"zones": "ناحیه‌ها", "zones": "ناحیه‌ها",
"ratio": "نسبت", "ratio": "نسبت",
"area": "مساحت", "area": "مساحت",
"score": "امتیاز" "score": "امتیاز",
"computedScore": "امتیاز محاسبه‌شده",
"topScore": "بالاترین امتیاز",
"toggleAdvancedScores": "فعال/غیرفعال‌سازی امتیازهای پیشرفته"
} }
}, },
"title": "جزئیات ردیابی", "title": "جزئیات ردیابی",
@@ -216,6 +220,13 @@
}, },
"deleteTrackedObject": { "deleteTrackedObject": {
"label": "حذف این شیء ردیابی‌شده" "label": "حذف این شیء ردیابی‌شده"
},
"debugReplay": {
"label": "بازپخش اشکال‌زدایی",
"aria": "مشاهدهٔ این شیء ردیابی‌شده در نمای بازپخش اشکال‌زدایی"
},
"more": {
"aria": "بیشتر"
} }
}, },
"noTrackedObjects": "هیچ شیء ردیابی‌شده‌ای پیدا نشد", "noTrackedObjects": "هیچ شیء ردیابی‌شده‌ای پیدا نشد",
@@ -226,6 +237,9 @@
"confirmDelete": { "confirmDelete": {
"title": "تأیید حذف", "title": "تأیید حذف",
"desc": "حذف این شیء ردیابی‌شده عکس فوری، هرگونه امبدینگ ذخیره‌شده و هر ورودی مرتبط با جزئیات ردیابی را حذف می‌کند. فیلم ضبط‌شدهٔ این شیء ردیابی‌شده در نمای تاریخ <em>حذف نخواهد شد</em>.<br /><br />آیا مطمئنید می‌خواهید ادامه دهید؟" "desc": "حذف این شیء ردیابی‌شده عکس فوری، هرگونه امبدینگ ذخیره‌شده و هر ورودی مرتبط با جزئیات ردیابی را حذف می‌کند. فیلم ضبط‌شدهٔ این شیء ردیابی‌شده در نمای تاریخ <em>حذف نخواهد شد</em>.<br /><br />آیا مطمئنید می‌خواهید ادامه دهید؟"
},
"toast": {
"error": "خطا در حذف این شیء ردیابی‌شده: {{errorMessage}}"
} }
}, },
"searchResult": { "searchResult": {
@@ -244,5 +258,8 @@
}, },
"concerns": { "concerns": {
"label": "نگرانی‌ها" "label": "نگرانی‌ها"
},
"objectLifecycle": {
"noImageFound": "برای این شیء ردیابی‌شده هیچ تصویری یافت نشد."
} }
} }
+104 -4
View File
@@ -2,7 +2,9 @@
"search": "جستجو", "search": "جستجو",
"documentTitle": "گرفتن خروجی - فریگیت", "documentTitle": "گرفتن خروجی - فریگیت",
"noExports": "هیچ خروجی یافت نشد", "noExports": "هیچ خروجی یافت نشد",
"deleteExport": "حذف خروجی", "deleteExport": {
"label": "حذف خروجی"
},
"deleteExport.desc": "آیا مطمئن هستید که می‌خواهید {{exportName}} را حذف کنید؟", "deleteExport.desc": "آیا مطمئن هستید که می‌خواهید {{exportName}} را حذف کنید؟",
"editExport": { "editExport": {
"title": "تغییر نام خروجی", "title": "تغییر نام خروجی",
@@ -14,15 +16,113 @@
"downloadVideo": "دانلود ویدئو", "downloadVideo": "دانلود ویدئو",
"editName": "ویرایش نام", "editName": "ویرایش نام",
"deleteExport": "حذف خروجی", "deleteExport": "حذف خروجی",
"assignToCase": "به مورد اضافه کنید" "assignToCase": "اضافه کردن به مورد",
"removeFromCase": "حذف از مورد"
}, },
"toast": { "toast": {
"error": { "error": {
"renameExportFailed": "تغییر نام خروجی ناموفق بود: {{errorMessage}}" "renameExportFailed": "تغییر نام خروجی ناموفق بود: {{errorMessage}}",
"assignCaseFailed": "به‌روزرسانی انتساب مورد ناموفق بود: {{errorMessage}}",
"caseSaveFailed": "ذخیرهٔ مورد ناموفق بود: {{errorMessage}}",
"caseDeleteFailed": "حذف مورد ناموفق بود: {{errorMessage}}"
} }
}, },
"headings": { "headings": {
"cases": "موارد", "cases": "موارد",
"uncategorizedExports": "خروجی دسته‌بندی نشده" "uncategorizedExports": "خروجی های دسته بندی نشده"
},
"toolbar": {
"newCase": "مورد جدید",
"addExport": "افزودن خروجی",
"editCase": "ویرایش مورد",
"deleteCase": "حذف مورد"
},
"deleteCase": {
"label": "حذف مورد",
"desc": "آیا مطمئن هستید که می‌خواهید {{caseName}} را حذف کنید؟",
"descKeepExports": "خروجی‌ها به‌عنوان خروجی‌های دسته‌بندی‌نشده در دسترس باقی خواهند ماند.",
"descDeleteExports": "همهٔ خروجی‌های این مورد به‌طور دائمی حذف خواهند شد.",
"deleteExports": "حذف خروجی‌ها نیز"
},
"caseDialog": {
"title": "افزودن به مورد",
"description": "یک مورد موجود انتخاب کنید یا مورد جدیدی ایجاد کنید.",
"selectLabel": "مورد",
"newCaseOption": "ایجاد مورد جدید",
"nameLabel": "نام مورد",
"descriptionLabel": "توضیحات"
},
"caseCard": {
"emptyCase": "هنوز خروجی‌ای وجود ندارد"
},
"jobCard": {
"defaultName": "خروجی {{camera}}",
"queued": "در صف",
"running": "در حال اجرا",
"preparing": "در حال آماده‌سازی",
"copying": "در حال کپی",
"encoding": "در حال رمزگذاری",
"encodingRetry": "رمزگذاری (تلاش مجدد)",
"finalizing": "در حال نهایی‌سازی"
},
"caseView": {
"noDescription": "بدون توضیحات",
"createdAt": "ایجاد شده {{value}}",
"exportCount_one": "۱ خروجی",
"exportCount_other": "{{count}} خروجی",
"cameraCount_one": "۱ دوربین",
"cameraCount_other": "{{count}} دوربین",
"showMore": "نمایش بیشتر",
"showLess": "نمایش کمتر",
"emptyTitle": "این مورد خالی است",
"emptyDescription": "برای سازماندهی مورد، خروجی‌های دسته‌بندی‌نشده موجود را اضافه کنید.",
"emptyDescriptionNoExports": "هنوز خروجی دسته‌بندی‌نشده‌ای برای افزودن وجود ندارد."
},
"caseEditor": {
"createTitle": "ایجاد مورد",
"editTitle": "ویرایش مورد",
"namePlaceholder": "نام مورد",
"descriptionPlaceholder": "یادداشت یا زمینه برای این مورد اضافه کنید"
},
"addExportDialog": {
"title": "افزودن خروجی به {{caseName}}",
"searchPlaceholder": "جست‌وجوی خروجی‌های دسته‌بندی‌نشده",
"empty": "هیچ خروجی دسته‌بندی‌نشده‌ای با این جست‌وجو مطابقت ندارد.",
"addButton_one": "افزودن ۱ خروجی",
"addButton_other": "افزودن {{count}} خروجی",
"adding": "در حال افزودن..."
},
"selected_one": "{{count}} انتخاب شد",
"selected_other": "{{count}} انتخاب شد",
"bulkActions": {
"addToCase": "افزودن به مورد",
"moveToCase": "انتقال به مورد",
"removeFromCase": "حذف از مورد",
"delete": "حذف",
"deleteNow": "حذف فوری"
},
"bulkDelete": {
"title": "حذف خروجی‌ها",
"desc_one": "آیا مطمئن هستید که می‌خواهید {{count}} خروجی را حذف کنید؟",
"desc_other": "آیا مطمئن هستید که می‌خواهید {{count}} خروجی را حذف کنید؟"
},
"bulkRemoveFromCase": {
"title": "حذف از مورد",
"desc_one": "حذف {{count}} خروجی از این مورد؟",
"desc_other": "حذف {{count}} خروجی از این مورد؟",
"descKeepExports": "خروجی‌ها به دسته‌بندی‌نشده منتقل خواهند شد.",
"descDeleteExports": "خروجی‌ها به‌طور دائمی حذف خواهند شد.",
"deleteExports": "حذف خروجی‌ها به‌جای آن"
},
"bulkToast": {
"success": {
"delete": "خروجی‌ها با موفقیت حذف شدند",
"reassign": "انتساب مورد با موفقیت به‌روزرسانی شد",
"remove": "خروجی‌ها با موفقیت از مورد حذف شدند"
},
"error": {
"deleteFailed": "حذف خروجی‌ها ناموفق بود: {{errorMessage}}",
"reassignFailed": "به‌روزرسانی انتساب مورد ناموفق بود: {{errorMessage}}"
}
} }
} }

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