Compare commits

...
Author SHA1 Message Date
f141b585bc Translated using Weblate (Cantonese (Traditional Han script))
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (182 of 182 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: beginner2047 <leoywng44@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/yue_Hant/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
9de7f80727 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (50 of 50 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/components-camera/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-live/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/nb_NO/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
2101b1f91d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (182 of 182 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/components-camera/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-search/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/zh_Hans/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
2b5a30e23b Translated using Weblate (Urdu)
Currently translated at 6.1% (7 of 114 strings)

Translated using Weblate (Urdu)

Currently translated at 1.9% (7 of 352 strings)

Translated using Weblate (Urdu)

Currently translated at 14.5% (7 of 48 strings)

Translated using Weblate (Urdu)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Urdu)

Currently translated at 8.7% (7 of 80 strings)

Translated using Weblate (Urdu)

Currently translated at 11.2% (7 of 62 strings)

Translated using Weblate (Urdu)

Currently translated at 6.0% (7 of 115 strings)

Translated using Weblate (Urdu)

Currently translated at 28.0% (7 of 25 strings)

Translated using Weblate (Urdu)

Currently translated at 15.2% (7 of 46 strings)

Translated using Weblate (Urdu)

Currently translated at 5.9% (7 of 118 strings)

Translated using Weblate (Urdu)

Currently translated at 77.7% (7 of 9 strings)

Translated using Weblate (Urdu)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Urdu)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Urdu)

Currently translated at 33.3% (8 of 24 strings)

Translated using Weblate (Urdu)

Currently translated at 77.7% (7 of 9 strings)

Translated using Weblate (Urdu)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Urdu)

Currently translated at 12.1% (8 of 66 strings)

Translated using Weblate (Urdu)

Currently translated at 16.0% (8 of 50 strings)

Translated using Weblate (Urdu)

Currently translated at 16.4% (30 of 182 strings)

Translated using Weblate (Urdu)

Currently translated at 16.4% (30 of 182 strings)

Translated using Weblate (Urdu)

Currently translated at 8.4% (36 of 427 strings)

Translated using Weblate (Urdu)

Currently translated at 8.4% (36 of 427 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ryan Alvi <ryanalvi2010@gmail.com>
Co-authored-by: Uzair Ali <uzairali001@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ur/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-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-05-30 21:34:51 -06:00
dd6114586c Translated using Weblate (Slovenian)
Currently translated at 2.5% (9 of 352 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Slovenian)

Currently translated at 16.1% (10 of 62 strings)

Translated using Weblate (Slovenian)

Currently translated at 18.0% (9 of 50 strings)

Translated using Weblate (Slovenian)

Currently translated at 8.2% (15 of 182 strings)

Translated using Weblate (Slovenian)

Currently translated at 5.2% (6 of 114 strings)

Translated using Weblate (Slovenian)

Currently translated at 1.7% (6 of 352 strings)

Translated using Weblate (Slovenian)

Currently translated at 14.5% (7 of 48 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Slovenian)

Currently translated at 8.7% (7 of 80 strings)

Translated using Weblate (Slovenian)

Currently translated at 9.6% (6 of 62 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Slovenian)

Currently translated at 6.9% (8 of 115 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Slovenian)

Currently translated at 13.6% (9 of 66 strings)

Translated using Weblate (Slovenian)

Currently translated at 16.0% (8 of 50 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Slovenian)

Currently translated at 4.3% (8 of 182 strings)

Translated using Weblate (Slovenian)

Currently translated at 2.6% (3 of 114 strings)

Translated using Weblate (Slovenian)

Currently translated at 0.8% (3 of 352 strings)

Translated using Weblate (Slovenian)

Currently translated at 8.3% (4 of 48 strings)

Translated using Weblate (Slovenian)

Currently translated at 66.6% (4 of 6 strings)

Translated using Weblate (Slovenian)

Currently translated at 5.0% (4 of 80 strings)

Translated using Weblate (Slovenian)

Currently translated at 6.4% (4 of 62 strings)

Translated using Weblate (Slovenian)

Currently translated at 44.4% (4 of 9 strings)

Translated using Weblate (Slovenian)

Currently translated at 3.4% (4 of 115 strings)

Translated using Weblate (Slovenian)

Currently translated at 20.0% (5 of 25 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Slovenian)

Currently translated at 7.5% (5 of 66 strings)

Translated using Weblate (Slovenian)

Currently translated at 10.0% (5 of 50 strings)

Translated using Weblate (Slovenian)

Currently translated at 10.8% (5 of 46 strings)

Translated using Weblate (Slovenian)

Currently translated at 55.5% (5 of 9 strings)

Translated using Weblate (Slovenian)

Currently translated at 2.7% (5 of 182 strings)

Translated using Weblate (Slovenian)

Currently translated at 12.8% (55 of 427 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nik Grebovšek <nikigre@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sl/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-configeditor
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-05-30 21:34:51 -06:00
d508a8b5fe Translated using Weblate (Finnish)
Currently translated at 3.5% (4 of 114 strings)

Translated using Weblate (Finnish)

Currently translated at 2.0% (1 of 48 strings)

Translated using Weblate (Finnish)

Currently translated at 1.6% (1 of 62 strings)

Translated using Weblate (Finnish)

Currently translated at 22.2% (2 of 9 strings)

Translated using Weblate (Finnish)

Currently translated at 0.8% (1 of 115 strings)

Translated using Weblate (Finnish)

Currently translated at 4.1% (1 of 24 strings)

Translated using Weblate (Finnish)

Currently translated at 12.5% (1 of 8 strings)

Translated using Weblate (Finnish)

Currently translated at 12.0% (3 of 25 strings)

Translated using Weblate (Finnish)

Currently translated at 50.0% (1 of 2 strings)

Translated using Weblate (Finnish)

Currently translated at 50.0% (1 of 2 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Sampo Koskenohi <sampo.koskenohi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fi/
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
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-search
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
feb40e9d05 Translated using Weblate (Persian)
Currently translated at 3.5% (15 of 427 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: kayvan Azarbarzin <kayvan.azarbarzin@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/fa/
Translation: Frigate NVR/audio
2025-05-30 21:34:51 -06:00
2548af13b9 Translated using Weblate (Swedish)
Currently translated at 12.7% (45 of 352 strings)

Translated using Weblate (Swedish)

Currently translated at 37.4% (160 of 427 strings)

Translated using Weblate (Swedish)

Currently translated at 29.0% (18 of 62 strings)

Translated using Weblate (Swedish)

Currently translated at 6.9% (8 of 115 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Swedish)

Currently translated at 27.4% (17 of 62 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Swedish)

Currently translated at 37.4% (160 of 427 strings)

Translated using Weblate (Swedish)

Currently translated at 37.4% (160 of 427 strings)

Translated using Weblate (Swedish)

Currently translated at 4.4% (5 of 113 strings)

Translated using Weblate (Swedish)

Currently translated at 1.7% (6 of 340 strings)

Translated using Weblate (Swedish)

Currently translated at 28.7% (23 of 80 strings)

Translated using Weblate (Swedish)

Currently translated at 43.9% (29 of 66 strings)

Translated using Weblate (Swedish)

Currently translated at 13.9% (6 of 43 strings)

Translated using Weblate (Swedish)

Currently translated at 34.1% (146 of 427 strings)

Co-authored-by: Fredrik B <fredrik@brannvall.nu>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Tobias Hasson <tobbe@hansson.me>
Co-authored-by: TubalQ <TubalQayinn@protonmail.com>
Co-authored-by: jsm198 <jsm.persson@gmail.com>
Co-authored-by: pierrebengtsson <pierre.bengtsson@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-facelibrary/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sv/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
547c61f54c Translated using Weblate (French)
Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (French)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (French)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (French)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (French)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (French)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (French)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (French)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (French)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (French)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (French)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (French)

Currently translated at 98.8% (348 of 352 strings)

Translated using Weblate (French)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (French)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (French)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (French)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (French)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (French)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (French)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (French)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (French)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (French)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (French)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (French)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (French)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (French)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

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/components-camera/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-search/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fr/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
34e92ef4ae Translated using Weblate (Spanish)
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (66 of 66 strings)

Co-authored-by: Eduard Frigola <eduardfrigola@yahoo.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Santiago Burgues <santibur06@gmail.com>
Co-authored-by: jjavin <javiernovoa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/es/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
dbf440b0c7 Translated using Weblate (Dutch)
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (179 of 179 strings)

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/components-camera/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/nl/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
321fd136c0 Translated using Weblate (Arabic)
Currently translated at 13.5% (58 of 427 strings)

Co-authored-by: Hex <130850854+Hexer-7@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ar/
Translation: Frigate NVR/audio
2025-05-30 21:34:51 -06:00
c9708102a9 Translated using Weblate (Italian)
Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (179 of 179 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/common/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
85d8d7b136 Translated using Weblate (Polish)
Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (179 of 179 strings)

Co-authored-by: Damian Radecki <damianradecki97@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mateusz Kulis <kulis.matis@gmail.com>
Co-authored-by: Mateusz Paś <piciuok@gmail.com>
Co-authored-by: Patryk Smoliński <smolinski.patryk@mensa.org.pl>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/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/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pl/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
b0e6a64bd0 Translated using Weblate (Hebrew)
Currently translated at 11.1% (1 of 9 strings)

Translated using Weblate (Hebrew)

Currently translated at 16.6% (1 of 6 strings)

Translated using Weblate (Hebrew)

Currently translated at 2.0% (1 of 50 strings)

Translated using Weblate (Hebrew)

Currently translated at 1.6% (1 of 62 strings)

Translated using Weblate (Hebrew)

Currently translated at 2.1% (1 of 46 strings)

Translated using Weblate (Hebrew)

Currently translated at 2.0% (1 of 48 strings)

Translated using Weblate (Hebrew)

Currently translated at 11.1% (1 of 9 strings)

Translated using Weblate (Hebrew)

Currently translated at 50.0% (1 of 2 strings)

Translated using Weblate (Hebrew)

Currently translated at 4.0% (1 of 25 strings)

Translated using Weblate (Hebrew)

Currently translated at 12.5% (1 of 8 strings)

Translated using Weblate (Hebrew)

Currently translated at 1.5% (1 of 66 strings)

Translated using Weblate (Hebrew)

Currently translated at 0.8% (1 of 115 strings)

Translated using Weblate (Hebrew)

Currently translated at 0.8% (1 of 118 strings)

Translated using Weblate (Hebrew)

Currently translated at 50.0% (1 of 2 strings)

Translated using Weblate (Hebrew)

Currently translated at 4.1% (1 of 24 strings)

Translated using Weblate (Hebrew)

Currently translated at 7.7% (33 of 427 strings)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hebrew)

Co-authored-by: Dennis Zheleznyak <Dennis.zheleznyak@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Yechiel Ben David <yy.bendavid@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/he/
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-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-search
2025-05-30 21:34:51 -06:00
2af1d22643 Translated using Weblate (Portuguese)
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 55.6% (196 of 352 strings)

Translated using Weblate (Portuguese)

Currently translated at 69.0% (295 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 65.6% (231 of 352 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Portuguese)

Currently translated at 67.6% (289 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Portuguese)

Currently translated at 60.5% (213 of 352 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.4% (181 of 182 strings)

Translated using Weblate (Portuguese)

Currently translated at 53.1% (227 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 91.9% (57 of 62 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.3% (112 of 115 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Portuguese)

Currently translated at 46.6% (199 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Portuguese)

Currently translated at 46.3% (198 of 427 strings)

Co-authored-by: Daniel Barradas <daniel.barradas@gmail.com>
Co-authored-by: Filipe Ferreira <filipeferreira1705@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: P1LH4 <joao.calby@gmail.com>
Co-authored-by: danielsaul@msn.com <danielsaul@msn.com>
Co-authored-by: lbiscuola <lbiscuola@gmail.com>
Co-authored-by: piscinas <nuno.piscinas@me.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pt/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
c026060ff3 Translated using Weblate (Czech)
Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 74.7% (254 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 58.8% (200 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 49.4% (168 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 32.3% (110 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (43 of 43 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Czech)

Currently translated at 34.5% (39 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 1.1% (4 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 5.0% (4 of 80 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Czech)

Currently translated at 4.4% (5 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 6.0% (4 of 66 strings)

Translated using Weblate (Czech)

Currently translated at 8.0% (4 of 50 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Czech)

Currently translated at 96.0% (410 of 427 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Czech)

Currently translated at 95.7% (409 of 427 strings)

Translated using Weblate (Czech)

Currently translated at 31.8% (36 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 0.2% (1 of 340 strings)

Translated using Weblate (Czech)

Currently translated at 29.1% (14 of 48 strings)

Translated using Weblate (Czech)

Currently translated at 1.2% (1 of 80 strings)

Translated using Weblate (Czech)

Currently translated at 59.0% (36 of 61 strings)

Translated using Weblate (Czech)

Currently translated at 1.7% (2 of 113 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Czech)

Currently translated at 3.0% (2 of 66 strings)

Translated using Weblate (Czech)

Currently translated at 4.0% (2 of 50 strings)

Translated using Weblate (Czech)

Currently translated at 58.1% (25 of 43 strings)

Translated using Weblate (Czech)

Currently translated at 35.1% (63 of 179 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jakub <kincl.kuba@seznam.cz>
Co-authored-by: Martin Brož <code@martin-broz.cz>
Co-authored-by: Michal Basler <minecz.michal.basler@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/cs/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
8a117de87e Translated using Weblate (Catalan)
Currently translated at 78.2% (334 of 427 strings)

Translated using Weblate (Catalan)

Currently translated at 22.8% (26 of 114 strings)

Translated using Weblate (Catalan)

Currently translated at 7.1% (25 of 352 strings)

Translated using Weblate (Catalan)

Currently translated at 56.2% (27 of 48 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Catalan)

Currently translated at 33.7% (27 of 80 strings)

Translated using Weblate (Catalan)

Currently translated at 43.5% (27 of 62 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Catalan)

Currently translated at 24.3% (28 of 115 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Catalan)

Currently translated at 97.8% (45 of 46 strings)

Translated using Weblate (Catalan)

Currently translated at 18.5% (79 of 427 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Catalan)

Currently translated at 16.6% (1 of 6 strings)

Translated using Weblate (Catalan)

Currently translated at 40.9% (27 of 66 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Catalan)

Currently translated at 78.0% (39 of 50 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Catalan)

Currently translated at 1.6% (1 of 62 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (43 of 43 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Catalan)

Currently translated at 84.7% (100 of 118 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Catalan)

Currently translated at 11.7% (50 of 427 strings)

Co-authored-by: Eduard Frigola <eduardfrigola@yahoo.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-exports/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ca/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-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-05-30 21:34:51 -06:00
a43f87c477 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (66 of 66 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-exports/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/uk/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
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-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
15c7a4074b Translated using Weblate (Romanian)
Currently translated at 26.5% (30 of 113 strings)

Translated using Weblate (Romanian)

Currently translated at 1.4% (5 of 340 strings)

Translated using Weblate (Romanian)

Currently translated at 10.4% (5 of 48 strings)

Translated using Weblate (Romanian)

Currently translated at 6.2% (5 of 80 strings)

Translated using Weblate (Romanian)

Currently translated at 87.5% (21 of 24 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Romanian)

Currently translated at 2.7% (5 of 179 strings)

Translated using Weblate (Romanian)

Currently translated at 69.3% (296 of 427 strings)

Co-authored-by: Bogdan Turlea <bogdan.a.turlea@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ro/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
ca650a29a1 Translated using Weblate (Russian)
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Russian)

Currently translated at 90.0% (317 of 352 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (113 of 113 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Артём Владимиров <artyomka71@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-settings/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ru/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
0896df6500 Translated using Weblate (German)
Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (German)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (German)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (German)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (German)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (German)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (German)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (German)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (German)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (German)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (German)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (German)

Currently translated at 100.0% (342 of 342 strings)

Translated using Weblate (German)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (German)

Currently translated at 100.0% (61 of 61 strings)

Translated using Weblate (German)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (German)

Currently translated at 100.0% (179 of 179 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Thiemo <Firewolf.3.7.1987@gmail.com>
Co-authored-by: Viktor Stier <viktor-stier@gmx.de>
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-camera/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/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-live/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/de/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
01998dfb70 Translated using Weblate (Thai)
Currently translated at 28.6% (101 of 352 strings)

Translated using Weblate (Thai)

Currently translated at 79.1% (38 of 48 strings)

Translated using Weblate (Thai)

Currently translated at 6.9% (8 of 115 strings)

Translated using Weblate (Thai)

Currently translated at 66.6% (44 of 66 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Thai)

Currently translated at 98.3% (116 of 118 strings)

Translated using Weblate (Thai)

Currently translated at 9.6% (11 of 114 strings)

Translated using Weblate (Thai)

Currently translated at 25.8% (91 of 352 strings)

Translated using Weblate (Thai)

Currently translated at 6.2% (3 of 48 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Thai)

Currently translated at 15.0% (12 of 80 strings)

Translated using Weblate (Thai)

Currently translated at 32.2% (20 of 62 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Thai)

Currently translated at 1.5% (1 of 66 strings)

Translated using Weblate (Thai)

Currently translated at 10.0% (5 of 50 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Thai)

Currently translated at 89.8% (106 of 118 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (181 of 181 strings)

Translated using Weblate (Thai)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Thai)

Currently translated at 68.5% (124 of 181 strings)

Translated using Weblate (Thai)

Currently translated at 0.7% (3 of 427 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 15.4% (28 of 181 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 14.3% (26 of 181 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 13.8% (25 of 181 strings)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 13.2% (24 of 181 strings)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 12.7% (23 of 181 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 12.1% (22 of 181 strings)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 11.6% (21 of 181 strings)

Added translation using Weblate (Thai)

Update translation files

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

Translated using Weblate (Thai)

Currently translated at 11.0% (20 of 181 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 8.2% (15 of 181 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 7.7% (14 of 181 strings)

Added translation using Weblate (Thai)

Translated using Weblate (Thai)

Currently translated at 6.6% (12 of 181 strings)

Added translation using Weblate (Thai)

Added translation using Weblate (Thai)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Phongsakorn Wisetthon <boy1556@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/th/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/th/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-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-05-30 21:34:51 -06:00
cb1cdcc814 Translated using Weblate (Turkish)
Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (352 of 352 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (115 of 115 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Turkish)

Currently translated at 91.8% (56 of 61 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (66 of 66 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (179 of 179 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kadir ALTIN <kadir.altin@outlook.com.tr>
Co-authored-by: pcislocked <git@pcislocked.net>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/tr/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-30 21:34:51 -06:00
Josh HawkinsandGitHub 63473b0b82 Fixes (#18485) 2025-05-30 21:32:22 -06:00
maksz42andGitHub 2ada0d22cb Add Periscope to third party extensions (#18480) 2025-05-30 05:32:38 -06:00
Felipe SantosandGitHub 81f5f362a9 Fix healthcheck on stopping (#18476) 2025-05-29 20:58:31 -06:00
Nicolas MowenandGitHub 27914fc3b8 Don't run bird classification if model is not downloaded yet (#18474) 2025-05-29 16:15:25 -05:00
Josh HawkinsandGitHub c11ca42fb5 Logging bugfix (#18465)
* use mp Manager to handle logging queues

A Python bug (https://github.com/python/cpython/issues/91555) was preventing logs from the embeddings maintainer process from printing. The bug is fixed in Python 3.14, but a viable workaround is to use the multiprocessing Manager, which better manages mp queues and causes the logging to work correctly.

* consolidate

* fix typing
2025-05-29 09:02:17 -06:00
GuoQing LiuandGitHub 9b7d4d0a8f feat: document locale url (#18409)
* feat: document locale url

* fix: remove debug code

* fix readme_cn line

* feat: add menu document locale url
2025-05-28 06:10:45 -06:00
cbdac9ece5 Fixes (#18379)
* Don't return weighted name if it has the same number of results

* Remove link to incorrect format yolov9 models

* Fix command list from appearing when other inputs are focused

the description box in the tracked object details pane was causing the command input list to show when focused.

* clarify face docs

* Add note about python yolov9 export

* Check if hailort thread is still alive when timeout error is run into

* Reduce inference timeout

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2025-05-27 10:25:34 -05:00
Thomas A.andGitHub 63f9689b0e Improve documentation for YOLO-NAS notebook (#18381)
* Add readme file for notebooks
It took me quite some time to find out this workbook opens in Google Colab, so i though it may help someone else to have this visible in the folder aswell.

* Add explicit link to Google Colab for YOLO-NAS
2025-05-24 09:42:32 -06:00
Nicolas MowenandGitHub 6969a7fd47 Cleanup builds (#18372)
* Cleanup builds

* Uninstall onnxurntime not openvino
2025-05-23 21:02:24 -05:00
Nicolas MowenandGitHub 8274b733da Fix Jetson ONNXRuntime (#18370)
* Correctly save jetson wheel

* Temp docker build
2025-05-23 15:27:37 -05:00
858c1241db Fixes (#18354)
* Rename classification to enrichments

* Clean up config updating checks

* Add Portugese

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-22 18:16:35 -06:00
c16e536b46 Fixes (#18338)
* improve spacing of face selection in mobile drawer

* fix spacing

* sort face names alphabetically

* Improve face selection dialog

* Use a state to track when face image loads

The naturalWidth and naturalHeight will always be 0 until the image loads. So we use onLoad and a state to track loading and then calculate the area after it has loaded

* Verify that a camera only tracks objects that are possible to track

* Fix test

* genai docs tweak

* Disable openvino model cache

* Clenaup

* Sanitize floats for estimated speed and angle

Users can configure speed zones in such a way that velocity estimates from Norfair cause a value of inf to be stored as an estimated speed. FastAPI doesn't serialize inf as a float, so trying to return this value would result in an API error. Sanitizing the value before storing should correct this.

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-22 09:38:14 -06:00
Josh HawkinsandGitHub 49c6073de6 Add ability to specify separator used in proxy headers (#18336) 2025-05-21 06:02:13 -06:00
Luca PaoliniandGitHub 2a8b62acb1 Disable autoCorrect, autoCapitalize and spellCheck on username input. (#18333) 2025-05-21 06:10:13 -05:00
37d40558b7 Fixes (#18319)
* Add Thai (still need to merge weblate)

* Apply attribute logic to all label types

* Fix area check

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-20 16:47:05 -06:00
Josh HawkinsandGitHub afe513336c fix missing i18n keys (#18309) 2025-05-19 16:45:02 -05:00
8a143b4284 Fixes (#18304)
* fix recordings check

* Only calculate inpoint offset for beginning of hour segment

* Cleanup

* Fix seeking

* add Czech

* explore i18n fix

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-19 14:43:22 -06:00
Nicolas MowenandGitHub 717517aeb5 Misc fixes (#18289)
* Fix key not moved

* Account for HLS start offset when seeking and calculating update time
2025-05-18 18:23:01 -05:00
Felipe SantosandGitHub f4d5ebf4db Update s6-overlay from 3.1.5.0 to 3.2.1.0 (#18276) 2025-05-18 07:21:26 -05:00
Nicolas MowenandGitHub b18572a3b6 Min face configuration option (#18284)
* Add option to specify minimum face attempts for an object

* Set upper bound
2025-05-18 07:20:59 -05:00
Sean KellyandGitHub b10615aef1 Fix dead link (#18279) 2025-05-17 18:16:24 -06:00
Josh HawkinsandGitHub 5d13925d2b Fixes (#18275) 2025-05-17 16:11:19 -06:00
ebae6cb1ed Fixes (#18262)
* Don't use timezone in export dialog timestamps

Revert an unnecessary change made in https://github.com/blakeblackshear/frigate/pull/18257

* Ensure notifications register button is only disabled when both all cameras and every individual camera is disabled

* Send test notification if any cameras are enabled

* clarify docs about disabling cameras

* fix crash in autotracking zoom

* clean up

* masks and zones i18n fixes

* Check if camera is enabled in config

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-16 16:37:25 -06:00
Josh HawkinsandGitHub 2f9b373c1a Improve timezone handling (#18257)
* Ensure review activity calendar uses correct timezone

react-day-picker 9.x adds a timeZone prop and a TZDate() handler to show the calendar based on a timezone and better handle dates passed to it in timezones

* Ensure calendar range uses correct timezone

* clean up

* ensure range is timezone aware

* ensure export dates are timezone aware
2025-05-15 17:13:32 -05:00
f48356cbee Fixes (#18245)
* Only check if an object is stationary to avoid mqtt snapshot

* docs heading tweak

* Add more API descriptions

* Add missing lib for new rocm onnxruntime whl

* Update inference times to reflect better rocm performance

* Cleanup resetting tracked object activity

* remove print

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2025-05-15 17:13:18 -05:00
Josh HawkinsandGitHub 3538a1df3d Update react-day-picker to 9.x (#18247)
* update react-day-picker to 9.x

* fix shadcn calendar component

* update review activity custom component for rdp

* fix colors
2025-05-15 10:10:14 -06:00
Josh HawkinsandGitHub c15fbee537 Fixes (#18235)
* fix face library i18n

* Docs updates
2025-05-14 20:54:15 -06:00
jrhelbertandGitHub 7612c3ece0 Remove hardcoded usage of /dev/dri/card0 in call to intel_gpu_top (#18236)
Not everyone is using /dev/dri/card0. I recommend switching to these more generic parameters for intel_gpu_top_command (line 307 in the 0.16.0-beta2 version of services.py):
intel_gpu_top_command += ["-d", "sriov"]
2025-05-14 20:54:06 -06:00
d3d05fa397 Fixes (#18220)
* Add option to not trim clip

* Improve API

* Update snapshot for new best objects

* Fix missing strings

* Convert to separate key

* Always include bounding box on snapshots

* improve autotracking relative zooming time calculation

* update proxy docs to note the need for comma separated header roles

* Add count translation

* tracked object lifecycle i18n fix

* update speed estimation docs

* clarity

* Re-initialize onvif information when toggling camera on live view

* Move time ago to card info and add face area

* Clarify face recognition docs

* Increase minimum face recognition area

* use clipFrom to in vod module endpoint to start at the correct time

* Cleanup media api

* Don't change duration

* Use search detail dialog for face library

* Move to segment based

* Cleanup

* Add back duration modification

* clean up docs

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2025-05-14 16:44:06 -06:00
1fa7ce5486 Translated using Weblate (Cantonese (Traditional Han script))
Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 100.0% (53 of 53 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: beginner2047 <leoywng44@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/yue_Hant/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
bc74ba5b35 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (53 of 53 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/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/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/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
caebc583da Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 90.0% (54 of 60 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 95.8% (23 of 24 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 98.1% (52 of 53 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/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-system/zh_Hans/
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
57e933e68a Added translation using Weblate (Urdu)
Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Update translation files

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

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Update translation files

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

Translated using Weblate (Urdu)

Currently translated at 4.9% (21 of 427 strings)

Translated using Weblate (Urdu)

Currently translated at 4.6% (20 of 427 strings)

Update translation files

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

Translated using Weblate (Urdu)

Currently translated at 4.2% (18 of 427 strings)

Translated using Weblate (Urdu)

Currently translated at 2.5% (11 of 427 strings)

Added translation using Weblate (Urdu)

Translated using Weblate (Urdu)

Currently translated at 2.1% (9 of 427 strings)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Translated using Weblate (Urdu)

Currently translated at 1.8% (8 of 427 strings)

Added translation using Weblate (Urdu)

Translated using Weblate (Urdu)

Currently translated at 1.1% (5 of 427 strings)

Added translation using Weblate (Urdu)

Translated using Weblate (Urdu)

Currently translated at 0.9% (4 of 427 strings)

Added translation using Weblate (Urdu)

Added translation using Weblate (Urdu)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: yousaf465 <yousaf465@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ur/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
2025-05-13 08:30:20 -06:00
c104913f81 Translated using Weblate (Slovenian)
Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Slovenian)

Currently translated at 12.4% (53 of 427 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mitja Ahlin <mitja@ahlin.si>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/sl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sl/
Translation: Frigate NVR/audio
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
2025-05-13 08:30:20 -06:00
d38a5659a9 Added translation using Weblate (Persian)
Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Added translation using Weblate (Persian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Omid Nateghi <MasterDeveloper313@gmail.com>
2025-05-13 08:30:20 -06:00
0c54d2c47c Translated using Weblate (French)
Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (French)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (French)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (French)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (French)

Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (French)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (French)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (French)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (French)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (French)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (French)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (French)

Currently translated at 100.0% (177 of 177 strings)

Translated using Weblate (French)

Currently translated at 100.0% (177 of 177 strings)

Translated using Weblate (French)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (French)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (French)

Currently translated at 100.0% (53 of 53 strings)

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/components-dialog/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-search/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fr/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
4bb52f0357 Translated using Weblate (Spanish)
Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (53 of 53 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jjavin <javiernovoa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/es/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
c1594b2482 Translated using Weblate (Dutch)
Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (427 of 427 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (53 of 53 strings)

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/audio/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/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-search/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/
Translation: Frigate NVR/audio
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
41919f9648 Translated using Weblate (Indonesian)
Currently translated at 5.3% (6 of 113 strings)

Translated using Weblate (Indonesian)

Currently translated at 1.8% (6 of 333 strings)

Translated using Weblate (Indonesian)

Currently translated at 12.5% (6 of 48 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Indonesian)

Currently translated at 7.5% (6 of 80 strings)

Translated using Weblate (Indonesian)

Currently translated at 11.3% (6 of 53 strings)

Translated using Weblate (Indonesian)

Currently translated at 66.6% (6 of 9 strings)

Translated using Weblate (Indonesian)

Currently translated at 5.4% (6 of 110 strings)

Translated using Weblate (Indonesian)

Currently translated at 30.4% (7 of 23 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Indonesian)

Currently translated at 28.0% (7 of 25 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Indonesian)

Currently translated at 10.7% (7 of 65 strings)

Translated using Weblate (Indonesian)

Currently translated at 14.5% (7 of 48 strings)

Translated using Weblate (Indonesian)

Currently translated at 16.2% (7 of 43 strings)

Translated using Weblate (Indonesian)

Currently translated at 77.7% (7 of 9 strings)

Translated using Weblate (Indonesian)

Currently translated at 5.9% (7 of 118 strings)

Translated using Weblate (Indonesian)

Currently translated at 3.3% (6 of 177 strings)

Translated using Weblate (Indonesian)

Currently translated at 6.5% (28 of 427 strings)

Translated using Weblate (Indonesian)

Currently translated at 3.5% (4 of 113 strings)

Translated using Weblate (Indonesian)

Currently translated at 1.2% (4 of 333 strings)

Translated using Weblate (Indonesian)

Currently translated at 8.3% (4 of 48 strings)

Translated using Weblate (Indonesian)

Currently translated at 66.6% (4 of 6 strings)

Translated using Weblate (Indonesian)

Currently translated at 5.0% (4 of 80 strings)

Translated using Weblate (Indonesian)

Currently translated at 7.5% (4 of 53 strings)

Translated using Weblate (Indonesian)

Currently translated at 55.5% (5 of 9 strings)

Translated using Weblate (Indonesian)

Currently translated at 4.5% (5 of 110 strings)

Translated using Weblate (Indonesian)

Currently translated at 21.7% (5 of 23 strings)

Translated using Weblate (Indonesian)

Currently translated at 71.4% (5 of 7 strings)

Translated using Weblate (Indonesian)

Currently translated at 20.0% (5 of 25 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Indonesian)

Currently translated at 7.6% (5 of 65 strings)

Translated using Weblate (Indonesian)

Currently translated at 10.4% (5 of 48 strings)

Translated using Weblate (Indonesian)

Currently translated at 11.6% (5 of 43 strings)

Translated using Weblate (Indonesian)

Currently translated at 55.5% (5 of 9 strings)

Translated using Weblate (Indonesian)

Currently translated at 4.2% (5 of 118 strings)

Translated using Weblate (Indonesian)

Currently translated at 2.2% (4 of 177 strings)

Translated using Weblate (Indonesian)

Currently translated at 6.0% (26 of 427 strings)

Co-authored-by: Catto <sisharyadi@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Viktor Stier <viktor-stier@gmx.de>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/id/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-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-05-13 08:30:20 -06:00
79e0fd1343 Translated using Weblate (Italian)
Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (177 of 177 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (53 of 53 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/common/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/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
b1b78feec2 Translated using Weblate (Polish)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (65 of 65 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (177 of 177 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mateusz Paś <piciuok@gmail.com>
Co-authored-by: Patryk Smoliński <smolinski.patryk@mensa.org.pl>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/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/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pl/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-filter
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
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
f27538607e Translated using Weblate (Hungarian)
Currently translated at 7.5% (25 of 333 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Pintér István <thestevepappa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/hu/
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
2fc70c62a5 Translated using Weblate (Portuguese)
Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Portuguese)

Currently translated at 62.7% (209 of 333 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (65 of 65 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (177 of 177 strings)

Translated using Weblate (Portuguese)

Currently translated at 81.4% (92 of 113 strings)

Translated using Weblate (Portuguese)

Currently translated at 57.0% (190 of 333 strings)

Translated using Weblate (Portuguese)

Currently translated at 85.4% (41 of 48 strings)

Translated using Weblate (Portuguese)

Currently translated at 86.7% (46 of 53 strings)

Translated using Weblate (Portuguese)

Currently translated at 88.2% (98 of 111 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Portuguese)

Currently translated at 95.8% (46 of 48 strings)

Translated using Weblate (Portuguese)

Currently translated at 76.2% (135 of 177 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Portuguese)

Currently translated at 44.4% (190 of 427 strings)

Translated using Weblate (Portuguese)

Currently translated at 67.7% (120 of 177 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Interlig <haylanzinho@gmail.com>
Co-authored-by: Ivan Martins Pereira <vodikus@users.noreply.hosted.weblate.org>
Co-authored-by: Peter Williams BA Hons MBCS <hello@p-williams.com>
Co-authored-by: Артём Владимиров <artyomka71@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pt/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
a3890c0304 Added translation using Weblate (Japanese)
Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Update translation files

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

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Added translation using Weblate (Japanese)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Tomoya Hashimoto <tomoya.hashimoto@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translation: Frigate NVR/common
2025-05-13 08:30:20 -06:00
953d7d0428 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (43 of 43 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (177 of 177 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Sotski Eugene <jekakmail@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/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-live/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/uk/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-icons
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
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
1a0e2abb78 Translated using Weblate (Bulgarian)
Currently translated at 1.1% (2 of 177 strings)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Bulgarian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: elgratea <weblate@fastmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/bg/
Translation: Frigate NVR/common
2025-05-13 08:30:20 -06:00
5b0c7694d3 Translated using Weblate (Russian)
Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (179 of 179 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (65 of 65 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (177 of 177 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Артём Владимиров <artyomka71@gmail.com>
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/components-filter/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-search/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ru/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
2025-05-13 08:30:20 -06:00
9e0b0778f8 Translated using Weblate (German)
Currently translated at 100.0% (340 of 340 strings)

Translated using Weblate (German)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (German)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (German)

Currently translated at 100.0% (339 of 339 strings)

Translated using Weblate (German)

Currently translated at 100.0% (59 of 59 strings)

Translated using Weblate (German)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (German)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (German)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (German)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (German)

Currently translated at 86.7% (289 of 333 strings)

Translated using Weblate (German)

Currently translated at 86.7% (289 of 333 strings)

Translated using Weblate (German)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (German)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (German)

Currently translated at 100.0% (111 of 111 strings)

Translated using Weblate (German)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (German)

Currently translated at 100.0% (177 of 177 strings)

Translated using Weblate (German)

Currently translated at 71.7% (239 of 333 strings)

Translated using Weblate (German)

Currently translated at 66.9% (223 of 333 strings)

Translated using Weblate (German)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (German)

Currently translated at 100.0% (177 of 177 strings)

Translated using Weblate (German)

Currently translated at 43.8% (146 of 333 strings)

Translated using Weblate (German)

Currently translated at 97.3% (110 of 113 strings)

Translated using Weblate (German)

Currently translated at 43.5% (145 of 333 strings)

Translated using Weblate (German)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (German)

Currently translated at 39.6% (132 of 333 strings)

Translated using Weblate (German)

Currently translated at 39.6% (132 of 333 strings)

Translated using Weblate (German)

Currently translated at 97.9% (47 of 48 strings)

Translated using Weblate (German)

Currently translated at 100.0% (53 of 53 strings)

Co-authored-by: BOFH90 <michael@becker-lan.de>
Co-authored-by: Darkyputz <darkwing@gmx.li>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J <zippelman@web.de>
Co-authored-by: Mehmet Uyanik <met456@gmail.com>
Co-authored-by: Phil Jope <phil@jope.cloud>
Co-authored-by: Viktor Stier <viktor-stier@gmx.de>
Co-authored-by: engels0n <christian.engelbarts@outlook.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-configeditor/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/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-search/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/de/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
120fff31a7 Translated using Weblate (Turkish)
Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (24 of 24 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (113 of 113 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (333 of 333 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (177 of 177 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kemal <kemal@korkmazlar.nl>
Co-authored-by: pcislocked <git@pcislocked.net>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/tr/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-05-13 08:30:20 -06:00
Martin WeineltandGitHub 4d4d54d030 Fix various typing issues (#18187)
* Fix the `Any` typing hint treewide

There has been confusion between the Any type[1] and the any function[2]
in typing hints.

[1] https://docs.python.org/3/library/typing.html#typing.Any
[2] https://docs.python.org/3/library/functions.html#any

* Fix typing for various frame_shape members

Frame shapes are most likely defined by height and width, so a single int
cannot express that.

* Wrap gpu stats functions in Optional[]

These can return `None`, so they need to be `Type | None`, which is what
`Optional` expresses very nicely.

* Fix return type in get_latest_segment_datetime

Returns a datetime object, not an integer.

* Make the return type of FrameManager.write optional

This is necessary since the SharedMemoryFrameManager.write function can
return None.

* Fix total_seconds() return type in get_tz_modifiers

The function returns a float, not an int.

https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds

* Account for floating point results in to_relative_box

Because the function uses division the return types may either be int or
float.

* Resolve ruff deprecation warning

The config has been split into formatter and linter, and the global
options are deprecated.
2025-05-13 08:27:20 -06:00
2c9bfaa49c Fixes (#18176)
* Add camera name tooltip to previews in recording view

* Apply face area check to cv2 face detection

* Delete review thumbnails

* Don't import hailo until it is used

* Add comment

* Clean up camera name

* Filter out empty keys when updating yaml config

HA ingress seems to randomly add an equal sign to the PUT urls for updating the config from the UI. This fix prevents empty keys from being processed, but still allows empty values.

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-13 08:27:07 -06:00
f39ddbc00d Fixes (#18139)
* Catch error and show toast when failing to delete review items

* i18n keys

* add link to speed estimation docs in zone edit pane

* Implement reset of tracked object update for each camera

* Cleanup

* register mqtt callbacks for toggling alerts and detections

* clarify snapshots docs

* clarify semantic search reindexing

* add ukrainian

* adjust date granularity for last recording time

The api endpoint only returns granularity down to the day

* Add amd hardware

* fix crash in face library on initial start after enabling

* Fix recordings view for mobile landscape

The events view incorrectly was displaying two columns on landscape view and it only took up 20% of the screen width. Additionally, in landscape view the timeline was too wide (especially on iPads of various screen sizes) and would overlap the main video

* face rec overfitting instructions

* Clarify

* face docs

* clarify

* clarify

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-11 12:03:53 -06:00
8094dd4075 Fixes (#18117)
* face library i18n fixes

* face library i18n fixes

* add ability to use ctrl/cmd S to save in the config editor

* Use datetime as ID

* Update metrics inference speed to start with 0 ms

* fix android formatted thumbnail

* ensure role is comma separated and stripped correctly

* improve face library deletion

- add a confirmation dialog
- add ability to select all / delete faces in collections

* Implement lazy loading for video previews

* Force GPU for large embedding model

* GPU is required

* settings i18n fixes

* Don't delete train tab

* webpush debugging logs

* Fix incorrectly copying zones

* copy path data

* Ensure that cache dir exists for Frigate+

* face docs update

* Add description to upload image step to clarify the image

* Clean up

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-09 07:36:44 -06:00
Felipe SantosandGitHub 52d94231c7 Avoid unhealthy container when Frigate is stopping (#18021) 2025-05-07 19:43:51 -05:00
ac8e647b92 Fixes (#18077)
* fix onvif reinitialization

* api docs: clarify usage of clip.mp4 endpoint

* Always show train tab

* Add description to API

* catch lpr model inference exceptions

* always apply motion mask when using yolov9 plate detection

* lpr faq

* fix incorrect focus when reopening search detail dialog on video tab

* only use keyboard listener in face library when train tab is active

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-07 16:31:24 -06:00
Josh HawkinsandGitHub da1fb935b4 Refactor async ONVIF (#18093)
* use async/await instead of asyncio.run()

* fix autotracking

* create cameras in same event loop that will use them

* more debug

* try using existing event loop instead of creating a new one

* merge dev

* fixes

* run get_camera_info onvifcontroller calls in dedicated loop

* move coroutine call with loop to api

* use asyncio for autotracking move queues

* clean up

* fix calibration

* improve exception logging
2025-05-07 07:53:29 -06:00
GuoQing LiuandGitHub 83188e7ea4 Add chinese docs (#17954)
* add docs chinese i18n

* fix some broken links

* update some i18n

* update chinese docs

* add chinese community docs

* Change docs i18n chinese label
2025-05-06 08:49:49 -06:00
Nicolas MowenandGitHub 3a69273f0c revert onnx runtime update (#18074)
* revert onnx runtime update

* Fix docs
2025-05-06 09:02:34 -05:00
511542eaf8 Fixes (#18055)
* frigate+ pane i18n fix

* catch more exceptions

* explore search result tooltip i18n fix

* i18n fix

* remove comments about deprecated strftime_fmt

* Catch producers exists but is None

* Formatting

* fix live camera view i18n

* Add default role config for proxy users

This allows users to specify a default role for users when using a proxy for auth. This can be useful for users who can't/don't want to define a header mapping for the remote-role header.

* update reference config and auth docs

* clarify face rec camera level config

* clarify auth docs

* Fix onnx not working with openvino

* Update openvino to fix failed npu plugin check

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-05-05 20:42:24 -06:00
idxlicsandGitHub 976863518b Use HF_ENDPOINT env instead of hardcoding https://huggingface.co (#18036)
* Update jina_v1_embedding.py

* Update jina_v2_embedding.py
2025-05-04 19:38:17 -05:00
Nicolas MowenandGitHub 895afcdb0e Various Fixes (#18035)
* Support multi and single core rknn npus

* Update docs config to be more clear
2025-05-04 09:33:27 -06:00
Josh HawkinsandGitHub da2636d6f7 Docs tweaks (#18025)
* face recognition usage instructions

* clarify lpr docs for motorcycles

* person must be detected before face

* add note about coral

* add note about local

* update reference config for face model size

* clarify reference config for face
2025-05-03 20:13:22 -06:00
Felipe SantosandGitHub 27d3be0356 Fix go2rtc homeassistant config dir (#18017) 2025-05-03 10:24:16 -06:00
Nicolas MowenandGitHub fa196f85a7 Opt out of OpenVINO telemetry (#18015) 2025-05-03 08:55:22 -05:00
Blake BlackshearandGitHub 9a3a64a9bd cleanup variants (#18010) 2025-05-03 06:24:30 -06:00
544 changed files with 14694 additions and 2129 deletions
+2
View File
@@ -109,6 +109,7 @@ imdecode
imencode
imread
imwrite
inpoint
interp
iostat
iotop
@@ -264,6 +265,7 @@ tensorrt
tflite
thresholded
timelapse
titlecase
tmpfs
tobytes
toggleable
+2 -2
View File
@@ -39,14 +39,14 @@ jobs:
STABLE_TAG=${BASE}:stable
PULL_TAG=${BASE}:${BUILD_TAG}
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${VERSION_TAG}
for variant in standard-arm64 tensorrt tensorrt-jp5 tensorrt-jp6 rk h8l rocm; do
for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm; do
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${VERSION_TAG}-${variant}
done
# stable tag
if [[ "${BUILD_TYPE}" == "stable" ]]; then
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${STABLE_TAG}
for variant in standard-arm64 tensorrt tensorrt-jp5 tensorrt-jp6 rk h8l rocm; do
for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm; do
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${STABLE_TAG}-${variant}
done
fi
+7 -6
View File
@@ -24,11 +24,9 @@
- 通过RTSP重新流传输以减少摄像头的连接数
- 支持WebRTC和MSE,实现低延迟的实时观看
## 文档(英文)
## 社区中文翻译文档
你可以在这里查看文档 https://docs.frigate.video
文档还暂时没有提供翻译,将会在未来提供。
你可以在这里查看文档 https://docs.frigate-cn.video
## 赞助
@@ -60,5 +58,8 @@
## 翻译
我们使用 [Weblate](https://hosted.weblate.org/projects/frigate-nvr/) 平台提供翻译支持,欢迎参与进来一起完善。
## 中文讨论社区
欢迎加入非官方中文讨论QQ群:1043861059
## 非官方中文讨论社区
欢迎加入中文讨论QQ群:1043861059
Bilibilihttps://space.bilibili.com/3546894915602564
+1 -1
View File
@@ -260,7 +260,7 @@ ENTRYPOINT ["/init"]
CMD []
HEALTHCHECK --start-period=300s --start-interval=5s --interval=15s --timeout=5s --retries=3 \
CMD curl --fail --silent --show-error http://127.0.0.1:5000/api/version || exit 1
CMD test -f /dev/shm/.frigate-is-stopping && exit 0; curl --fail --silent --show-error http://127.0.0.1:5000/api/version || exit 1
# Frigate deps with Node.js and NPM for devcontainer
FROM deps AS devcontainer
+1 -1
View File
@@ -2,7 +2,7 @@
set -euxo pipefail
s6_version="3.1.5.0"
s6_version="3.2.1.0"
if [[ "${TARGETARCH}" == "amd64" ]]; then
s6_arch="x86_64"
+1
View File
@@ -31,6 +31,7 @@ norfair == 2.2.*
setproctitle == 1.3.*
ws4py == 0.5.*
unidecode == 1.3.*
titlecase == 2.4.*
# Image Manipulation
numpy == 1.26.*
opencv-python-headless == 4.11.0.*
@@ -4,6 +4,11 @@
set -o errexit -o nounset -o pipefail
# opt out of openvino telemetry
if [ -e /usr/local/bin/opt_in_out ]; then
/usr/local/bin/opt_in_out --opt_out
fi
# Logs should be sent to stdout so that s6 can collect them
# Tell S6-Overlay not to restart this service
@@ -138,5 +138,9 @@ function migrate_db_from_media_to_config() {
fi
}
# remove leftover from last run, not normally needed, but just in case
# used by the docker healthcheck
rm -f /dev/shm/.frigate-is-stopping
migrate_addon_config_dir
migrate_db_from_media_to_config
@@ -1,5 +1,6 @@
import json
import sys
from typing import Any
from ruamel.yaml import YAML
@@ -21,11 +22,11 @@ try:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.load(raw_config)
config: dict[str, Any] = yaml.load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
config: dict[str, Any] = json.loads(raw_config)
except FileNotFoundError:
config: dict[str, any] = {}
config: dict[str, Any] = {}
path = config.get("ffmpeg", {}).get("path", "default")
if path == "default":
@@ -4,6 +4,7 @@ import json
import os
import sys
from pathlib import Path
from typing import Any
from ruamel.yaml import YAML
@@ -37,13 +38,13 @@ try:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.load(raw_config)
config: dict[str, Any] = yaml.load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
config: dict[str, Any] = json.loads(raw_config)
except FileNotFoundError:
config: dict[str, any] = {}
config: dict[str, Any] = {}
go2rtc_config: dict[str, any] = config.get("go2rtc", {})
go2rtc_config: dict[str, Any] = config.get("go2rtc", {})
# Need to enable CORS for go2rtc so the frigate integration / card work automatically
if go2rtc_config.get("api") is None:
@@ -53,7 +54,7 @@ elif go2rtc_config["api"].get("origin") is None:
# Need to set default location for HA config
if go2rtc_config.get("hass") is None:
go2rtc_config["hass"] = {"config": "/config"}
go2rtc_config["hass"] = {"config": "/homeassistant"}
# we want to ensure that logs are easy to read
if go2rtc_config.get("log") is None:
@@ -134,7 +135,7 @@ for name in go2rtc_config.get("streams", {}):
# add birdseye restream stream if enabled
if config.get("birdseye", {}).get("restream", False):
birdseye: dict[str, any] = config.get("birdseye")
birdseye: dict[str, Any] = config.get("birdseye")
input = f"-f rawvideo -pix_fmt yuv420p -video_size {birdseye.get('width', 1280)}x{birdseye.get('height', 720)} -r 10 -i {BIRDSEYE_PIPE}"
ffmpeg_cmd = f"exec:{parse_preset_hardware_acceleration_encode(ffmpeg_path, config.get('ffmpeg', {}).get('hwaccel_args', ''), input, '-rtsp_transport tcp -f rtsp {output}')}"
@@ -2,9 +2,10 @@
import json
import os
from typing import Any
base_path = os.environ.get("FRIGATE_BASE_PATH", "")
result: dict[str, any] = {"base_path": base_path}
result: dict[str, Any] = {"base_path": base_path}
print(json.dumps(result))
@@ -2,6 +2,7 @@
import json
import sys
from typing import Any
from ruamel.yaml import YAML
@@ -19,12 +20,12 @@ try:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.load(raw_config)
config: dict[str, Any] = yaml.load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
config: dict[str, Any] = json.loads(raw_config)
except FileNotFoundError:
config: dict[str, any] = {}
config: dict[str, Any] = {}
tls_config: dict[str, any] = config.get("tls", {"enabled": True})
tls_config: dict[str, Any] = config.get("tls", {"enabled": True})
print(json.dumps(tls_config))
+1 -1
View File
@@ -22,7 +22,7 @@ RUN apt update && \
RUN mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib
RUN cd /opt/rocm-$ROCM/lib && \
cp -dpr libMIOpen*.so* libamd*.so* libhip*.so* libhsa*.so* libmigraphx*.so* librocm*.so* librocblas*.so* libroctracer*.so* librocfft*.so* librocprofiler*.so* libroctx*.so* /opt/rocm-dist/opt/rocm-$ROCM/lib/ && \
cp -dpr libMIOpen*.so* libamd*.so* libhip*.so* libhsa*.so* libmigraphx*.so* librocm*.so* librocblas*.so* libroctracer*.so* librocsolver*.so* librocfft*.so* librocprofiler*.so* libroctx*.so* /opt/rocm-dist/opt/rocm-$ROCM/lib/ && \
mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib/migraphx/lib && \
cp -dpr migraphx/lib/* /opt/rocm-dist/opt/rocm-$ROCM/lib/migraphx/lib
RUN cd /opt/rocm-dist/opt/ && ln -s rocm-$ROCM rocm
+6 -6
View File
@@ -47,12 +47,11 @@ RUN --mount=type=bind,source=docker/tensorrt/detector/build_python_tensorrt.sh,t
&& TENSORRT_VER=$(cat /etc/TENSORRT_VER) /deps/build_python_tensorrt.sh
COPY docker/tensorrt/requirements-arm64.txt /requirements-tensorrt.txt
# See https://elinux.org/Jetson_Zoo#ONNX_Runtime
ADD https://nvidia.box.com/shared/static/9yvw05k6u343qfnkhdv2x6xhygze0aq1.whl /tmp/onnxruntime_gpu-1.19.0-cp311-cp311-linux_aarch64.whl
RUN pip3 uninstall -y onnxruntime-openvino \
&& pip3 wheel --wheel-dir=/trt-wheels -r /requirements-tensorrt.txt \
&& pip3 install --no-deps /tmp/onnxruntime_gpu-1.19.0-cp311-cp311-linux_aarch64.whl
RUN pip3 wheel --wheel-dir=/trt-wheels -r /requirements-tensorrt.txt
# See https://elinux.org/Jetson_Zoo#ONNX_Runtime
ADD https://nvidia.box.com/shared/static/9yvw05k6u343qfnkhdv2x6xhygze0aq1.whl /trt-wheels/onnxruntime_gpu-1.19.0-cp311-cp311-linux_aarch64.whl
FROM build-wheels AS trt-model-wheels
ARG DEBIAN_FRONTEND
@@ -93,7 +92,8 @@ RUN mkdir -p /etc/ld.so.conf.d && echo /usr/lib/ffmpeg/jetson/lib/ > /etc/ld.so.
COPY --from=trt-wheels /etc/TENSORRT_VER /etc/TENSORRT_VER
RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \
--mount=type=bind,from=trt-model-wheels,source=/trt-model-wheels,target=/deps/trt-model-wheels \
pip3 install -U /deps/trt-wheels/*.whl /deps/trt-model-wheels/*.whl \
pip3 uninstall -y onnxruntime \
&& pip3 install -U /deps/trt-wheels/*.whl /deps/trt-model-wheels/*.whl \
&& ldconfig
WORKDIR /opt/frigate/
+11 -2
View File
@@ -77,7 +77,7 @@ Changing the secret will invalidate current tokens.
Frigate can be configured to leverage features of common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth.
If you are leveraging the authentication of an upstream proxy, you likely want to disable Frigate's authentication. Optionally, if communication between the reverse proxy and Frigate is over an untrusted network, you should set an `auth_secret` in the `proxy` config and configure the proxy to send the secret value as a header named `X-Proxy-Secret`. Assuming this is an untrusted network, you will also want to [configure a real TLS certificate](tls.md) to ensure the traffic can't simply be sniffed to steal the secret.
If you are leveraging the authentication of an upstream proxy, you likely want to disable Frigate's authentication as there is no correspondence between users in Frigate's database and users authenticated via the proxy. Optionally, if communication between the reverse proxy and Frigate is over an untrusted network, you should set an `auth_secret` in the `proxy` config and configure the proxy to send the secret value as a header named `X-Proxy-Secret`. Assuming this is an untrusted network, you will also want to [configure a real TLS certificate](tls.md) to ensure the traffic can't simply be sniffed to steal the secret.
Here is an example of how to disable Frigate's authentication and also ensure the requests come only from your known proxy.
@@ -97,11 +97,12 @@ python3 -c 'import secrets; print(secrets.token_hex(64))'
### Header mapping
If you have disabled Frigate's authentication and your proxy supports passing a header with authenticated usernames and/or roles, you can use the `header_map` config to specify the header name so it is passed to Frigate. For example, the following will map the `X-Forwarded-User` and `X-Forwarded-Role` values. Header names are not case sensitive.
If you have disabled Frigate's authentication and your proxy supports passing a header with authenticated usernames and/or roles, you can use the `header_map` config to specify the header name so it is passed to Frigate. For example, the following will map the `X-Forwarded-User` and `X-Forwarded-Role` values. Header names are not case sensitive. Multiple values can be included in the role header. Frigate expects that the character separating the roles is a comma, but this can be specified using the `separator` config entry.
```yaml
proxy:
...
separator: "|" # This value defaults to a comma, but Authentik uses a pipe, for example.
header_map:
user: x-forwarded-user
role: x-forwarded-role
@@ -109,6 +110,14 @@ proxy:
Frigate supports both `admin` and `viewer` roles (see below). When using port `8971`, Frigate validates these headers and subsequent requests use the headers `remote-user` and `remote-role` for authorization.
A default role can be provided. Any value in the mapped `role` header will override the default.
```yaml
proxy:
...
default_role: viewer
```
#### Port Considerations
**Authenticated Port (8971)**
+51 -9
View File
@@ -3,7 +3,7 @@ id: face_recognition
title: Face Recognition
---
Face recognition identifies known individuals by matching detected faces with previously learned facial data. When a known person is recognized, their name will be added as a `sub_label`. This information is included in the UI, filters, as well as in notifications.
Face recognition identifies known individuals by matching detected faces with previously learned facial data. When a known `person` is recognized, their name will be added as a `sub_label`. This information is included in the UI, filters, as well as in notifications.
## Model Requirements
@@ -13,6 +13,12 @@ When running a Frigate+ model (or any custom model that natively detects faces)
When running a default COCO model or another model that does not include `face` as a detectable label, face detection will run via CV2 using a lightweight DNN model that runs on the CPU. In this case, you should _not_ define `face` in your list of objects to track.
:::note
Frigate needs to first detect a `person` before it can detect and recognize a face.
:::
### Face Recognition
Frigate has support for two face recognition model types:
@@ -22,11 +28,13 @@ Frigate has support for two face recognition model types:
In both cases, a lightweight face landmark detection model is also used to align faces before running recognition.
All of these features run locally on your system.
## Minimum System Requirements
The `small` model is optimized for efficiency and runs on the CPU, most CPUs should run the model efficiently.
The `large` model is optimized for accuracy, an integrated or discrete GPU is highly recommended. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation.
The `large` model is optimized for accuracy, an integrated or discrete GPU is required. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation.
## Configuration
@@ -39,7 +47,7 @@ face_recognition:
## Advanced Configuration
Fine-tune face recognition with these optional parameters:
Fine-tune face recognition with these optional parameters at the global level of your config. The only optional parameters that can be set at the camera level are `enabled` and `min_area`.
### Detection
@@ -57,11 +65,22 @@ Fine-tune face recognition with these optional parameters:
- Default: `0.8`.
- `recognition_threshold`: Recognition confidence score required to add the face to the object as a sub label.
- Default: `0.9`.
- `min_faces`: Min face attempts for the sub label to be applied to the person object.
- Default: `1`
- `save_attempts`: Number of images of recognized faces to save for training.
- Default: `100`.
- `blur_confidence_filter`: Enables a filter that calculates how blurry the face is and adjusts the confidence based on this.
- Default: `True`.
## Usage
Follow these steps to begin:
1. **Enable face recognition** in your configuration file and restart Frigate.
2. **Upload one face** using the **Add Face** button's wizard in the Face Library section of the Frigate UI. Read below for the best practices on expanding your training set.
3. When Frigate detects and attempts to recognize a face, it will appear in the **Train** tab of the Face Library, along with its associated recognition confidence.
4. From the **Train** tab, you can **assign the face** to a new or existing person to improve recognition accuracy for the future.
## Creating a Robust Training Set
The number of images needed for a sufficient training set for face recognition varies depending on several factors:
@@ -90,22 +109,32 @@ When choosing images to include in the face training set it is recommended to al
:::
### Understanding the Train Tab
The Train tab in the face library displays recent face recognition attempts. Detected face images are grouped according to the person they were identified as potentially matching.
Each face image is labeled with a name (or `Unknown`) along with the confidence score of the recognition attempt. While each image can be used to train the system for a specific person, not all images are suitable for training.
Refer to the guidelines below for best practices on selecting images for training.
### Step 1 - Building a Strong Foundation
When first enabling face recognition it is important to build a foundation of strong images. It is recommended to start by uploading 1-5 "portrait" photos for each person. It is important that the person's face in the photo is straight-on and not turned which will ensure a good starting point.
When first enabling face recognition it is important to build a foundation of strong images. It is recommended to start by uploading 1-5 photos containing just this person's face. It is important that the person's face in the photo is front-facing and not turned, this will ensure a good starting point.
Then it is recommended to use the `Face Library` tab in Frigate to select and train images for each person as they are detected. When building a strong foundation it is strongly recommended to only train on images that are straight-on. Ignore images from cameras that recognize faces from an angle.
Then it is recommended to use the `Face Library` tab in Frigate to select and train images for each person as they are detected. When building a strong foundation it is strongly recommended to only train on images that are front-facing. Ignore images from cameras that recognize faces from an angle. Aim to strike a balance between the quality of images while also having a range of conditions (day / night, different weather conditions, different times of day, etc.) in order to have diversity in the images used for each person and not have over-fitting.
Aim to strike a balance between the quality of images while also having a range of conditions (day / night, different weather conditions, different times of day, etc.) in order to have diversity in the images used for each person and not have over-fitting.
Once a person starts to be consistently recognized correctly on images that are straight-on, it is time to move on to the next step.
You do not want to train images that are 90%+ as these are already being confidently recognized. In this step the goal is to train on clear, lower scoring front-facing images until the majority of front-facing images for a given person are consistently recognized correctly. Then it is time to move on to step 2.
### Step 2 - Expanding The Dataset
Once straight-on images are performing well, start choosing slightly off-angle images to include for training. It is important to still choose images where enough face detail is visible to recognize someone.
Once front-facing images are performing well, start choosing slightly off-angle images to include for training. It is important to still choose images where enough face detail is visible to recognize someone, and you still only want to train on images that score lower.
## FAQ
### Detection does not work well with blurry images?
Accuracy is definitely a going to be improved with higher quality cameras / streams. It is important to look at the DORI (Detection Observation Recognition Identification) range of your camera, if that specification is posted. This specification explains the distance from the camera that a person can be detected, observed, recognized, and identified. The identification range is the most relevant here, and the distance listed by the camera is the furthest that face recognition will realistically work.
### Why can't I bulk upload photos?
It is important to methodically add photos to the library, bulk importing photos (especially from a general photo library) will lead to over-fitting in that particular scenario and hurt recognition performance.
@@ -122,6 +151,15 @@ This can happen for a few different reasons, but this is usually an indicator th
- When you provide images with different poses, lighting, and expressions, the algorithm extracts features that are consistent across those variations.
- By training on a diverse set of images, the algorithm becomes less sensitive to minor variations and noise in the input image.
Review your face collections and remove most of the unclear or low-quality images. Then, use the **Reprocess** button on each face in the **Train** tab to evaluate how the changes affect recognition scores.
Avoid training on images that already score highly, as this can lead to over-fitting. Instead, focus on relatively clear images that score lower - ideally with different lighting, angles, and conditions—to help the model generalize more effectively.
### Frigate misidentified a face. Can I tell it that a face is "not" a specific person?
No, face recognition does not support negative training (i.e., explicitly telling it who someone is _not_). Instead, the best approach is to improve the training data by using a more diverse and representative set of images for each person.
For more guidance, refer to the section above on improving recognition accuracy.
### I see scores above the threshold in the train tab, but a sub label wasn't assigned?
The Frigate considers the recognition scores across all recognition attempts for each person object. The scores are continually weighted based on the area of the face, and a sub label will only be assigned to person if a person is confidently recognized consistently. This avoids cases where a single high confidence recognition would throw off the results.
@@ -141,3 +179,7 @@ Face recognition does not run on the recording stream, this would be suboptimal
### I get an unknown error when taking a photo directly with my iPhone
By default iOS devices will use HEIC (High Efficiency Image Container) for images, but this format is not supported for uploads. Choosing `large` as the format instead of `original` will use JPG which will work correctly.
### How can I delete the face database and start over?
Frigate does not store anything in its database related to face recognition. You can simply delete all of your faces through the Frigate UI or remove the contents of the `/media/frigate/clips/faces` directory.
+1 -1
View File
@@ -167,7 +167,7 @@ Analyze the sequence of images containing the {label}. Focus on the likely inten
:::tip
Prompts can use variable replacements like `{label}`, `{sub_label}`, and `{camera}` to substitute information from the tracked object as part of the prompt.
Prompts can use variable replacements `{label}`, `{sub_label}`, and `{camera}` to substitute information from the tracked object as part of the prompt.
:::
@@ -24,3 +24,9 @@ Object detection and enrichments (like Semantic Search, Face Recognition, and Li
- Jetson devices will automatically be detected and used for enrichments in the `-tensorrt-jp6` Frigate image.
Utilizing a GPU for enrichments does not require you to use the same GPU for object detection. For example, you can run the `tensorrt` Docker image for enrichments and still use other dedicated hardware for object detection.
:::note
A Google Coral is a TPU (Tensor Processing Unit), not a dedicated GPU (Graphics Processing Unit) and therefore does not provide any kind of acceleration for Frigate's enrichments.
:::
@@ -3,7 +3,7 @@ id: license_plate_recognition
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`. 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 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. However, LPR does not run on stationary vehicles.
@@ -13,7 +13,7 @@ When a plate is recognized, the details are:
- Viewable in the Review Item Details pane in Review (sub labels).
- Viewable in the Tracked Object Details pane in Explore (sub labels and recognized license plates).
- 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` tracked object.
- 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/tracked_object_update` MQTT topic with `name` (if known) and `plate`.
## Model Requirements
@@ -24,7 +24,7 @@ Users without a model that detects license plates can still run LPR. Frigate use
:::note
In the default mode, Frigate's LPR needs to first detect a `car` before it can recognize a license plate. If you're using a dedicated LPR camera and have a zoomed-in view where a `car` will not be detected, you can still run LPR, but the configuration parameters will differ from the default mode. See the [Dedicated LPR Cameras](#dedicated-lpr-cameras) section below.
In the default mode, Frigate's LPR needs to first detect a `car` or `motorcycle` before it can recognize a license plate. If you're using a dedicated LPR camera and have a zoomed-in view where a `car` or `motorcycle` will not be detected, you can still run LPR, but the configuration parameters will differ from the default mode. See the [Dedicated LPR Cameras](#dedicated-lpr-cameras) section below.
:::
@@ -51,7 +51,7 @@ cameras:
enabled: False
```
For non-dedicated LPR cameras, ensure that your camera is configured to detect objects of type `car`, and that a car is actually being detected by Frigate. Otherwise, LPR will not run.
For non-dedicated LPR cameras, ensure that your camera is configured to detect objects of type `car` or `motorcycle`, and that a car or motorcycle is actually being detected by Frigate. Otherwise, LPR will not run.
Like the other real-time processors in Frigate, license plate recognition runs on the camera stream defined by the `detect` role in your config. To ensure optimal performance, select a suitable resolution for this stream in your camera's firmware that fits your specific scene and requirements.
@@ -87,7 +87,7 @@ Fine-tune the LPR feature using these optional parameters at the global level of
### Matching
- **`known_plates`**: List of strings or regular expressions that assign custom a `sub_label` to `car` objects when a recognized plate matches a known value.
- **`known_plates`**: List of strings or regular expressions that assign custom a `sub_label` to `car` and `motorcycle` objects when a recognized plate matches a known value.
- These labels appear in the UI, filters, and notifications.
- Unknown plates are still saved but are added to the `recognized_license_plate` field rather than the `sub_label`.
- **`match_distance`**: Allows for minor variations (missing/incorrect characters) when matching a detected plate to a known plate.
@@ -164,11 +164,17 @@ Dedicated LPR cameras are single-purpose cameras with powerful optical zoom to c
To mark a camera as a dedicated LPR camera, add `type: "lpr"` the camera configuration.
:::note
Frigate's dedicated LPR mode is optimized for cameras with a narrow field of view, specifically positioned and zoomed to capture license plates exclusively. If your camera provides a general overview of a scene rather than a tightly focused view, this mode is not recommended.
:::
Users can configure Frigate's dedicated LPR mode in two different ways depending on whether a Frigate+ (or native `license_plate` detecting) model is used:
### Using a Frigate+ (or Native `license_plate` Detecting) Model
Users running a Frigate+ model (or any model that natively detects `license_plate`) can take advantage of `license_plate` detection. This allows license plates to be treated as standard objects in dedicated LPR mode, meaning that alerts, detections, snapshots, zones, and other Frigate features work as usual, and plates are detected efficiently through your configured object detector.
Users running a Frigate+ model (or any model that natively detects `license_plate`) can take advantage of `license_plate` detection. This allows license plates to be treated as standard objects in dedicated LPR mode, meaning that alerts, detections, snapshots, and other Frigate features work as usual, and plates are detected efficiently through your configured object detector.
An example configuration for a dedicated LPR camera using a `license_plate`-detecting model:
@@ -213,11 +219,11 @@ cameras:
With this setup:
- License plates are treated as normal objects in Frigate.
- Scores, alerts, detections, snapshots, zones, and object masks work as expected.
- Scores, alerts, detections, and snapshots work as expected.
- Snapshots will have license plate bounding boxes on them.
- The `frigate/events` MQTT topic will publish tracked object updates.
- Debug view will display `license_plate` bounding boxes.
- If you are using a Frigate+ model and want to submit images from your dedicated LPR camera for model training and fine-tuning, annotate both the `car` and the `license_plate` in the snapshots on the Frigate+ website, even if the car is barely visible.
- If you are using a Frigate+ model and want to submit images from your dedicated LPR camera for model training and fine-tuning, annotate both the `car` / `motorcycle` and the `license_plate` in the snapshots on the Frigate+ website, even if the car is barely visible.
### Using the Secondary LPR Pipeline (Without Frigate+)
@@ -279,7 +285,6 @@ With this setup:
| License Plate Detection | Uses `license_plate` as a tracked object | Runs a dedicated LPR pipeline |
| FPS Setting | 5 (increase for fast-moving cars) | 5 (increase for fast-moving cars, but it may use much more CPU) |
| Object Detection | Standard Frigate+ detection applies | Bypasses standard object detection |
| Zones & Object Masks | Supported | Not supported |
| Debug View | May show `license_plate` bounding boxes | May **not** show `license_plate` bounding boxes |
| MQTT `frigate/events` | Publishes tracked object updates | Publishes limited updates |
| Explore | Recognized plates available in More Filters | Recognized plates available in More Filters |
@@ -311,9 +316,9 @@ Recognized plates will show as object labels in the debug view and will appear i
If you are still having issues detecting plates, start with a basic configuration and see the debugging tips below.
### Can I run LPR without detecting `car` objects?
### Can I run LPR without detecting `car` or `motorcycle` objects?
In normal LPR mode, Frigate requires a `car` to be detected first before recognizing a license plate. If you have a dedicated LPR camera, you can change the camera `type` to `"lpr"` to use the Dedicated LPR Camera algorithm. This comes with important caveats, though. See the [Dedicated LPR Cameras](#dedicated-lpr-cameras) section above.
In normal LPR mode, Frigate requires a `car` or `motorcycle` to be detected first before recognizing a license plate. If you have a dedicated LPR camera, you can change the camera `type` to `"lpr"` to use the Dedicated LPR Camera algorithm. This comes with important caveats, though. See the [Dedicated LPR Cameras](#dedicated-lpr-cameras) section above.
### How can I improve detection accuracy?
@@ -335,19 +340,37 @@ Use `match_distance` to allow small character mismatches. Alternatively, define
### How do I debug LPR issues?
- View MQTT messages for `frigate/events` to verify detected plates.
- If you are using a Frigate+ model or a model that detects license plates, watch the debug view (Settings --> Debug) to ensure that `license_plate` is being detected with a `car`.
- Watch the debug view to see plates recognized in real-time. For non-dedicated LPR cameras, the `car` label will change to the recognized plate when LPR is enabled and working.
- Adjust `detection_threshold` and `recognition_threshold` settings per the suggestions [above](#advanced-configuration).
- Enable `debug_save_plates` to save images of detected text on plates to the clips directory (`/media/frigate/clips/lpr`). Ensure these images are readable and the text is clear.
- Enable debug logs for LPR by adding `frigate.data_processing.common.license_plate: debug` to your `logger` configuration. These logs are _very_ verbose, so only enable this when necessary.
Start with ["Why isn't my license plate being detected and recognized?"](#why-isnt-my-license-plate-being-detected-and-recognized). If you are still having issues, work through these steps.
```yaml
logger:
default: info
logs:
frigate.data_processing.common.license_plate: debug
```
1. Enable debug logs to see exactly what Frigate is doing.
- Enable debug logs for LPR by adding `frigate.data_processing.common.license_plate: debug` to your `logger` configuration. These logs are _very_ verbose, so only keep this enabled when necessary.
```yaml
logger:
default: info
logs:
frigate.data_processing.common.license_plate: debug
```
2. Ensure your plates are being _detected_.
If you are using a Frigate+ or `license_plate` detecting model:
- Watch the debug view (Settings --> Debug) to ensure that `license_plate` is being detected.
- View MQTT messages for `frigate/events` to verify detected plates.
- You may need to adjust your `min_score` and/or `threshold` for the `license_plate` object if your plates are not being detected.
If you are **not** using a Frigate+ or `license_plate` detecting model:
- Watch the debug logs for messages from the YOLOv9 plate detector.
- You may need to adjust your `detection_threshold` if your plates are not being detected.
3. Ensure the characters on detected plates are being _recognized_.
- Enable `debug_save_plates` to save images of detected text on plates to the clips directory (`/media/frigate/clips/lpr`). Ensure these images are readable and the text is clear.
- Watch the debug view to see plates recognized in real-time. For non-dedicated LPR cameras, the `car` or `motorcycle` label will change to the recognized plate when LPR is enabled and working.
- Adjust `recognition_threshold` settings per the suggestions [above](#advanced-configuration).
### Will LPR slow down my system?
@@ -357,12 +380,16 @@ LPR's performance impact depends on your hardware. Ensure you have at least 4GB
The YOLOv9 license plate detector model will run (and the metric will appear) if you've enabled LPR but haven't defined `license_plate` as an object to track, either at the global or camera level.
If you are detecting `car` on cameras where you don't want to run LPR, make sure you disable LPR it at the camera level. And if you do want to run LPR on those cameras, make sure you define `license_plate` as an object to track.
If you are detecting `car` or `motorcycle` on cameras where you don't want to run LPR, make sure you disable LPR it at the camera level. And if you do want to run LPR on those cameras, make sure you define `license_plate` as an object to track.
### It looks like Frigate picked up my camera's timestamp as the license plate. How can I prevent this?
### It looks like Frigate picked up my camera's timestamp or overlay text as the license plate. How can I prevent this?
This could happen if cars travel close to your camera's timestamp. You could either move the timestamp through your camera's firmware, or apply a mask to it in Frigate.
This could happen if cars or motorcycles travel close to your camera's timestamp or overlay text. You could either move the text through your camera's firmware, or apply a mask to it in Frigate.
If you are using a model that natively detects `license_plate`, add an _object mask_ of type `license_plate` and a _motion mask_ over your timestamp.
If you are using a model that natively detects `license_plate`, add an _object mask_ of type `license_plate` and a _motion mask_ over your text.
If you are using dedicated LPR camera mode, only a _motion mask_ over your timestamp is required.
If you are not using a model that natively detects `license_plate` or you are using dedicated LPR camera mode, only a _motion mask_ over your text is required.
### I see "Error running ... model" in my logs. How can I fix this?
This usually happens when your GPU is unable to compile or use one of the LPR models. Set your `device` to `CPU` and try again. GPU acceleration only provides a slight performance increase, and the models are lightweight enough to run without issue on most CPUs.
+6
View File
@@ -197,6 +197,12 @@ The default dashboard ("All Cameras") will always use Smart Streaming and the fi
Cameras can be temporarily disabled through the Frigate UI and through [MQTT](/integrations/mqtt#frigatecamera_nameenabledset) to conserve system resources. When disabled, Frigate's ffmpeg processes are terminated — recording stops, object detection is paused, and the Live dashboard displays a blank image with a disabled message. Review items, tracked objects, and historical footage for disabled cameras can still be accessed via the UI.
:::note
Disabling a camera via the Frigate UI or MQTT is temporary and does not persist through restarts of Frigate.
:::
For restreamed cameras, go2rtc remains active but does not use system resources for decoding or processing unless there are active external consumers (such as the Advanced Camera Card in Home Assistant using a go2rtc source).
Note that disabling a camera through the config file (`enabled: False`) removes all related UI elements, including historical footage access. To retain access while disabling the camera, keep it enabled in the config and use the UI or MQTT to disable it temporarily.
+7 -6
View File
@@ -152,7 +152,7 @@ Use this configuration for YOLO-based models. When no custom model path or URL i
```yaml
detectors:
hailo8l:
hailo:
type: hailo8l
device: PCIe
@@ -185,7 +185,7 @@ For SSD-based models, provide either a model path or URL to your compiled SSD mo
```yaml
detectors:
hailo8l:
hailo:
type: hailo8l
device: PCIe
@@ -209,7 +209,7 @@ The Hailo detector supports all YOLO models compiled for Hailo hardware that inc
```yaml
detectors:
hailo8l:
hailo:
type: hailo8l
device: PCIe
@@ -1024,7 +1024,7 @@ x.export()
### Downloading YOLO-NAS Model
You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb).
You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) which can be run directly in [Google Colab](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb).
:::warning
@@ -1053,13 +1053,14 @@ python3 yolo_to_onnx.py -m yolov7-320
#### YOLOv9
YOLOv9 models can be exported using the below code or they [can be downloaded from hugging face](https://huggingface.co/Xenova/yolov9-onnx/tree/main)
YOLOv9 models can be exported using the below code
```sh
git clone https://github.com/WongKinYiu/yolov9
cd yolov9
# setup the virtual environment so installation doesn't affect main system
# NOTE: Virtual environment must be using Python 3.11 or older.
python3 -m venv ./
bin/pip install -r requirements.txt
bin/pip install onnx onnxruntime onnx-simplifier>=0.4.1
@@ -1069,5 +1070,5 @@ wget -O yolov9-t.pt "https://github.com/WongKinYiu/yolov9/releases/download/v0.1
# prepare and run export script
sed -i "s/ckpt = torch.load(attempt_download(w), map_location='cpu')/ckpt = torch.load(attempt_download(w), map_location='cpu', weights_only=False)/g" ./models/experimental.py
python3 export.py --weights ./yolov9-t.pt --imgsz 320 --simplify --include onnx
bin/python3 export.py --weights ./yolov9-t.pt --imgsz 320 --simplify --include onnx
```
+12 -3
View File
@@ -78,16 +78,21 @@ proxy:
# Optional: Mapping for headers from upstream proxies. Only used if Frigate's auth
# is disabled.
# NOTE: Many authentication proxies pass a header downstream with the authenticated
# user name. Not all values are supported. It must be a whitelisted header.
# user name and role. Not all values are supported. It must be a whitelisted header.
# See the docs for more info.
header_map:
user: x-forwarded-user
role: x-forwarded-role
# Optional: Url for logging out a user. This sets the location of the logout url in
# the UI.
logout_url: /api/logout
# Optional: Auth secret that is checked against the X-Proxy-Secret header sent from
# the proxy. If not set, all requests are trusted regardless of origin.
auth_secret: None
# Optional: The default role to use for proxy auth. Must be "admin" or "viewer"
default_role: viewer
# Optional: The character used to separate multiple values in the proxy headers. (default: shown below)
separator: ","
# Optional: Authentication configuration
auth:
@@ -543,9 +548,9 @@ semantic_search:
model_size: "small"
# Optional: Configuration for face recognition capability
# NOTE: Can (enabled, min_area) be overridden at the camera level
# NOTE: enabled, min_area can be overridden at the camera level
face_recognition:
# Optional: Enable semantic search (default: shown below)
# Optional: Enable face recognition (default: shown below)
enabled: False
# Optional: Minimum face distance score required to mark as a potential match (default: shown below)
unknown_score: 0.8
@@ -556,10 +561,14 @@ face_recognition:
recognition_threshold: 0.9
# Optional: Min area of detected face box to consider running face recognition (default: shown below)
min_area: 500
# Optional: Min face attempts for the sub label to be applied to the person object (default: shown below)
min_faces: 1
# Optional: Number of images of recognized faces to save for training (default: shown below)
save_attempts: 100
# Optional: Apply a blur quality filter to adjust confidence based on the blur level of the image (default: shown below)
blur_confidence_filter: True
# Optional: Set the model size used face recognition. (default: shown below)
model_size: small
# Optional: Configuration for license plate recognition capability
# NOTE: enabled, min_area, and enhancement can be overridden at the camera level
+4 -4
View File
@@ -19,7 +19,7 @@ For best performance, 16GB or more of RAM and a dedicated GPU are recommended.
## Configuration
Semantic Search is disabled by default, and must be enabled in your config file or in the UI's Settings page before it can be used. Semantic Search is a global configuration setting.
Semantic Search is disabled by default, and must be enabled in your config file or in the UI's Enrichments Settings page before it can be used. Semantic Search is a global configuration setting.
```yaml
semantic_search:
@@ -29,9 +29,9 @@ semantic_search:
:::tip
The embeddings database can be re-indexed from the existing tracked objects in your database by adding `reindex: True` to your `semantic_search` configuration or by toggling the switch on the Search Settings page in the UI and restarting Frigate. Depending on the number of tracked objects you have, it can take a long while to complete and may max out your CPU while indexing. Make sure to turn the UI's switch off or set the config back to `False` before restarting Frigate again.
The embeddings database can be re-indexed from the existing tracked objects in your database by pressing the "Reindex" button in the Enrichments Settings in the UI or by adding `reindex: True` to your `semantic_search` configuration and restarting Frigate. Depending on the number of tracked objects you have, it can take a long while to complete and may max out your CPU while indexing.
If you are enabling Semantic Search for the first time, be advised that Frigate does not automatically index older tracked objects. You will need to enable the `reindex` feature in order to do that.
If you are enabling Semantic Search for the first time, be advised that Frigate does not automatically index older tracked objects. You will need to reindex as described above.
:::
@@ -72,7 +72,7 @@ For most users, especially native English speakers, the V1 model remains the rec
:::note
Switching between V1 and V2 requires reindexing your embeddings. To do this, set `reindex: True` in your Semantic Search configuration and restart Frigate. The embeddings from V1 and V2 are incompatible, and failing to reindex will result in incorrect search results.
Switching between V1 and V2 requires reindexing your embeddings. The embeddings from V1 and V2 are incompatible, and failing to reindex will result in incorrect search results.
:::
+1 -1
View File
@@ -5,7 +5,7 @@ title: Snapshots
Frigate can save a snapshot image to `/media/frigate/clips` for each object that is detected named as `<camera>-<id>.jpg`. They are also accessible [via the api](../integrations/api/event-snapshot-events-event-id-snapshot-jpg-get.api.mdx)
For users with Frigate+ enabled, snapshots are accessible in the UI in the Frigate+ pane to allow for quick submission to the Frigate+ service.
Snapshots are accessible in the UI in the Explore pane. This allows for quick submission to the Frigate+ service.
To only save snapshots for objects that enter a specific zone, [see the zone docs](./zones.md#restricting-snapshots-to-specific-zones)
+4 -3
View File
@@ -136,7 +136,7 @@ Your zone must be defined with exactly 4 points and should be aligned to the gro
![Ground plane 4-point zone](/img/ground-plane.jpg)
Speed estimation requires a minimum number of frames for your object to be tracked before a valid estimate can be calculated, so create your zone away from places where objects enter and exit for the best results. _Your zone should not take up the full frame._ An object's speed is tracked while it is in the zone and then saved to Frigate's database.
Speed estimation requires a minimum number of frames for your object to be tracked before a valid estimate can be calculated, so create your zone away from places where objects enter and exit for the best results. The object's bounding box must be stable and remain a constant size as it enters and exits the zone. _Your zone should not take up the full frame, and the zone does **not** need to be the same size or larger than the objects passing through it._ An object's speed is tracked while it passes through the zone and then saved to Frigate's database.
Accurate real-world distance measurements are required to estimate speeds. These distances can be specified in your zone config through the `distances` field.
@@ -165,8 +165,9 @@ These speed values are output as a number in miles per hour (mph) or kilometers
#### Best practices and caveats
- Speed estimation works best with a straight road or path when your object travels in a straight line across that path. Avoid creating your zone near intersections or anywhere that objects would make a turn. If the bounding box changes shape (either because the object made a turn or became partially obscured, for example), speed estimation will not be accurate.
- Create a zone where the bottom center of your object's bounding box travels directly through it and does not become obscured at any time. See the photo example above.
- Speed estimation works best with a straight road or path when your object travels in a straight line across that path. Avoid creating your zone near intersections or anywhere that objects would make a turn.
- Create a zone where the bottom center of your object's bounding box travels directly through it and does not become obscured at any time.
- A large zone can be used (as in the photo example above), but it may cause inaccurate estimation if the object's bounding box changes shape (such as when it turns or becomes partially hidden). Generally it's best to make your zone large enough to capture a few frames, but small enough so that the bounding box doesn't change size as it enters, travels through, and exits the zone.
- Depending on the size and location of your zone, you may want to decrease the zone's `inertia` value from the default of 3.
- The more accurate your real-world dimensions can be measured, the more accurate speed estimation will be. However, due to the way Frigate's tracking algorithm works, you may need to tweak the real-world distance values so that estimated speeds better match real-world speeds.
- Once an object leaves the zone, speed accuracy will likely decrease due to perspective distortion and misalignment with the calibrated area. Therefore, speed values will show as a zero through MQTT and will not be visible on the debug view when an object is outside of a speed tracking zone.
+4 -3
View File
@@ -143,9 +143,10 @@ Inference speeds will vary greatly depending on the GPU and the model used.
With the [rocm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many discrete AMD GPUs.
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time |
| -------- | --------------------- | ------------------------- |
| AMD 780M | ~ 14 ms | 320: ~ 30 ms 640: ~ 60 ms |
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time |
| --------- | --------------------- | ------------------------- |
| AMD 780M | ~ 14 ms | 320: ~ 25 ms 640: ~ 50 ms |
| AMD 8700G | | 320: ~ 20 ms 640: ~ 40 ms |
## Community Supported Detectors
@@ -26,3 +26,7 @@ This is a fork (with fixed errors and new features) of [original Double Take](ht
## [Frigate telegram](https://github.com/OldTyT/frigate-telegram)
[Frigate telegram](https://github.com/OldTyT/frigate-telegram) makes it possible to send events from Frigate to Telegram. Events are sent as a message with a text description, video, and thumbnail.
## [Periscope](https://github.com/maksz42/periscope)
[Periscope](https://github.com/maksz42/periscope) is a lightweight Android app that turns old devices into live viewers for Frigate. It works on Android 2.2 and above, including Android TV. It supports authentication and HTTPS.
+19
View File
@@ -17,6 +17,15 @@ const config: Config = {
markdown: {
mermaid: true,
},
i18n: {
defaultLocale: 'en',
locales: ['en'],
localeConfigs: {
en: {
label: 'English',
}
},
},
themeConfig: {
algolia: {
appId: 'WIURGBNBPY',
@@ -82,6 +91,16 @@ const config: Config = {
label: 'Demo',
position: 'right',
},
{
type: 'localeDropdown',
position: 'right',
dropdownItemsAfter: [
{
label: '简体中文(社区翻译)',
href: 'https://docs.frigate-cn.video',
}
]
},
{
href: 'https://github.com/blakeblackshear/frigate',
label: 'GitHub',
@@ -0,0 +1,25 @@
import React, { useEffect, useState } from 'react';
import { useLocation } from '@docusaurus/router';
import styles from './styles.module.css';
export default function LanguageAlert() {
const [showAlert, setShowAlert] = useState(false);
const { pathname } = useLocation();
useEffect(() => {
const userLanguage = navigator?.language || 'en';
const isChineseUser = userLanguage.includes('zh');
setShowAlert(isChineseUser);
}, [pathname]);
if (!showAlert) return null;
return (
<div className={styles.alert}>
<span>检测到您的主要语言为中文您可以访问由中文社区翻译的</span>
<a href={'https://docs.frigate-cn.video'+pathname}>中文文档</a>
<span> 以获得更好的体验</span>
</div>
);
}
@@ -0,0 +1,13 @@
.alert {
padding: 12px;
background: #fff8e6;
border-bottom: 1px solid #ffd166;
text-align: center;
font-size: 15px;
}
.alert a {
color: #1890ff;
font-weight: 500;
margin-left: 6px;
}
+15
View File
@@ -0,0 +1,15 @@
import React from 'react';
import NavbarLayout from '@theme/Navbar/Layout';
import NavbarContent from '@theme/Navbar/Content';
import LanguageAlert from '../../components/LanguageAlert';
export default function Navbar() {
return (
<>
<NavbarLayout>
<NavbarContent />
</NavbarLayout>
<LanguageAlert />
</>
);
}
+2
View File
@@ -2926,6 +2926,8 @@ paths:
tags:
- Media
summary: Recording Clip
description: >-
For iOS devices, use the master.m3u8 HLS link instead of clip.mp4. Safari does not reliably process progressive mp4 files.
operationId: recording_clip__camera_name__start__start_ts__end__end_ts__clip_mp4_get
parameters:
- name: camera_name
+5 -5
View File
@@ -74,7 +74,7 @@ def go2rtc_streams():
)
stream_data = r.json()
for data in stream_data.values():
for producer in data.get("producers", []):
for producer in data.get("producers") or []:
producer["url"] = clean_camera_user_pass(producer.get("url", ""))
return JSONResponse(content=stream_data)
@@ -131,7 +131,7 @@ def metrics(request: Request):
@router.get("/config")
def config(request: Request):
config_obj: FrigateConfig = request.app.frigate_config
config: dict[str, dict[str, any]] = config_obj.model_dump(
config: dict[str, dict[str, Any]] = config_obj.model_dump(
mode="json", warnings="none", exclude_none=True
)
@@ -158,7 +158,7 @@ def config(request: Request):
camera_dict["zones"][zone_name]["color"] = zone.color
# remove go2rtc stream passwords
go2rtc: dict[str, any] = config_obj.go2rtc.model_dump(
go2rtc: dict[str, Any] = config_obj.go2rtc.model_dump(
mode="json", warnings="none", exclude_none=True
)
for stream_name, stream in go2rtc.get("streams", {}).items():
@@ -648,7 +648,7 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
status_code=400,
)
models: dict[any, any] = request.app.frigate_config.plus_api.get_models()
models: dict[Any, Any] = request.app.frigate_config.plus_api.get_models()
if not models["list"]:
return JSONResponse(
@@ -801,7 +801,7 @@ def hourly_timeline(params: AppTimelineHourlyQueryParameters = Depends()):
count = 0
start = 0
end = 0
hours: dict[str, list[dict[str, any]]] = {}
hours: dict[str, list[dict[str, Any]]] = {}
for t in timeline:
if count == 0:
+14 -5
View File
@@ -202,9 +202,15 @@ async def get_current_user(request: Request):
def require_role(required_roles: List[str]):
async def role_checker(request: Request):
proxy_config: ProxyConfig = request.app.frigate_config.proxy
# Get role from header (could be comma-separated)
role_header = request.headers.get("remote-role")
roles = [r.strip() for r in role_header.split(",")] if role_header else []
roles = (
[r.strip() for r in role_header.split(proxy_config.separator)]
if role_header
else []
)
# Check if we have any roles
if not roles:
@@ -261,14 +267,17 @@ def auth(request: Request):
role_header = proxy_config.header_map.role
role = (
request.headers.get(role_header, default="viewer")
request.headers.get(role_header, default=proxy_config.default_role)
if role_header
else "viewer"
else proxy_config.default_role
)
# if comma-separated with "admin", use "admin", else "viewer"
# if comma-separated with "admin", use "admin", else use default role
success_response.headers["remote-role"] = (
"admin" if role and "admin" in role else "viewer"
"admin"
if role
and "admin" in [r.strip() for r in role.split(proxy_config.separator)]
else proxy_config.default_role
)
return success_response
+6 -7
View File
@@ -1,10 +1,10 @@
"""Object classification APIs."""
import datetime
import logging
import os
import random
import shutil
import string
from typing import Any
import cv2
from fastapi import APIRouter, Depends, Request, UploadFile
@@ -59,7 +59,7 @@ def reclassify_face(request: Request, body: dict = None):
content={"message": "Face recognition is not enabled.", "success": False},
)
json: dict[str, any] = body or {}
json: dict[str, Any] = body or {}
training_file = os.path.join(
FACE_DIR, f"train/{sanitize_filename(json.get('training_file', ''))}"
)
@@ -92,7 +92,7 @@ def train_face(request: Request, name: str, body: dict = None):
content={"message": "Face recognition is not enabled.", "success": False},
)
json: dict[str, any] = body or {}
json: dict[str, Any] = body or {}
training_file_name = sanitize_filename(json.get("training_file", ""))
training_file = os.path.join(FACE_DIR, f"train/{training_file_name}")
event_id = json.get("event_id")
@@ -120,8 +120,7 @@ def train_face(request: Request, name: str, body: dict = None):
)
sanitized_name = sanitize_filename(name)
rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
new_name = f"{sanitized_name}-{rand_id}.webp"
new_name = f"{sanitized_name}-{datetime.datetime.now().timestamp()}.webp"
new_file_folder = os.path.join(FACE_DIR, f"{sanitized_name}")
if not os.path.exists(new_file_folder):
@@ -248,7 +247,7 @@ def deregister_faces(request: Request, name: str, body: dict = None):
content={"message": "Face recognition is not enabled.", "success": False},
)
json: dict[str, any] = body or {}
json: dict[str, Any] = body or {}
list_of_ids = json.get("ids", "")
context: EmbeddingsContext = request.app.embeddings
+48 -16
View File
@@ -1,5 +1,6 @@
"""Image and video apis."""
import asyncio
import glob
import logging
import math
@@ -8,6 +9,7 @@ import subprocess as sp
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path as FilePath
from typing import Any
from urllib.parse import unquote
import cv2
@@ -88,7 +90,7 @@ def imagestream(
camera_name: str,
fps: int,
height: int,
draw_options: dict[str, any],
draw_options: dict[str, Any],
):
while True:
# max out at specified FPS
@@ -110,9 +112,12 @@ def imagestream(
@router.get("/{camera_name}/ptz/info")
async def camera_ptz_info(request: Request, camera_name: str):
if camera_name in request.app.frigate_config.cameras:
return JSONResponse(
content=await request.app.onvif.get_camera_info(camera_name),
# Schedule get_camera_info in the OnvifController's event loop
future = asyncio.run_coroutine_threadsafe(
request.app.onvif.get_camera_info(camera_name), request.app.onvif.loop
)
result = future.result()
return JSONResponse(content=result)
else:
return JSONResponse(
content={"success": False, "message": "Camera not found"},
@@ -537,7 +542,10 @@ def recordings(
return JSONResponse(content=list(recordings))
@router.get("/{camera_name}/start/{start_ts}/end/{end_ts}/clip.mp4")
@router.get(
"/{camera_name}/start/{start_ts}/end/{end_ts}/clip.mp4",
description="For iOS devices, use the master.m3u8 HLS link instead of clip.mp4. Safari does not reliably process progressive mp4 files.",
)
def recording_clip(
request: Request,
camera_name: str,
@@ -585,10 +593,12 @@ def recording_clip(
clip: Recordings
for clip in recordings:
file.write(f"file '{clip.path}'\n")
# if this is the starting clip, add an inpoint
if clip.start_time < start_ts:
file.write(f"inpoint {int(start_ts - clip.start_time)}\n")
# if this is the ending clip, add an outpoint
# if this is the ending clip and end trim is enabled, add an outpoint
if clip.end_time > end_ts:
file.write(f"outpoint {int(end_ts - clip.start_time)}\n")
@@ -630,10 +640,18 @@ def recording_clip(
)
@router.get("/vod/{camera_name}/start/{start_ts}/end/{end_ts}")
@router.get(
"/vod/{camera_name}/start/{start_ts}/end/{end_ts}",
description="Returns an HLS playlist for the specified timestamp-range on the specified camera. Append /master.m3u8 or /index.m3u8 for HLS playback.",
)
def vod_ts(camera_name: str, start_ts: float, end_ts: float):
recordings = (
Recordings.select(Recordings.path, Recordings.duration, Recordings.end_time)
Recordings.select(
Recordings.path,
Recordings.duration,
Recordings.end_time,
Recordings.start_time,
)
.where(
Recordings.start_time.between(start_ts, end_ts)
| Recordings.end_time.between(start_ts, end_ts)
@@ -653,14 +671,19 @@ def vod_ts(camera_name: str, start_ts: float, end_ts: float):
clip = {"type": "source", "path": recording.path}
duration = int(recording.duration * 1000)
# Determine if we need to end the last clip early
# adjust start offset if start_ts is after recording.start_time
if start_ts > recording.start_time:
inpoint = int((start_ts - recording.start_time) * 1000)
clip["clipFrom"] = inpoint
duration -= inpoint
# adjust end if recording.end_time is after end_ts
if recording.end_time > end_ts:
duration -= int((recording.end_time - end_ts) * 1000)
if duration == 0:
# this means the segment starts right at the end of the requested time range
# and it does not need to be included
continue
if duration <= 0:
# skip if the clip has no valid duration
continue
if 0 < duration < max_duration_ms:
clip["keyFrameDurations"] = [duration]
@@ -694,7 +717,10 @@ def vod_ts(camera_name: str, start_ts: float, end_ts: float):
)
@router.get("/vod/{year_month}/{day}/{hour}/{camera_name}")
@router.get(
"/vod/{year_month}/{day}/{hour}/{camera_name}",
description="Returns an HLS playlist for the specified date-time on the specified camera. Append /master.m3u8 or /index.m3u8 for HLS playback.",
)
def vod_hour_no_timezone(year_month: str, day: int, hour: int, camera_name: str):
"""VOD for specific hour. Uses the default timezone (UTC)."""
return vod_hour(
@@ -702,7 +728,10 @@ def vod_hour_no_timezone(year_month: str, day: int, hour: int, camera_name: str)
)
@router.get("/vod/{year_month}/{day}/{hour}/{camera_name}/{tz_name}")
@router.get(
"/vod/{year_month}/{day}/{hour}/{camera_name}/{tz_name}",
description="Returns an HLS playlist for the specified date-time (with timezone) on the specified camera. Append /master.m3u8 or /index.m3u8 for HLS playback.",
)
def vod_hour(year_month: str, day: int, hour: int, camera_name: str, tz_name: str):
parts = year_month.split("-")
start_date = (
@@ -716,7 +745,10 @@ def vod_hour(year_month: str, day: int, hour: int, camera_name: str, tz_name: st
return vod_ts(camera_name, start_ts, end_ts)
@router.get("/vod/event/{event_id}")
@router.get(
"/vod/event/{event_id}",
description="Returns an HLS playlist for the specified object. Append /master.m3u8 or /index.m3u8 for HLS playback.",
)
def vod_event(event_id: str):
try:
event: Event = Event.get(Event.id == event_id)
@@ -902,7 +934,7 @@ def event_thumbnail(
elif extension == "webp":
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), 60]
_, img = cv2.imencode(f".{img}", thumbnail, quality_params)
_, img = cv2.imencode(f".{extension}", thumbnail, quality_params)
thumbnail_bytes = img.tobytes()
return Response(
+2 -1
View File
@@ -2,6 +2,7 @@
import logging
import os
from typing import Any
from cryptography.hazmat.primitives import serialization
from fastapi import APIRouter, Request
@@ -41,7 +42,7 @@ def register_notifications(request: Request, body: dict = None):
else:
username = "admin"
json: dict[str, any] = body or {}
json: dict[str, Any] = body or {}
sub = json.get("sub")
if not sub:
+14 -2
View File
@@ -6,6 +6,7 @@ import secrets
import shutil
from multiprocessing import Queue
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Optional
import psutil
@@ -44,6 +45,7 @@ from frigate.embeddings import EmbeddingsContext, manage_embeddings
from frigate.events.audio import AudioProcessor
from frigate.events.cleanup import EventCleanup
from frigate.events.maintainer import EventProcessor
from frigate.log import _stop_logging
from frigate.models import (
Event,
Export,
@@ -438,7 +440,7 @@ class FrigateApp:
def start_camera_processors(self) -> None:
for name, config in self.config.cameras.items():
if not self.config.cameras[name].enabled:
if not self.config.cameras[name].enabled_in_config:
logger.info(f"Camera processor not started for disabled camera {name}")
continue
@@ -467,7 +469,7 @@ class FrigateApp:
shm_frame_count = self.shm_frame_count()
for name, config in self.config.cameras.items():
if not self.config.cameras[name].enabled:
if not self.config.cameras[name].enabled_in_config:
logger.info(f"Capture process not started for disabled camera {name}")
continue
@@ -684,6 +686,9 @@ class FrigateApp:
def stop(self) -> None:
logger.info("Stopping...")
# used by the docker healthcheck
Path("/dev/shm/.frigate-is-stopping").touch()
self.stop_event.set()
# set an end_time on entries without an end_time before exiting
@@ -699,6 +704,10 @@ class FrigateApp:
self.audio_process.terminate()
self.audio_process.join()
# stop the onvif controller
if self.onvif_controller:
self.onvif_controller.close()
# ensure the capture processes are done
for camera, metrics in self.camera_metrics.items():
capture_process = metrics.capture_process
@@ -767,4 +776,7 @@ class FrigateApp:
shm.close()
shm.unlink()
# exit the mp Manager process
_stop_logging()
os._exit(os.EX_OK)
+6 -6
View File
@@ -1,18 +1,18 @@
"""Manage camera activity and updating listeners."""
from collections import Counter
from typing import Callable
from typing import Any, Callable
from frigate.config.config import FrigateConfig
class CameraActivityManager:
def __init__(
self, config: FrigateConfig, publish: Callable[[str, any], None]
self, config: FrigateConfig, publish: Callable[[str, Any], None]
) -> None:
self.config = config
self.publish = publish
self.last_camera_activity: dict[str, dict[str, any]] = {}
self.last_camera_activity: dict[str, dict[str, Any]] = {}
self.camera_all_object_counts: dict[str, Counter] = {}
self.camera_active_object_counts: dict[str, Counter] = {}
self.zone_all_object_counts: dict[str, Counter] = {}
@@ -39,8 +39,8 @@ class CameraActivityManager:
else camera_config.objects.track
)
def update_activity(self, new_activity: dict[str, dict[str, any]]) -> None:
all_objects: list[dict[str, any]] = []
def update_activity(self, new_activity: dict[str, dict[str, Any]]) -> None:
all_objects: list[dict[str, Any]] = []
for camera in new_activity.keys():
new_objects = new_activity[camera].get("objects", [])
@@ -93,7 +93,7 @@ class CameraActivityManager:
self.last_camera_activity = new_activity
def compare_camera_activity(
self, camera: str, new_activity: dict[str, any]
self, camera: str, new_activity: dict[str, Any]
) -> None:
all_objects = Counter(
obj["label"].replace("-verified", "") for obj in new_activity
+9 -4
View File
@@ -172,6 +172,7 @@ class CameraState:
# draw any attributes
for attribute in obj["current_attributes"]:
box = attribute["box"]
box_area = int((box[2] - box[0]) * (box[3] - box[1]))
draw_box_with_label(
frame_copy,
box[0],
@@ -179,7 +180,7 @@ class CameraState:
box[2],
box[3],
attribute["label"],
f"{attribute['score']:.0%}",
f"{attribute['score']:.0%} {str(box_area)}",
thickness=thickness,
color=color,
)
@@ -239,7 +240,7 @@ class CameraState:
self,
frame_name: str,
frame_time: float,
current_detections: dict[str, dict[str, any]],
current_detections: dict[str, dict[str, Any]],
motion_boxes: list[tuple[int, int, int, int]],
regions: list[tuple[int, int, int, int]],
):
@@ -282,9 +283,13 @@ class CameraState:
}
new_obj.thumbnail_data = thumbnail_data
tracked_objects[id].thumbnail_data = thumbnail_data
self.best_objects[new_obj.obj_data["label"]] = new_obj
object_type = new_obj.obj_data["label"]
self.best_objects[object_type] = new_obj
# call event handlers
for c in self.callbacks["snapshot"]:
c(self.name, self.best_objects[object_type], frame_name)
for c in self.callbacks["start"]:
c(self.name, new_obj, frame_name)
@@ -337,7 +342,7 @@ class CameraState:
# TODO: can i switch to looking this up and only changing when an event ends?
# maintain best objects
camera_activity: dict[str, list[any]] = {
camera_activity: dict[str, list[Any]] = {
"motion": len(motion_boxes) > 0,
"objects": [],
}
+3 -3
View File
@@ -2,7 +2,7 @@
import multiprocessing as mp
from multiprocessing.synchronize import Event as MpEvent
from typing import Optional
from typing import Any, Optional
import zmq
@@ -18,7 +18,7 @@ class ConfigPublisher:
self.socket.bind(SOCKET_PUB_SUB)
self.stop_event: MpEvent = mp.Event()
def publish(self, topic: str, payload: any) -> None:
def publish(self, topic: str, payload: Any) -> None:
"""There is no communication back to the processes."""
self.socket.send_string(topic, flags=zmq.SNDMORE)
self.socket.send_pyobj(payload)
@@ -40,7 +40,7 @@ class ConfigSubscriber:
self.socket.setsockopt_string(zmq.SUBSCRIBE, topic)
self.socket.connect(SOCKET_PUB_SUB)
def check_for_update(self) -> Optional[tuple[str, any]]:
def check_for_update(self) -> Optional[tuple[str, Any]]:
"""Returns updated config or None if no update."""
try:
topic = self.socket.recv_string(flags=zmq.NOBLOCK)
+3 -3
View File
@@ -1,7 +1,7 @@
"""Facilitates communication between processes."""
from enum import Enum
from typing import Optional
from typing import Any, Optional
from .zmq_proxy import Publisher, Subscriber
@@ -35,10 +35,10 @@ class DetectionSubscriber(Subscriber):
def check_for_update(
self, timeout: float = None
) -> Optional[tuple[DetectionTypeEnum, any]]:
) -> Optional[tuple[DetectionTypeEnum, Any]]:
return super().check_for_update(timeout)
def _return_object(self, topic: str, payload: any) -> any:
def _return_object(self, topic: str, payload: Any) -> Any:
if payload is None:
return (None, None)
return (DetectionTypeEnum[topic[len(self.topic_base) :]], payload)
+2 -2
View File
@@ -1,7 +1,7 @@
"""Facilitates communication between processes."""
from enum import Enum
from typing import Callable
from typing import Any, Callable
import zmq
@@ -58,7 +58,7 @@ class EmbeddingsRequestor:
self.socket = self.context.socket(zmq.REQ)
self.socket.connect(SOCKET_REP_REQ)
def send_data(self, topic: str, data: any) -> str:
def send_data(self, topic: str, data: Any) -> str:
"""Sends data and then waits for reply."""
try:
self.socket.send_json((topic, data))
+2 -1
View File
@@ -2,6 +2,7 @@
import logging
from enum import Enum
from typing import Any
from .zmq_proxy import Publisher, Subscriber
@@ -27,7 +28,7 @@ class EventMetadataPublisher(Publisher):
def __init__(self) -> None:
super().__init__()
def publish(self, topic: EventMetadataTypeEnum, payload: any) -> None:
def publish(self, topic: EventMetadataTypeEnum, payload: Any) -> None:
super().publish(payload, topic.value)
+4 -2
View File
@@ -1,5 +1,7 @@
"""Facilitates communication between processes."""
from typing import Any
from frigate.events.types import EventStateEnum, EventTypeEnum
from .zmq_proxy import Publisher, Subscriber
@@ -14,7 +16,7 @@ class EventUpdatePublisher(Publisher):
super().__init__("update")
def publish(
self, payload: tuple[EventTypeEnum, EventStateEnum, str, str, dict[str, any]]
self, payload: tuple[EventTypeEnum, EventStateEnum, str, str, dict[str, Any]]
) -> None:
super().publish(payload)
@@ -37,7 +39,7 @@ class EventEndPublisher(Publisher):
super().__init__("finalized")
def publish(
self, payload: tuple[EventTypeEnum, EventStateEnum, str, dict[str, any]]
self, payload: tuple[EventTypeEnum, EventStateEnum, str, dict[str, Any]]
) -> None:
super().publish(payload)
+2 -2
View File
@@ -3,7 +3,7 @@
import multiprocessing as mp
import threading
from multiprocessing.synchronize import Event as MpEvent
from typing import Callable
from typing import Any, Callable
import zmq
@@ -63,7 +63,7 @@ class InterProcessRequestor:
self.socket = self.context.socket(zmq.REQ)
self.socket.connect(SOCKET_REP_REQ)
def send_data(self, topic: str, data: any) -> any:
def send_data(self, topic: str, data: Any) -> Any:
"""Sends data and then waits for reply."""
try:
self.socket.send_json((topic, data))
+2
View File
@@ -213,6 +213,8 @@ class MqttClient(Communicator): # type: ignore[misc]
"motion_contour_area",
"birdseye",
"birdseye_mode",
"review_alerts",
"review_detections",
]
for name in self.config.cameras.keys():
+14 -3
View File
@@ -12,6 +12,7 @@ from typing import Any, Callable
from py_vapid import Vapid01
from pywebpush import WebPusher
from titlecase import titlecase
from frigate.comms.base_communicator import Communicator
from frigate.comms.config_updater import ConfigSubscriber
@@ -173,7 +174,12 @@ class WebPushClient(Communicator): # type: ignore[misc]
return
self.send_alert(decoded)
elif topic == "notification_test":
if not self.config.notifications.enabled:
if not self.config.notifications.enabled and not any(
cam.notifications.enabled for cam in self.config.cameras.values()
):
logger.debug(
"No cameras have notifications enabled, test notification not sent"
)
return
self.send_notification_test()
@@ -303,6 +309,9 @@ class WebPushClient(Communicator): # type: ignore[misc]
and len(payload["before"]["data"]["zones"])
== len(payload["after"]["data"]["zones"])
):
logger.debug(
f"Skipping notification for {camera} - message is an update and important fields don't have an update"
)
return
self.last_camera_notification_time[camera] = current_time
@@ -317,14 +326,16 @@ class WebPushClient(Communicator): # type: ignore[misc]
sorted_objects.update(payload["after"]["data"]["sub_labels"])
title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
message = f"Detected on {camera.replace('_', ' ').title()}"
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}"
message = f"Detected on {titlecase(camera.replace('_', ' '))}"
image = f"{payload['after']['thumb_path'].replace('/media/frigate', '')}"
# if event is ongoing open to live view otherwise open to recordings view
direct_url = f"/review?id={reviewId}" if state == "end" else f"/#{camera}"
ttl = 3600 if state == "end" else 0
logger.debug(f"Sending push notification for {camera}, review ID {reviewId}")
for user in self.web_pushers:
self.send_push_notification(
user=user,
+4 -4
View File
@@ -2,7 +2,7 @@
import json
import threading
from typing import Optional
from typing import Any, Optional
import zmq
@@ -58,7 +58,7 @@ class Publisher:
self.socket = self.context.socket(zmq.PUB)
self.socket.connect(SOCKET_PUB)
def publish(self, payload: any, sub_topic: str = "") -> None:
def publish(self, payload: Any, sub_topic: str = "") -> None:
"""Publish message."""
self.socket.send_string(f"{self.topic}{sub_topic} {json.dumps(payload)}")
@@ -81,7 +81,7 @@ class Subscriber:
def check_for_update(
self, timeout: float = FAST_QUEUE_TIMEOUT
) -> Optional[tuple[str, any]]:
) -> Optional[tuple[str, Any]]:
"""Returns message or None if no update."""
try:
has_update, _, _ = zmq.select([self.socket], [], [], timeout)
@@ -98,5 +98,5 @@ class Subscriber:
self.socket.close()
self.context.destroy()
def _return_object(self, topic: str, payload: any) -> any:
def _return_object(self, topic: str, payload: Any) -> Any:
return payload
+8 -2
View File
@@ -78,7 +78,13 @@ class FaceRecognitionConfig(FrigateBaseModel):
le=1.0,
)
min_area: int = Field(
default=500, title="Min area of face box to consider running face recognition."
default=750, title="Min area of face box to consider running face recognition."
)
min_faces: int = Field(
default=1,
gt=0,
le=6,
title="Min face attempts for the sub label to be applied to the person object.",
)
save_attempts: int = Field(
default=100, ge=0, title="Number of face attempts to save in the train tab."
@@ -91,7 +97,7 @@ class FaceRecognitionConfig(FrigateBaseModel):
class CameraFaceRecognitionConfig(FrigateBaseModel):
enabled: bool = Field(default=False, title="Enable face recognition.")
min_area: int = Field(
default=500, title="Min area of face box to consider running face recognition."
default=750, title="Min area of face box to consider running face recognition."
)
model_config = ConfigDict(extra="forbid", protected_namespaces=())
+48 -30
View File
@@ -299,6 +299,22 @@ def verify_motion_and_detect(camera_config: CameraConfig) -> ValueError | None:
)
def verify_objects_track(
camera_config: CameraConfig, enabled_objects: list[str]
) -> None:
"""Verify that a user has not specified an object to track that is not in the labelmap."""
valid_objects = [
obj for obj in camera_config.objects.track if obj in enabled_objects
]
if len(valid_objects) != len(camera_config.objects.track):
invalid_objects = set(camera_config.objects.track) - set(valid_objects)
logger.warning(
f"{camera_config.name} is configured to track {list(invalid_objects)} objects, which are not supported by the current model."
)
camera_config.objects.track = valid_objects
def verify_lpr_and_face(
frigate_config: FrigateConfig, camera_config: CameraConfig
) -> ValueError | None:
@@ -471,6 +487,37 @@ class FrigateConfig(FrigateBaseModel):
exclude_unset=True,
)
for key, detector in self.detectors.items():
adapter = TypeAdapter(DetectorConfig)
model_dict = (
detector
if isinstance(detector, dict)
else detector.model_dump(warnings="none")
)
detector_config: BaseDetectorConfig = adapter.validate_python(model_dict)
# users should not set model themselves
if detector_config.model:
detector_config.model = None
model_config = self.model.model_dump(exclude_unset=True, warnings="none")
if detector_config.model_path:
model_config["path"] = detector_config.model_path
if "path" not in model_config:
if detector_config.type == "cpu":
model_config["path"] = "/cpu_model.tflite"
elif detector_config.type == "edgetpu":
model_config["path"] = "/edgetpu_model.tflite"
model = ModelConfig.model_validate(model_config)
model.check_and_load_plus_model(self.plus_api, detector_config.type)
model.compute_model_hash()
labelmap_objects = model.merged_labelmap.values()
detector_config.model = model
self.detectors[key] = detector_config
for name, camera in self.cameras.items():
modified_global_config = global_config.copy()
@@ -644,6 +691,7 @@ class FrigateConfig(FrigateBaseModel):
verify_required_zones_exist(camera_config)
verify_autotrack_zones(camera_config)
verify_motion_and_detect(camera_config)
verify_objects_track(camera_config, labelmap_objects)
verify_lpr_and_face(self, camera_config)
self.objects.parse_all_objects(self.cameras)
@@ -655,36 +703,6 @@ class FrigateConfig(FrigateBaseModel):
"Frigate+ is configured but clean snapshots are not enabled, submissions to Frigate+ will not be possible./"
)
for key, detector in self.detectors.items():
adapter = TypeAdapter(DetectorConfig)
model_dict = (
detector
if isinstance(detector, dict)
else detector.model_dump(warnings="none")
)
detector_config: BaseDetectorConfig = adapter.validate_python(model_dict)
# users should not set model themselves
if detector_config.model:
detector_config.model = None
model_config = self.model.model_dump(exclude_unset=True, warnings="none")
if detector_config.model_path:
model_config["path"] = detector_config.model_path
if "path" not in model_config:
if detector_config.type == "cpu":
model_config["path"] = "/cpu_model.tflite"
elif detector_config.type == "edgetpu":
model_config["path"] = "/edgetpu_model.tflite"
model = ModelConfig.model_validate(model_config)
model.check_and_load_plus_model(self.plus_api, detector_config.type)
model.compute_model_hash()
detector_config.model = model
self.detectors[key] = detector_config
return self
@field_validator("cameras")
+15 -1
View File
@@ -1,6 +1,6 @@
from typing import Optional
from pydantic import Field
from pydantic import Field, field_validator
from .base import FrigateBaseModel
from .env import EnvString
@@ -30,3 +30,17 @@ class ProxyConfig(FrigateBaseModel):
default=None,
title="Secret value for proxy authentication.",
)
default_role: Optional[str] = Field(
default="viewer", title="Default role for proxy users."
)
separator: Optional[str] = Field(
default=",",
title="The character used to separate values in a mapped header.",
)
@field_validator("separator", mode="before")
@classmethod
def validate_separator_length(cls, v):
if v is not None and len(v) != 1:
raise ValueError("Separator must be exactly one character")
return v
@@ -10,7 +10,7 @@ import random
import re
import string
from pathlib import Path
from typing import List, Optional, Tuple
from typing import Any, List, Optional, Tuple
import cv2
import numpy as np
@@ -25,7 +25,7 @@ from frigate.comms.event_metadata_updater import (
from frigate.const import CLIPS_DIR
from frigate.embeddings.onnx.lpr_embedding import LPR_EMBEDDING_SIZE
from frigate.types import TrackedObjectUpdateTypesEnum
from frigate.util.builtin import EventsPerSecond
from frigate.util.builtin import EventsPerSecond, InferenceSpeed
from frigate.util.image import area
logger = logging.getLogger(__name__)
@@ -36,8 +36,10 @@ WRITE_DEBUG_IMAGES = False
class LicensePlateProcessingMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.plate_rec_speed = InferenceSpeed(self.metrics.alpr_speed)
self.plates_rec_second = EventsPerSecond()
self.plates_rec_second.start()
self.plate_det_speed = InferenceSpeed(self.metrics.yolov9_lpr_speed)
self.plates_det_second = EventsPerSecond()
self.plates_det_second.start()
self.event_metadata_publisher = EventMetadataPublisher()
@@ -79,7 +81,12 @@ class LicensePlateProcessingMixin:
resized_image,
)
outputs = self.model_runner.detection_model([normalized_image])[0]
try:
outputs = self.model_runner.detection_model([normalized_image])[0]
except Exception as e:
logger.warning(f"Error running LPR box detection model: {e}")
return []
outputs = outputs[0, :, :]
if False:
@@ -115,7 +122,11 @@ class LicensePlateProcessingMixin:
norm_img = norm_img[np.newaxis, :]
norm_images.append(norm_img)
outputs = self.model_runner.classification_model(norm_images)
try:
outputs = self.model_runner.classification_model(norm_images)
except Exception as e:
logger.warning(f"Error running LPR classification model: {e}")
return
return self._process_classification_output(images, outputs)
@@ -152,7 +163,10 @@ class LicensePlateProcessingMixin:
norm_image = norm_image[np.newaxis, :]
norm_images.append(norm_image)
outputs = self.model_runner.recognition_model(norm_images)
try:
outputs = self.model_runner.recognition_model(norm_images)
except Exception as e:
logger.warning(f"Error running LPR recognition model: {e}")
return self.ctc_decoder(outputs)
def _process_license_plate(
@@ -968,7 +982,11 @@ class LicensePlateProcessingMixin:
Return the dimensions of the detected plate as [x1, y1, x2, y2].
"""
predictions = self.model_runner.yolov9_detection_model(input)
try:
predictions = self.model_runner.yolov9_detection_model(input)
except Exception as e:
logger.warning(f"Error running YOLOv9 license plate detection model: {e}")
return None
confidence_threshold = self.lpr_config.detection_threshold
@@ -1141,22 +1159,6 @@ class LicensePlateProcessingMixin:
# 5. Return True if previous plate scores higher
return prev_score > curr_score
def __update_yolov9_metrics(self, duration: float) -> None:
"""
Update inference metrics.
"""
self.metrics.yolov9_lpr_speed.value = (
self.metrics.yolov9_lpr_speed.value * 9 + duration
) / 10
def __update_lpr_metrics(self, duration: float) -> None:
"""
Update inference metrics.
"""
self.metrics.alpr_speed.value = (
self.metrics.alpr_speed.value * 9 + duration
) / 10
def _generate_plate_event(self, camera: str, plate: str, plate_score: float) -> str:
"""Generate a unique ID for a plate event based on camera and text."""
now = datetime.datetime.now().timestamp()
@@ -1179,7 +1181,7 @@ class LicensePlateProcessingMixin:
return event_id
def lpr_process(
self, obj_data: dict[str, any], frame: np.ndarray, dedicated_lpr: bool = False
self, obj_data: dict[str, Any], frame: np.ndarray, dedicated_lpr: bool = False
):
"""Look for license plates in image."""
self.metrics.alpr_pps.value = self.plates_rec_second.eps()
@@ -1212,7 +1214,7 @@ class LicensePlateProcessingMixin:
f"{camera}: YOLOv9 LPD inference time: {(datetime.datetime.now().timestamp() - yolov9_start) * 1000:.2f} ms"
)
self.plates_det_second.update()
self.__update_yolov9_metrics(
self.plate_det_speed.update(
datetime.datetime.now().timestamp() - yolov9_start
)
@@ -1270,7 +1272,7 @@ class LicensePlateProcessingMixin:
)
return
license_plate: Optional[dict[str, any]] = None
license_plate: Optional[dict[str, Any]] = None
if "license_plate" not in self.config.cameras[camera].objects.track:
logger.debug(f"{camera}: Running manual license_plate detection.")
@@ -1281,6 +1283,10 @@ class LicensePlateProcessingMixin:
return
rgb = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
# apply motion mask
rgb[self.config.cameras[camera].motion.mask == 0] = [0, 0, 0]
left, top, right, bottom = car_box
car = rgb[top:bottom, left:right]
@@ -1299,7 +1305,7 @@ class LicensePlateProcessingMixin:
f"{camera}: YOLOv9 LPD inference time: {(datetime.datetime.now().timestamp() - yolov9_start) * 1000:.2f} ms"
)
self.plates_det_second.update()
self.__update_yolov9_metrics(
self.plate_det_speed.update(
datetime.datetime.now().timestamp() - yolov9_start
)
@@ -1335,7 +1341,7 @@ class LicensePlateProcessingMixin:
return
if obj_data.get("label") in ["car", "motorcycle"]:
attributes: list[dict[str, any]] = obj_data.get(
attributes: list[dict[str, Any]] = obj_data.get(
"current_attributes", []
)
for attr in attributes:
@@ -1413,7 +1419,7 @@ class LicensePlateProcessingMixin:
camera, id, license_plate_frame
)
self.plates_rec_second.update()
self.__update_lpr_metrics(datetime.datetime.now().timestamp() - start)
self.plate_rec_speed.update(datetime.datetime.now().timestamp() - start)
if license_plates:
for plate, confidence, text_area in zip(license_plates, confidences, areas):
@@ -1546,6 +1552,12 @@ class LicensePlateProcessingMixin:
(base64.b64encode(encoded_img).decode("ASCII"), id, camera),
)
if id not in self.detected_license_plates:
if camera not in self.camera_current_cars:
self.camera_current_cars[camera] = []
self.camera_current_cars[camera].append(id)
self.detected_license_plates[id] = {
"plate": top_plate,
"char_confidences": top_char_confidences,
@@ -1555,13 +1567,29 @@ class LicensePlateProcessingMixin:
"last_seen": current_time if dedicated_lpr else None,
}
def handle_request(self, topic, request_data) -> dict[str, any] | None:
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
return
def expire_object(self, object_id: str):
def lpr_expire(self, object_id: str, camera: str):
if object_id in self.detected_license_plates:
self.detected_license_plates.pop(object_id)
if object_id in self.camera_current_cars.get(camera, []):
self.camera_current_cars[camera].remove(object_id)
if len(self.camera_current_cars[camera]) == 0:
self.requestor.send_data(
"tracked_object_update",
json.dumps(
{
"type": TrackedObjectUpdateTypesEnum.lpr,
"name": None,
"plate": None,
"camera": camera,
}
),
)
class CTCDecoder:
"""
+3 -2
View File
@@ -2,6 +2,7 @@
import logging
from abc import ABC, abstractmethod
from typing import Any
from frigate.config import FrigateConfig
@@ -25,7 +26,7 @@ class PostProcessorApi(ABC):
@abstractmethod
def process_data(
self, data: dict[str, any], data_type: PostProcessDataEnum
self, data: dict[str, Any], data_type: PostProcessDataEnum
) -> None:
"""Processes the data of data type.
Args:
@@ -38,7 +39,7 @@ class PostProcessorApi(ABC):
pass
@abstractmethod
def handle_request(self, request_data: dict[str, any]) -> dict[str, any] | None:
def handle_request(self, request_data: dict[str, Any]) -> dict[str, Any] | None:
"""Handle metadata requests.
Args:
request_data (dict): containing data about requested change to process.
@@ -2,6 +2,7 @@
import datetime
import logging
from typing import Any
import cv2
import numpy as np
@@ -36,7 +37,7 @@ class LicensePlatePostProcessor(LicensePlateProcessingMixin, PostProcessorApi):
sub_label_publisher: EventMetadataPublisher,
metrics: DataProcessorMetrics,
model_runner: LicensePlateModelRunner,
detected_license_plates: dict[str, dict[str, any]],
detected_license_plates: dict[str, dict[str, Any]],
):
self.requestor = requestor
self.detected_license_plates = detected_license_plates
@@ -47,7 +48,7 @@ class LicensePlatePostProcessor(LicensePlateProcessingMixin, PostProcessorApi):
super().__init__(config, metrics, model_runner)
def process_data(
self, data: dict[str, any], data_type: PostProcessDataEnum
self, data: dict[str, Any], data_type: PostProcessDataEnum
) -> None:
"""Look for license plates in recording stream image
Args:
@@ -214,7 +215,7 @@ class LicensePlatePostProcessor(LicensePlateProcessingMixin, PostProcessorApi):
logger.debug(f"Post processing plate: {event_id}, {frame_time}")
self.lpr_process(keyframe_obj_data, frame)
def handle_request(self, topic, request_data) -> dict[str, any] | None:
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
if topic == EmbeddingsRequestEnum.reprocess_plate.value:
event = request_data["event"]
+6 -4
View File
@@ -2,6 +2,7 @@
import logging
from abc import ABC, abstractmethod
from typing import Any
import numpy as np
@@ -24,7 +25,7 @@ class RealTimeProcessorApi(ABC):
pass
@abstractmethod
def process_frame(self, obj_data: dict[str, any], frame: np.ndarray) -> None:
def process_frame(self, obj_data: dict[str, Any], frame: np.ndarray) -> None:
"""Processes the frame with object data.
Args:
obj_data (dict): containing data about focused object in frame.
@@ -37,8 +38,8 @@ class RealTimeProcessorApi(ABC):
@abstractmethod
def handle_request(
self, topic: str, request_data: dict[str, any]
) -> dict[str, any] | None:
self, topic: str, request_data: dict[str, Any]
) -> dict[str, Any] | None:
"""Handle metadata requests.
Args:
topic (str): topic that dictates what work is requested.
@@ -50,10 +51,11 @@ class RealTimeProcessorApi(ABC):
pass
@abstractmethod
def expire_object(self, object_id: str) -> None:
def expire_object(self, object_id: str, camera: str) -> None:
"""Handle objects that are no longer detected.
Args:
object_id (str): id of object that is no longer detected.
camera (str): name of camera that object was detected on.
Returns:
None.
+7 -3
View File
@@ -2,6 +2,7 @@
import logging
import os
from typing import Any
import cv2
import numpy as np
@@ -35,8 +36,8 @@ class BirdRealTimeProcessor(RealTimeProcessorApi):
super().__init__(config, metrics)
self.interpreter: Interpreter = None
self.sub_label_publisher = sub_label_publisher
self.tensor_input_details: dict[str, any] = None
self.tensor_output_details: dict[str, any] = None
self.tensor_input_details: dict[str, Any] = None
self.tensor_output_details: dict[str, Any] = None
self.detected_birds: dict[str, float] = {}
self.labelmap: dict[int, str] = {}
@@ -96,6 +97,9 @@ class BirdRealTimeProcessor(RealTimeProcessorApi):
line = f.readline()
def process_frame(self, obj_data, frame):
if not self.interpreter:
return
if obj_data["label"] != "bird":
return
@@ -152,6 +156,6 @@ class BirdRealTimeProcessor(RealTimeProcessorApi):
def handle_request(self, topic, request_data):
return None
def expire_object(self, object_id):
def expire_object(self, object_id, camera):
if object_id in self.detected_birds:
self.detected_birds.pop(object_id)
+56 -19
View File
@@ -5,10 +5,8 @@ import datetime
import json
import logging
import os
import random
import shutil
import string
from typing import Optional
from typing import Any, Optional
import cv2
import numpy as np
@@ -27,7 +25,7 @@ from frigate.data_processing.common.face.model import (
FaceRecognizer,
)
from frigate.types import TrackedObjectUpdateTypesEnum
from frigate.util.builtin import EventsPerSecond
from frigate.util.builtin import EventsPerSecond, InferenceSpeed
from frigate.util.image import area
from ..types import DataProcessorMetrics
@@ -56,8 +54,10 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
self.face_detector: cv2.FaceDetectorYN = None
self.requires_face_detection = "face" not in self.config.objects.all_objects
self.person_face_history: dict[str, list[tuple[str, float, int]]] = {}
self.camera_current_people: dict[str, list[str]] = {}
self.recognizer: FaceRecognizer | None = None
self.faces_per_second = EventsPerSecond()
self.inference_speed = InferenceSpeed(self.metrics.face_rec_speed)
download_path = os.path.join(MODEL_CACHE_DIR, "facedet")
self.model_files = {
@@ -155,11 +155,9 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
def __update_metrics(self, duration: float) -> None:
self.faces_per_second.update()
self.metrics.face_rec_speed.value = (
self.metrics.face_rec_speed.value * 9 + duration
) / 10
self.inference_speed.update(duration)
def process_frame(self, obj_data: dict[str, any], frame: np.ndarray):
def process_frame(self, obj_data: dict[str, Any], frame: np.ndarray):
"""Look for faces in image."""
self.metrics.face_rec_fps.value = self.faces_per_second.eps()
camera = obj_data["camera"]
@@ -200,7 +198,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
logger.debug("Not processing due to hitting max rec attempts.")
return
face: Optional[dict[str, any]] = None
face: Optional[dict[str, Any]] = None
if self.requires_face_detection:
logger.debug("Running manual face detection.")
@@ -223,6 +221,13 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
max(0, face_box[0]) : min(frame.shape[1], face_box[2]),
]
# check that face is correct size
if area(face_box) < self.config.cameras[camera].face_recognition.min_area:
logger.debug(
f"Detected face that is smaller than the min_area {face} < {self.config.cameras[camera].face_recognition.min_area}"
)
return
try:
face_frame = cv2.cvtColor(face_frame, cv2.COLOR_RGB2BGR)
except Exception:
@@ -233,7 +238,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
logger.debug("No attributes to parse.")
return
attributes: list[dict[str, any]] = obj_data.get("current_attributes", [])
attributes: list[dict[str, Any]] = obj_data.get("current_attributes", [])
for attr in attributes:
if attr.get("label") != "face":
continue
@@ -285,6 +290,11 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
if id not in self.person_face_history:
self.person_face_history[id] = []
if camera not in self.camera_current_people:
self.camera_current_people[camera] = []
self.camera_current_people[camera].append(id)
self.person_face_history[id].append(
(sub_label, score, face_frame.shape[0] * face_frame.shape[1])
)
@@ -292,6 +302,9 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
self.person_face_history[id]
)
if len(self.person_face_history[id]) < self.face_config.min_faces:
weighted_sub_label = "unknown"
self.requestor.send_data(
"tracked_object_update",
json.dumps(
@@ -314,7 +327,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
self.__update_metrics(datetime.datetime.now().timestamp() - start)
def handle_request(self, topic, request_data) -> dict[str, any] | None:
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
if topic == EmbeddingsRequestEnum.clear_face_classifier.value:
self.recognizer.clear()
elif topic == EmbeddingsRequestEnum.recognize_face.value:
@@ -343,11 +356,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
return {"success": True, "score": score, "face_name": sub_label}
elif topic == EmbeddingsRequestEnum.register_face.value:
rand_id = "".join(
random.choices(string.ascii_lowercase + string.digits, k=6)
)
label = request_data["face_name"]
id = f"{label}-{rand_id}"
if request_data.get("cropped"):
thumbnail = request_data["image"]
@@ -376,7 +385,9 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
# write face to library
folder = os.path.join(FACE_DIR, label)
file = os.path.join(folder, f"{id}.webp")
file = os.path.join(
folder, f"{label}_{datetime.datetime.now().timestamp()}.webp"
)
os.makedirs(folder, exist_ok=True)
# save face image
@@ -425,10 +436,25 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
)
shutil.move(current_file, new_file)
def expire_object(self, object_id: str):
def expire_object(self, object_id: str, camera: str):
if object_id in self.person_face_history:
self.person_face_history.pop(object_id)
if object_id in self.camera_current_people.get(camera, []):
self.camera_current_people[camera].remove(object_id)
if len(self.camera_current_people[camera]) == 0:
self.requestor.send_data(
"tracked_object_update",
json.dumps(
{
"type": TrackedObjectUpdateTypesEnum.face,
"name": None,
"camera": camera,
}
),
)
def weighted_average(
self, results_list: list[tuple[str, float, int]], max_weight: int = 4000
):
@@ -445,17 +471,22 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
if not results_list:
return None, 0.0
weighted_scores = {}
total_weights = {}
counts: dict[str, int] = {}
weighted_scores: dict[str, int] = {}
total_weights: dict[str, int] = {}
for name, score, face_area in results_list:
if name == "unknown":
continue
if name not in weighted_scores:
counts[name] = 0
weighted_scores[name] = 0.0
total_weights[name] = 0.0
# increase count
counts[name] += 1
# Capped weight based on face area
weight = min(face_area, max_weight)
@@ -468,6 +499,12 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
return None, 0.0
best_name = max(weighted_scores, key=weighted_scores.get)
# If the best name has the same number of results as another name, we are not confident it is a correct result
for name, count in counts.items():
if name != best_name and counts[best_name] == count:
return None, 0.0
weighted_average = weighted_scores[best_name] / total_weights[best_name]
return best_name, weighted_average
@@ -1,6 +1,7 @@
"""Handle processing images for face detection and recognition."""
import logging
from typing import Any
import numpy as np
@@ -28,7 +29,7 @@ class LicensePlateRealTimeProcessor(LicensePlateProcessingMixin, RealTimeProcess
sub_label_publisher: EventMetadataPublisher,
metrics: DataProcessorMetrics,
model_runner: LicensePlateModelRunner,
detected_license_plates: dict[str, dict[str, any]],
detected_license_plates: dict[str, dict[str, Any]],
):
self.requestor = requestor
self.detected_license_plates = detected_license_plates
@@ -36,20 +37,21 @@ class LicensePlateRealTimeProcessor(LicensePlateProcessingMixin, RealTimeProcess
self.lpr_config = config.lpr
self.config = config
self.sub_label_publisher = sub_label_publisher
self.camera_current_cars: dict[str, list[str]] = {}
super().__init__(config, metrics)
def process_frame(
self,
obj_data: dict[str, any],
obj_data: dict[str, Any],
frame: np.ndarray,
dedicated_lpr: bool | None = False,
):
"""Look for license plates in image."""
self.lpr_process(obj_data, frame, dedicated_lpr)
def handle_request(self, topic, request_data) -> dict[str, any] | None:
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
return
def expire_object(self, object_id: str):
if object_id in self.detected_license_plates:
self.detected_license_plates.pop(object_id)
def expire_object(self, object_id: str, camera: str):
"""Expire lpr objects."""
self.lpr_expire(object_id, camera)
+7 -5
View File
@@ -7,7 +7,9 @@ from multiprocessing.sharedctypes import Synchronized
class DataProcessorMetrics:
image_embeddings_speed: Synchronized
image_embeddings_eps: Synchronized
text_embeddings_speed: Synchronized
text_embeddings_eps: Synchronized
face_rec_speed: Synchronized
face_rec_fps: Synchronized
alpr_speed: Synchronized
@@ -16,15 +18,15 @@ class DataProcessorMetrics:
yolov9_lpr_pps: Synchronized
def __init__(self):
self.image_embeddings_speed = mp.Value("d", 0.01)
self.image_embeddings_speed = mp.Value("d", 0.0)
self.image_embeddings_eps = mp.Value("d", 0.0)
self.text_embeddings_speed = mp.Value("d", 0.01)
self.text_embeddings_speed = mp.Value("d", 0.0)
self.text_embeddings_eps = mp.Value("d", 0.0)
self.face_rec_speed = mp.Value("d", 0.01)
self.face_rec_speed = mp.Value("d", 0.0)
self.face_rec_fps = mp.Value("d", 0.0)
self.alpr_speed = mp.Value("d", 0.01)
self.alpr_speed = mp.Value("d", 0.0)
self.alpr_pps = mp.Value("d", 0.0)
self.yolov9_lpr_speed = mp.Value("d", 0.01)
self.yolov9_lpr_speed = mp.Value("d", 0.0)
self.yolov9_lpr_pps = mp.Value("d", 0.0)
+5 -2
View File
@@ -3,7 +3,7 @@ import json
import logging
import os
from enum import Enum
from typing import Dict, Optional, Tuple
from typing import Any, Dict, Optional, Tuple
import requests
from pydantic import BaseModel, ConfigDict, Field
@@ -126,6 +126,9 @@ class ModelConfig(BaseModel):
if not self.path or not self.path.startswith("plus://"):
return
# ensure that model cache dir exists
os.makedirs(MODEL_CACHE_DIR, exist_ok=True)
model_id = self.path[7:]
self.path = os.path.join(MODEL_CACHE_DIR, model_id)
model_info_path = f"{self.path}.json"
@@ -144,7 +147,7 @@ class ModelConfig(BaseModel):
json.dump(model_info, f)
else:
with open(model_info_path, "r") as f:
model_info: dict[str, any] = json.load(f)
model_info: dict[str, Any] = json.load(f)
if detector and detector not in model_info["supportedDetectors"]:
raise ValueError(f"Model does not support detector type of {detector}")
+27 -25
View File
@@ -8,17 +8,6 @@ from typing import Dict, List, Optional, Tuple
import cv2
import numpy as np
try:
from hailo_platform import (
HEF,
FormatType,
HailoSchedulingAlgorithm,
VDevice,
)
except ModuleNotFoundError:
pass
from pydantic import Field
from typing_extensions import Literal
@@ -102,6 +91,18 @@ class HailoAsyncInference:
output_type: Optional[Dict[str, str]] = None,
send_original_frame: bool = False,
) -> None:
# when importing hailo it activates the driver
# which leaves processes running even though it may not be used.
try:
from hailo_platform import (
HEF,
FormatType,
HailoSchedulingAlgorithm,
VDevice,
)
except ModuleNotFoundError:
pass
self.input_store = input_store
self.output_store = output_store
@@ -112,24 +113,19 @@ class HailoAsyncInference:
self.target = VDevice(params)
self.infer_model = self.target.create_infer_model(hef_path)
self.infer_model.set_batch_size(batch_size)
if input_type is not None:
self._set_input_type(input_type)
self.infer_model.input().set_format_type(getattr(FormatType, input_type))
if output_type is not None:
self._set_output_type(output_type)
for output_name, output_type in output_type.items():
self.infer_model.output(output_name).set_format_type(
getattr(FormatType, output_type)
)
self.output_type = output_type
self.send_original_frame = send_original_frame
def _set_input_type(self, input_type: Optional[str] = None) -> None:
self.infer_model.input().set_format_type(getattr(FormatType, input_type))
def _set_output_type(
self, output_type_dict: Optional[Dict[str, str]] = None
) -> None:
for output_name, output_type in output_type_dict.items():
self.infer_model.output(output_name).set_format_type(
getattr(FormatType, output_type)
)
def callback(
self,
completion_info,
@@ -349,11 +345,17 @@ class HailoDetector(DetectionApi):
request_id = self.input_store.put(tensor_input)
try:
_, infer_results = self.response_store.get(request_id, timeout=10.0)
_, infer_results = self.response_store.get(request_id, timeout=1.0)
except TimeoutError:
logger.error(
f"Timeout waiting for inference results for request {request_id}"
)
if not self.inference_thread.is_alive():
raise RuntimeError(
"HailoRT inference thread has stopped, restart required."
)
return np.zeros((20, 6), dtype=np.float32)
if isinstance(infer_results, list) and len(infer_results) == 1:
-6
View File
@@ -3,11 +3,9 @@ import os
import numpy as np
import openvino as ov
import openvino.properties as props
from pydantic import Field
from typing_extensions import Literal
from frigate.const import MODEL_CACHE_DIR
from frigate.detectors.detection_api import DetectionApi
from frigate.detectors.detector_config import BaseDetectorConfig, ModelTypeEnum
from frigate.util.model import (
@@ -49,10 +47,6 @@ class OvDetector(DetectionApi):
logger.error(f"OpenVino model file {detector_config.model.path} not found.")
raise FileNotFoundError
os.makedirs(os.path.join(MODEL_CACHE_DIR, "openvino"), exist_ok=True)
self.ov_core.set_property(
{props.cache_dir: os.path.join(MODEL_CACHE_DIR, "openvino")}
)
self.interpreter = self.ov_core.compile_model(
model=detector_config.model.path, device_name=detector_config.device
)
+11 -11
View File
@@ -5,12 +5,12 @@ import json
import logging
import multiprocessing as mp
import os
import re
import signal
import threading
from types import FrameType
from typing import Optional, Union
from typing import Any, Optional, Union
import regex
from pathvalidate import ValidationError, sanitize_filename
from setproctitle import setproctitle
@@ -190,7 +190,7 @@ class EmbeddingsContext:
return results
def register_face(self, face_name: str, image_data: bytes) -> dict[str, any]:
def register_face(self, face_name: str, image_data: bytes) -> dict[str, Any]:
return self.requestor.send_data(
EmbeddingsRequestEnum.register_face.value,
{
@@ -199,7 +199,7 @@ class EmbeddingsContext:
},
)
def recognize_face(self, image_data: bytes) -> dict[str, any]:
def recognize_face(self, image_data: bytes) -> dict[str, Any]:
return self.requestor.send_data(
EmbeddingsRequestEnum.recognize_face.value,
{
@@ -217,7 +217,7 @@ class EmbeddingsContext:
return self.db.execute_sql(sql_query).fetchall()
def reprocess_face(self, face_file: str) -> dict[str, any]:
def reprocess_face(self, face_file: str) -> dict[str, Any]:
return self.requestor.send_data(
EmbeddingsRequestEnum.reprocess_face.value, {"image_file": face_file}
)
@@ -235,7 +235,7 @@ class EmbeddingsContext:
if os.path.isfile(file_path):
os.unlink(file_path)
if len(os.listdir(folder)) == 0:
if face != "train" and len(os.listdir(folder)) == 0:
os.rmdir(folder)
self.requestor.send_data(
@@ -243,7 +243,7 @@ class EmbeddingsContext:
)
def rename_face(self, old_name: str, new_name: str) -> None:
valid_name_pattern = r"^[a-zA-Z0-9\s_-]{1,50}$"
valid_name_pattern = r"^[\p{L}\p{N}\s'_-]{1,50}$"
try:
sanitized_old_name = sanitize_filename(old_name, replacement_text="_")
@@ -251,9 +251,9 @@ class EmbeddingsContext:
except ValidationError as e:
raise ValueError(f"Invalid face name: {str(e)}")
if not re.match(valid_name_pattern, old_name):
if not regex.match(valid_name_pattern, old_name):
raise ValueError(f"Invalid old face name: {old_name}")
if not re.match(valid_name_pattern, new_name):
if not regex.match(valid_name_pattern, new_name):
raise ValueError(f"Invalid new face name: {new_name}")
if sanitized_old_name != old_name:
raise ValueError(f"Old face name contains invalid characters: {old_name}")
@@ -284,10 +284,10 @@ class EmbeddingsContext:
{"id": event_id, "description": description},
)
def reprocess_plate(self, event: dict[str, any]) -> dict[str, any]:
def reprocess_plate(self, event: dict[str, Any]) -> dict[str, Any]:
return self.requestor.send_data(
EmbeddingsRequestEnum.reprocess_plate.value, {"event": event}
)
def reindex_embeddings(self) -> dict[str, any]:
def reindex_embeddings(self) -> dict[str, Any]:
return self.requestor.send_data(EmbeddingsRequestEnum.reindex.value, {})
+7 -16
View File
@@ -21,7 +21,7 @@ from frigate.data_processing.types import DataProcessorMetrics
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.models import Event
from frigate.types import ModelStatusTypesEnum
from frigate.util.builtin import EventsPerSecond, serialize
from frigate.util.builtin import EventsPerSecond, InferenceSpeed, serialize
from frigate.util.path import get_event_thumbnail_bytes
from .onnx.jina_v1_embedding import JinaV1ImageEmbedding, JinaV1TextEmbedding
@@ -75,8 +75,10 @@ class Embeddings:
self.metrics = metrics
self.requestor = InterProcessRequestor()
self.image_inference_speed = InferenceSpeed(self.metrics.image_embeddings_speed)
self.image_eps = EventsPerSecond()
self.image_eps.start()
self.text_inference_speed = InferenceSpeed(self.metrics.text_embeddings_speed)
self.text_eps = EventsPerSecond()
self.text_eps.start()
@@ -183,10 +185,7 @@ class Embeddings:
(event_id, serialize(embedding)),
)
duration = datetime.datetime.now().timestamp() - start
self.metrics.image_embeddings_speed.value = (
self.metrics.image_embeddings_speed.value * 9 + duration
) / 10
self.image_inference_speed.update(datetime.datetime.now().timestamp() - start)
self.image_eps.update()
return embedding
@@ -220,9 +219,7 @@ class Embeddings:
)
duration = datetime.datetime.now().timestamp() - start
self.metrics.text_embeddings_speed.value = (
self.metrics.text_embeddings_speed.value * 9 + (duration / len(ids))
) / 10
self.text_inference_speed.update(duration / len(ids))
return embeddings
@@ -241,10 +238,7 @@ class Embeddings:
(event_id, serialize(embedding)),
)
duration = datetime.datetime.now().timestamp() - start
self.metrics.text_embeddings_speed.value = (
self.metrics.text_embeddings_speed.value * 9 + duration
) / 10
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
self.text_eps.update()
return embedding
@@ -276,10 +270,7 @@ class Embeddings:
items,
)
duration = datetime.datetime.now().timestamp() - start
self.metrics.text_embeddings_speed.value = (
self.metrics.text_embeddings_speed.value * 9 + (duration / len(ids))
) / 10
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
return embeddings
+5 -5
View File
@@ -7,7 +7,7 @@ import os
import threading
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Optional
from typing import Any, Optional
import cv2
import numpy as np
@@ -104,7 +104,7 @@ class EmbeddingMaintainer(threading.Thread):
self.embeddings_responder = EmbeddingsResponder()
self.frame_manager = SharedMemoryFrameManager()
self.detected_license_plates: dict[str, dict[str, any]] = {}
self.detected_license_plates: dict[str, dict[str, Any]] = {}
# model runners to share between realtime and post processors
if self.config.lpr.enabled:
@@ -159,7 +159,7 @@ class EmbeddingMaintainer(threading.Thread):
)
self.stop_event = stop_event
self.tracked_events: dict[str, list[any]] = {}
self.tracked_events: dict[str, list[Any]] = {}
self.early_request_sent: dict[str, bool] = {}
self.genai_client = get_genai_client(config)
@@ -190,7 +190,7 @@ class EmbeddingMaintainer(threading.Thread):
def _process_requests(self) -> None:
"""Process embeddings requests"""
def _handle_request(topic: str, data: dict[str, any]) -> str:
def _handle_request(topic: str, data: dict[str, Any]) -> str:
try:
# First handle the embedding-specific topics when semantic search is enabled
if self.config.semantic_search.enabled:
@@ -359,7 +359,7 @@ class EmbeddingMaintainer(threading.Thread):
# expire in realtime processors
for processor in self.realtime_processors:
processor.expire_object(event_id)
processor.expire_object(event_id, camera)
if updated_db:
try:
+4 -3
View File
@@ -5,6 +5,7 @@ import os
from abc import ABC, abstractmethod
from enum import Enum
from io import BytesIO
from typing import Any
import numpy as np
import requests
@@ -59,7 +60,7 @@ class BaseEmbedding(ABC):
pass
@abstractmethod
def _preprocess_inputs(self, raw_inputs: any) -> any:
def _preprocess_inputs(self, raw_inputs: Any) -> Any:
pass
def _process_image(self, image, output: str = "RGB") -> Image.Image:
@@ -74,7 +75,7 @@ class BaseEmbedding(ABC):
return image
def _postprocess_outputs(self, outputs: any) -> any:
def _postprocess_outputs(self, outputs: Any) -> Any:
return outputs
def __call__(
@@ -84,7 +85,7 @@ class BaseEmbedding(ABC):
processed = self._preprocess_inputs(inputs)
input_names = self.runner.get_input_names()
onnx_inputs = {name: [] for name in input_names}
input: dict[str, any]
input: dict[str, Any]
for input in processed:
for key, value in input.items():
if key in input_names:
+3 -11
View File
@@ -23,10 +23,7 @@ FACENET_INPUT_SIZE = 160
class FaceNetEmbedding(BaseEmbedding):
def __init__(
self,
device: str = "AUTO",
):
def __init__(self):
super().__init__(
model_name="facedet",
model_file="facenet.tflite",
@@ -34,7 +31,6 @@ class FaceNetEmbedding(BaseEmbedding):
"facenet.tflite": "https://github.com/NickM-27/facenet-onnx/releases/download/v1.0/facenet.tflite",
},
)
self.device = device
self.download_path = os.path.join(MODEL_CACHE_DIR, self.model_name)
self.tokenizer = None
self.feature_extractor = None
@@ -113,10 +109,7 @@ class FaceNetEmbedding(BaseEmbedding):
class ArcfaceEmbedding(BaseEmbedding):
def __init__(
self,
device: str = "AUTO",
):
def __init__(self):
super().__init__(
model_name="facedet",
model_file="arcface.onnx",
@@ -124,7 +117,6 @@ class ArcfaceEmbedding(BaseEmbedding):
"arcface.onnx": "https://github.com/NickM-27/facenet-onnx/releases/download/v1.0/arcface.onnx",
},
)
self.device = device
self.download_path = os.path.join(MODEL_CACHE_DIR, self.model_name)
self.tokenizer = None
self.feature_extractor = None
@@ -154,7 +146,7 @@ class ArcfaceEmbedding(BaseEmbedding):
self.runner = ONNXModelRunner(
os.path.join(self.download_path, self.model_file),
self.device,
"GPU",
)
def _preprocess_inputs(self, raw_inputs):
+5 -3
View File
@@ -36,11 +36,12 @@ class JinaV1TextEmbedding(BaseEmbedding):
requestor: InterProcessRequestor,
device: str = "AUTO",
):
HF_ENDPOINT = os.environ.get("HF_ENDPOINT", "https://huggingface.co")
super().__init__(
model_name="jinaai/jina-clip-v1",
model_file="text_model_fp16.onnx",
download_urls={
"text_model_fp16.onnx": "https://huggingface.co/jinaai/jina-clip-v1/resolve/main/onnx/text_model_fp16.onnx",
"text_model_fp16.onnx": f"{HF_ENDPOINT}/jinaai/jina-clip-v1/resolve/main/onnx/text_model_fp16.onnx",
},
)
self.tokenizer_file = "tokenizer"
@@ -156,12 +157,13 @@ class JinaV1ImageEmbedding(BaseEmbedding):
if model_size == "large"
else "vision_model_quantized.onnx"
)
HF_ENDPOINT = os.environ.get("HF_ENDPOINT", "https://huggingface.co")
super().__init__(
model_name="jinaai/jina-clip-v1",
model_file=model_file,
download_urls={
model_file: f"https://huggingface.co/jinaai/jina-clip-v1/resolve/main/onnx/{model_file}",
"preprocessor_config.json": "https://huggingface.co/jinaai/jina-clip-v1/resolve/main/preprocessor_config.json",
model_file: f"{HF_ENDPOINT}/jinaai/jina-clip-v1/resolve/main/onnx/{model_file}",
"preprocessor_config.json": f"{HF_ENDPOINT}/jinaai/jina-clip-v1/resolve/main/preprocessor_config.json",
},
)
self.requestor = requestor
+3 -2
View File
@@ -34,12 +34,13 @@ class JinaV2Embedding(BaseEmbedding):
model_file = (
"model_fp16.onnx" if model_size == "large" else "model_quantized.onnx"
)
HF_ENDPOINT = os.environ.get("HF_ENDPOINT", "https://huggingface.co")
super().__init__(
model_name="jinaai/jina-clip-v2",
model_file=model_file,
download_urls={
model_file: f"https://huggingface.co/jinaai/jina-clip-v2/resolve/main/onnx/{model_file}",
"preprocessor_config.json": "https://huggingface.co/jinaai/jina-clip-v2/resolve/main/preprocessor_config.json",
model_file: f"{HF_ENDPOINT}/jinaai/jina-clip-v2/resolve/main/onnx/{model_file}",
"preprocessor_config.json": f"{HF_ENDPOINT}/jinaai/jina-clip-v2/resolve/main/preprocessor_config.json",
},
)
self.tokenizer_file = "tokenizer"
+2 -2
View File
@@ -6,7 +6,7 @@ import random
import string
import threading
import time
from typing import Tuple
from typing import Any, Tuple
import numpy as np
@@ -126,7 +126,7 @@ class AudioEventMaintainer(threading.Thread):
self.config = camera
self.camera_metrics = camera_metrics
self.detections: dict[dict[str, any]] = {}
self.detections: dict[dict[str, Any]] = {}
self.stop_event = stop_event
self.detector = AudioTfl(stop_event, self.config.audio.num_threads)
self.shape = (int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE)),)
+2 -1
View File
@@ -6,6 +6,7 @@ import os
import threading
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Any
from frigate.config import FrigateConfig
from frigate.const import CLIPS_DIR
@@ -29,7 +30,7 @@ class EventCleanup(threading.Thread):
self.db = db
self.camera_keys = list(self.config.cameras.keys())
self.removed_camera_labels: list[str] = None
self.camera_labels: dict[str, dict[str, any]] = {}
self.camera_labels: dict[str, dict[str, Any]] = {}
def get_removed_camera_labels(self) -> list[Event]:
"""Get a list of distinct labels for removed cameras."""
+11 -5
View File
@@ -1,3 +1,4 @@
# In log.py
import atexit
import logging
import multiprocessing as mp
@@ -6,6 +7,7 @@ import sys
import threading
from collections import deque
from logging.handlers import QueueHandler, QueueListener
from queue import Queue
from typing import Deque, Optional
from frigate.util.builtin import clean_camera_user_pass
@@ -32,12 +34,14 @@ LOG_HANDLER.addFilter(
)
log_listener: Optional[QueueListener] = None
log_queue: Optional[Queue] = None
manager = None
def setup_logging() -> None:
global log_listener
log_queue: mp.Queue = mp.Queue()
global log_listener, log_queue, manager
manager = mp.Manager()
log_queue = manager.Queue()
log_listener = QueueListener(log_queue, LOG_HANDLER, respect_handler_level=True)
atexit.register(_stop_logging)
@@ -53,11 +57,13 @@ def setup_logging() -> None:
def _stop_logging() -> None:
global log_listener
global log_listener, manager
if log_listener is not None:
log_listener.stop()
log_listener = None
if manager is not None:
manager.shutdown()
manager = None
# When a multiprocessing.Process exits, python tries to flush stdout and stderr. However, if the
+5 -5
View File
@@ -10,7 +10,7 @@ import queue
import subprocess as sp
import threading
import traceback
from typing import Optional
from typing import Any, Optional
import cv2
import numpy as np
@@ -542,10 +542,10 @@ class BirdsEyeFrameManager:
self,
cameras_to_add: list[str],
coefficient: float,
) -> tuple[any]:
) -> tuple[Any]:
"""Calculate the optimal layout for 2+ cameras."""
def map_layout(camera_layout: list[list[any]], row_height: int):
def map_layout(camera_layout: list[list[Any]], row_height: int):
"""Map the calculated layout."""
candidate_layout = []
starting_x = 0
@@ -588,7 +588,7 @@ class BirdsEyeFrameManager:
return max_width, y, candidate_layout
canvas_aspect_x, canvas_aspect_y = self.canvas.get_aspect(coefficient)
camera_layout: list[list[any]] = []
camera_layout: list[list[Any]] = []
camera_layout.append([])
starting_x = 0
x = starting_x
@@ -786,7 +786,7 @@ class Birdseye:
def write_data(
self,
camera: str,
current_tracked_objects: list[dict[str, any]],
current_tracked_objects: list[dict[str, Any]],
motion_boxes: list[list[int]],
frame_time: float,
frame: np.ndarray,
+1 -1
View File
@@ -111,7 +111,7 @@ def output_frames(
move_preview_frames("cache")
for camera, cam_config in config.cameras.items():
if not cam_config.enabled:
if not cam_config.enabled_in_config:
continue
jsmpeg_cameras[camera] = JsmpegCamera(cam_config, stop_event, websocket_server)
+4 -2
View File
@@ -8,6 +8,7 @@ import subprocess as sp
import threading
import time
from pathlib import Path
from typing import Any
import cv2
import numpy as np
@@ -255,7 +256,7 @@ class PreviewRecorder:
def should_write_frame(
self,
current_tracked_objects: list[dict[str, any]],
current_tracked_objects: list[dict[str, Any]],
motion_boxes: list[list[int]],
frame_time: float,
) -> bool:
@@ -315,7 +316,7 @@ class PreviewRecorder:
def write_data(
self,
current_tracked_objects: list[dict[str, any]],
current_tracked_objects: list[dict[str, Any]],
motion_boxes: list[list[int]],
frame_time: float,
frame: np.ndarray,
@@ -402,6 +403,7 @@ class PreviewRecorder:
self.reset_frame_cache(frame_time)
def stop(self) -> None:
self.config_subscriber.stop()
self.requestor.stop()
+80 -63
View File
@@ -3,12 +3,11 @@
import asyncio
import copy
import logging
import queue
import threading
import time
from collections import deque
from functools import partial
from multiprocessing.synchronize import Event as MpEvent
from typing import Any
import cv2
import numpy as np
@@ -61,7 +60,7 @@ class PtzMotionEstimator:
def motion_estimator(
self,
detections: list[dict[str, any]],
detections: list[dict[str, Any]],
frame_name: str,
frame_time: float,
camera: str,
@@ -169,7 +168,12 @@ class PtzAutoTrackerThread(threading.Thread):
continue
if camera_config.onvif.autotracking.enabled:
self.ptz_autotracker.camera_maintenance(camera)
future = asyncio.run_coroutine_threadsafe(
self.ptz_autotracker.camera_maintenance(camera),
self.ptz_autotracker.onvif.loop,
)
# Wait for the coroutine to complete
future.result()
else:
# disabled dynamically by mqtt
if self.ptz_autotracker.tracked_object.get(camera):
@@ -219,9 +223,13 @@ class PtzAutoTracker:
camera_config.onvif.autotracking.enabled
and camera_config.onvif.autotracking.enabled_in_config
):
self._autotracker_setup(camera_config, camera)
future = asyncio.run_coroutine_threadsafe(
self._autotracker_setup(camera_config, camera), self.onvif.loop
)
# Wait for the coroutine to complete
future.result()
def _autotracker_setup(self, camera_config: CameraConfig, camera: str):
async def _autotracker_setup(self, camera_config: CameraConfig, camera: str):
logger.debug(f"{camera}: Autotracker init")
self.object_types[camera] = camera_config.onvif.autotracking.track
@@ -242,8 +250,8 @@ class PtzAutoTracker:
self.intercept[camera] = None
self.move_coefficients[camera] = []
self.move_queues[camera] = queue.Queue()
self.move_queue_locks[camera] = threading.Lock()
self.move_queues[camera] = asyncio.Queue()
self.move_queue_locks[camera] = asyncio.Lock()
# handle onvif constructor failing due to no connection
if camera not in self.onvif.cams:
@@ -255,7 +263,7 @@ class PtzAutoTracker:
return
if not self.onvif.cams[camera]["init"]:
if not asyncio.run(self.onvif._init_onvif(camera)):
if not await self.onvif._init_onvif(camera):
logger.warning(
f"Disabling autotracking for {camera}: Unable to initialize onvif"
)
@@ -271,7 +279,7 @@ class PtzAutoTracker:
self.ptz_metrics[camera].autotracker_enabled.value = False
return
move_status_supported = self.onvif.get_service_capabilities(camera)
move_status_supported = await self.onvif.get_service_capabilities(camera)
if not (
isinstance(move_status_supported, bool) and move_status_supported
@@ -287,15 +295,12 @@ class PtzAutoTracker:
return
if self.onvif.cams[camera]["init"]:
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
# movement thread per camera
self.move_threads[camera] = threading.Thread(
name=f"ptz_move_thread_{camera}",
target=partial(self._process_move_queue, camera),
# movement queue with asyncio on OnvifController loop
asyncio.run_coroutine_threadsafe(
self._process_move_queue(camera), self.onvif.loop
)
self.move_threads[camera].daemon = True
self.move_threads[camera].start()
if camera_config.onvif.autotracking.movement_weights:
if len(camera_config.onvif.autotracking.movement_weights) == 6:
@@ -330,7 +335,7 @@ class PtzAutoTracker:
)
if camera_config.onvif.autotracking.calibrate_on_startup:
self._calibrate_camera(camera)
await self._calibrate_camera(camera)
self.ptz_metrics[camera].tracking_active.clear()
self.dispatcher.publish(f"{camera}/ptz_autotracker/active", "OFF", retain=False)
@@ -349,7 +354,7 @@ class PtzAutoTracker:
self.config.cameras[camera].onvif.autotracking.movement_weights,
)
def _calibrate_camera(self, camera):
async def _calibrate_camera(self, camera):
# move the camera from the preset in steps and measure the time it takes to move that amount
# this will allow us to predict movement times with a simple linear regression
# start with 0 so we can determine a baseline (to be used as the intercept in the regression calc)
@@ -373,25 +378,25 @@ class PtzAutoTracker:
for i in range(2):
# absolute move to 0 - fully zoomed out
self.onvif._zoom_absolute(
await self.onvif._zoom_absolute(
camera,
self.onvif.cams[camera]["absolute_zoom_range"]["XRange"]["Min"],
1,
)
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
zoom_out_values.append(self.ptz_metrics[camera].zoom_level.value)
self.onvif._zoom_absolute(
await self.onvif._zoom_absolute(
camera,
self.onvif.cams[camera]["absolute_zoom_range"]["XRange"]["Max"],
1,
)
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
zoom_in_values.append(self.ptz_metrics[camera].zoom_level.value)
@@ -400,7 +405,7 @@ class PtzAutoTracker:
== ZoomingModeEnum.relative
):
# relative move to -0.01
self.onvif._move_relative(
await self.onvif._move_relative(
camera,
0,
0,
@@ -409,13 +414,13 @@ class PtzAutoTracker:
)
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
zoom_out_values.append(self.ptz_metrics[camera].zoom_level.value)
zoom_start_time = time.time()
# relative move to 0.01
self.onvif._move_relative(
await self.onvif._move_relative(
camera,
0,
0,
@@ -424,13 +429,13 @@ class PtzAutoTracker:
)
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
zoom_stop_time = time.time()
full_relative_start_time = time.time()
self.onvif._move_relative(
await self.onvif._move_relative(
camera,
-1,
-1,
@@ -439,11 +444,11 @@ class PtzAutoTracker:
)
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
full_relative_stop_time = time.time()
self.onvif._move_relative(
await self.onvif._move_relative(
camera,
1,
1,
@@ -452,7 +457,7 @@ class PtzAutoTracker:
)
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
self.zoom_time[camera] = (
full_relative_stop_time - full_relative_start_time
@@ -471,7 +476,7 @@ class PtzAutoTracker:
self.ptz_metrics[camera].max_zoom.value = 1
self.ptz_metrics[camera].min_zoom.value = 0
self.onvif._move_to_preset(
await self.onvif._move_to_preset(
camera,
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
)
@@ -480,18 +485,18 @@ class PtzAutoTracker:
# Wait until the camera finishes moving
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
for step in range(num_steps):
pan = step_sizes[step]
tilt = step_sizes[step]
start_time = time.time()
self.onvif._move_relative(camera, pan, tilt, 0, 1)
await self.onvif._move_relative(camera, pan, tilt, 0, 1)
# Wait until the camera finishes moving
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
stop_time = time.time()
self.move_metrics[camera].append(
@@ -503,7 +508,7 @@ class PtzAutoTracker:
}
)
self.onvif._move_to_preset(
await self.onvif._move_to_preset(
camera,
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
)
@@ -512,7 +517,7 @@ class PtzAutoTracker:
# Wait until the camera finishes moving
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
logger.info(
f"Calibration for {camera} in progress: {round((step / num_steps) * 100)}% complete"
@@ -709,18 +714,17 @@ class PtzAutoTracker:
centroid_distance < self.tracked_object_metrics[camera]["distance"]
)
def _process_move_queue(self, camera):
camera_config = self.config.cameras[camera]
camera_config.frame_shape[1]
camera_config.frame_shape[0]
async def _process_move_queue(self, camera):
move_queue = self.move_queues[camera]
while not self.stop_event.is_set():
try:
move_data = self.move_queues[camera].get(True, 0.1)
except queue.Empty:
# Asynchronously wait for move data with a timeout
move_data = await asyncio.wait_for(move_queue.get(), timeout=0.1)
except asyncio.TimeoutError:
continue
with self.move_queue_locks[camera]:
async with self.move_queue_locks[camera]:
frame_time, pan, tilt, zoom = move_data
# if we're receiving move requests during a PTZ move, ignore them
@@ -729,8 +733,6 @@ class PtzAutoTracker:
self.ptz_metrics[camera].start_time.value,
self.ptz_metrics[camera].stop_time.value,
):
# instead of dequeueing this might be a good place to preemptively move based
# on an estimate - for fast moving objects, etc.
logger.debug(
f"{camera}: Move queue: PTZ moving, dequeueing move request - frame time: {frame_time}, final pan: {pan}, final tilt: {tilt}, final zoom: {zoom}"
)
@@ -741,25 +743,24 @@ class PtzAutoTracker:
self.config.cameras[camera].onvif.autotracking.zooming
== ZoomingModeEnum.relative
):
self.onvif._move_relative(camera, pan, tilt, zoom, 1)
await self.onvif._move_relative(camera, pan, tilt, zoom, 1)
else:
if pan != 0 or tilt != 0:
self.onvif._move_relative(camera, pan, tilt, 0, 1)
await self.onvif._move_relative(camera, pan, tilt, 0, 1)
# Wait until the camera finishes moving
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
if (
zoom > 0
and self.ptz_metrics[camera].zoom_level.value != zoom
):
self.onvif._zoom_absolute(camera, zoom, 1)
await self.onvif._zoom_absolute(camera, zoom, 1)
# Wait until the camera finishes moving
while not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
if self.config.cameras[camera].onvif.autotracking.movement_weights:
logger.debug(
@@ -796,6 +797,10 @@ class PtzAutoTracker:
# calculate new coefficients if we have enough data
self._calculate_move_coefficients(camera)
# Clean up the queue on exit
while not move_queue.empty():
await move_queue.get()
def _enqueue_move(self, camera, frame_time, pan, tilt, zoom):
def split_value(value, suppress_diff=True):
clipped = np.clip(value, -1, 1)
@@ -824,7 +829,9 @@ class PtzAutoTracker:
f"{camera}: Enqueue movement for frame time: {frame_time} pan: {pan}, tilt: {tilt}, zoom: {zoom}"
)
move_data = (frame_time, pan, tilt, zoom)
self.move_queues[camera].put(move_data)
self.onvif.loop.call_soon_threadsafe(
self.move_queues[camera].put_nowait, move_data
)
# reset values to not split up large movements
pan = 0
@@ -1102,6 +1109,7 @@ class PtzAutoTracker:
camera_height = camera_config.frame_shape[0]
camera_fps = camera_config.detect.fps
predicted_movement_time = 0
zoom_distance = 0
average_velocity = np.zeros((4,))
predicted_box = obj.obj_data["box"]
@@ -1164,7 +1172,20 @@ class PtzAutoTracker:
zoom_predicted_movement_time = 0
if np.any(average_velocity):
zoom_predicted_movement_time = abs(zoom) * self.zoom_time[camera]
# Calculate the intended change in zoom level
zoom_change = (1 - abs(zoom)) * (1 if zoom >= 0 else -1)
# Calculate new zoom level and clamp to [0, 1]
new_zoom = max(
0, min(1, self.ptz_metrics[camera].zoom_level.value + zoom_change)
)
# Calculate the actual zoom distance
zoom_distance = abs(
new_zoom - self.ptz_metrics[camera].zoom_level.value
)
zoom_predicted_movement_time = zoom_distance * self.zoom_time[camera]
zoom_predicted_box = (
predicted_box
@@ -1181,7 +1202,7 @@ class PtzAutoTracker:
tilt = (0.5 - (centroid_y / camera_height)) * 2
logger.debug(
f"{camera}: Zoom amount: {zoom}, zoom predicted time: {zoom_predicted_movement_time}, zoom predicted box: {tuple(zoom_predicted_box)}"
f"{camera}: Zoom amount: {zoom}, zoom distance: {zoom_distance}, zoom predicted time: {zoom_predicted_movement_time}, zoom predicted box: {tuple(zoom_predicted_box)}"
)
self._enqueue_move(camera, obj.obj_data["frame_time"], pan, tilt, zoom)
@@ -1420,7 +1441,7 @@ class PtzAutoTracker:
** (1 / self.zoom_factor[camera])
}
def camera_maintenance(self, camera):
async def camera_maintenance(self, camera):
# bail and don't check anything if we're calibrating or tracking an object
if (
not self.autotracker_init[camera]
@@ -1437,7 +1458,7 @@ class PtzAutoTracker:
self._autotracker_setup(self.config.cameras[camera], camera)
# regularly update camera status
if not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
# return to preset if tracking is over
if (
@@ -1455,22 +1476,18 @@ class PtzAutoTracker:
self.tracked_object[camera] = None
self.tracked_object_history[camera].clear()
# empty move queue
while not self.move_queues[camera].empty():
self.move_queues[camera].get()
self.ptz_metrics[camera].motor_stopped.wait()
logger.debug(
f"{camera}: Time is {self.ptz_metrics[camera].frame_time.value}, returning to preset: {autotracker_config.return_preset}"
)
self.onvif._move_to_preset(
await self.onvif._move_to_preset(
camera,
autotracker_config.return_preset.lower(),
)
# update stored zoom level from preset
if not self.ptz_metrics[camera].motor_stopped.is_set():
self.onvif.get_camera_status(camera)
await self.onvif.get_camera_status(camera)
self.ptz_metrics[camera].tracking_active.clear()
self.dispatcher.publish(
+144 -80
View File
@@ -2,10 +2,12 @@
import asyncio
import logging
import threading
import time
from enum import Enum
from importlib.util import find_spec
from pathlib import Path
from typing import Any
import numpy
from onvif import ONVIFCamera, ONVIFError, ONVIFService
@@ -39,27 +41,56 @@ class OnvifController:
def __init__(
self, config: FrigateConfig, ptz_metrics: dict[str, PTZMetrics]
) -> None:
self.cams: dict[str, ONVIFCamera] = {}
self.cams: dict[str, dict] = {}
self.failed_cams: dict[str, dict] = {}
self.max_retries = 5
self.reset_timeout = 900 # 15 minutes
self.config = config
self.ptz_metrics = ptz_metrics
# Create a dedicated event loop and run it in a separate thread
self.loop = asyncio.new_event_loop()
self.loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
self.loop_thread.start()
self.camera_configs = {}
for cam_name, cam in config.cameras.items():
if not cam.enabled:
continue
if cam.onvif.host:
result = self._create_onvif_camera(cam_name, cam)
if result:
self.cams[cam_name] = result
self.camera_configs[cam_name] = cam
def _create_onvif_camera(self, cam_name: str, cam) -> dict | None:
"""Create an ONVIF camera instance and handle failures."""
asyncio.run_coroutine_threadsafe(self._init_cameras(), self.loop)
def _run_event_loop(self) -> None:
"""Run the event loop in a separate thread."""
asyncio.set_event_loop(self.loop)
try:
return {
self.loop.run_forever()
except Exception as e:
logger.error(f"Onvif event loop terminated unexpectedly: {e}")
async def _init_cameras(self) -> None:
"""Initialize all configured cameras."""
for cam_name in self.camera_configs:
await self._init_single_camera(cam_name)
async def _init_single_camera(self, cam_name: str) -> bool:
"""Initialize a single camera by name.
Args:
cam_name: The name of the camera to initialize
Returns:
bool: True if initialization succeeded, False otherwise
"""
if cam_name not in self.camera_configs:
logger.error(f"No configuration found for camera {cam_name}")
return False
cam = self.camera_configs[cam_name]
try:
self.cams[cam_name] = {
"onvif": ONVIFCamera(
cam.onvif.host,
cam.onvif.port,
@@ -74,7 +105,8 @@ class OnvifController:
"features": [],
"presets": {},
}
except ONVIFError as e:
return True
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.error(f"Failed to create ONVIF camera instance for {cam_name}: {e}")
# track initial failures
self.failed_cams[cam_name] = {
@@ -82,7 +114,7 @@ class OnvifController:
"last_error": str(e),
"last_attempt": time.time(),
}
return None
return False
async def _init_onvif(self, camera_name: str) -> bool:
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
@@ -100,7 +132,7 @@ class OnvifController:
# this will fire an exception if camera is not a ptz
capabilities = onvif.get_definition("ptz")
logger.debug(f"Onvif capabilities for {camera_name}: {capabilities}")
except (ONVIFError, Fault, TransportError) as e:
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.error(
f"Unable to get Onvif capabilities for camera: {camera_name}: {e}"
)
@@ -109,7 +141,7 @@ class OnvifController:
try:
profiles = await media.GetProfiles()
logger.debug(f"Onvif profiles for {camera_name}: {profiles}")
except (ONVIFError, Fault, TransportError) as e:
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.error(
f"Unable to get Onvif media profiles for camera: {camera_name}: {e}"
)
@@ -240,12 +272,12 @@ class OnvifController:
logger.debug(
f"{camera_name}: Relative move request after deleting zoom: {move_request}"
)
except Exception:
except Exception as e:
self.config.cameras[
camera_name
].onvif.autotracking.zooming = ZoomingModeEnum.disabled
logger.warning(
f"Disabling autotracking zooming for {camera_name}: Relative zoom not supported"
f"Disabling autotracking zooming for {camera_name}: Relative zoom not supported. Exception: {e}"
)
if move_request.Speed is None:
@@ -263,7 +295,7 @@ class OnvifController:
# setup existing presets
try:
presets: list[dict] = await ptz.GetPresets({"ProfileToken": profile.token})
except ONVIFError as e:
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.warning(f"Unable to get presets from camera: {camera_name}: {e}")
presets = []
@@ -295,7 +327,7 @@ class OnvifController:
self.cams[camera_name]["relative_zoom_range"] = (
ptz_config.Spaces.RelativeZoomTranslationSpace[0]
)
except Exception:
except Exception as e:
if (
self.config.cameras[camera_name].onvif.autotracking.zooming
== ZoomingModeEnum.relative
@@ -304,7 +336,7 @@ class OnvifController:
camera_name
].onvif.autotracking.zooming = ZoomingModeEnum.disabled
logger.warning(
f"Disabling autotracking zooming for {camera_name}: Relative zoom not supported"
f"Disabling autotracking zooming for {camera_name}: Relative zoom not supported. Exception: {e}"
)
if configs.DefaultAbsoluteZoomPositionSpace:
@@ -319,13 +351,13 @@ class OnvifController:
ptz_config.Spaces.AbsoluteZoomPositionSpace[0]
)
self.cams[camera_name]["zoom_limits"] = configs.ZoomLimits
except Exception:
except Exception as e:
if self.config.cameras[camera_name].onvif.autotracking.zooming:
self.config.cameras[
camera_name
].onvif.autotracking.zooming = ZoomingModeEnum.disabled
logger.warning(
f"Disabling autotracking zooming for {camera_name}: Absolute zoom not supported"
f"Disabling autotracking zooming for {camera_name}: Absolute zoom not supported. Exception: {e}"
)
# set relative pan/tilt space for autotracker
@@ -344,25 +376,23 @@ class OnvifController:
self.cams[camera_name]["init"] = True
return True
def _stop(self, camera_name: str) -> None:
async def _stop(self, camera_name: str) -> None:
move_request = self.cams[camera_name]["move_request"]
asyncio.run(
self.cams[camera_name]["ptz"].Stop(
{
"ProfileToken": move_request.ProfileToken,
"PanTilt": True,
"Zoom": True,
}
)
await self.cams[camera_name]["ptz"].Stop(
{
"ProfileToken": move_request.ProfileToken,
"PanTilt": True,
"Zoom": True,
}
)
self.cams[camera_name]["active"] = False
def _move(self, camera_name: str, command: OnvifCommandEnum) -> None:
async def _move(self, camera_name: str, command: OnvifCommandEnum) -> None:
if self.cams[camera_name]["active"]:
logger.warning(
f"{camera_name} is already performing an action, stopping..."
)
self._stop(camera_name)
await self._stop(camera_name)
if "pt" not in self.cams[camera_name]["features"]:
logger.error(f"{camera_name} does not support ONVIF pan/tilt movement.")
@@ -391,11 +421,11 @@ class OnvifController:
}
try:
asyncio.run(self.cams[camera_name]["ptz"].ContinuousMove(move_request))
except ONVIFError as e:
await self.cams[camera_name]["ptz"].ContinuousMove(move_request)
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.warning(f"Onvif sending move request to {camera_name} failed: {e}")
def _move_relative(self, camera_name: str, pan, tilt, zoom, speed) -> None:
async def _move_relative(self, camera_name: str, pan, tilt, zoom, speed) -> None:
if "pt-r-fov" not in self.cams[camera_name]["features"]:
logger.error(f"{camera_name} does not support ONVIF RelativeMove (FOV).")
return
@@ -464,7 +494,7 @@ class OnvifController:
}
move_request.Translation.Zoom.x = zoom
asyncio.run(self.cams[camera_name]["ptz"].RelativeMove(move_request))
await self.cams[camera_name]["ptz"].RelativeMove(move_request)
# reset after the move request
move_request.Translation.PanTilt.x = 0
@@ -479,7 +509,7 @@ class OnvifController:
self.cams[camera_name]["active"] = False
def _move_to_preset(self, camera_name: str, preset: str) -> None:
async def _move_to_preset(self, camera_name: str, preset: str) -> None:
if preset not in self.cams[camera_name]["presets"]:
logger.error(f"{preset} is not a valid preset for {camera_name}")
return
@@ -489,23 +519,22 @@ class OnvifController:
self.ptz_metrics[camera_name].stop_time.value = 0
move_request = self.cams[camera_name]["move_request"]
preset_token = self.cams[camera_name]["presets"][preset]
asyncio.run(
self.cams[camera_name]["ptz"].GotoPreset(
{
"ProfileToken": move_request.ProfileToken,
"PresetToken": preset_token,
}
)
await self.cams[camera_name]["ptz"].GotoPreset(
{
"ProfileToken": move_request.ProfileToken,
"PresetToken": preset_token,
}
)
self.cams[camera_name]["active"] = False
def _zoom(self, camera_name: str, command: OnvifCommandEnum) -> None:
async def _zoom(self, camera_name: str, command: OnvifCommandEnum) -> None:
if self.cams[camera_name]["active"]:
logger.warning(
f"{camera_name} is already performing an action, stopping..."
)
self._stop(camera_name)
await self._stop(camera_name)
if "zoom" not in self.cams[camera_name]["features"]:
logger.error(f"{camera_name} does not support ONVIF zooming.")
@@ -519,9 +548,9 @@ class OnvifController:
elif command == OnvifCommandEnum.zoom_out:
move_request.Velocity = {"Zoom": {"x": -0.5}}
asyncio.run(self.cams[camera_name]["ptz"].ContinuousMove(move_request))
await self.cams[camera_name]["ptz"].ContinuousMove(move_request)
def _zoom_absolute(self, camera_name: str, zoom, speed) -> None:
async def _zoom_absolute(self, camera_name: str, zoom, speed) -> None:
if "zoom-a" not in self.cams[camera_name]["features"]:
logger.error(f"{camera_name} does not support ONVIF AbsoluteMove zooming.")
return
@@ -560,19 +589,20 @@ class OnvifController:
logger.debug(f"{camera_name}: Absolute zoom: {zoom}")
asyncio.run(self.cams[camera_name]["ptz"].AbsoluteMove(move_request))
await self.cams[camera_name]["ptz"].AbsoluteMove(move_request)
self.cams[camera_name]["active"] = False
def handle_command(
async def handle_command_async(
self, camera_name: str, command: OnvifCommandEnum, param: str = ""
) -> None:
"""Handle ONVIF commands asynchronously"""
if camera_name not in self.cams.keys():
logger.error(f"ONVIF is not configured for {camera_name}")
return
if not self.cams[camera_name]["init"]:
if not asyncio.run(self._init_onvif(camera_name)):
if not await self._init_onvif(camera_name):
return
try:
@@ -580,23 +610,44 @@ class OnvifController:
# already init
return
elif command == OnvifCommandEnum.stop:
self._stop(camera_name)
await self._stop(camera_name)
elif command == OnvifCommandEnum.preset:
self._move_to_preset(camera_name, param)
await self._move_to_preset(camera_name, param)
elif command == OnvifCommandEnum.move_relative:
_, pan, tilt = param.split("_")
self._move_relative(camera_name, float(pan), float(tilt), 0, 1)
await self._move_relative(camera_name, float(pan), float(tilt), 0, 1)
elif (
command == OnvifCommandEnum.zoom_in
or command == OnvifCommandEnum.zoom_out
):
self._zoom(camera_name, command)
await self._zoom(camera_name, command)
else:
self._move(camera_name, command)
except ONVIFError as e:
await self._move(camera_name, command)
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.error(f"Unable to handle onvif command: {e}")
async def get_camera_info(self, camera_name: str) -> dict[str, any]:
def handle_command(
self, camera_name: str, command: OnvifCommandEnum, param: str = ""
) -> None:
"""
Handle ONVIF commands by scheduling them in the event loop.
This is the synchronous interface that schedules async work.
"""
future = asyncio.run_coroutine_threadsafe(
self.handle_command_async(camera_name, command, param), self.loop
)
try:
# Wait with a timeout to prevent blocking indefinitely
future.result(timeout=10)
except asyncio.TimeoutError:
logger.error(f"Command {command} timed out for camera {camera_name}")
except Exception as e:
logger.error(
f"Error executing command {command} for camera {camera_name}: {e}"
)
async def get_camera_info(self, camera_name: str) -> dict[str, Any]:
"""
Get ptz capabilities and presets, attempting to reconnect if ONVIF is configured
but not initialized.
@@ -609,26 +660,23 @@ class OnvifController:
)
return {}
if camera_name not in self.cams and (
if camera_name not in self.cams.keys() and (
camera_name not in self.config.cameras
or not self.config.cameras[camera_name].onvif.host
):
logger.debug(f"ONVIF is not configured for {camera_name}")
return {}
if camera_name in self.cams and self.cams[camera_name]["init"]:
if camera_name in self.cams.keys() and self.cams[camera_name]["init"]:
return {
"name": camera_name,
"features": self.cams[camera_name]["features"],
"presets": list(self.cams[camera_name]["presets"].keys()),
}
if camera_name not in self.cams and camera_name in self.config.cameras:
cam = self.config.cameras[camera_name]
result = self._create_onvif_camera(camera_name, cam)
if result:
self.cams[camera_name] = result
else:
if camera_name not in self.cams.keys() and camera_name in self.config.cameras:
success = await self._init_single_camera(camera_name)
if not success:
return {}
# Reset retry count after timeout
@@ -681,23 +729,21 @@ class OnvifController:
logger.debug(f"Could not initialize ONVIF for {camera_name}")
return {}
def get_service_capabilities(self, camera_name: str) -> None:
async def get_service_capabilities(self, camera_name: str) -> None:
if camera_name not in self.cams.keys():
logger.error(f"ONVIF is not configured for {camera_name}")
return {}
if not self.cams[camera_name]["init"]:
asyncio.run(self._init_onvif(camera_name))
await self._init_onvif(camera_name)
service_capabilities_request = self.cams[camera_name][
"service_capabilities_request"
]
try:
service_capabilities = asyncio.run(
self.cams[camera_name]["ptz"].GetServiceCapabilities(
service_capabilities_request
)
)
service_capabilities = await self.cams[camera_name][
"ptz"
].GetServiceCapabilities(service_capabilities_request)
logger.debug(
f"Onvif service capabilities for {camera_name}: {service_capabilities}"
@@ -705,25 +751,24 @@ class OnvifController:
# MoveStatus is required for autotracking - should return "true" if supported
return find_by_key(vars(service_capabilities), "MoveStatus")
except Exception:
except Exception as e:
logger.warning(
f"Camera {camera_name} does not support the ONVIF GetServiceCapabilities method. Autotracking will not function correctly and must be disabled in your config."
f"Camera {camera_name} does not support the ONVIF GetServiceCapabilities method. Autotracking will not function correctly and must be disabled in your config. Exception: {e}"
)
return False
def get_camera_status(self, camera_name: str) -> None:
async def get_camera_status(self, camera_name: str) -> None:
if camera_name not in self.cams.keys():
logger.error(f"ONVIF is not configured for {camera_name}")
return {}
return
if not self.cams[camera_name]["init"]:
asyncio.run(self._init_onvif(camera_name))
if not await self._init_onvif(camera_name):
return
status_request = self.cams[camera_name]["status_request"]
try:
status = asyncio.run(
self.cams[camera_name]["ptz"].GetStatus(status_request)
)
status = await self.cams[camera_name]["ptz"].GetStatus(status_request)
except Exception:
pass # We're unsupported, that'll be reported in the next check.
@@ -807,3 +852,22 @@ class OnvifController:
camera_name
].frame_time.value
logger.warning(f"Camera {camera_name} is still in ONVIF 'MOVING' status.")
def close(self) -> None:
"""Gracefully shut down the ONVIF controller."""
if not hasattr(self, "loop") or self.loop.is_closed():
logger.debug("ONVIF controller already closed")
return
logger.info("Exiting ONVIF controller...")
def stop_and_cleanup():
try:
self.loop.stop()
except Exception as e:
logger.error(f"Error during loop cleanup: {e}")
# Schedule stop and cleanup in the loop thread
self.loop.call_soon_threadsafe(stop_and_cleanup)
self.loop_thread.join()
+5 -1
View File
@@ -69,7 +69,7 @@ class RecordingCleanup(threading.Thread):
now - datetime.timedelta(days=config.record.detections.retain.days)
).timestamp()
expired_reviews: ReviewSegment = (
ReviewSegment.select(ReviewSegment.id)
ReviewSegment.select(ReviewSegment.id, ReviewSegment.thumb_path)
.where(ReviewSegment.camera == config.name)
.where(
(
@@ -84,6 +84,10 @@ class RecordingCleanup(threading.Thread):
.namedtuples()
)
thumbs_to_delete = list(map(lambda x: x[1], expired_reviews))
for thumb_path in thumbs_to_delete:
Path(thumb_path).unlink(missing_ok=True)
max_deletes = 100000
deleted_reviews_list = list(map(lambda x: x[0], expired_reviews))
for i in range(0, len(deleted_reviews_list), max_deletes):
+1 -1
View File
@@ -242,7 +242,7 @@ class RecordingMaintainer(threading.Thread):
self.end_time_cache.pop(cache_path, None)
async def validate_and_move_segment(
self, camera: str, reviews: list[ReviewSegment], recording: dict[str, any]
self, camera: str, reviews: list[ReviewSegment], recording: dict[str, Any]
) -> None:
cache_path: str = recording["cache_path"]
start_time: datetime.datetime = recording["start_time"]
+22 -19
View File
@@ -1,5 +1,6 @@
"""Maintain review segments in db."""
import copy
import json
import logging
import os
@@ -9,7 +10,7 @@ import sys
import threading
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Optional
from typing import Any, Optional
import cv2
import numpy as np
@@ -119,21 +120,23 @@ class PendingReviewSegment:
)
def get_data(self, ended: bool) -> dict:
return {
ReviewSegment.id.name: self.id,
ReviewSegment.camera.name: self.camera,
ReviewSegment.start_time.name: self.start_time,
ReviewSegment.end_time.name: self.last_update if ended else None,
ReviewSegment.severity.name: self.severity.value,
ReviewSegment.thumb_path.name: self.frame_path,
ReviewSegment.data.name: {
"detections": list(set(self.detections.keys())),
"objects": list(set(self.detections.values())),
"sub_labels": list(self.sub_labels.values()),
"zones": self.zones,
"audio": list(self.audio),
},
}.copy()
return copy.deepcopy(
{
ReviewSegment.id.name: self.id,
ReviewSegment.camera.name: self.camera,
ReviewSegment.start_time.name: self.start_time,
ReviewSegment.end_time.name: self.last_update if ended else None,
ReviewSegment.severity.name: self.severity.value,
ReviewSegment.thumb_path.name: self.frame_path,
ReviewSegment.data.name: {
"detections": list(set(self.detections.keys())),
"objects": list(set(self.detections.values())),
"sub_labels": list(self.sub_labels.values()),
"zones": self.zones,
"audio": list(self.audio),
},
}
)
class ReviewSegmentMaintainer(threading.Thread):
@@ -153,7 +156,7 @@ class ReviewSegmentMaintainer(threading.Thread):
self.detection_subscriber = DetectionSubscriber(DetectionTypeEnum.all)
# manual events
self.indefinite_events: dict[str, dict[str, any]] = {}
self.indefinite_events: dict[str, dict[str, Any]] = {}
# ensure dirs
Path(os.path.join(CLIPS_DIR, "review")).mkdir(exist_ok=True)
@@ -191,7 +194,7 @@ class ReviewSegmentMaintainer(threading.Thread):
camera_config: CameraConfig,
frame,
objects: list[TrackedObject],
prev_data: dict[str, any],
prev_data: dict[str, Any],
) -> None:
"""Update segment."""
if frame is not None:
@@ -216,7 +219,7 @@ class ReviewSegmentMaintainer(threading.Thread):
def _publish_segment_end(
self,
segment: PendingReviewSegment,
prev_data: dict[str, any],
prev_data: dict[str, Any],
) -> None:
"""End segment."""
final_data = segment.get_data(ended=True)
+5 -5
View File
@@ -6,7 +6,7 @@ import logging
import threading
import time
from multiprocessing.synchronize import Event as MpEvent
from typing import Optional
from typing import Any, Optional
from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import FrigateConfig
@@ -33,12 +33,12 @@ class StatsEmitter(threading.Thread):
self.stats_tracking = stats_tracking
self.stop_event = stop_event
self.hwaccel_errors: list[str] = []
self.stats_history: list[dict[str, any]] = []
self.stats_history: list[dict[str, Any]] = []
# create communication for stats
self.requestor = InterProcessRequestor()
def get_latest_stats(self) -> dict[str, any]:
def get_latest_stats(self) -> dict[str, Any]:
"""Get latest stats."""
if len(self.stats_history) > 0:
return self.stats_history[-1]
@@ -51,12 +51,12 @@ class StatsEmitter(threading.Thread):
def get_stats_history(
self, keys: Optional[list[str]] = None
) -> list[dict[str, any]]:
) -> list[dict[str, Any]]:
"""Get stats history."""
if not keys:
return self.stats_history
selected_stats: list[dict[str, any]] = []
selected_stats: list[dict[str, Any]] = []
for s in self.stats_history:
selected = {}
+1 -1
View File
@@ -39,7 +39,7 @@ class TestConfig(unittest.TestCase):
"description": "Fine tuned model",
"trainDate": "2023-04-28T23:22:01.262Z",
"type": "ssd",
"supportedDetectors": ["edgetpu"],
"supportedDetectors": ["cpu", "edgetpu"],
"width": 320,
"height": 320,
"inputShape": "nhwc",
+8 -7
View File
@@ -5,6 +5,7 @@ import queue
import threading
from multiprocessing import Queue
from multiprocessing.synchronize import Event as MpEvent
from typing import Any
from frigate.config import FrigateConfig
from frigate.events.maintainer import EventStateEnum, EventTypeEnum
@@ -27,7 +28,7 @@ class TimelineProcessor(threading.Thread):
self.config = config
self.queue = queue
self.stop_event = stop_event
self.pre_event_cache: dict[str, list[dict[str, any]]] = {}
self.pre_event_cache: dict[str, list[dict[str, Any]]] = {}
def run(self) -> None:
while not self.stop_event.is_set():
@@ -55,9 +56,9 @@ class TimelineProcessor(threading.Thread):
def insert_or_save(
self,
entry: dict[str, any],
prev_event_data: dict[any, any],
event_data: dict[any, any],
entry: dict[str, Any],
prev_event_data: dict[Any, Any],
event_data: dict[Any, Any],
) -> None:
"""Insert into db or cache."""
id = entry[Timeline.source_id]
@@ -81,8 +82,8 @@ class TimelineProcessor(threading.Thread):
self,
camera: str,
event_type: str,
prev_event_data: dict[any, any],
event_data: dict[any, any],
prev_event_data: dict[Any, Any],
event_data: dict[Any, Any],
) -> bool:
"""Handle object detection."""
save = False
@@ -153,7 +154,7 @@ class TimelineProcessor(threading.Thread):
self,
camera: str,
event_type: str,
event_data: dict[any, any],
event_data: dict[Any, Any],
) -> bool:
if event_type != "new":
return False
+2 -1
View File
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from typing import Any
from frigate.config import DetectConfig
@@ -10,6 +11,6 @@ class ObjectTracker(ABC):
@abstractmethod
def match_and_update(
self, frame_name: str, frame_time: float, detections: list[dict[str, any]]
self, frame_name: str, frame_time: float, detections: list[dict[str, Any]]
) -> None:
pass
+2 -2
View File
@@ -1,7 +1,7 @@
import logging
import random
import string
from typing import Sequence
from typing import Any, Sequence
import cv2
import numpy as np
@@ -460,7 +460,7 @@ class NorfairTracker(ObjectTracker):
self.match_and_update(frame_name, frame_time, detections=detections)
def match_and_update(
self, frame_name: str, frame_time: float, detections: list[dict[str, any]]
self, frame_name: str, frame_time: float, detections: list[dict[str, Any]]
):
# Group detections by object type
detections_by_type = {}
+4 -3
View File
@@ -7,6 +7,7 @@ import threading
from collections import defaultdict
from enum import Enum
from multiprocessing.synchronize import Event as MpEvent
from typing import Any
import cv2
import numpy as np
@@ -70,7 +71,7 @@ class TrackedObjectProcessor(threading.Thread):
self.event_end_subscriber = EventEndSubscriber()
self.sub_label_subscriber = EventMetadataSubscriber(EventMetadataTypeEnum.all)
self.camera_activity: dict[str, dict[str, any]] = {}
self.camera_activity: dict[str, dict[str, Any]] = {}
self.ongoing_manual_events: dict[str, str] = {}
# {
@@ -248,7 +249,7 @@ class TrackedObjectProcessor(threading.Thread):
def should_mqtt_snapshot(self, camera, obj: TrackedObject):
# object never changed position
if obj.obj_data["position_changes"] == 0:
if obj.is_stationary():
return False
# if there are required zones and there is no overlap
@@ -301,7 +302,7 @@ class TrackedObjectProcessor(threading.Thread):
return {}
def get_current_frame(
self, camera: str, draw_options: dict[str, any] = {}
self, camera: str, draw_options: dict[str, Any] = {}
) -> np.ndarray | None:
if camera == "birdseye":
return self.frame_manager.get(
+22 -14
View File
@@ -5,7 +5,7 @@ import math
import os
from collections import defaultdict
from statistics import median
from typing import Optional
from typing import Any, Optional
import cv2
import numpy as np
@@ -18,6 +18,7 @@ from frigate.config import (
)
from frigate.const import CLIPS_DIR, THUMB_DIR
from frigate.review.types import SeverityEnum
from frigate.util.builtin import sanitize_float
from frigate.util.image import (
area,
calculate_region,
@@ -38,7 +39,7 @@ class TrackedObject:
camera_config: CameraConfig,
ui_config: UIConfig,
frame_cache,
obj_data: dict[str, any],
obj_data: dict[str, Any],
):
# set the score history then remove as it is not part of object state
self.score_history = obj_data["score_history"]
@@ -154,7 +155,7 @@ class TrackedObject:
"attributes": obj_data["attributes"],
"current_estimated_speed": self.current_estimated_speed,
"velocity_angle": self.velocity_angle,
"path_data": self.path_data,
"path_data": self.path_data.copy(),
"recognized_license_plate": obj_data.get(
"recognized_license_plate"
),
@@ -202,6 +203,11 @@ class TrackedObject:
)
)
# users can configure speed zones incorrectly, so sanitize speed_magnitude
# and velocity_angle in case the values come back as inf or NaN
speed_magnitude = sanitize_float(speed_magnitude)
self.velocity_angle = sanitize_float(self.velocity_angle)
if self.ui_config.unit_system == "metric":
self.current_estimated_speed = (
speed_magnitude * 3.6
@@ -378,22 +384,22 @@ class TrackedObject:
"current_estimated_speed": self.current_estimated_speed,
"average_estimated_speed": self.average_estimated_speed,
"velocity_angle": self.velocity_angle,
"path_data": self.path_data,
"path_data": self.path_data.copy(),
"recognized_license_plate": self.obj_data.get("recognized_license_plate"),
}
return event
def is_active(self):
def is_active(self) -> bool:
return not self.is_stationary()
def is_stationary(self):
def is_stationary(self) -> bool:
return (
self.obj_data["motionless_count"]
> self.camera_config.detect.stationary.threshold
)
def get_thumbnail(self, ext: str):
def get_thumbnail(self, ext: str) -> bytes | None:
img_bytes = self.get_img_bytes(
ext, timestamp=False, bounding_box=False, crop=True, height=175
)
@@ -404,7 +410,7 @@ class TrackedObject:
_, img = cv2.imencode(f".{ext}", np.zeros((175, 175, 3), np.uint8))
return img.tobytes()
def get_clean_png(self):
def get_clean_png(self) -> bytes | None:
if self.thumbnail_data is None:
return None
@@ -433,7 +439,7 @@ class TrackedObject:
crop=False,
height: int | None = None,
quality: int | None = None,
):
) -> bytes | None:
if self.thumbnail_data is None:
return None
@@ -474,6 +480,7 @@ class TrackedObject:
# draw any attributes
for attribute in self.thumbnail_data["attributes"]:
box = attribute["box"]
box_area = int((box[2] - box[0]) * (box[3] - box[1]))
draw_box_with_label(
best_frame,
box[0],
@@ -481,7 +488,7 @@ class TrackedObject:
box[2],
box[3],
attribute["label"],
f"{attribute['score']:.0%}",
f"{attribute['score']:.0%} {str(box_area)}",
thickness=thickness,
color=color,
)
@@ -621,7 +628,7 @@ class TrackedObjectAttribute:
self.ratio = raw_data[4]
self.region = raw_data[5]
def get_tracking_data(self) -> dict[str, any]:
def get_tracking_data(self) -> dict[str, Any]:
"""Return data saved to the object."""
return {
"label": self.label,
@@ -629,7 +636,7 @@ class TrackedObjectAttribute:
"box": self.box,
}
def find_best_object(self, objects: list[dict[str, any]]) -> Optional[str]:
def find_best_object(self, objects: list[dict[str, Any]]) -> Optional[str]:
"""Find the best attribute for each object and return its ID."""
best_object_area = None
best_object_id = None
@@ -649,8 +656,9 @@ class TrackedObjectAttribute:
best_object_id = obj["id"]
best_object_label = obj["label"]
else:
if best_object_label == "car" and obj["label"] == "car":
# if multiple cars are overlapping with the same label then the label will not be assigned
if best_object_label == obj["label"]:
# if multiple objects of the same type are overlapping
# then the attribute will not be assigned
return None
elif object_area < best_object_area:
# if a car and person are overlapping then assign the label to the smaller object (which should be the person)
+36 -7
View File
@@ -4,6 +4,7 @@ import ast
import copy
import datetime
import logging
import math
import multiprocessing as mp
import queue
import re
@@ -11,6 +12,7 @@ import shlex
import struct
import urllib.parse
from collections.abc import Mapping
from multiprocessing.sharedctypes import Synchronized
from pathlib import Path
from typing import Any, Optional, Tuple, Union
from zoneinfo import ZoneInfoNotFoundError
@@ -26,16 +28,16 @@ logger = logging.getLogger(__name__)
class EventsPerSecond:
def __init__(self, max_events=1000, last_n_seconds=10):
def __init__(self, max_events=1000, last_n_seconds=10) -> None:
self._start = None
self._max_events = max_events
self._last_n_seconds = last_n_seconds
self._timestamps = []
def start(self):
def start(self) -> None:
self._start = datetime.datetime.now().timestamp()
def update(self):
def update(self) -> None:
now = datetime.datetime.now().timestamp()
if self._start is None:
self._start = now
@@ -45,7 +47,7 @@ class EventsPerSecond:
self._timestamps = self._timestamps[(1 - self._max_events) :]
self.expire_timestamps(now)
def eps(self):
def eps(self) -> float:
now = datetime.datetime.now().timestamp()
if self._start is None:
self._start = now
@@ -58,12 +60,29 @@ class EventsPerSecond:
return len(self._timestamps) / seconds
# remove aged out timestamps
def expire_timestamps(self, now):
def expire_timestamps(self, now: float) -> None:
threshold = now - self._last_n_seconds
while self._timestamps and self._timestamps[0] < threshold:
del self._timestamps[0]
class InferenceSpeed:
def __init__(self, metric: Synchronized) -> None:
self.__metric = metric
self.__initialized = False
def update(self, inference_time: float) -> None:
if not self.__initialized:
self.__metric.value = inference_time
self.__initialized = True
return
self.__metric.value = (self.__metric.value * 9 + inference_time) / 10
def current(self) -> float:
return self.__metric.value
def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:
"""
:param dct1: First dict to merge
@@ -138,7 +157,7 @@ def load_labels(path: Optional[str], encoding="utf-8", prefill=91):
return labels
def get_tz_modifiers(tz_name: str) -> Tuple[str, str, int]:
def get_tz_modifiers(tz_name: str) -> Tuple[str, str, float]:
seconds_offset = (
datetime.datetime.now(pytz.timezone(tz_name)).utcoffset().total_seconds()
)
@@ -151,7 +170,7 @@ def get_tz_modifiers(tz_name: str) -> Tuple[str, str, int]:
def to_relative_box(
width: int, height: int, box: Tuple[int, int, int, int]
) -> Tuple[int, int, int, int]:
) -> Tuple[int | float, int | float, int | float, int | float]:
return (
box[0] / width, # x
box[1] / height, # y
@@ -169,6 +188,9 @@ def update_yaml_from_url(file_path, url):
parsed_url = urllib.parse.urlparse(url)
query_string = urllib.parse.parse_qs(parsed_url.query, keep_blank_values=True)
# Filter out empty keys but keep blank values for non-empty keys
query_string = {k: v for k, v in query_string.items() if k}
for key_path_str, new_value_list in query_string.items():
key_path = key_path_str.split(".")
for i in range(len(key_path)):
@@ -378,3 +400,10 @@ def serialize(
def deserialize(bytes_data: bytes) -> list[float]:
"""Deserializes a compact "raw bytes" format into a list of floats"""
return list(struct.unpack("%sf" % (len(bytes_data) // 4), bytes_data))
def sanitize_float(value):
"""Replace NaN or inf with 0.0."""
if isinstance(value, (int, float)) and not math.isfinite(value):
return 0.0
return value
+10 -10
View File
@@ -4,7 +4,7 @@ import asyncio
import logging
import os
import shutil
from typing import Optional, Union
from typing import Any, Optional, Union
from ruamel.yaml import YAML
@@ -37,7 +37,7 @@ def migrate_frigate_config(config_file: str):
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
with open(config_file, "r") as f:
config: dict[str, dict[str, any]] = yaml.load(f)
config: dict[str, dict[str, Any]] = yaml.load(f)
if config is None:
logger.error(f"Failed to load config at {config_file}")
@@ -94,7 +94,7 @@ def migrate_frigate_config(config_file: str):
logger.info("Finished frigate config migration...")
def migrate_014(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
def migrate_014(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Handle migrating frigate config to 0.14"""
# migrate record.events.required_zones to review.alerts.required_zones
new_config = config.copy()
@@ -142,7 +142,7 @@ def migrate_014(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
del new_config["rtmp"]
for name, camera in config.get("cameras", {}).items():
camera_config: dict[str, dict[str, any]] = camera.copy()
camera_config: dict[str, dict[str, Any]] = camera.copy()
required_zones = (
camera_config.get("record", {}).get("events", {}).get("required_zones", [])
)
@@ -181,7 +181,7 @@ def migrate_014(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
return new_config
def migrate_015_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
def migrate_015_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Handle migrating frigate config to 0.15-0"""
new_config = config.copy()
@@ -232,9 +232,9 @@ def migrate_015_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]
del new_config["record"]["events"]
for name, camera in config.get("cameras", {}).items():
camera_config: dict[str, dict[str, any]] = camera.copy()
camera_config: dict[str, dict[str, Any]] = camera.copy()
record_events: dict[str, any] = camera_config.get("record", {}).get("events")
record_events: dict[str, Any] = camera_config.get("record", {}).get("events")
if record_events:
alerts_retention = {"retain": {}}
@@ -281,7 +281,7 @@ def migrate_015_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]
return new_config
def migrate_015_1(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
def migrate_015_1(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Handle migrating frigate config to 0.15-1"""
new_config = config.copy()
@@ -296,7 +296,7 @@ def migrate_015_1(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]
return new_config
def migrate_016_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]:
def migrate_016_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Handle migrating frigate config to 0.16-0"""
new_config = config.copy()
@@ -307,7 +307,7 @@ def migrate_016_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]
new_config["detect"] = detect_config
for name, camera in config.get("cameras", {}).items():
camera_config: dict[str, dict[str, any]] = camera.copy()
camera_config: dict[str, dict[str, Any]] = camera.copy()
live_config = camera_config.get("live", {})
if "stream_name" in live_config:
+4 -4
View File
@@ -8,7 +8,7 @@ from abc import ABC, abstractmethod
from multiprocessing import resource_tracker as _mprt
from multiprocessing import shared_memory as _mpshm
from string import printable
from typing import AnyStr, Optional
from typing import Any, AnyStr, Optional
import cv2
import numpy as np
@@ -766,7 +766,7 @@ class FrameManager(ABC):
pass
@abstractmethod
def write(self, name: str) -> memoryview:
def write(self, name: str) -> Optional[memoryview]:
pass
@abstractmethod
@@ -847,7 +847,7 @@ class SharedMemoryFrameManager(FrameManager):
self.shm_store[name] = shm
return shm.buf
def write(self, name: str) -> memoryview:
def write(self, name: str) -> Optional[memoryview]:
try:
if name in self.shm_store:
shm = self.shm_store[name]
@@ -944,7 +944,7 @@ def get_image_from_recording(
relative_frame_time: float,
codec: str,
height: Optional[int] = None,
) -> Optional[any]:
) -> Optional[Any]:
"""retrieve a frame from given time in recording file."""
ffmpeg_cmd = [
+2 -2
View File
@@ -2,6 +2,7 @@
import logging
import os
from typing import Any
import cv2
import numpy as np
@@ -284,7 +285,7 @@ def post_process_yolox(
def get_ort_providers(
force_cpu: bool = False, device: str = "AUTO", requires_fp16: bool = False
) -> tuple[list[str], list[dict[str, any]]]:
) -> tuple[list[str], list[dict[str, Any]]]:
if force_cpu:
return (
["CPUExecutionProvider"],
@@ -340,7 +341,6 @@ def get_ort_providers(
providers.append(provider)
options.append(
{
"arena_extend_strategy": "kSameAsRequested",
"cache_dir": os.path.join(MODEL_CACHE_DIR, "openvino/ort"),
"device_type": device,
}
+11 -10
View File
@@ -4,6 +4,7 @@ import datetime
import logging
import math
from collections import defaultdict
from typing import Any
import cv2
import numpy as np
@@ -38,7 +39,7 @@ def get_camera_regions_grid(
name: str,
detect: DetectConfig,
min_region_size: int,
) -> list[list[dict[str, any]]]:
) -> list[list[dict[str, Any]]]:
"""Build a grid of expected region sizes for a camera."""
# get grid from db if available
try:
@@ -163,10 +164,10 @@ def get_cluster_region_from_grid(frame_shape, min_region, cluster, boxes, region
def get_region_from_grid(
frame_shape: tuple[int],
frame_shape: tuple[int, int],
cluster: list[int],
min_region: int,
region_grid: list[list[dict[str, any]]],
region_grid: list[list[dict[str, Any]]],
) -> list[int]:
"""Get a region for a box based on the region grid."""
box = calculate_region(
@@ -446,9 +447,9 @@ def get_cluster_region(frame_shape, min_region, cluster, boxes):
def get_startup_regions(
frame_shape: tuple[int],
frame_shape: tuple[int, int],
region_min_size: int,
region_grid: list[list[dict[str, any]]],
region_grid: list[list[dict[str, Any]]],
) -> list[list[int]]:
"""Get a list of regions to run on startup."""
# return 8 most popular regions for the camera
@@ -480,12 +481,12 @@ def get_startup_regions(
def reduce_detections(
frame_shape: tuple[int],
all_detections: list[tuple[any]],
) -> list[tuple[any]]:
frame_shape: tuple[int, int],
all_detections: list[tuple[Any]],
) -> list[tuple[Any]]:
"""Take a list of detections and reduce overlaps to create a list of confident detections."""
def reduce_overlapping_detections(detections: list[tuple[any]]) -> list[tuple[any]]:
def reduce_overlapping_detections(detections: list[tuple[Any]]) -> list[tuple[Any]]:
"""apply non-maxima suppression to suppress weak, overlapping bounding boxes."""
detected_object_groups = defaultdict(lambda: [])
for detection in detections:
@@ -524,7 +525,7 @@ def reduce_detections(
# set the detections list to only include top objects
return selected_objects
def get_consolidated_object_detections(detections: list[tuple[any]]):
def get_consolidated_object_detections(detections: list[tuple[Any]]):
"""Drop detections that overlap too much."""
detected_object_groups = defaultdict(lambda: [])
for detection in detections:
+17 -11
View File
@@ -9,7 +9,7 @@ import signal
import subprocess as sp
import traceback
from datetime import datetime
from typing import List, Optional, Tuple
from typing import Any, List, Optional, Tuple
import cv2
import psutil
@@ -230,7 +230,7 @@ def is_vaapi_amd_driver() -> bool:
return any("AMD Radeon Graphics" in line for line in output)
def get_amd_gpu_stats() -> dict[str, str]:
def get_amd_gpu_stats() -> Optional[dict[str, str]]:
"""Get stats using radeontop."""
radeontop_command = ["radeontop", "-d", "-", "-l", "1"]
@@ -256,7 +256,7 @@ def get_amd_gpu_stats() -> dict[str, str]:
return results
def get_intel_gpu_stats(sriov: bool) -> dict[str, str]:
def get_intel_gpu_stats(sriov: bool) -> Optional[dict[str, str]]:
"""Get stats using intel_gpu_top."""
def get_stats_manually(output: str) -> dict[str, str]:
@@ -304,7 +304,7 @@ def get_intel_gpu_stats(sriov: bool) -> dict[str, str]:
]
if sriov:
intel_gpu_top_command += ["-d", "drm:/dev/dri/card0"]
intel_gpu_top_command += ["-d", "sriov"]
p = sp.run(
intel_gpu_top_command,
@@ -382,7 +382,7 @@ def get_intel_gpu_stats(sriov: bool) -> dict[str, str]:
return results
def get_rockchip_gpu_stats() -> dict[str, str]:
def get_rockchip_gpu_stats() -> Optional[dict[str, str]]:
"""Get GPU stats using rk."""
try:
with open("/sys/kernel/debug/rkrga/load", "r") as f:
@@ -403,12 +403,18 @@ def get_rockchip_gpu_stats() -> dict[str, str]:
return {"gpu": average_load, "mem": "-"}
def get_rockchip_npu_stats() -> dict[str, str]:
def get_rockchip_npu_stats() -> Optional[dict[str, float | str]]:
"""Get NPU stats using rk."""
try:
with open("/sys/kernel/debug/rknpu/load", "r") as f:
npu_output = f.read()
core_loads = re.findall(r"Core\d+:\s*(\d+)%", npu_output)
if "Core0:" in npu_output:
# multi core NPU
core_loads = re.findall(r"Core\d+:\s*(\d+)%", npu_output)
else:
# single core NPU
core_loads = re.findall(r"NPU load:\s+(\d+)%", npu_output)
except FileNotFoundError:
core_loads = None
@@ -488,7 +494,7 @@ def get_nvidia_gpu_stats() -> dict[int, dict]:
return results
def get_jetson_stats() -> dict[int, dict]:
def get_jetson_stats() -> Optional[dict[int, dict]]:
results = {}
try:
@@ -531,7 +537,7 @@ def vainfo_hwaccel(device_name: Optional[str] = None) -> sp.CompletedProcess:
return sp.run(ffprobe_cmd, capture_output=True)
def get_nvidia_driver_info() -> dict[str, any]:
def get_nvidia_driver_info() -> dict[str, Any]:
"""Get general hardware info for nvidia GPU."""
results = {}
try:
@@ -590,8 +596,8 @@ def auto_detect_hwaccel() -> str:
async def get_video_properties(
ffmpeg, url: str, get_duration: bool = False
) -> dict[str, any]:
async def calculate_duration(video: Optional[any]) -> float:
) -> dict[str, Any]:
async def calculate_duration(video: Optional[Any]) -> float:
duration = None
if video is not None:
+5 -3
View File
@@ -184,7 +184,7 @@ class CameraWatchdog(threading.Thread):
self.capture_thread = None
self.ffmpeg_detect_process = None
self.logpipe = LogPipe(f"ffmpeg.{self.camera_name}.detect")
self.ffmpeg_other_processes: list[dict[str, any]] = []
self.ffmpeg_other_processes: list[dict[str, Any]] = []
self.camera_fps = camera_fps
self.skipped_fps = skipped_fps
self.ffmpeg_pid = ffmpeg_pid
@@ -371,7 +371,9 @@ class CameraWatchdog(threading.Thread):
p["logpipe"].close()
self.ffmpeg_other_processes.clear()
def get_latest_segment_datetime(self, latest_segment: datetime.datetime) -> int:
def get_latest_segment_datetime(
self, latest_segment: datetime.datetime
) -> datetime.datetime:
"""Checks if ffmpeg is still writing recording segments to cache."""
cache_files = sorted(
[
@@ -859,7 +861,7 @@ def process_frames(
detections[obj["id"]] = {**obj, "attributes": []}
# find the best object for each attribute to be assigned to
all_objects: list[dict[str, any]] = object_tracker.tracked_objects.values()
all_objects: list[dict[str, Any]] = object_tracker.tracked_objects.values()
for attributes in attribute_detections.values():
for attribute in attributes:
filtered_objects = filter(
+10
View File
@@ -0,0 +1,10 @@
# Notebooks
## YOLO-NAS Pretrained
You can build and download a compatible model with pre-trained weights using [Google Colab](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb).
> [!WARNING]
> The pre-trained YOLO-NAS weights from DeciAI are subject to their license and can't be used commercially. For more information, see: https://docs.deci.ai/super-gradients/latest/LICENSE.YOLONAS.html
The input image size in this notebook is set to 320x320. This results in lower CPU usage and faster inference times without impacting performance in most cases due to the way Frigate crops video frames to areas of interest before running detection. The notebook and config can be updated to 640x640 if desired. By default, YOLO_NAS_S is built with YOLO_NAS_M and YOLO_NAS_L sizes also being available for export.
+2 -2
View File
@@ -1,3 +1,3 @@
[tool.ruff]
[tool.ruff.lint]
ignore = ["E501","E711","E712"]
extend-select = ["I"]
extend-select = ["I"]
+201 -11
View File
@@ -25,7 +25,7 @@
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toggle": "^1.1.2",
@@ -54,7 +54,7 @@
"nosleep.js": "^0.12.0",
"react": "^18.3.1",
"react-apexcharts": "^1.4.1",
"react-day-picker": "^8.10.1",
"react-day-picker": "^9.7.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.3.1",
"react-dropzone": "^14.3.8",
@@ -268,6 +268,12 @@
"integrity": "sha512-U9DBDe5fxHmbwQww9rFxMLNI2Wlg7DhPzI7AVFpq8GehiUP7+NwuMPXpP4zAd52sgkxtOqOeMjgE5g0ZLnQZ0w==",
"license": "MIT"
},
"node_modules/@date-fns/tz": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz",
"integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
@@ -1244,6 +1250,24 @@
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz",
@@ -1346,6 +1370,24 @@
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
@@ -1440,6 +1482,24 @@
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
@@ -1663,6 +1723,24 @@
}
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz",
@@ -1700,6 +1778,24 @@
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz",
@@ -1803,6 +1899,24 @@
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-radio-group": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz",
@@ -1940,6 +2054,24 @@
}
}
},
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz",
@@ -1997,12 +2129,12 @@
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz",
"integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
@@ -2014,6 +2146,21 @@
}
}
},
"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-switch": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz",
@@ -2161,6 +2308,24 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@@ -4395,11 +4560,18 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/date-fns-jalali": {
"version": "4.1.0-0",
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
"license": "MIT"
},
"node_modules/date-fns-tz": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
@@ -7196,16 +7368,34 @@
}
},
"node_modules/react-day-picker": {
"version": "8.10.1",
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
"integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==",
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.7.0.tgz",
"integrity": "sha512-urlK4C9XJZVpQ81tmVgd2O7lZ0VQldZeHzNejbwLWZSkzHH498KnArT0EHNfKBOWwKc935iMLGZdxXPRISzUxQ==",
"license": "MIT",
"dependencies": {
"@date-fns/tz": "1.2.0",
"date-fns": "4.1.0",
"date-fns-jalali": "4.1.0-0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/gpbl"
},
"peerDependencies": {
"date-fns": "^2.28.0 || ^3.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": ">=16.8.0"
}
},
"node_modules/react-day-picker/node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/react-device-detect": {
+2 -2
View File
@@ -31,7 +31,7 @@
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toggle": "^1.1.2",
@@ -60,7 +60,7 @@
"nosleep.js": "^0.12.0",
"react": "^18.3.1",
"react-apexcharts": "^1.4.1",
"react-day-picker": "^8.10.1",
"react-day-picker": "^9.7.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.3.1",
"react-dropzone": "^14.3.8",
+13 -13
View File
@@ -3,25 +3,25 @@
"snort": "نَفْخَة",
"heartbeat": "نَبْض القَلْب",
"pets": "حَيَوَانَات أَلِيفَة",
"whoop": "هُتَاف",
"whoop": "هتاف",
"humming": "هَمْهَمَة",
"chewing": "مَضْغ",
"yodeling": "تَزَلْغُط",
"yodeling": "غناء متقلب",
"howl": "عُوَاء",
"speech": َلَام",
"speech": لام",
"hiccup": "فُوَاق",
"dog": "كَلْب",
"yip": "نُبَيْحَة",
"babbling": َرْثَرَة",
"yell": ُرَاخ",
"bellow": "خُوَار",
"whispering": "هَمْس",
"laughter": َحِك",
"snicker": "تَضَاحُك",
"crying": ُكَاء",
"sigh": َنَهُّد",
"singing": ِنَاء",
"choir": "جَوْقَة",
"babbling": رثرة",
"yell": راخ",
"bellow": "زمجرة",
"whispering": "همس",
"laughter": حك",
"snicker": "ضحكة خفيفه",
"crying": كاء",
"sigh": نهد",
"singing": ناء",
"choir": "فرقة غناء",
"chant": "تَرْنِيم",
"mantra": "تَرْنِيمَة",
"child_singing": "غِنَاء طِفْل",

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