Compare commits

..

24 Commits

Author SHA1 Message Date
Hosted Weblate
1687ed3d06
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 93.1% (108 of 116 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (118 of 118 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/common/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-events/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translation: Frigate NVR/common
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-18 17:00:30 +01:00
Hosted Weblate
10933f2274
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (214 of 214 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/common/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/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-explore/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2025-11-18 17:00:28 +01:00
Hosted Weblate
c5ddfe7b90
Translated using Weblate (Slovenian)
Currently translated at 100.0% (214 of 214 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kaboom <kaboom083@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sl/
Translation: Frigate NVR/common
2025-11-18 17:00:27 +01:00
Hosted Weblate
2e92528d1e
Translated using Weblate (Swedish)
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (118 of 118 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kristian Johansson <knmjohansson@gmail.com>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sv/
Translation: Frigate NVR/common
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-11-18 17:00:26 +01:00
Hosted Weblate
55a12302d0
Translated using Weblate (French)
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (French)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (French)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (French)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (French)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (French)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (French)

Currently translated at 100.0% (635 of 635 strings)

Translated using Weblate (French)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (French)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (French)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (French)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (French)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (French)

Currently translated at 97.1% (103 of 106 strings)

Translated using Weblate (French)

Currently translated at 97.1% (103 of 106 strings)

Translated using Weblate (French)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (French)

Currently translated at 100.0% (127 of 127 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Apocoloquintose <bertrand.moreux@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fr/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-18 17:00:24 +01:00
Hosted Weblate
30f6383c86
Translated using Weblate (Spanish)
Currently translated at 24.1% (28 of 116 strings)

Translated using Weblate (Spanish)

Currently translated at 25.4% (27 of 106 strings)

Translated using Weblate (Spanish)

Currently translated at 26.4% (28 of 106 strings)

Translated using Weblate (Spanish)

Currently translated at 76.3% (97 of 127 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (39 of 39 strings)

Co-authored-by: Adrian C <adriancuervo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ramòn Rueda <virem1@gmail.com>
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/
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
2025-11-18 17:00:23 +01:00
Hosted Weblate
f166a2bfb3
Translated using Weblate (Dutch)
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (635 of 635 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (207 of 207 strings)

Translated using Weblate (Dutch)

Currently translated at 97.1% (103 of 106 strings)

Translated using Weblate (Dutch)

Currently translated at 97.1% (103 of 106 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (127 of 127 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marijn <168113859+Marijn0@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-18 17:00:22 +01:00
Hosted Weblate
27d3079cc7
Translated using Weblate (Italian)
Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (501 of 501 strings)

Co-authored-by: Gringo <ita.translations@tiscali.it>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/it/
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/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-facelibrary/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/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-18 17:00:20 +01:00
Hosted Weblate
6bf2fc005d
Translated using Weblate (Polish)
Currently translated at 63.8% (408 of 639 strings)

Translated using Weblate (Polish)

Currently translated at 30.0% (34 of 113 strings)

Translated using Weblate (Polish)

Currently translated at 75.5% (96 of 127 strings)

Translated using Weblate (Polish)

Currently translated at 27.3% (29 of 106 strings)

Translated using Weblate (Polish)

Currently translated at 68.3% (409 of 598 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Polish)

Currently translated at 98.1% (53 of 54 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Polish)

Currently translated at 74.8% (95 of 127 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (10 of 10 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mateusz Paś <piciuok@gmail.com>
Co-authored-by: Wojciech Niziński <niziak-weblate@spox.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pl/
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-18 17:00:19 +01:00
Hosted Weblate
4039363d77
Translated using Weblate (Croatian)
Currently translated at 33.3% (2 of 6 strings)

Translated using Weblate (Croatian)

Currently translated at 21.1% (11 of 52 strings)

Translated using Weblate (Croatian)

Currently translated at 2.7% (2 of 72 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Josip <josipmiki54@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/hr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/hr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/hr/
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-recording
2025-11-18 17:00:17 +01:00
Hosted Weblate
5ff6c46335
Translated using Weblate (Czech)
Currently translated at 4.7% (5 of 106 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Martin Janda <janda@chilliit.cz>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/cs/
Translation: Frigate NVR/views-classificationmodel
2025-11-18 17:00:16 +01:00
Hosted Weblate
1de75b6347
Translated using Weblate (Catalan)
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (Catalan)

Currently translated at 97.1% (103 of 106 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (127 of 127 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Gerard Ricart Castells <gerard.ricart@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-18 17:00:14 +01:00
Hosted Weblate
076313752e
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (635 of 635 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.1% (103 of 106 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (127 of 127 strings)

Co-authored-by: Alex Taran <oleksii.taran@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/uk/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-18 17:00:12 +01:00
Hosted Weblate
a4a7e36649
Translated using Weblate (Bulgarian)
Currently translated at 31.1% (28 of 90 strings)

Translated using Weblate (Bulgarian)

Currently translated at 7.6% (1 of 13 strings)

Translated using Weblate (Bulgarian)

Currently translated at 50.0% (1 of 2 strings)

Translated using Weblate (Bulgarian)

Currently translated at 50.0% (1 of 2 strings)

Translated using Weblate (Bulgarian)

Currently translated at 7.4% (4 of 54 strings)

Translated using Weblate (Bulgarian)

Currently translated at 10.0% (1 of 10 strings)

Translated using Weblate (Bulgarian)

Currently translated at 17.7% (21 of 118 strings)

Co-authored-by: Borislav <sartheris@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/bg/
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
2025-11-18 17:00:11 +01:00
Hosted Weblate
d2bc2e1802
Translated using Weblate (Romanian)
Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (598 of 598 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (Romanian)

Currently translated at 97.1% (103 of 106 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/common/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ro/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-18 17:00:10 +01:00
Hosted Weblate
c31476b953
Translated using Weblate (Russian)
Currently translated at 98.5% (211 of 214 strings)

Translated using Weblate (Russian)

Currently translated at 95.5% (108 of 113 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (106 of 106 strings)

Translated using Weblate (Russian)

Currently translated at 78.0% (467 of 598 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Russian)

Currently translated at 73.9% (442 of 598 strings)

Translated using Weblate (Russian)

Currently translated at 95.5% (86 of 90 strings)

Translated using Weblate (Russian)

Currently translated at 98.0% (51 of 52 strings)

Translated using Weblate (Russian)

Currently translated at 71.6% (91 of 127 strings)

Translated using Weblate (Russian)

Currently translated at 86.4% (433 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Артём Владимиров <artyomka71@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ru/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2025-11-18 17:00:08 +01:00
Hosted Weblate
2cf58b20aa
Translated using Weblate (Greek)
Currently translated at 100.0% (10 of 10 strings)

Co-authored-by: Christos Sidiropoulos <dev@csidirop.de>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/el/
Translation: Frigate NVR/components-auth
2025-11-18 17:00:07 +01:00
Hosted Weblate
438b1db4dd
Translated using Weblate (Danish)
Currently translated at 48.3% (57 of 118 strings)

Translated using Weblate (Danish)

Currently translated at 16.6% (9 of 54 strings)

Translated using Weblate (Danish)

Currently translated at 7.7% (9 of 116 strings)

Translated using Weblate (Danish)

Currently translated at 6.7% (8 of 118 strings)

Translated using Weblate (Danish)

Currently translated at 1.4% (9 of 639 strings)

Translated using Weblate (Danish)

Currently translated at 16.6% (8 of 48 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Danish)

Currently translated at 10.0% (9 of 90 strings)

Translated using Weblate (Danish)

Currently translated at 17.3% (9 of 52 strings)

Translated using Weblate (Danish)

Currently translated at 61.5% (8 of 13 strings)

Translated using Weblate (Danish)

Currently translated at 9.4% (12 of 127 strings)

Translated using Weblate (Danish)

Currently translated at 25.6% (10 of 39 strings)

Translated using Weblate (Danish)

Currently translated at 80.0% (8 of 10 strings)

Translated using Weblate (Danish)

Currently translated at 36.0% (9 of 25 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Danish)

Currently translated at 12.5% (9 of 72 strings)

Translated using Weblate (Danish)

Currently translated at 14.8% (8 of 54 strings)

Translated using Weblate (Danish)

Currently translated at 19.5% (9 of 46 strings)

Translated using Weblate (Danish)

Currently translated at 90.0% (9 of 10 strings)

Translated using Weblate (Danish)

Currently translated at 16.9% (85 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: demention666 <anders+GITHUB@familien-harder.dk>
Co-authored-by: dinf60 <dinf60@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/da/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-18 17:00:06 +01:00
Hosted Weblate
c9e9dd25f9
Translated using Weblate (German)
Currently translated at 6.0% (7 of 116 strings)

Translated using Weblate (German)

Currently translated at 92.3% (48 of 52 strings)

Translated using Weblate (German)

Currently translated at 93.7% (119 of 127 strings)

Translated using Weblate (German)

Currently translated at 71.6% (91 of 127 strings)

Translated using Weblate (German)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (635 of 635 strings)

Translated using Weblate (German)

Currently translated at 100.0% (209 of 209 strings)

Translated using Weblate (German)

Currently translated at 88.4% (443 of 501 strings)

Co-authored-by: Christos Sidiropoulos <dev@csidirop.de>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marijn <168113859+Marijn0@users.noreply.github.com>
Co-authored-by: mvdberge <micha.vordemberge@christmann.info>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-18 17:00:04 +01:00
Hosted Weblate
1368050598
Translated using Weblate (Portuguese (Brazil))
Currently translated at 24.1% (28 of 116 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 68.7% (439 of 639 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.4% (38 of 39 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marcelo Popper Costa <marcelo_popper@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pt_BR/
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-settings
2025-11-18 17:00:02 +01:00
Hosted Weblate
ce7d0a2b6a
Translated using Weblate (Lithuanian)
Currently translated at 30.1% (32 of 106 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/views-classificationmodel/lt/
Translation: Frigate NVR/views-classificationmodel
2025-11-18 17:00:01 +01:00
Hosted Weblate
ace0bab97e
Added translation using Weblate (Latvian)
Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Update translation files

Updated by "Squash Git commits" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translation: Frigate NVR/common
2025-11-18 16:59:59 +01:00
Hosted Weblate
c932ad6e37
Translated using Weblate (Turkish)
Currently translated at 35.8% (38 of 106 strings)

Co-authored-by: Emircanos <emircan368@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/tr/
Translation: Frigate NVR/views-classificationmodel
2025-11-18 16:59:57 +01:00
Josh Hawkins
fbf4388b37
Miscellaneous Fixes (#20897)
Some checks failed
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
* don't flatten the search result cache when updating

this would cause an infinite swr fetch if something was mutated and then fetch was called again

* Properly sort keys for recording summary in StorageMetrics

* tracked object description box tweaks

* Remove ability to right click on elements inside of face popup

* Update reprocess message

* don't show object track until video metadata is loaded

* fix blue line height calc for in progress events

* Use timeline tab by default for notifications but add a query arg for customization

* Try and improve notification opening behavior

* Reduce review item buffering behavior

* ensure logging config is passed to camera capture and tracker processes

* ensure on demand recording stops when browser closes

* improve active line progress height with resize observer

* remove icons and duplicate find similar link in explore context menu

* fix for initial broken image when creating trigger from explore

* display friendly names for triggers in toasts

* lpr and triggers docs updates

* remove icons from dropdowns in face and classification

* fix comma dangle linter issue

* re-add incorrectly removed face library button icons

* fix sidebar nav links on < 768px desktop layout

* allow text to wrap on mark as reviewed button

* match exact pixels

* clarify LPR docs

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-11-17 08:12:05 -06:00
39 changed files with 555 additions and 245 deletions

View File

@ -3,18 +3,18 @@ id: license_plate_recognition
title: License Plate Recognition (LPR) title: License Plate Recognition (LPR)
--- ---
Frigate can recognize license plates on vehicles and automatically add the detected characters to the `recognized_license_plate` field or a known name as a `sub_label` to tracked objects of type `car` or `motorcycle`. A common use case may be to read the license plates of cars pulling into a driveway or cars passing by on a street. Frigate can recognize license plates on vehicles and automatically add the detected characters to the `recognized_license_plate` field or a [known](#matching) name as a `sub_label` to tracked objects of type `car` or `motorcycle`. A common use case may be to read the license plates of cars pulling into a driveway or cars passing by on a street.
LPR works best when the license plate is clearly visible to the camera. For moving vehicles, Frigate continuously refines the recognition process, keeping the most confident result. When a vehicle becomes stationary, LPR continues to run for a short time after to attempt recognition. LPR works best when the license plate is clearly visible to the camera. For moving vehicles, Frigate continuously refines the recognition process, keeping the most confident result. When a vehicle becomes stationary, LPR continues to run for a short time after to attempt recognition.
When a plate is recognized, the details are: When a plate is recognized, the details are:
- Added as a `sub_label` (if known) or the `recognized_license_plate` field (if unknown) to a tracked object. - Added as a `sub_label` (if [known](#matching)) or the `recognized_license_plate` field (if unknown) to a tracked object.
- Viewable in the Review Item Details pane in Review (sub labels). - Viewable in the Details pane in Review/History.
- Viewable in the Tracked Object Details pane in Explore (sub labels and recognized license plates). - Viewable in the Tracked Object Details pane in Explore (sub labels and recognized license plates).
- Filterable through the More Filters menu in Explore. - Filterable through the More Filters menu in Explore.
- Published via the `frigate/events` MQTT topic as a `sub_label` (known) or `recognized_license_plate` (unknown) for the `car` or `motorcycle` tracked object. - Published via the `frigate/events` MQTT topic as a `sub_label` ([known](#matching)) or `recognized_license_plate` (unknown) for the `car` or `motorcycle` tracked object.
- Published via the `frigate/tracked_object_update` MQTT topic with `name` (if known) and `plate`. - Published via the `frigate/tracked_object_update` MQTT topic with `name` (if [known](#matching)) and `plate`.
## Model Requirements ## Model Requirements
@ -31,6 +31,7 @@ In the default mode, Frigate's LPR needs to first detect a `car` or `motorcycle`
## Minimum System Requirements ## Minimum System Requirements
License plate recognition works by running AI models locally on your system. The YOLOv9 plate detector model and the OCR models ([PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)) are relatively lightweight and can run on your CPU or GPU, depending on your configuration. At least 4GB of RAM is required. License plate recognition works by running AI models locally on your system. The YOLOv9 plate detector model and the OCR models ([PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)) are relatively lightweight and can run on your CPU or GPU, depending on your configuration. At least 4GB of RAM is required.
## Configuration ## Configuration
License plate recognition is disabled by default. Enable it in your config file: License plate recognition is disabled by default. Enable it in your config file:
@ -73,8 +74,8 @@ Fine-tune the LPR feature using these optional parameters at the global level of
- Default: `small` - Default: `small`
- This can be `small` or `large`. - This can be `small` or `large`.
- The `small` model is fast and identifies groups of Latin and Chinese characters. - The `small` model is fast and identifies groups of Latin and Chinese characters.
- The `large` model identifies Latin characters only, but uses an enhanced text detector and is more capable at finding characters on multi-line plates. It is significantly slower than the `small` model. Note that using the `large` model does not improve _text recognition_, but it may improve _text detection_. - The `large` model identifies Latin characters only, and uses an enhanced text detector to find characters on multi-line plates. It is significantly slower than the `small` model.
- For most users, the `small` model is recommended. - If your country or region does not use multi-line plates, you should use the `small` model as performance is much better for single-line plates.
### Recognition ### Recognition
@ -177,7 +178,7 @@ lpr:
:::note :::note
If you want to detect cars on cameras but don't want to use resources to run LPR on those cars, you should disable LPR for those specific cameras. If a camera is configured to detect `car` or `motorcycle` but you don't want Frigate to run LPR for that camera, disable LPR at the camera level:
```yaml ```yaml
cameras: cameras:
@ -305,7 +306,7 @@ With this setup:
- Review items will always be classified as a `detection`. - Review items will always be classified as a `detection`.
- Snapshots will always be saved. - Snapshots will always be saved.
- Zones and object masks are **not** used. - Zones and object masks are **not** used.
- The `frigate/events` MQTT topic will **not** publish tracked object updates with the license plate bounding box and score, though `frigate/reviews` will publish if recordings are enabled. If a plate is recognized as a known plate, publishing will occur with an updated `sub_label` field. If characters are recognized, publishing will occur with an updated `recognized_license_plate` field. - The `frigate/events` MQTT topic will **not** publish tracked object updates with the license plate bounding box and score, though `frigate/reviews` will publish if recordings are enabled. If a plate is recognized as a [known](#matching) plate, publishing will occur with an updated `sub_label` field. If characters are recognized, publishing will occur with an updated `recognized_license_plate` field.
- License plate snapshots are saved at the highest-scoring moment and appear in Explore. - License plate snapshots are saved at the highest-scoring moment and appear in Explore.
- Debug view will not show `license_plate` bounding boxes. - Debug view will not show `license_plate` bounding boxes.

View File

@ -141,7 +141,7 @@ Triggers are best configured through the Frigate UI.
Check the `Add Attribute` box to add the trigger's internal ID (e.g., "red_car_alert") to a data attribute on the tracked object that can be processed via the API or MQTT. Check the `Add Attribute` box to add the trigger's internal ID (e.g., "red_car_alert") to a data attribute on the tracked object that can be processed via the API or MQTT.
5. Save the trigger to update the configuration and store the embedding in the database. 5. Save the trigger to update the configuration and store the embedding in the database.
When a trigger fires, the UI highlights the trigger with a blue dot for 3 seconds for easy identification. When a trigger fires, the UI highlights the trigger with a blue dot for 3 seconds for easy identification. Additionally, the UI will show the last date/time and tracked object ID that activated your trigger. The last triggered timestamp is not saved to the database or persisted through restarts of Frigate.
### Usage and Best Practices ### Usage and Best Practices

View File

@ -1781,9 +1781,8 @@ def create_trigger_embedding(
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}."
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception(
logger.error(
f"Failed to write thumbnail for trigger with data {body.data} in {camera_name}" f"Failed to write thumbnail for trigger with data {body.data} in {camera_name}"
) )
@ -1807,8 +1806,8 @@ def create_trigger_embedding(
status_code=200, status_code=200,
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception("Error creating trigger embedding")
return JSONResponse( return JSONResponse(
content={ content={
"success": False, "success": False,
@ -1917,9 +1916,8 @@ def update_trigger_embedding(
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}."
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception(
logger.error(
f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera_name}" f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera_name}"
) )
@ -1958,9 +1956,8 @@ def update_trigger_embedding(
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}."
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception(
logger.error(
f"Failed to write thumbnail for trigger with data {body.data} in {camera_name}" f"Failed to write thumbnail for trigger with data {body.data} in {camera_name}"
) )
@ -1972,8 +1969,8 @@ def update_trigger_embedding(
status_code=200, status_code=200,
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception("Error updating trigger embedding")
return JSONResponse( return JSONResponse(
content={ content={
"success": False, "success": False,
@ -2033,9 +2030,8 @@ def delete_trigger_embedding(
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}."
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception(
logger.error(
f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera_name}" f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera_name}"
) )
@ -2047,8 +2043,8 @@ def delete_trigger_embedding(
status_code=200, status_code=200,
) )
except Exception as e: except Exception:
logger.error(e.with_traceback()) logger.exception("Error deleting trigger embedding")
return JSONResponse( return JSONResponse(
content={ content={
"success": False, "success": False,

View File

@ -136,6 +136,7 @@ class CameraMaintainer(threading.Thread):
self.ptz_metrics[name], self.ptz_metrics[name],
self.region_grids[name], self.region_grids[name],
self.stop_event, self.stop_event,
self.config.logger,
) )
self.camera_processes[config.name] = camera_process self.camera_processes[config.name] = camera_process
camera_process.start() camera_process.start()
@ -156,7 +157,11 @@ class CameraMaintainer(threading.Thread):
self.frame_manager.create(f"{config.name}_frame{i}", frame_size) self.frame_manager.create(f"{config.name}_frame{i}", frame_size)
capture_process = CameraCapture( capture_process = CameraCapture(
config, count, self.camera_metrics[name], self.stop_event config,
count,
self.camera_metrics[name],
self.stop_event,
self.config.logger,
) )
capture_process.daemon = True capture_process.daemon = True
self.capture_processes[name] = capture_process self.capture_processes[name] = capture_process

View File

@ -132,17 +132,15 @@ class ReviewDescriptionProcessor(PostProcessorApi):
if image_source == ImageSourceEnum.recordings: if image_source == ImageSourceEnum.recordings:
duration = final_data["end_time"] - final_data["start_time"] duration = final_data["end_time"] - final_data["start_time"]
buffer_extension = min( buffer_extension = min(5, duration * RECORDING_BUFFER_EXTENSION_PERCENT)
10, max(2, duration * RECORDING_BUFFER_EXTENSION_PERCENT)
)
# Ensure minimum total duration for short review items # Ensure minimum total duration for short review items
# This provides better context for brief events # This provides better context for brief events
total_duration = duration + (2 * buffer_extension) total_duration = duration + (2 * buffer_extension)
if total_duration < MIN_RECORDING_DURATION: if total_duration < MIN_RECORDING_DURATION:
# Expand buffer to reach minimum duration, still respecting max of 10s per side # Expand buffer to reach minimum duration, still respecting max of 5s per side
additional_buffer_per_side = (MIN_RECORDING_DURATION - duration) / 2 additional_buffer_per_side = (MIN_RECORDING_DURATION - duration) / 2
buffer_extension = min(10, additional_buffer_per_side) buffer_extension = min(5, additional_buffer_per_side)
thumbs = self.get_recording_frames( thumbs = self.get_recording_frames(
camera, camera,

View File

@ -424,7 +424,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
if not res: if not res:
return { return {
"message": "No face was recognized.", "message": "Model is still training, please try again in a few moments.",
"success": False, "success": False,
} }

View File

@ -16,7 +16,7 @@ from frigate.comms.recordings_updater import (
RecordingsDataSubscriber, RecordingsDataSubscriber,
RecordingsDataTypeEnum, RecordingsDataTypeEnum,
) )
from frigate.config import CameraConfig, DetectConfig, ModelConfig from frigate.config import CameraConfig, DetectConfig, LoggerConfig, ModelConfig
from frigate.config.camera.camera import CameraTypeEnum from frigate.config.camera.camera import CameraTypeEnum
from frigate.config.camera.updater import ( from frigate.config.camera.updater import (
CameraConfigUpdateEnum, CameraConfigUpdateEnum,
@ -539,6 +539,7 @@ class CameraCapture(FrigateProcess):
shm_frame_count: int, shm_frame_count: int,
camera_metrics: CameraMetrics, camera_metrics: CameraMetrics,
stop_event: MpEvent, stop_event: MpEvent,
log_config: LoggerConfig | None = None,
) -> None: ) -> None:
super().__init__( super().__init__(
stop_event, stop_event,
@ -549,9 +550,10 @@ class CameraCapture(FrigateProcess):
self.config = config self.config = config
self.shm_frame_count = shm_frame_count self.shm_frame_count = shm_frame_count
self.camera_metrics = camera_metrics self.camera_metrics = camera_metrics
self.log_config = log_config
def run(self) -> None: def run(self) -> None:
self.pre_run_setup() self.pre_run_setup(self.log_config)
camera_watchdog = CameraWatchdog( camera_watchdog = CameraWatchdog(
self.config, self.config,
self.shm_frame_count, self.shm_frame_count,
@ -577,6 +579,7 @@ class CameraTracker(FrigateProcess):
ptz_metrics: PTZMetrics, ptz_metrics: PTZMetrics,
region_grid: list[list[dict[str, Any]]], region_grid: list[list[dict[str, Any]]],
stop_event: MpEvent, stop_event: MpEvent,
log_config: LoggerConfig | None = None,
) -> None: ) -> None:
super().__init__( super().__init__(
stop_event, stop_event,
@ -592,9 +595,10 @@ class CameraTracker(FrigateProcess):
self.camera_metrics = camera_metrics self.camera_metrics = camera_metrics
self.ptz_metrics = ptz_metrics self.ptz_metrics = ptz_metrics
self.region_grid = region_grid self.region_grid = region_grid
self.log_config = log_config
def run(self) -> None: def run(self) -> None:
self.pre_run_setup() self.pre_run_setup(self.log_config)
frame_queue = self.camera_metrics.frame_queue frame_queue = self.camera_metrics.frame_queue
frame_shape = self.config.frame_shape frame_shape = self.config.frame_shape

View File

@ -1 +1,5 @@
{} {
"form": {
"user": "Потребителско име"
}
}

View File

@ -8,5 +8,8 @@
"lastHour_other": "Последните {{count}} часа" "lastHour_other": "Последните {{count}} часа"
}, },
"select": "Избери" "select": "Избери"
},
"restart": {
"title": "Сигурен ли сте, че искате да рестартирате Frigate?"
} }
} }

View File

@ -1 +1,5 @@
{} {
"iconPicker": {
"selectIcon": "Изберете иконка"
}
}

View File

@ -1 +1,7 @@
{} {
"button": {
"downloadVideo": {
"label": "Свали видео"
}
}
}

View File

@ -18,5 +18,6 @@
"bicycle": "Велосипед", "bicycle": "Велосипед",
"skateboard": "Скейтборд", "skateboard": "Скейтборд",
"door": "Врата", "door": "Врата",
"blender": "Блендер" "blender": "Блендер",
"person": "Човек"
} }

View File

@ -1 +1,3 @@
{} {
"documentTitle": "Експорт - Frigate"
}

View File

@ -63,5 +63,6 @@
}, },
"cameraSettings": { "cameraSettings": {
"cameraEnabled": "Камерата е включена" "cameraEnabled": "Камерата е включена"
} },
"documentTitle": "Наживо - Frigate"
} }

View File

@ -10,5 +10,94 @@
"general": "Generelle statistikker - Frigate", "general": "Generelle statistikker - Frigate",
"enrichments": "Beredningsstatistikker - Frigate" "enrichments": "Beredningsstatistikker - Frigate"
}, },
"title": "System" "title": "System",
"logs": {
"copy": {
"label": "Kopier til udklipsholder",
"success": "Logs er kopieret til udklipsholder",
"error": "Kunne ikke kopiere logs til udklipsholder"
},
"type": {
"label": "Type",
"timestamp": "Tidsstempel",
"message": "Besked",
"tag": "Tag"
},
"tips": "Logs bliver streamet fra serveren",
"toast": {
"error": {
"fetchingLogsFailed": "Fejl ved indhentning af logs: {{errorMessage}}",
"whileStreamingLogs": "Fejl ved streaming af logs: {{errorMessage}}"
}
},
"download": {
"label": "Download logs"
}
},
"general": {
"title": "Generelt",
"hardwareInfo": {
"gpuUsage": "GPU forbrug",
"gpuMemory": "GPU hukommelse",
"gpuEncoder": "GPU indkoder",
"gpuDecoder": "GPU afkoder",
"title": "Hardware information",
"gpuInfo": {
"closeInfo": {
"label": "Luk GPU information"
},
"copyInfo": {
"label": "Kopier GPU information"
},
"toast": {
"success": "Kopierede GPU information til udklipsholder"
}
},
"npuUsage": "NPU forbrug",
"npuMemory": "NPU hukommelse"
},
"detector": {
"title": "Detektorer",
"inferenceSpeed": "Detektorinferenshastighed",
"temperature": "Detektor temperatur",
"cpuUsage": "Detektor CPU forbrug",
"cpuUsageInformation": "CPU brugt til at forberede input- og outputdata til/fra detektionsmodeller. Denne værdi måler ikke inferensforbrug, selvom der bruges en GPU eller accelerator.",
"memoryUsage": "Detektorhummelsesforbrug"
},
"otherProcesses": {
"title": "Andre processer",
"processCpuUsage": "Proces CPU forbrug",
"processMemoryUsage": "Proceshukommelsesforbrug"
}
},
"metrics": "System metrikker",
"storage": {
"title": "Lagring",
"overview": "Overblik",
"recordings": {
"title": "Optagelser",
"tips": "Denne værdi repræsenterer den samlede lagerplads, der bruges af optagelserne i Frigates database. Frigate sporer ikke lagerpladsforbruget for alle filer på din disk.",
"earliestRecording": "Tidligste optagelse til rådighed:"
},
"shm": {
"title": "SHM (delt hukommelse) tildeling",
"warning": "Den nuværende SHM størrelse af {{total}}MB er for lille. Øg den til minimum {{min_shm}}MB."
},
"cameraStorage": {
"title": "Kamera lagring",
"camera": "Kamera",
"unusedStorageInformation": "Ubrugt lagringsinformation",
"storageUsed": "Lagring",
"percentageOfTotalUsed": "Procentandel af total",
"bandwidth": "Båndbredde",
"unused": {
"title": "Ubrugt",
"tips": "Denne værdi repræsenterer muligvis ikke nøjagtigt den ledige plads, der er tilgængelig for Frigate, hvis du har andre filer gemt på dit drev ud over Frigates optagelser. Frigate sporer ikke lagerforbrug ud over sine optagelser."
}
}
},
"cameras": {
"title": "Kameraer",
"overview": "Overblik"
}
} }

View File

@ -1 +1,13 @@
{} {
"documentTitle": "Klassifizierungsmodelle",
"details": {
"scoreInfo": "Die Punktzahl gibt die durchschnittliche Klassifizierungssicherheit aller Erkennungen dieses Objekts wieder."
},
"button": {
"deleteClassificationAttempts": "Lösche Klassifizierungs-Bilder",
"renameCategory": "Klasse umbenennen",
"deleteCategory": "Klasse löschen",
"deleteImages": "Bilder löschen",
"trainModel": "Modell trainieren"
}
}

View File

@ -196,12 +196,22 @@
"addTrigger": { "addTrigger": {
"aria": "Einen Trigger für dieses verfolgte Objekt hinzufügen", "aria": "Einen Trigger für dieses verfolgte Objekt hinzufügen",
"label": "Trigger hinzufügen" "label": "Trigger hinzufügen"
},
"viewTrackingDetails": {
"label": "Details zum Verfolgen anzeigen",
"aria": "Details zum Verfolgen anzeigen"
},
"showObjectDetails": {
"label": "Objektpfad anzeigen"
},
"hideObjectDetails": {
"label": "Objektpfad verbergen"
} }
}, },
"dialog": { "dialog": {
"confirmDelete": { "confirmDelete": {
"title": "Löschen bestätigen", "title": "Löschen bestätigen",
"desc": "Beim Löschen dieses verfolgten Objekts werden der Schnappschuss, alle gespeicherten Einbettungen und alle zugehörigen Objektlebenszykluseinträge entfernt. Aufgezeichnetes Filmmaterial dieses verfolgten Objekts in der Verlaufsansicht wird <em>NICHT</em> gelöscht. <br /><br />Sind Sie sicher, dass Sie fortfahren möchten?" "desc": "Beim Löschen dieses verfolgten Objekts werden der Schnappschuss, alle gespeicherten Einbettungen und alle zugehörigen Verfolgungsdetails entfernt. Aufgezeichnetes Filmmaterial dieses verfolgten Objekts in der Verlaufsansicht wird <em>NICHT</em> gelöscht. <br /><br />Sind Sie sicher, dass Sie fortfahren möchten?"
} }
}, },
"searchResult": { "searchResult": {
@ -211,7 +221,9 @@
"error": "Das verfolgte Objekt konnte nicht gelöscht werden: {{errorMessage}}" "error": "Das verfolgte Objekt konnte nicht gelöscht werden: {{errorMessage}}"
} }
}, },
"tooltip": "Entspricht {{type}} bei {{confidence}}%" "tooltip": "Entspricht {{type}} bei {{confidence}}%",
"previousTrackedObject": "Vorheriges verfolgtes Objekt",
"nextTrackedObject": "Nächstes verfolgtes Objekt"
}, },
"noTrackedObjects": "Keine verfolgten Objekte gefunden", "noTrackedObjects": "Keine verfolgten Objekte gefunden",
"fetchingTrackedObjectsFailed": "Fehler beim Abrufen von verfolgten Objekten: {{errorMessage}}", "fetchingTrackedObjectsFailed": "Fehler beim Abrufen von verfolgten Objekten: {{errorMessage}}",
@ -227,6 +239,44 @@
"trackingDetails": { "trackingDetails": {
"noImageFound": "Kein Bild mit diesem Zeitstempel gefunden.", "noImageFound": "Kein Bild mit diesem Zeitstempel gefunden.",
"createObjectMask": "Objekt-Maske erstellen", "createObjectMask": "Objekt-Maske erstellen",
"scrollViewTips": "Klicke, um die relevanten Momente aus dem Lebenszyklus dieses Objektes zu sehen." "scrollViewTips": "Klicke, um die relevanten Momente aus dem Lebenszyklus dieses Objektes zu sehen.",
"lifecycleItemDesc": {
"visible": "{{label}} erkannt",
"entered_zone": "{{label}} betrat {{zones}}",
"active": "{{label}} wurde aktiv",
"stationary": "{{label}} wurde stationär",
"attribute": {
"faceOrLicense_plate": "{{attribute}} erkannt für {{label}}",
"other": "{{label}} erkannt als {{attribute}}"
},
"gone": "{{label}} verließ",
"heard": "{{label}} wurde gehört",
"external": "{{label}} erkannt",
"header": {
"zones": "Zonen",
"ratio": "Verhältnis",
"area": "Bereich"
}
},
"annotationSettings": {
"title": "Anmerkungseinstellungen",
"showAllZones": {
"title": "Zeige alle Zonen",
"desc": "Immer Zonen auf Rahmen anzeigen, in die Objekte eingetreten sind."
},
"offset": {
"label": "Anmerkungen Versatz",
"desc": "Diese Daten stammen aus dem Erkennungsfeed der Kamera, werden jedoch über Bilder aus dem Aufzeichnungsfeed gelegt. Es ist unwahrscheinlich, dass beide Streams perfekt synchron sind. Daher stimmen der Begrenzungsrahmen und das Filmmaterial nicht vollständig überein. Mit dieser Einstellung lassen sich die Anmerkungen zeitlich nach vorne oder hinten verschieben, um sie besser an das aufgezeichnete Filmmaterial anzupassen.",
"millisecondsToOffset": "Millisekunden, um Erkennungs-Anmerkungen zu verschieben. <em>Standard: 0</em>",
"tips": "Verringere den Wert, wenn die Videowiedergabe den Boxen und Wegpunkten voraus ist, und erhöhe den Wert, wenn die Videowiedergabe hinter ihnen zurückbleibt. Dieser Wert kann negativ sein.",
"toast": {
"success": "Der Anmerkungs-Offset für {{camera}} wurde in der Konfigurationsdatei gespeichert. Starte Frigate neu, um Ihre Änderungen zu übernehmen."
}
}
},
"carousel": {
"previous": "Vorherige Anzeige",
"next": "Nächste Anzeige"
}
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"description": { "description": {
"placeholder": "Gib einen Name für diese Kollektion ein", "placeholder": "Gib einen Name für diese Kollektion ein",
"addFace": "Anleitung für das Hinzufügen einer neuen Kollektion zur Gesichtsbibliothek.", "addFace": "Füge der Gesichtsbibliothek eine neue Sammlung hinzu, indem ein erstes Bild hochgeladen wird.",
"invalidName": "Ungültiger Name. Namen dürfen nur Buchstaben, Zahlen, Leerzeichen, Apostrophe, Unterstriche und Bindestriche enthalten." "invalidName": "Ungültiger Name. Namen dürfen nur Buchstaben, Zahlen, Leerzeichen, Apostrophe, Unterstriche und Bindestriche enthalten."
}, },
"details": { "details": {

View File

@ -2,7 +2,7 @@
"documentTitle": "Klassifiseringsmodeller", "documentTitle": "Klassifiseringsmodeller",
"button": { "button": {
"deleteClassificationAttempts": "Slett klassifiseringsbilder", "deleteClassificationAttempts": "Slett klassifiseringsbilder",
"renameCategory": "Gi nytt navn til klasse", "renameCategory": "Omdøp klasse",
"deleteCategory": "Slett klasse", "deleteCategory": "Slett klasse",
"deleteImages": "Slett bilder", "deleteImages": "Slett bilder",
"trainModel": "Tren modell", "trainModel": "Tren modell",
@ -72,7 +72,7 @@
"buttonText": "Opprett objektmodell" "buttonText": "Opprett objektmodell"
}, },
"state": { "state": {
"title": "Ingen tilstands­klassifiseringsmodeller", "title": "Ingen tilstandsklassifiseringsmodeller",
"description": "Opprett en tilpasset modell for å overvåke og klassifisere tilstandsendringer i spesifikke kamerasoner.", "description": "Opprett en tilpasset modell for å overvåke og klassifisere tilstandsendringer i spesifikke kamerasoner.",
"buttonText": "Opprett tilstandsmodell" "buttonText": "Opprett tilstandsmodell"
} }
@ -102,7 +102,7 @@
"classesTip": "Lær om kategorier", "classesTip": "Lær om kategorier",
"classesStateDesc": "Definer de ulike tilstandene kamerasonen kan være i. For eksempel: 'åpen' og 'lukket' for en garasjeport.", "classesStateDesc": "Definer de ulike tilstandene kamerasonen kan være i. For eksempel: 'åpen' og 'lukket' for en garasjeport.",
"classesObjectDesc": "Definer kategoriene du vil klassifisere oppdagede objekter i. For eksempel: 'bud', 'beboer', 'fremmed' for personklassifisering.", "classesObjectDesc": "Definer kategoriene du vil klassifisere oppdagede objekter i. For eksempel: 'bud', 'beboer', 'fremmed' for personklassifisering.",
"classPlaceholder": "Skriv inn kategorinavn...", "classPlaceholder": "Skriv inn tilstandsnavn...",
"errors": { "errors": {
"nameRequired": "Modellnavn er påkrevd", "nameRequired": "Modellnavn er påkrevd",
"nameLength": "Modellnavn må være på 64 tegn eller mindre", "nameLength": "Modellnavn må være på 64 tegn eller mindre",

View File

@ -1262,7 +1262,7 @@
"desc": "Midlertidig aktiver/deaktiver inspeksjonsbeskrivelser med generativ KI for dette kameraet. Når deaktivert, vil det ikke bli bedt om KI-genererte beskrivelser for inspeksjonselementer på dette kameraet." "desc": "Midlertidig aktiver/deaktiver inspeksjonsbeskrivelser med generativ KI for dette kameraet. Når deaktivert, vil det ikke bli bedt om KI-genererte beskrivelser for inspeksjonselementer på dette kameraet."
}, },
"review": { "review": {
"title": "Inspeksjon", "title": "Inspiser",
"desc": "Aktiver/deaktiver varsler og deteksjoner midlertidig for dette kameraet til Frigate startes på nytt. Når deaktivert, vil det ikke genereres nye inspeksjonselementer. ", "desc": "Aktiver/deaktiver varsler og deteksjoner midlertidig for dette kameraet til Frigate startes på nytt. Når deaktivert, vil det ikke genereres nye inspeksjonselementer. ",
"alerts": "Varsler ", "alerts": "Varsler ",
"detections": "Deteksjoner " "detections": "Deteksjoner "

View File

@ -81,7 +81,7 @@
"masksAndZones": "Edytor Masek i Stref - Frigate", "masksAndZones": "Edytor Masek i Stref - Frigate",
"frigatePlus": "Ustawienia Frigate+ - Frigate", "frigatePlus": "Ustawienia Frigate+ - Frigate",
"classification": "Ustawienia Klasyfikacji - Frigate", "classification": "Ustawienia Klasyfikacji - Frigate",
"general": "Ustawienia Ogólne - Frigate", "general": "Ustawienia Interfejsu - Frigate",
"authentication": "Ustawienia Uwierzytelniania - Frigate", "authentication": "Ustawienia Uwierzytelniania - Frigate",
"motionTuner": "Konfigurator Ruchu - Frigate", "motionTuner": "Konfigurator Ruchu - Frigate",
"object": "Debug - Frigate", "object": "Debug - Frigate",

View File

@ -51,7 +51,7 @@
"h": "{{time}}h", "h": "{{time}}h",
"m": "{{time}}m", "m": "{{time}}m",
"s": "{{time}}s", "s": "{{time}}s",
"yr": "le", "yr": "{{time}}l.",
"formattedTimestamp": { "formattedTimestamp": {
"12hour": "d MMM, h:mm:ss aaa", "12hour": "d MMM, h:mm:ss aaa",
"24hour": "d MMM, HH:mm:ss" "24hour": "d MMM, HH:mm:ss"
@ -84,7 +84,10 @@
"formattedTimestampFilename": { "formattedTimestampFilename": {
"12hour": "dd-MM-yy-h-mm-ss-a", "12hour": "dd-MM-yy-h-mm-ss-a",
"24hour": "dd-MM-yy-HH-mm-ss" "24hour": "dd-MM-yy-HH-mm-ss"
} },
"invalidStartTime": "Napačen čas začetka",
"invalidEndTime": "Napačen čas konca",
"inProgress": "V teku"
}, },
"menu": { "menu": {
"live": { "live": {
@ -152,7 +155,13 @@
"bg": "Български (bulgarščina)", "bg": "Български (bulgarščina)",
"withSystem": { "withSystem": {
"label": "Uporabi sistemske nastavitve za jezik" "label": "Uporabi sistemske nastavitve za jezik"
} },
"ptBR": "Português brasileiro (Brazilska portugalščina)",
"ca": "Català (Katalonščina)",
"lt": "Lietuvių (Litovščina)",
"gl": "Galego (Galicijščina)",
"id": "Bahasa Indonesia (Indonezijščina)",
"ur": "اردو (Urdujščina)"
}, },
"appearance": "Izgled", "appearance": "Izgled",
"darkMode": { "darkMode": {
@ -179,7 +188,9 @@
"anonymous": "anonimen", "anonymous": "anonimen",
"logout": "Odjava", "logout": "Odjava",
"setPassword": "Nastavi Geslo" "setPassword": "Nastavi Geslo"
} },
"uiPlayground": "UI Peskovnik",
"classification": "Klasifikacija"
}, },
"button": { "button": {
"apply": "Uporabi", "apply": "Uporabi",
@ -216,7 +227,8 @@
"unselect": "Odznači", "unselect": "Odznači",
"export": "Izvoz", "export": "Izvoz",
"deleteNow": "Izbriši Zdaj", "deleteNow": "Izbriši Zdaj",
"next": "Naprej" "next": "Naprej",
"continue": "Nadaljuj"
}, },
"unit": { "unit": {
"speed": { "speed": {
@ -226,10 +238,23 @@
"length": { "length": {
"feet": "čevelj", "feet": "čevelj",
"meters": "metri" "meters": "metri"
},
"data": {
"kbps": "kB/s",
"mbps": "MB/s",
"gbps": "GB/s",
"kbph": "kB/uro",
"mbph": "MB/uro",
"gbph": "GB/uro"
} }
}, },
"label": { "label": {
"back": "Pojdi nazaj" "back": "Pojdi nazaj",
"hide": "Skrij {{item}}",
"show": "Prikaži {{item}}",
"ID": "ID",
"none": "Brez",
"all": "Vse"
}, },
"pagination": { "pagination": {
"next": { "next": {
@ -270,5 +295,17 @@
"title": "404", "title": "404",
"desc": "Stran ni najdena" "desc": "Stran ni najdena"
}, },
"readTheDocumentation": "Preberite dokumentacijo" "readTheDocumentation": "Preberite dokumentacijo",
"list": {
"two": "{{0}} in {{1}}",
"many": "{{items}}, in {{last}}",
"separatorWithSpace": ", "
},
"field": {
"optional": "Izbirno",
"internalID": "Interni ID, ki ga Frigate uporablja v konfiguraciji in podatkovni bazi"
},
"information": {
"pixels": "{{area}}px"
}
} }

View File

@ -2,7 +2,7 @@
"documentTitle": { "documentTitle": {
"camera": "Kamerainställningar - Frigate", "camera": "Kamerainställningar - Frigate",
"default": "Inställningar - Frigate", "default": "Inställningar - Frigate",
"general": "UI inställningar - Frigate", "general": "UI Inställningar - Frigate",
"authentication": "Autentiseringsinställningar - Frigate", "authentication": "Autentiseringsinställningar - Frigate",
"classification": "Klassificeringsinställningar - Frigate", "classification": "Klassificeringsinställningar - Frigate",
"masksAndZones": "Maskerings- och zonverktyg - Frigate", "masksAndZones": "Maskerings- och zonverktyg - Frigate",

View File

@ -44,11 +44,16 @@ self.addEventListener("notificationclick", (event) => {
switch (event.action ?? "default") { switch (event.action ?? "default") {
case "markReviewed": case "markReviewed":
if (event.notification.data) { if (event.notification.data) {
fetch("/api/reviews/viewed", { event.waitUntil(
method: "POST", fetch("/api/reviews/viewed", {
headers: { "Content-Type": "application/json", "X-CSRF-TOKEN": 1 }, method: "POST",
body: JSON.stringify({ ids: [event.notification.data.id] }), headers: {
}); "Content-Type": "application/json",
"X-CSRF-TOKEN": 1,
},
body: JSON.stringify({ ids: [event.notification.data.id] }),
}), // eslint-disable-line comma-dangle
);
} }
break; break;
default: default:
@ -58,7 +63,7 @@ self.addEventListener("notificationclick", (event) => {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
if (clients.openWindow) { if (clients.openWindow) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
return clients.openWindow(url); event.waitUntil(clients.openWindow(url));
} }
} }
} }

View File

@ -398,11 +398,7 @@ export function GroupedClassificationCard({
threshold={threshold} threshold={threshold}
selected={false} selected={false}
i18nLibrary={i18nLibrary} i18nLibrary={i18nLibrary}
onClick={(data, meta) => { onClick={() => {}}
if (meta || selectedItems.length > 0) {
onClick(data);
}
}}
> >
{children?.(data)} {children?.(data)}
</ClassificationCard> </ClassificationCard>

View File

@ -4,9 +4,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
import { baseUrl } from "@/api/baseUrl"; import { baseUrl } from "@/api/baseUrl";
import { toast } from "sonner"; import { toast } from "sonner";
import axios from "axios"; import axios from "axios";
import { LuCamera, LuDownload, LuTrash2 } from "react-icons/lu";
import { FiMoreVertical } from "react-icons/fi"; import { FiMoreVertical } from "react-icons/fi";
import { MdImageSearch } from "react-icons/md";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { import {
ContextMenu, ContextMenu,
@ -31,11 +29,8 @@ import {
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import useSWR from "swr"; import useSWR from "swr";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { BsFillLightningFill } from "react-icons/bs";
import BlurredIconButton from "../button/BlurredIconButton"; import BlurredIconButton from "../button/BlurredIconButton";
import { PiPath } from "react-icons/pi";
type SearchResultActionsProps = { type SearchResultActionsProps = {
searchResult: SearchResult; searchResult: SearchResult;
@ -98,7 +93,6 @@ export default function SearchResultActions({
href={`${baseUrl}api/events/${searchResult.id}/clip.mp4`} href={`${baseUrl}api/events/${searchResult.id}/clip.mp4`}
download={`${searchResult.camera}_${searchResult.label}.mp4`} download={`${searchResult.camera}_${searchResult.label}.mp4`}
> >
<LuDownload className="mr-2 size-4" />
<span>{t("itemMenu.downloadVideo.label")}</span> <span>{t("itemMenu.downloadVideo.label")}</span>
</a> </a>
</MenuItem> </MenuItem>
@ -110,7 +104,6 @@ export default function SearchResultActions({
href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg`} href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg`}
download={`${searchResult.camera}_${searchResult.label}.jpg`} download={`${searchResult.camera}_${searchResult.label}.jpg`}
> >
<LuCamera className="mr-2 size-4" />
<span>{t("itemMenu.downloadSnapshot.label")}</span> <span>{t("itemMenu.downloadSnapshot.label")}</span>
</a> </a>
</MenuItem> </MenuItem>
@ -120,44 +113,31 @@ export default function SearchResultActions({
aria-label={t("itemMenu.viewTrackingDetails.aria")} aria-label={t("itemMenu.viewTrackingDetails.aria")}
onClick={showTrackingDetails} onClick={showTrackingDetails}
> >
<PiPath className="mr-2 size-4" />
<span>{t("itemMenu.viewTrackingDetails.label")}</span> <span>{t("itemMenu.viewTrackingDetails.label")}</span>
</MenuItem> </MenuItem>
)} )}
{config?.semantic_search?.enabled && isContextMenu && (
<MenuItem
aria-label={t("itemMenu.findSimilar.aria")}
onClick={findSimilar}
>
<MdImageSearch className="mr-2 size-4" />
<span>{t("itemMenu.findSimilar.label")}</span>
</MenuItem>
)}
{config?.semantic_search?.enabled &&
searchResult.data.type == "object" && (
<MenuItem
aria-label={t("itemMenu.addTrigger.aria")}
onClick={addTrigger}
>
<BsFillLightningFill className="mr-2 size-4" />
<span>{t("itemMenu.addTrigger.label")}</span>
</MenuItem>
)}
{config?.semantic_search?.enabled && {config?.semantic_search?.enabled &&
searchResult.data.type == "object" && ( searchResult.data.type == "object" && (
<MenuItem <MenuItem
aria-label={t("itemMenu.findSimilar.aria")} aria-label={t("itemMenu.findSimilar.aria")}
onClick={findSimilar} onClick={findSimilar}
> >
<MdImageSearch className="mr-2 size-4" />
<span>{t("itemMenu.findSimilar.label")}</span> <span>{t("itemMenu.findSimilar.label")}</span>
</MenuItem> </MenuItem>
)} )}
{config?.semantic_search?.enabled &&
searchResult.data.type == "object" && (
<MenuItem
aria-label={t("itemMenu.addTrigger.aria")}
onClick={addTrigger}
>
<span>{t("itemMenu.addTrigger.label")}</span>
</MenuItem>
)}
<MenuItem <MenuItem
aria-label={t("itemMenu.deleteTrackedObject.label")} aria-label={t("itemMenu.deleteTrackedObject.label")}
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen(true)}
> >
<LuTrash2 className="mr-2 size-4" />
<span>{t("button.delete", { ns: "common" })}</span> <span>{t("button.delete", { ns: "common" })}</span>
</MenuItem> </MenuItem>
</> </>

View File

@ -46,13 +46,13 @@ export default function NavItem({
onClick={onClick} onClick={onClick}
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
"flex flex-col items-center justify-center rounded-lg", "flex flex-col items-center justify-center rounded-lg p-[6px]",
className, className,
variants[item.variant ?? "primary"][isActive ? "active" : "inactive"], variants[item.variant ?? "primary"][isActive ? "active" : "inactive"],
) )
} }
> >
<Icon className="size-5 md:m-[6px]" /> <Icon className="size-5" />
</NavLink> </NavLink>
); );

View File

@ -12,6 +12,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { import {
@ -20,7 +21,6 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { isDesktop, isMobile } from "react-device-detect"; import { isDesktop, isMobile } from "react-device-detect";
import { LuPlus, LuScanFace } from "react-icons/lu";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import React, { ReactNode, useMemo, useState } from "react"; import React, { ReactNode, useMemo, useState } from "react";
@ -89,27 +89,26 @@ export default function FaceSelectionDialog({
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel> <DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
<div <div
className={cn( className={cn(
"flex max-h-[40dvh] flex-col overflow-y-auto", "flex max-h-[40dvh] flex-col overflow-y-auto overflow-x-hidden",
isMobile && "gap-2 pb-4", isMobile && "gap-2 pb-4",
)} )}
> >
<SelectorItem
className="flex cursor-pointer gap-2 smart-capitalize"
onClick={() => setNewFace(true)}
>
<LuPlus />
{t("createFaceLibrary.new")}
</SelectorItem>
{faceNames.sort().map((faceName) => ( {faceNames.sort().map((faceName) => (
<SelectorItem <SelectorItem
key={faceName} key={faceName}
className="flex cursor-pointer gap-2 smart-capitalize" className="flex cursor-pointer gap-2 smart-capitalize"
onClick={() => onTrainAttempt(faceName)} onClick={() => onTrainAttempt(faceName)}
> >
<LuScanFace />
{faceName} {faceName}
</SelectorItem> </SelectorItem>
))} ))}
<DropdownMenuSeparator />
<SelectorItem
className="flex cursor-pointer gap-2 smart-capitalize"
onClick={() => setNewFace(true)}
>
{t("createFaceLibrary.new")}
</SelectorItem>
</div> </div>
</SelectorContent> </SelectorContent>
</Selector> </Selector>

View File

@ -171,6 +171,18 @@ export default function ImagePicker({
alt={selectedImage?.label || "Selected image"} alt={selectedImage?.label || "Selected image"}
className="size-16 rounded object-cover" className="size-16 rounded object-cover"
onLoad={() => handleImageLoad(selectedImageId || "")} onLoad={() => handleImageLoad(selectedImageId || "")}
onError={(e) => {
// If trigger thumbnail fails to load, fall back to event thumbnail
if (!selectedImage) {
const target = e.target as HTMLImageElement;
if (
target.src.includes("clips/triggers") &&
selectedImageId
) {
target.src = `${apiHost}api/events/${selectedImageId}/thumbnail.webp`;
}
}
}}
loading="lazy" loading="lazy"
/> />
{selectedImageId && !loadedImages.has(selectedImageId) && ( {selectedImageId && !loadedImages.has(selectedImageId) && (

View File

@ -683,6 +683,22 @@ function ObjectDetailsTab({
const mutate = useGlobalMutation(); const mutate = useGlobalMutation();
// Helper to map over SWR cached search results while preserving
// either paginated format (SearchResult[][]) or flat format (SearchResult[])
const mapSearchResults = useCallback(
(
currentData: SearchResult[][] | SearchResult[] | undefined,
fn: (event: SearchResult) => SearchResult,
) => {
if (!currentData) return currentData;
if (Array.isArray(currentData[0])) {
return (currentData as SearchResult[][]).map((page) => page.map(fn));
}
return (currentData as SearchResult[]).map(fn);
},
[],
);
// users // users
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
@ -810,17 +826,12 @@ function ObjectDetailsTab({
(key.includes("events") || (key.includes("events") ||
key.includes("events/search") || key.includes("events/search") ||
key.includes("events/explore")), key.includes("events/explore")),
(currentData: SearchResult[][] | SearchResult[] | undefined) => { (currentData: SearchResult[][] | SearchResult[] | undefined) =>
if (!currentData) return currentData; mapSearchResults(currentData, (event) =>
// optimistic update event.id === search.id
return currentData ? { ...event, data: { ...event.data, description: desc } }
.flat() : event,
.map((event) => ),
event.id === search.id
? { ...event, data: { ...event.data, description: desc } }
: event,
);
},
{ {
optimisticData: true, optimisticData: true,
rollbackOnError: true, rollbackOnError: true,
@ -843,7 +854,7 @@ function ObjectDetailsTab({
); );
setDesc(search.data.description); setDesc(search.data.description);
}); });
}, [desc, search, mutate, t]); }, [desc, search, mutate, t, mapSearchResults]);
const regenerateDescription = useCallback( const regenerateDescription = useCallback(
(source: "snapshot" | "thumbnails") => { (source: "snapshot" | "thumbnails") => {
@ -915,9 +926,8 @@ function ObjectDetailsTab({
(key.includes("events") || (key.includes("events") ||
key.includes("events/search") || key.includes("events/search") ||
key.includes("events/explore")), key.includes("events/explore")),
(currentData: SearchResult[][] | SearchResult[] | undefined) => { (currentData: SearchResult[][] | SearchResult[] | undefined) =>
if (!currentData) return currentData; mapSearchResults(currentData, (event) =>
return currentData.flat().map((event) =>
event.id === search.id event.id === search.id
? { ? {
...event, ...event,
@ -928,8 +938,7 @@ function ObjectDetailsTab({
}, },
} }
: event, : event,
); ),
},
{ {
optimisticData: true, optimisticData: true,
rollbackOnError: true, rollbackOnError: true,
@ -963,7 +972,7 @@ function ObjectDetailsTab({
); );
}); });
}, },
[search, apiHost, mutate, setSearch, t], [search, apiHost, mutate, setSearch, t, mapSearchResults],
); );
// recognized plate // recognized plate
@ -992,9 +1001,8 @@ function ObjectDetailsTab({
(key.includes("events") || (key.includes("events") ||
key.includes("events/search") || key.includes("events/search") ||
key.includes("events/explore")), key.includes("events/explore")),
(currentData: SearchResult[][] | SearchResult[] | undefined) => { (currentData: SearchResult[][] | SearchResult[] | undefined) =>
if (!currentData) return currentData; mapSearchResults(currentData, (event) =>
return currentData.flat().map((event) =>
event.id === search.id event.id === search.id
? { ? {
...event, ...event,
@ -1005,8 +1013,7 @@ function ObjectDetailsTab({
}, },
} }
: event, : event,
); ),
},
{ {
optimisticData: true, optimisticData: true,
rollbackOnError: true, rollbackOnError: true,
@ -1040,7 +1047,7 @@ function ObjectDetailsTab({
); );
}); });
}, },
[search, apiHost, mutate, setSearch, t], [search, apiHost, mutate, setSearch, t, mapSearchResults],
); );
// speech transcription // speech transcription
@ -1102,17 +1109,12 @@ function ObjectDetailsTab({
(key.includes("events") || (key.includes("events") ||
key.includes("events/search") || key.includes("events/search") ||
key.includes("events/explore")), key.includes("events/explore")),
(currentData: SearchResult[][] | SearchResult[] | undefined) => { (currentData: SearchResult[][] | SearchResult[] | undefined) =>
if (!currentData) return currentData; mapSearchResults(currentData, (event) =>
// optimistic update event.id === search.id
return currentData ? { ...event, plus_id: "new_upload" }
.flat() : event,
.map((event) => ),
event.id === search.id
? { ...event, plus_id: "new_upload" }
: event,
);
},
{ {
optimisticData: true, optimisticData: true,
rollbackOnError: true, rollbackOnError: true,
@ -1120,7 +1122,7 @@ function ObjectDetailsTab({
}, },
); );
}, },
[search, mutate], [search, mutate, mapSearchResults],
); );
const popoverContainerRef = useRef<HTMLDivElement | null>(null); const popoverContainerRef = useRef<HTMLDivElement | null>(null);
@ -1503,7 +1505,7 @@ function ObjectDetailsTab({
) : ( ) : (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Textarea <Textarea
className="text-md h-32" className="text-md h-32 md:text-sm"
placeholder={t("details.description.placeholder")} placeholder={t("details.description.placeholder")}
value={desc} value={desc}
onChange={(e) => setDesc(e.target.value)} onChange={(e) => setDesc(e.target.value)}
@ -1511,25 +1513,7 @@ function ObjectDetailsTab({
onBlur={handleDescriptionBlur} onBlur={handleDescriptionBlur}
autoFocus autoFocus
/> />
<div className="flex flex-row justify-end gap-4"> <div className="mb-10 flex flex-row justify-end gap-5">
<Tooltip>
<TooltipTrigger asChild>
<button
aria-label={t("button.save", { ns: "common" })}
className="text-primary/40 hover:text-primary/80"
onClick={() => {
setIsEditingDesc(false);
updateDescription();
}}
>
<FaCheck className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>
{t("button.save", { ns: "common" })}
</TooltipContent>
</Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
@ -1540,13 +1524,31 @@ function ObjectDetailsTab({
setDesc(originalDescRef.current ?? ""); setDesc(originalDescRef.current ?? "");
}} }}
> >
<FaTimes className="size-4" /> <FaTimes className="size-5" />
</button> </button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
aria-label={t("button.save", { ns: "common" })}
className="text-primary/40 hover:text-primary/80"
onClick={() => {
setIsEditingDesc(false);
updateDescription();
}}
>
<FaCheck className="size-5" />
</button>
</TooltipTrigger>
<TooltipContent>
{t("button.save", { ns: "common" })}
</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
)} )}

View File

@ -1,5 +1,6 @@
import useSWR from "swr"; import useSWR from "swr";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useResizeObserver } from "@/hooks/resize-observer";
import { Event } from "@/types/event"; import { Event } from "@/types/event";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import { TrackingDetailsSequence } from "@/types/timeline"; import { TrackingDetailsSequence } from "@/types/timeline";
@ -89,9 +90,16 @@ export function TrackingDetails({
}, [manualOverride, currentTime, annotationOffset]); }, [manualOverride, currentTime, annotationOffset]);
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const timelineContainerRef = useRef<HTMLDivElement | null>(null);
const rowRefs = useRef<(HTMLDivElement | null)[]>([]);
const [_selectedZone, setSelectedZone] = useState(""); const [_selectedZone, setSelectedZone] = useState("");
const [_lifecycleZones, setLifecycleZones] = useState<string[]>([]); const [_lifecycleZones, setLifecycleZones] = useState<string[]>([]);
const [seekToTimestamp, setSeekToTimestamp] = useState<number | null>(null); const [seekToTimestamp, setSeekToTimestamp] = useState<number | null>(null);
const [lineBottomOffsetPx, setLineBottomOffsetPx] = useState<number>(32);
const [lineTopOffsetPx, setLineTopOffsetPx] = useState<number>(8);
const [blueLineHeightPx, setBlueLineHeightPx] = useState<number>(0);
const [timelineSize] = useResizeObserver(timelineContainerRef);
const aspectRatio = useMemo(() => { const aspectRatio = useMemo(() => {
if (!config) { if (!config) {
@ -221,60 +229,74 @@ export function TrackingDetails({
displaySource, displaySource,
]); ]);
const isWithinEventRange = const isWithinEventRange = useMemo(() => {
effectiveTime !== undefined && if (effectiveTime === undefined || event.start_time === undefined) {
event.start_time !== undefined && return false;
event.end_time !== undefined &&
effectiveTime >= event.start_time &&
effectiveTime <= event.end_time;
// Calculate how far down the blue line should extend based on effectiveTime
const calculateLineHeight = useCallback(() => {
if (!eventSequence || eventSequence.length === 0 || !isWithinEventRange) {
return 0;
} }
// If an event has not ended yet, fall back to last timestamp in eventSequence
const currentTime = effectiveTime ?? 0; let eventEnd = event.end_time;
if (eventEnd == null && eventSequence && eventSequence.length > 0) {
// Find which events have been passed const last = eventSequence[eventSequence.length - 1];
let lastPassedIndex = -1; if (last && last.timestamp !== undefined) {
for (let i = 0; i < eventSequence.length; i++) { eventEnd = last.timestamp;
if (currentTime >= (eventSequence[i].timestamp ?? 0)) {
lastPassedIndex = i;
} else {
break;
} }
} }
// No events passed yet if (eventEnd == null) {
if (lastPassedIndex < 0) return 0; return false;
}
return effectiveTime >= event.start_time && effectiveTime <= eventEnd;
}, [effectiveTime, event.start_time, event.end_time, eventSequence]);
// All events passed // Dynamically compute pixel offsets so the timeline line starts at the
if (lastPassedIndex >= eventSequence.length - 1) return 100; // first row midpoint and ends at the last row midpoint. For accuracy,
// measure the center Y of each lifecycle row and interpolate the current
// effective time into a pixel position; then set the blue line height
// so it reaches the center dot at the same time the dot becomes active.
useEffect(() => {
if (!timelineContainerRef.current || !eventSequence) return;
// Calculate percentage based on item position, not time const containerRect = timelineContainerRef.current.getBoundingClientRect();
// Each item occupies an equal visual space regardless of time gaps const validRefs = rowRefs.current.filter((r) => r !== null);
const itemPercentage = 100 / (eventSequence.length - 1); if (validRefs.length === 0) return;
// Find progress between current and next event for smooth transition const centers = validRefs.map((n) => {
const currentEvent = eventSequence[lastPassedIndex]; const r = n.getBoundingClientRect();
const nextEvent = eventSequence[lastPassedIndex + 1]; return r.top + r.height / 2 - containerRect.top;
const currentTimestamp = currentEvent.timestamp ?? 0; });
const nextTimestamp = nextEvent.timestamp ?? 0;
// Calculate interpolation between the two events const topOffset = Math.max(0, centers[0]);
const timeBetween = nextTimestamp - currentTimestamp; const bottomOffset = Math.max(
const timeElapsed = currentTime - currentTimestamp; 0,
const interpolation = timeBetween > 0 ? timeElapsed / timeBetween : 0; containerRect.height - centers[centers.length - 1],
// Base position plus interpolated progress to next item
return Math.min(
100,
lastPassedIndex * itemPercentage + interpolation * itemPercentage,
); );
}, [eventSequence, effectiveTime, isWithinEventRange]);
const blueLineHeight = calculateLineHeight(); setLineTopOffsetPx(Math.round(topOffset));
setLineBottomOffsetPx(Math.round(bottomOffset));
const eff = effectiveTime ?? 0;
const timestamps = eventSequence.map((s) => s.timestamp ?? 0);
let pixelPos = centers[0];
if (eff <= timestamps[0]) {
pixelPos = centers[0];
} else if (eff >= timestamps[timestamps.length - 1]) {
pixelPos = centers[centers.length - 1];
} else {
for (let i = 0; i < timestamps.length - 1; i++) {
const t1 = timestamps[i];
const t2 = timestamps[i + 1];
if (eff >= t1 && eff <= t2) {
const ratio = t2 > t1 ? (eff - t1) / (t2 - t1) : 0;
pixelPos = centers[i] + ratio * (centers[i + 1] - centers[i]);
break;
}
}
}
const bluePx = Math.round(Math.max(0, pixelPos - topOffset));
setBlueLineHeightPx(bluePx);
}, [eventSequence, timelineSize.width, timelineSize.height, effectiveTime]);
const videoSource = useMemo(() => { const videoSource = useMemo(() => {
// event.start_time and event.end_time are in DETECT stream time // event.start_time and event.end_time are in DETECT stream time
@ -531,12 +553,21 @@ export function TrackingDetails({
{t("detail.noObjectDetailData", { ns: "views/events" })} {t("detail.noObjectDetailData", { ns: "views/events" })}
</div> </div>
) : ( ) : (
<div className="-pb-2 relative mx-0"> <div
<div className="absolute -top-2 bottom-8 left-6 z-0 w-0.5 -translate-x-1/2 bg-secondary-foreground" /> className="-pb-2 relative mx-0"
ref={timelineContainerRef}
>
<div
className="absolute -top-2 left-6 z-0 w-0.5 -translate-x-1/2 bg-secondary-foreground"
style={{ bottom: lineBottomOffsetPx }}
/>
{isWithinEventRange && ( {isWithinEventRange && (
<div <div
className="absolute left-6 top-2 z-[5] max-h-[calc(100%-3rem)] w-0.5 -translate-x-1/2 bg-selected transition-all duration-300" className="absolute left-6 z-[5] w-0.5 -translate-x-1/2 bg-selected transition-all duration-300"
style={{ height: `${blueLineHeight}%` }} style={{
top: `${lineTopOffsetPx}px`,
height: `${blueLineHeightPx}px`,
}}
/> />
)} )}
<div className="space-y-2"> <div className="space-y-2">
@ -589,20 +620,26 @@ export function TrackingDetails({
: undefined; : undefined;
return ( return (
<LifecycleIconRow <div
key={`${item.timestamp}-${item.source_id ?? ""}-${idx}`} key={`${item.timestamp}-${item.source_id ?? ""}-${idx}`}
item={item} ref={(el) => {
isActive={isActive} rowRefs.current[idx] = el;
formattedEventTimestamp={formattedEventTimestamp} }}
ratio={ratio} >
areaPx={areaPx} <LifecycleIconRow
areaPct={areaPct} item={item}
onClick={() => handleLifecycleClick(item)} isActive={isActive}
setSelectedZone={setSelectedZone} formattedEventTimestamp={formattedEventTimestamp}
getZoneColor={getZoneColor} ratio={ratio}
effectiveTime={effectiveTime} areaPx={areaPx}
isTimelineActive={isWithinEventRange} areaPct={areaPct}
/> onClick={() => handleLifecycleClick(item)}
setSelectedZone={setSelectedZone}
getZoneColor={getZoneColor}
effectiveTime={effectiveTime}
isTimelineActive={isWithinEventRange}
/>
</div>
); );
})} })}
</div> </div>

View File

@ -318,6 +318,7 @@ export default function HlsVideoPlayer({
{isDetailMode && {isDetailMode &&
camera && camera &&
currentTime && currentTime &&
loadedMetadata &&
videoDimensions.width > 0 && videoDimensions.width > 0 &&
videoDimensions.height > 0 && ( videoDimensions.height > 0 && (
<div className="absolute z-50 size-full"> <div className="absolute z-50 size-full">

View File

@ -15,6 +15,7 @@ import {
ReviewSummary, ReviewSummary,
SegmentedReviewData, SegmentedReviewData,
} from "@/types/review"; } from "@/types/review";
import { TimelineType } from "@/types/timeline";
import { import {
getBeginningOfDayTimestamp, getBeginningOfDayTimestamp,
getEndOfDayTimestamp, getEndOfDayTimestamp,
@ -49,6 +50,16 @@ export default function Events() {
false, false,
); );
const [notificationTab, setNotificationTab] =
useState<TimelineType>("timeline");
useSearchEffect("tab", (tab: string) => {
if (tab === "timeline" || tab === "events" || tab === "detail") {
setNotificationTab(tab as TimelineType);
}
return true;
});
useSearchEffect("id", (reviewId: string) => { useSearchEffect("id", (reviewId: string) => {
axios axios
.get(`review/${reviewId}`) .get(`review/${reviewId}`)
@ -66,7 +77,7 @@ export default function Events() {
camera: resp.data.camera, camera: resp.data.camera,
startTime, startTime,
severity: resp.data.severity, severity: resp.data.severity,
timelineType: "detail", timelineType: notificationTab,
}, },
true, true,
); );

View File

@ -1,4 +1,5 @@
import { ReviewSeverity } from "./review"; import { ReviewSeverity } from "./review";
import { TimelineType } from "./timeline";
export type Recording = { export type Recording = {
id: string; id: string;
@ -37,7 +38,7 @@ export type RecordingStartingPoint = {
camera: string; camera: string;
startTime: number; startTime: number;
severity: ReviewSeverity; severity: ReviewSeverity;
timelineType?: "timeline" | "events" | "detail"; timelineType?: TimelineType;
}; };
export type RecordingPlayerError = "stalled" | "startup"; export type RecordingPlayerError = "stalled" | "startup";

View File

@ -16,7 +16,6 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FaFolderPlus } from "react-icons/fa"; import { FaFolderPlus } from "react-icons/fa";
import { MdModelTraining } from "react-icons/md"; import { MdModelTraining } from "react-icons/md";
import { LuPencil, LuTrash2 } from "react-icons/lu";
import { FiMoreVertical } from "react-icons/fi"; import { FiMoreVertical } from "react-icons/fi";
import useSWR from "swr"; import useSWR from "swr";
import Heading from "@/components/ui/heading"; import Heading from "@/components/ui/heading";
@ -352,11 +351,9 @@ function ModelCard({ config, onClick, onUpdate, onDelete }: ModelCardProps) {
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<DropdownMenuItem onClick={handleEditClick}> <DropdownMenuItem onClick={handleEditClick}>
<LuPencil className="mr-2 size-4" />
<span>{t("button.edit", { ns: "common" })}</span> <span>{t("button.edit", { ns: "common" })}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={handleDeleteClick}> <DropdownMenuItem onClick={handleDeleteClick}>
<LuTrash2 className="mr-2 size-4" />
<span>{t("button.delete", { ns: "common" })}</span> <span>{t("button.delete", { ns: "common" })}</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@ -799,7 +799,7 @@ function DetectionReview({
(itemsToReview ?? 0) > 0 && ( (itemsToReview ?? 0) > 0 && (
<div className="col-span-full flex items-center justify-center"> <div className="col-span-full flex items-center justify-center">
<Button <Button
className="text-white" className="text-balance text-white"
aria-label={t("markTheseItemsAsReviewed")} aria-label={t("markTheseItemsAsReviewed")}
variant="select" variant="select"
onClick={() => { onClick={() => {

View File

@ -850,6 +850,29 @@ function FrigateCameraFeatures({
} }
}, [activeToastId, t]); }, [activeToastId, t]);
const endEventViaBeacon = useCallback(() => {
if (!recordingEventIdRef.current) return;
const url = `${window.location.origin}/api/events/${recordingEventIdRef.current}/end`;
const payload = JSON.stringify({
end_time: Math.ceil(Date.now() / 1000),
});
// this needs to be a synchronous XMLHttpRequest to guarantee the PUT
// reaches the server before the browser kills the page
const xhr = new XMLHttpRequest();
try {
xhr.open("PUT", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("X-CSRF-TOKEN", "1");
xhr.setRequestHeader("X-CACHE-BYPASS", "1");
xhr.withCredentials = true;
xhr.send(payload);
} catch (e) {
// Silently ignore errors during unload
}
}, []);
const handleEventButtonClick = useCallback(() => { const handleEventButtonClick = useCallback(() => {
if (isRecording) { if (isRecording) {
endEvent(); endEvent();
@ -887,8 +910,19 @@ function FrigateCameraFeatures({
}, [camera.name, isRestreamed, preferredLiveMode, t]); }, [camera.name, isRestreamed, preferredLiveMode, t]);
useEffect(() => { useEffect(() => {
// Handle page unload/close (browser close, tab close, refresh, navigation to external site)
const handleBeforeUnload = () => {
if (recordingEventIdRef.current) {
endEventViaBeacon();
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
// ensure manual event is stopped when component unmounts // ensure manual event is stopped when component unmounts
return () => { return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
if (recordingEventIdRef.current) { if (recordingEventIdRef.current) {
endEvent(); endEvent();
} }

View File

@ -201,12 +201,17 @@ export default function TriggerView({
.then((configResponse) => { .then((configResponse) => {
if (configResponse.status === 200) { if (configResponse.status === 200) {
updateConfig(); updateConfig();
const displayName =
friendly_name && friendly_name !== ""
? `${friendly_name} (${name})`
: name;
toast.success( toast.success(
t( t(
isEdit isEdit
? "triggers.toast.success.updateTrigger" ? "triggers.toast.success.updateTrigger"
: "triggers.toast.success.createTrigger", : "triggers.toast.success.createTrigger",
{ name }, { name: displayName },
), ),
{ position: "top-center" }, { position: "top-center" },
); );
@ -351,8 +356,19 @@ export default function TriggerView({
.then((configResponse) => { .then((configResponse) => {
if (configResponse.status === 200) { if (configResponse.status === 200) {
updateConfig(); updateConfig();
const friendly =
config?.cameras?.[selectedCamera]?.semantic_search
?.triggers?.[name]?.friendly_name;
const displayName =
friendly && friendly !== ""
? `${friendly} (${name})`
: name;
toast.success( toast.success(
t("triggers.toast.success.deleteTrigger", { name }), t("triggers.toast.success.deleteTrigger", {
name: displayName,
}),
{ {
position: "top-center", position: "top-center",
}, },
@ -381,7 +397,7 @@ export default function TriggerView({
setIsLoading(false); setIsLoading(false);
}); });
}, },
[t, updateConfig, selectedCamera, setUnsavedChanges], [t, updateConfig, selectedCamera, setUnsavedChanges, config],
); );
useEffect(() => { useEffect(() => {
@ -843,7 +859,14 @@ export default function TriggerView({
/> />
<DeleteTriggerDialog <DeleteTriggerDialog
show={showDelete} show={showDelete}
triggerName={selectedTrigger?.name ?? ""} triggerName={
selectedTrigger
? selectedTrigger.friendly_name &&
selectedTrigger.friendly_name !== ""
? `${selectedTrigger.friendly_name} (${selectedTrigger.name})`
: selectedTrigger.name
: ""
}
isLoading={isLoading} isLoading={isLoading}
onCancel={() => { onCancel={() => {
setShowDelete(false); setShowDelete(false);

View File

@ -72,8 +72,7 @@ export default function StorageMetrics({
const earliestDate = useMemo(() => { const earliestDate = useMemo(() => {
const keys = Object.keys(recordingsSummary || {}); const keys = Object.keys(recordingsSummary || {});
return keys.length return keys.length
? new TZDate(keys[keys.length - 1] + "T00:00:00", timezone).getTime() / ? new TZDate(keys[0] + "T00:00:00", timezone).getTime() / 1000
1000
: null; : null;
}, [recordingsSummary, timezone]); }, [recordingsSummary, timezone]);