Compare commits

...
53 Commits
Author SHA1 Message Date
dependabot[bot]andGitHub e253a308da Merge 5753b5b77c into 4ee12e6237 2026-07-07 07:56:12 +00:00
Nicolas MowenandGitHub 4ee12e6237 Increase ruff coverage (#23644)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Jetson Jetpack 6 (push) Waiting to run
* Pin ruff

* Add python upgrade fixes

This enables python upgrade checks in ruff to look for deprecated types and patterns. This namely fixes:
- usage of deprecated `Typing` which is now built in
- some specific exceptions which are caught and have new aliases

Some specific UP checks were also ignored as they are stylistic / unimportant and likely to cause bugs

* Remove async blocking calls

Use asyncio.to_thread on two remaining blocking calls to fix hanging event thread loop. Enable this specific rule to block it in the future.

* Use proper logging mechanism

* Correctly format logs

* Raise with context

When raising an exception include the from context to improve debugging

* Cleanup
2026-07-06 12:28:02 -05:00
455b8687e8 Tweaks (#23638)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* docs tweaks

* show reolink warning when using probe path in camera wizard

* note ffmpeg 8 default

* update links

* add faq about false positives

* tweak plus language

* Recommend OpenVINO uses YOLOv9 by default

* add mse/rtc live view faq

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2026-07-06 09:28:12 -06:00
Sysoev AlekseyandGitHub 279dcf9bca docs: add Frigate Notify Alert to third party extensions (#23634) 2026-07-06 07:01:09 -06:00
4e65cc1019 Translated using Weblate (Norwegian Bokmål)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
Currently translated at 100.0% (62 of 62 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% (23 of 23 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (239 of 239 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-player/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/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-motionsearch/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/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
c4b007686f Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (1282 of 1285 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (809 of 809 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1277 of 1277 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (475 of 475 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (49 of 49 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (188 of 188 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% (1276 of 1276 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Yechi Yang <yechiyang93@gmail.com>
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/config-cameras/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-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/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
75ca66679a Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (807 of 807 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (239 of 239 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jamie HUANG <114514020@live.asia.edu.tw>
Co-authored-by: 莊凱鈞 <kcchuang88@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/zh_Hant/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
9f52372cfd Translated using Weblate (Slovak)
Currently translated at 67.3% (68 of 101 strings)

Translated using Weblate (Slovak)

Currently translated at 0.9% (8 of 809 strings)

Translated using Weblate (Slovak)

Currently translated at 97.9% (234 of 239 strings)

Translated using Weblate (Slovak)

Currently translated at 98.0% (49 of 50 strings)

Translated using Weblate (Slovak)

Currently translated at 49.4% (631 of 1277 strings)

Translated using Weblate (Slovak)

Currently translated at 91.4% (118 of 129 strings)

Translated using Weblate (Slovak)

Currently translated at 1.6% (8 of 475 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Pavol Krnáč <palokrnac@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sk/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
1c610a7271 Translated using Weblate (Serbian)
Currently translated at 51.1% (65 of 127 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Serbian)

Currently translated at 46.7% (51 of 109 strings)

Co-authored-by: Dalibor Radovanović <darkobg@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/sr/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
2026-07-05 21:08:46 -05:00
075e5196a7 Translated using Weblate (Swedish)
Currently translated at 96.3% (105 of 109 strings)

Translated using Weblate (Swedish)

Currently translated at 2.7% (13 of 475 strings)

Translated using Weblate (Swedish)

Currently translated at 0.6% (5 of 809 strings)

Translated using Weblate (Swedish)

Currently translated at 50.7% (648 of 1277 strings)

Translated using Weblate (Swedish)

Currently translated at 0.1% (1 of 809 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Swedish)

Currently translated at 0.6% (3 of 475 strings)

Translated using Weblate (Swedish)

Currently translated at 94.4% (137 of 145 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (101 of 101 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (239 of 239 strings)

Co-authored-by: Christian Bengtsson <bnccnb@gmail.com>
Co-authored-by: Coffe <effocs@gmail.com>
Co-authored-by: Fredrik Tuomas <fredrik.tuomas@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kristian Johansson <knmjohansson@gmail.com>
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-dialog/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sv/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
3c3acbe8f8 Translated using Weblate (French)
Currently translated at 89.9% (98 of 109 strings)

Translated using Weblate (French)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (French)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (French)

Currently translated at 56.4% (35 of 62 strings)

Co-authored-by: Antoine de Champlain <mon.nom.tony@gmail.com>
Co-authored-by: Fräntz Miccoli <frantz.miccoli@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: LeBuzzy <bwinster2@outlook.com>
Co-authored-by: NicoA08 <nicolasantunes08@gmail.com>
Co-authored-by: shdw <weblate@assez.biz>
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-motionsearch/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-motionSearch
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
da20b134d2 Translated using Weblate (Spanish)
Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (1285 of 1285 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (809 of 809 strings)

Translated using Weblate (Spanish)

Currently translated at 99.4% (187 of 188 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (1277 of 1277 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (475 of 475 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Libre <6n0n1m0s@proton.me>
Co-authored-by: jjavin <javiernovoa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/es/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/es/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
87f9c6240a Translated using Weblate (Dutch)
Currently translated at 91.0% (92 of 101 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (239 of 239 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: laurensthedeveloper <laurensg100@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
2026-07-05 21:08:46 -05:00
2819e76491 Translated using Weblate (Indonesian)
Currently translated at 64.8% (70 of 108 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Indonesian)

Currently translated at 57.4% (58 of 101 strings)

Translated using Weblate (Indonesian)

Currently translated at 43.5% (44 of 101 strings)

Translated using Weblate (Indonesian)

Currently translated at 94.0% (47 of 50 strings)

Translated using Weblate (Indonesian)

Currently translated at 42.5% (43 of 101 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Indonesian)

Currently translated at 90.0% (45 of 50 strings)

Translated using Weblate (Indonesian)

Currently translated at 90.0% (45 of 50 strings)

Co-authored-by: Alberto-Audrix <alberto.suiwidjaya6@gmail.com>
Co-authored-by: Diazt Muhammad Firmansyah <diaztmuhammadfirmansyah@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Naufal F <fadhlurrahmannf0812@gmail.com>
Co-authored-by: Yeni Setiawan <yenisetiawan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/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/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
2026-07-05 21:08:46 -05:00
c5f77b3e20 Translated using Weblate (Arabic)
Currently translated at 19.6% (25 of 127 strings)

Translated using Weblate (Arabic)

Currently translated at 30.7% (154 of 501 strings)

Co-authored-by: Ahmed Marzouq <ahmed.marzouq.co@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ar/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ar/
Translation: Frigate NVR/audio
Translation: Frigate NVR/objects
2026-07-05 21:08:46 -05:00
723bfa9308 Translated using Weblate (Italian)
Currently translated at 74.2% (601 of 809 strings)

Translated using Weblate (Italian)

Currently translated at 99.9% (1276 of 1277 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 99.5% (473 of 475 strings)

Translated using Weblate (Italian)

Currently translated at 73.0% (591 of 809 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (1276 of 1276 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (475 of 475 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Italian)

Currently translated at 67.7% (548 of 809 strings)

Translated using Weblate (Italian)

Currently translated at 55.7% (451 of 809 strings)

Translated using Weblate (Italian)

Currently translated at 76.0% (361 of 475 strings)

Co-authored-by: Edoardo Sorrenti <ed.sorrenti@gmail.com>
Co-authored-by: Filippo-riccardo Franzin (filippo franzin) <filric01@gmail.com>
Co-authored-by: Gringo <ita.translations@tiscali.it>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/it/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
645601d83e Translated using Weblate (Polish)
Currently translated at 10.4% (84 of 807 strings)

Translated using Weblate (Polish)

Currently translated at 33.8% (160 of 473 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Polish)

Currently translated at 31.3% (149 of 475 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (101 of 101 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Polish)

Currently translated at 98.4% (127 of 129 strings)

Translated using Weblate (Polish)

Currently translated at 9.1% (74 of 809 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kamil Cybułka <kamil.cybulka@gmail.com>
Co-authored-by: Paweł Kapeluszny <cyberitsec@proton.me>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pl/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
2026-07-05 21:08:46 -05:00
ca1637ba5a Translated using Weblate (Hebrew)
Currently translated at 90.7% (217 of 239 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Raziel Zaarur <razielzaarur1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/he/
Translation: Frigate NVR/common
2026-07-05 21:08:46 -05:00
79fcf40d96 Translated using Weblate (Hungarian)
Currently translated at 31.4% (17 of 54 strings)

Translated using Weblate (Hungarian)

Currently translated at 86.8% (435 of 501 strings)

Translated using Weblate (Hungarian)

Currently translated at 72.8% (137 of 188 strings)

Translated using Weblate (Hungarian)

Currently translated at 85.6% (429 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Laszlo Bana <banalac@yahoo.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/hu/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/hu/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/hu/
Translation: Frigate NVR/audio
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
5e17aa685b Translated using Weblate (Portuguese)
Currently translated at 50.4% (55 of 109 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Portuguese)

Currently translated at 33.1% (427 of 1287 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.5% (238 of 239 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.4% (498 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: João Nuno <joaomnuno@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
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/views-settings/pt/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
f5d6ca2772 Translated using Weblate (Catalan)
Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1285 of 1285 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1277 of 1277 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (809 of 809 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (475 of 475 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1277 of 1277 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ca/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
39856aaa2d Translated using Weblate (Ukrainian)
Currently translated at 81.3% (153 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 6.7% (32 of 473 strings)

Translated using Weblate (Ukrainian)

Currently translated at 2.7% (22 of 807 strings)

Translated using Weblate (Ukrainian)

Currently translated at 80.8% (152 of 188 strings)

Translated using Weblate (Ukrainian)

Currently translated at 6.3% (30 of 473 strings)

Translated using Weblate (Ukrainian)

Currently translated at 2.6% (21 of 807 strings)

Translated using Weblate (Ukrainian)

Currently translated at 6.1% (29 of 475 strings)

Translated using Weblate (Ukrainian)

Currently translated at 2.4% (20 of 809 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Ukrainian)

Currently translated at 1.2% (10 of 809 strings)

Translated using Weblate (Ukrainian)

Currently translated at 4.0% (19 of 475 strings)

Translated using Weblate (Ukrainian)

Currently translated at 0.7% (6 of 809 strings)

Translated using Weblate (Ukrainian)

Currently translated at 3.1% (15 of 475 strings)

Translated using Weblate (Ukrainian)

Currently translated at 0.4% (4 of 809 strings)

Translated using Weblate (Ukrainian)

Currently translated at 2.5% (12 of 475 strings)

Translated using Weblate (Ukrainian)

Currently translated at 1.6% (8 of 475 strings)

Translated using Weblate (Ukrainian)

Currently translated at 96.1% (25 of 26 strings)

Translated using Weblate (Ukrainian)

Currently translated at 0.1% (1 of 809 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (50 of 50 strings)

Co-authored-by: A T <andrey.timchenko@gmail.com>
Co-authored-by: Den <denis.ua22@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Vitaliy Kreminskiy <vkrmk13@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-player/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/uk/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
3ae70f1808 Translated using Weblate (Bulgarian)
Currently translated at 11.6% (7 of 60 strings)

Translated using Weblate (Bulgarian)

Currently translated at 50.0% (43 of 86 strings)

Translated using Weblate (Bulgarian)

Currently translated at 20.0% (29 of 145 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.6% (27 of 475 strings)

Translated using Weblate (Bulgarian)

Currently translated at 7.0% (57 of 809 strings)

Translated using Weblate (Bulgarian)

Currently translated at 34.0% (34 of 100 strings)

Translated using Weblate (Bulgarian)

Currently translated at 3.1% (4 of 129 strings)

Translated using Weblate (Bulgarian)

Currently translated at 78.4% (393 of 501 strings)

Translated using Weblate (Bulgarian)

Currently translated at 15.6% (10 of 64 strings)

Translated using Weblate (Bulgarian)

Currently translated at 74.8% (375 of 501 strings)

Translated using Weblate (Bulgarian)

Currently translated at 20.2% (15 of 74 strings)

Translated using Weblate (Bulgarian)

Currently translated at 36.8% (88 of 239 strings)

Translated using Weblate (Bulgarian)

Currently translated at 36.8% (88 of 239 strings)

Translated using Weblate (Bulgarian)

Currently translated at 33.0% (33 of 100 strings)

Translated using Weblate (Bulgarian)

Currently translated at 0.2% (1 of 475 strings)

Translated using Weblate (Bulgarian)

Currently translated at 21.2% (27 of 127 strings)

Translated using Weblate (Bulgarian)

Currently translated at 2.3% (3 of 129 strings)

Translated using Weblate (Bulgarian)

Currently translated at 1.1% (9 of 809 strings)

Co-authored-by: Dobromir Kirov <kirov0407@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Plamen Stoyanov <fireto@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/bg/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
2026-07-05 21:08:46 -05:00
a9e4cd061c Translated using Weblate (Romanian)
Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (1285 of 1285 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (809 of 809 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (475 of 475 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (1277 of 1277 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ro/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
f59025681d Translated using Weblate (Russian)
Currently translated at 77.0% (84 of 109 strings)

Translated using Weblate (Russian)

Currently translated at 73.1% (79 of 108 strings)

Translated using Weblate (Russian)

Currently translated at 54.9% (706 of 1285 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Russian)

Currently translated at 92.4% (221 of 239 strings)

Co-authored-by: Artem Vladimirov <artyomka71@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Vladimir Bely <vlwwwwww@gmail.com>
Co-authored-by: Артем <artem_ibatullin@mail.ru>
Co-authored-by: Дмитрий Власкин <vdvlaskin@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-dialog/ru/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ru/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
126cbd9e5e Translated using Weblate (Greek)
Currently translated at 14.3% (72 of 501 strings)

Translated using Weblate (Greek)

Currently translated at 49.7% (119 of 239 strings)

Co-authored-by: George Rovolis <georgios@rovolis.co.uk>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/el/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/el/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
2026-07-05 21:08:46 -05:00
f85387570b Translated using Weblate (Danish)
Currently translated at 1.0% (13 of 1287 strings)

Translated using Weblate (Danish)

Currently translated at 28.4% (31 of 109 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Danish)

Currently translated at 41.3% (207 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: nicoleise <niceggert@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/da/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
b0cecd534b Translated using Weblate (German)
Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (German)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (German)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (German)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (German)

Currently translated at 100.0% (473 of 473 strings)

Translated using Weblate (German)

Currently translated at 100.0% (127 of 127 strings)

Translated using Weblate (German)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1285 of 1285 strings)

Translated using Weblate (German)

Currently translated at 100.0% (188 of 188 strings)

Translated using Weblate (German)

Currently translated at 100.0% (807 of 807 strings)

Translated using Weblate (German)

Currently translated at 100.0% (475 of 475 strings)

Translated using Weblate (German)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (German)

Currently translated at 100.0% (809 of 809 strings)

Translated using Weblate (German)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1276 of 1276 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nuendo-DE <tim.huehner@gmx.de>
Co-authored-by: Sebastian Sie <sebastian.neuplanitz@googlemail.com>
Co-authored-by: laurensthedeveloper <laurensg100@gmail.com>
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-dialog/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/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/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
7726d2ec97 Translated using Weblate (Portuguese (Brazil))
Currently translated at 28.8% (13 of 45 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 32.7% (265 of 808 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 56.3% (267 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 39.5% (509 of 1287 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.1% (107 of 109 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 90.7% (49 of 54 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 11.4% (92 of 807 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 17.7% (84 of 473 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 52.7% (68 of 129 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.4% (104 of 109 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (500 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ricardo RFPP <ricardo.inteli@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-groups/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pt_BR/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Groups
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
2026-07-05 21:08:46 -05:00
9662bf094f Translated using Weblate (Turkish)
Currently translated at 3.2% (26 of 807 strings)

Translated using Weblate (Turkish)

Currently translated at 6.1% (29 of 473 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Turkish)

Currently translated at 98.1% (106 of 108 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Turkish)

Currently translated at 73.4% (138 of 188 strings)

Translated using Weblate (Turkish)

Currently translated at 6.4% (4 of 62 strings)

Translated using Weblate (Turkish)

Currently translated at 98.0% (49 of 50 strings)

Translated using Weblate (Turkish)

Currently translated at 90.0% (91 of 101 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nazım Sarp Tekbaş <sarptekbas07@gmail.com>
Co-authored-by: Selim Kundakçıoğlu <selimkundakcioglu@gmail.com>
Co-authored-by: Turhan Munis <turhan.munis@gmail.com>
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-player/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/tr/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-system
2026-07-05 21:08:46 -05:00
Josh HawkinsandGitHub a26487c4f0 Register per-camera notifications MQTT callbacks (#23637)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Extra Build (push) Blocked by required conditions
* fix per-camera notification MQTT topics never being registered

register notifications/set and notifications/suspend callbacks for each camera, and gate the global notifications topics on per-camera config as well as global (matching WebPushClient creation in app.py). Unregistered topics were silently dropped by paho since only registered callbacks receive messages.

* add tests
2026-07-05 17:24:39 -05:00
Josh HawkinsandGitHub 1c745e0847 fix config corruption when deleting the last commented mask or zone (#23627)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Extra Build (push) Blocked by required conditions
Deleting the last entry of a mapping or sequence via config/set orphaned ruamel's comment tokens, which were then emitted above a flow-style {} / [] at column 0, which is unparseable yaml that failed validation and silently rolled the change back. The fix is to clear the emptied collection's stale comment metadata (and the parent's entry for it) so the dump stays valid. Non-empty collections are left untouched so sibling comments are preserved. This covers both emptied maps and emptied lists.
2026-07-04 17:10:32 -06:00
Josh HawkinsandGitHub 729ee86043 Miscellaneous fixes (#23619)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Jetson Jetpack 6 (push) Waiting to run
* fix stale active object indicators on the live dashboard

The camera_activity/<camera> snapshot cache is only written when a client sends onConnect, and object "end" events only update the local state of mounted useCameraActivity hooks, never the cache. As a result, a hook that seeded from a stale cache or missed an "end" event while disconnected showed objects that had already left, with no path to correct itself short of a full page reload.

This change will re-request the snapshot on hook mount (collapsed to one onConnect per task across camera cards), and always re-notify camera_activity topics so hooks reconcile against their own local state instead of relying on snapshot-vs-snapshot comparison, and clear the payload dedup cache on reconnect and resync so byte-identical snapshots still apply.

* docs tweaks

* fix mqtt log message

* use consistent values for lpr debug frame filenames

with millisecond resolution

* apply object events through a functional updater to prevent lost updates

The events effect derived a new objects list from the value captured at render time and wrote the whole list back. When events arrived close
together, a run derived from a stale list erased a concurrent run's removal; the resurrected object then had no remaining "end" event to clear it, and the add branch could mint a duplicate entry that no splice could ever remove, leaving the live dashboard showing active objects the backend had already cleared, until a page reload.

The fix is to apply each event inside setObjects so it operates on the true current list exactly once. Unchanged results return the same reference so React bails out of re-rendering, and the label rewrite is hoisted so added objects get the sub_label/verified label directly instead of relying on the effect re-running against its own state update.
2026-07-04 00:25:22 -06:00
Blake BlackshearandGitHub c007661a71 Enhancements (#23611)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
* better default handling if no proxy chain is present

* add note about SameSite for jwt cookie
2026-07-02 07:05:00 -05:00
9b02c7318d Miscellaneous fixes (#23610)
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
* Handle back seeking going to previous clip

* scope /recordings/unavailable query to the caller's allowed cameras

* listen for config updates in activity manager

* don't set search after awaited request

Intentionally do NOT setSearch() to mark the open event submitted. This runs after the awaited request, by which point the user may have closed the dialog; re-setting the parent's selected event would resurrect it and the force-open effect would reopen it (see #23599). The local "submitted" state covers the open card, and mutate() updates the events cache so the grid and any future open reflect the result.

* fix ruff

#23201 removed pathlib import but for some reason it's just now causing ruff to fail

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2026-07-01 15:03:34 -05:00
Blake BlackshearandClaude Opus 4.8 ea131e1663 Merge remote-tracking branch 'origin/master' into dev
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
Resolve conflicts in the export pipeline where dev's job-queue refactor
met master's chapter-metadata and security work.

- Unify chapter support under ChaptersEnum (none / recording_segments /
  review_items); the realtime stream-copy export selects the per-segment
  or per-review-item builder by the camera's configured mode. Thread
  chapters through ExportRecordingsBody -> _build_export_job -> ExportJob
  -> RecordingExporter.
- Keep master's creation_time/comment export metadata and fix a
  video_path duplication the textual merge introduced in the preview
  command.
- Move the chapters request field to ExportRecordingsBody (the single
  export endpoint) where it is actually honored.

Restore security fixes the automatic merge would have reverted:
- frigate/util/services.py: restore the #23493 rename to the public
  is_go2rtc_arbitrary_exec_allowed so create_config.py's dynamic-source
  exec guard imports and runs (the merge otherwise left a broken import).
- Preserve the export image-path ".." traversal check inside
  _sanitize_existing_image, applied to single/custom/batch exports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:04:01 +02:00
Josh HawkinsandGitHub e2ce0c82ff show Frigate+ submission failures in the UI instead of showing a false success (#23579)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
2026-06-27 16:38:24 -06:00
Josh HawkinsandGitHub 3d4dd3ac4b allow non-admin users to send PTZ commands for cameras they have access to (#23578)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
2026-06-27 15:55:39 -06:00
Josh HawkinsandGitHub 1ec511b66c Docs tweaks (#23572)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* small tweaks

* add link to docs from detect.fps field message
2026-06-26 15:11:51 -06:00
Nicolas MowenandGitHub accbab7afc Rebuild object docs (#23570)
* Rebuild object docs

* Tweak styling and fix mixing tabs

* Fix warning

* Cleanup styling
2026-06-26 06:48:42 -06:00
Josh HawkinsandGitHub cbf6d032cb Add optional docs link to config field messages (#23569)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
* add optional docs link to config field messages

* docs tweaks

* add field messages for model dimensions
2026-06-25 17:25:59 -06:00
Josh HawkinsandGitHub 933a7f1a3f resolve the leaked Query default so media Cache-Control max-age is always a valid int (#23553)
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
CI / AMD64 Build (push) Has been cancelled
2026-06-24 07:57:46 -05:00
Josh HawkinsandGitHub 4e5e8e3c59 Offload preview encoding and Plus upload off the API event loop (#23552)
* offload preview ffmpeg encoding to a thread to avoid blocking the api event loop

* offload Frigate+ recording snapshot upload to a thread to avoid blocking the api event loop
2026-06-24 07:17:23 -05:00
Nicolas MowenandGitHub b3ce4486b9 Catch edge cases in security protections (#23493)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
* Fix go2rtc nested key dict

* Don't allow path traversal
2026-06-16 08:07:12 -06:00
Nicolas MowenandGitHub 06e3d0ac5d Chapter tweaks (#23440)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
* Add camera metadata and fix preview chapters

* Add config option for chapters
2026-06-09 09:07:42 -06:00
Nicolas MowenandGitHub 28e3e1ec74 Add ability to control chapters set on MP4 Export (#23310)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
2026-05-25 13:06:16 -05:00
Josh HawkinsandGitHub fa07109a85 filter motion review by allowed cameras (#23294)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
2026-05-23 06:47:32 -06:00
Josh HawkinsandGitHub 910059281f update mask docs for more clarity (#23282) 2026-05-21 14:00:46 -06:00
Josh HawkinsandGitHub ef44c18c07 Docs update (#23280)
* stationary car detection troubleshooting tips

* tweak
2026-05-21 09:04:41 -05:00
Josh HawkinsandGitHub 06b059c36a fix admin response cache leak to non-admin users via nginx proxy_cache (#23261)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
2026-05-20 07:29:37 -05:00
Nicolas MowenandGitHub 26d31300e6 Add metadata for creation time to recording segments and exports (#23239)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
2026-05-18 10:58:10 -05:00
0013555528 Fixes (#23235)
* use stable empty object reference for swr metadata default

* version bump

* Refactor get_min_region_size for dimension normalization

Refactor get_min_region_size to normalize dimensions for smaller models and ensure minimum region size is 320 for larger models.

* reject restricted go2rtc stream sources when added via api

* add env var check function

* fix typing

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2026-05-18 10:32:39 -05:00
dependabot[bot]andGitHub 5753b5b77c Bump @melloware/react-logviewer from 6.1.2 to 6.5.2 in /web
Bumps [@melloware/react-logviewer](https://github.com/melloware/react-logviewer) from 6.1.2 to 6.5.2.
- [Release notes](https://github.com/melloware/react-logviewer/releases)
- [Commits](https://github.com/melloware/react-logviewer/compare/6.1.2...6.5.2)

---
updated-dependencies:
- dependency-name: "@melloware/react-logviewer"
  dependency-version: 6.5.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-01 13:18:27 +00:00
389 changed files with 8348 additions and 3616 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
ruff
ruff == 0.15.20
# types
types-peewee == 3.17.*
@@ -3,6 +3,7 @@
import json
import os
import sys
from pathlib import Path
from typing import Any
from ruamel.yaml import YAML
@@ -15,12 +16,24 @@ from frigate.const import (
)
from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode
from frigate.util.config import find_config_file, resolve_ffmpeg_path
from frigate.util.services import is_restricted_go2rtc_source
from frigate.util.services import (
is_go2rtc_arbitrary_exec_allowed,
is_restricted_go2rtc_source,
)
sys.path.remove("/opt/frigate")
yaml = YAML()
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
# read docker secret files as env vars too
if os.path.isdir("/run/secrets"):
for secret_file in os.listdir("/run/secrets"):
if secret_file.startswith("FRIGATE_"):
FRIGATE_ENV_VARS[secret_file] = (
Path(os.path.join("/run/secrets", secret_file)).read_text().strip()
)
config_file = find_config_file()
try:
@@ -100,7 +113,7 @@ for name in list(go2rtc_config.get("streams", {})):
if isinstance(stream, str):
try:
formatted_stream = substitute_frigate_vars(stream)
formatted_stream = stream.format(**FRIGATE_ENV_VARS)
if is_restricted_go2rtc_source(formatted_stream):
print(
f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. "
@@ -119,7 +132,7 @@ for name in list(go2rtc_config.get("streams", {})):
filtered_streams = []
for i, stream_item in enumerate(stream):
try:
formatted_stream = substitute_frigate_vars(stream_item)
formatted_stream = stream_item.format(**FRIGATE_ENV_VARS)
if is_restricted_go2rtc_source(formatted_stream):
print(
f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. "
@@ -143,6 +156,20 @@ for name in list(go2rtc_config.get("streams", {})):
)
del go2rtc_config["streams"][name]
elif isinstance(stream, dict):
# The map form ({"url": ...}) lets go2rtc resolve the source
# recursively, so it is effectively a dynamic way to generate the URL
# for a stream. That can only be backed by an exec source, so it cannot
# be allowed unless arbitrary exec is explicitly enabled. When it is
# enabled, leave the map untouched for go2rtc to resolve.
if not is_go2rtc_arbitrary_exec_allowed():
print(
f"[ERROR] Stream '{name}' uses a dynamic source format which is disabled by default for security. "
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
)
del go2rtc_config["streams"][name]
continue
# add birdseye restream stream if enabled
if config.get("birdseye", {}).get("restream", False):
birdseye: dict[str, Any] = config.get("birdseye")
+3 -3
View File
@@ -11,10 +11,10 @@ except FileNotFoundError:
pass
try:
with open("/config/conv2rknn.yaml", "r") as config_file:
with open("/config/conv2rknn.yaml") as config_file:
configuration = yaml.safe_load(config_file)
except FileNotFoundError:
raise Exception("Please place a config file at /config/conv2rknn.yaml")
raise Exception("Please place a config file at /config/conv2rknn.yaml") from None
if configuration["config"] != None:
rknn_config = configuration["config"]
@@ -31,7 +31,7 @@ if "soc" not in configuration:
with open("/proc/device-tree/compatible") as file:
soc = file.read().split(",")[-1].strip("\x00")
except FileNotFoundError:
raise Exception("Make sure to run docker in privileged mode.")
raise Exception("Make sure to run docker in privileged mode.") from None
configuration["soc"] = [
soc,
+349
View File
@@ -0,0 +1,349 @@
{
"edgeTPU": {
"title": "EdgeTPU",
"models": [
{
"key": "mobiledet",
"label": "Mobiledet",
"recommended": true,
"download": "A TensorFlow Lite model is provided in the container at `/edgetpu_model.tflite` and is used by this detector type by default. To provide your own model, bind mount the file into the container and provide the path with `model.path`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `usb`.",
"yaml": "detectors:\n coral:\n type: edgetpu\n device: usb"
},
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": false,
"download": "[Download the model](https://github.com/dbro/frigate-detector-edgetpu-yolo9/releases/download/v1.0/yolov9-s-relu6-best_320_int8_edgetpu.tflite), bind mount the file into the container, and provide the path with `model.path`. Note that the linked model requires a 17-label [labelmap file](https://raw.githubusercontent.com/dbro/frigate-detector-edgetpu-yolo9/refs/heads/main/labels-coco17.txt) that includes only 17 COCO classes.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `usb`. Then on the same page, in the **Custom Model** tab, configure the model settings:\n\n| Field | Value |\n| ---------------------------------------- | ----------------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize of the model) |\n| **Object detection model input height** | `320` (should match the imgsize of the model) |\n| **Custom object detector model path** | `/config/model_cache/yolov9-s-relu6-best_320_int8_edgetpu.tflite` |\n| **Label map for custom object detector** | `/config/labels-coco17.txt` |",
"yaml": "detectors:\n coral:\n type: edgetpu\n device: usb\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize of the model, typically 320\n height: 320 # <--- should match the imgsize of the model, typically 320\n path: /config/model_cache/yolov9-s-relu6-best_320_int8_edgetpu.tflite\n labelmap_path: /config/labels-coco17.txt"
}
]
},
"hailo8l": {
"title": "Hailo-8/Hailo-8L",
"models": [
{
"key": "yolo",
"label": "YOLO",
"recommended": true,
"download": "If no custom model path or URL is provided, the Hailo detector automatically downloads the default model (YOLOv6n) from the Hailo Model Zoo on first startup based on the detected hardware. Once cached under `/config/model_cache/hailo`, the model works fully offline.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then on the same page, in the **Custom Model** tab, configure the model settings:\n\n| Field | Value |\n| ---------------------------------------- | ----------------------- |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Model Input Pixel Color Format** | `rgb` |\n| **Model Input D Type** | `int` |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |\n\nThe detector automatically selects the default model based on your hardware. Optionally, specify a local model path or URL to override.",
"yaml": "detectors:\n hailo:\n type: hailo8l\n device: PCIe\n\nmodel:\n width: 320\n height: 320\n input_tensor: nhwc\n input_pixel_format: rgb\n input_dtype: int\n model_type: yolo-generic\n labelmap_path: /labelmap/coco-80.txt\n\n # The detector automatically selects the default model based on your hardware:\n # - For Hailo-8 hardware: YOLOv6n (default: yolov6n.hef)\n # - For Hailo-8L hardware: YOLOv6n (default: yolov6n.hef)\n #\n # Optionally, you can specify a local model path to override the default.\n # If a local path is provided and the file exists, it will be used instead of downloading.\n # Example:\n # path: /config/model_cache/hailo/yolov6n.hef\n #\n # You can also override using a custom URL:\n # path: https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.14.0/hailo8/yolov6n.hef\n # just make sure to give it the write configuration based on the model"
},
{
"key": "ssd",
"label": "SSD MobileNet v1",
"recommended": false,
"download": "For SSD-based models, provide either a model path or URL to your compiled SSD model. The integration will first check the local path before downloading if necessary. The model file is cached under `/config/model_cache/hailo`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then on the same page, in the **Custom Model** tab, configure the model settings:\n\n| Field | Value |\n| --------------------------------------- | ------ |\n| **Object detection model input width** | `300` |\n| **Object detection model input height** | `300` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Model Input Pixel Color Format** | `rgb` |\n| **Object Detection Model Type** | `ssd` |\n\nSpecify the local model path or URL for SSD MobileNet v1.",
"yaml": "detectors:\n hailo:\n type: hailo8l\n device: PCIe\n\nmodel:\n width: 300\n height: 300\n input_tensor: nhwc\n input_pixel_format: rgb\n model_type: ssd\n # Specify the local model path (if available) or URL for SSD MobileNet v1.\n # Example with a local path:\n # path: /config/model_cache/h8l_cache/ssd_mobilenet_v1.hef\n #\n # Or override using a custom URL:\n # path: https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.14.0/hailo8l/ssd_mobilenet_v1.hef"
}
]
},
"openvino": {
"title": "OpenVINO",
"models": [
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": true,
"download": "YOLOv9 model can be exported as ONNX using the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=t` and `IMG_SIZE=320` in the first line to the [model size](https://github.com/WongKinYiu/yolov9#performance) you would like to convert (available model sizes are `t`, `s`, `m`, `c`, and `e`, common image sizes are `320` and `640`).\n\n```sh\ndocker build . --build-arg MODEL_SIZE=t --build-arg IMG_SIZE=320 --output . -f- <<'EOF'\nFROM python:3.11 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y cmake libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /bin/\nWORKDIR /yolov9\nADD https://github.com/WongKinYiu/yolov9.git .\nRUN uv pip install --system -r requirements.txt\nRUN uv pip install --system onnx==1.18.0 onnxruntime onnx-simplifier==0.4.* onnxscript\nARG MODEL_SIZE\nARG IMG_SIZE\nADD https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-${MODEL_SIZE}-converted.pt yolov9-${MODEL_SIZE}.pt\nRUN 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\nRUN python3 export.py --weights ./yolov9-${MODEL_SIZE}.pt --imgsz ${IMG_SIZE} --simplify --include onnx\nFROM scratch\nARG MODEL_SIZE\nARG IMG_SIZE\nCOPY --from=build /yolov9/yolov9-${MODEL_SIZE}.onnx /yolov9-${MODEL_SIZE}-${IMG_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize set during model export) |\n| **Object detection model input height** | `320` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/yolo.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n ov:\n type: openvino\n device: GPU # or NPU\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize set during model export\n height: 320 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/yolo.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "ssd",
"label": "SSDLite MobileNet v2",
"recommended": false,
"download": "An OpenVINO model is provided in the container at `/openvino-model/ssdlite_mobilenet_v2.xml` and is used by this detector type by default. The model comes from Intel's Open Model Zoo [SSDLite MobileNet V2](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/ssdlite_mobilenet_v2) and is converted to an FP16 precision IR model.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------ |\n| **Object detection model input width** | `300` |\n| **Object detection model input height** | `300` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Model Input Pixel Color Format** | `bgr` |\n| **Custom object detector model path** | `/openvino-model/ssdlite_mobilenet_v2.xml` |\n| **Label map for custom object detector** | `/openvino-model/coco_91cl_bkgr.txt` |",
"yaml": "detectors:\n ov:\n type: openvino\n device: GPU # Or NPU\n\nmodel:\n width: 300\n height: 300\n input_tensor: nhwc\n input_pixel_format: bgr\n path: /openvino-model/ssdlite_mobilenet_v2.xml\n labelmap_path: /openvino-model/coco_91cl_bkgr.txt"
},
{
"key": "yolo-legacy",
"label": "YOLO (v3, v4, v7)",
"recommended": false,
"download": "To export as ONNX:\n\n```sh\ngit clone https://github.com/NateMeyer/tensorrt_demos\ncd tensorrt_demos/yolo\n./download_yolo.sh\npython3 yolo_to_onnx.py -m yolov7-320\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize set during model export) |\n| **Object detection model input height** | `320` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/yolo.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n ov:\n type: openvino\n device: GPU # or NPU\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize set during model export\n height: 320 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/yolo.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolonas",
"label": "YOLO-NAS",
"recommended": false,
"download": "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).\n\n:::warning\n\nThe 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\n\n:::\n\nThe 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.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------------- |\n| **Object Detection Model Type** | `yolonas` |\n| **Object detection model input width** | `320` (should match whatever was set in notebook) |\n| **Object detection model input height** | `320` (should match whatever was set in notebook) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input Pixel Color Format** | `bgr` |\n| **Custom object detector model path** | `/config/yolo_nas_s.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n ov:\n type: openvino\n device: GPU\n\nmodel:\n model_type: yolonas\n width: 320 # <--- should match whatever was set in notebook\n height: 320 # <--- should match whatever was set in notebook\n input_tensor: nchw\n input_pixel_format: bgr\n path: /config/yolo_nas_s.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolox",
"label": "YOLOX",
"recommended": false,
"download": "YOLOx models can be downloaded [from the YOLOx repo](https://github.com/Megvii-BaseDetection/YOLOX/tree/main/demo/ONNXRuntime).",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ------------------------------------- | -------------------------------- |\n| **Object Detection Model Type** | `yolox` |\n| **Custom object detector model path** | path to your YOLOX ONNX model |",
"yaml": "detectors:\n ov:\n type: openvino\n device: GPU\n\nmodel:\n model_type: yolox\n path: /config/model_cache/yolox.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "rfdetr",
"label": "RF-DETR",
"recommended": false,
"download": "RF-DETR can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=Nano` in the first line to `Nano`, `Small`, or `Medium` size.\n\n```sh\ndocker build . --build-arg MODEL_SIZE=Nano --rm --output . -f- <<'EOF'\nFROM python:3.12 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /bin/\nWORKDIR /rfdetr\nRUN uv pip install --system rfdetr[onnxexport] torch==2.8.0 onnx==1.19.1 transformers==4.57.6 onnxscript\nARG MODEL_SIZE\nRUN python3 -c \"from rfdetr import RFDETR${MODEL_SIZE}; x = RFDETR${MODEL_SIZE}(resolution=320); x.export(simplify=True)\"\nFROM scratch\nARG MODEL_SIZE\nCOPY --from=build /rfdetr/output/inference_model.onnx /rfdetr-${MODEL_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| --------------------------------------- | --------------------------------- |\n| **Object Detection Model Type** | `rfdetr` |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/rfdetr.onnx` |",
"yaml": "detectors:\n ov:\n type: openvino\n device: GPU\n\nmodel:\n model_type: rfdetr\n width: 320\n height: 320\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/rfdetr.onnx"
},
{
"key": "dfine",
"label": "D-FINE / DEIMv2",
"recommended": false,
"download": "#### D-FINE\n\nD-FINE can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=s` in the first line to `s`, `m`, or `l` size.\n\n```sh\ndocker build . --build-arg MODEL_SIZE=s --output . -f- <<'EOF'\nFROM python:3.11 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/\nWORKDIR /dfine\nRUN git clone https://github.com/Peterande/D-FINE.git .\nRUN uv pip install --system -r requirements.txt\nRUN uv pip install --system onnx onnxruntime onnxsim onnxscript\n# Create output directory and download checkpoint\nRUN mkdir -p output\nARG MODEL_SIZE\nRUN wget https://github.com/Peterande/storage/releases/download/dfinev1.0/dfine_${MODEL_SIZE}_obj2coco.pth -O output/dfine_${MODEL_SIZE}_obj2coco.pth\n# Modify line 58 of export_onnx.py to change batch size to 1\nRUN sed -i '58s/data = torch.rand(.*)/data = torch.rand(1, 3, 640, 640)/' tools/deployment/export_onnx.py\nRUN python3 tools/deployment/export_onnx.py -c configs/dfine/objects365/dfine_hgnetv2_${MODEL_SIZE}_obj2coco.yml -r output/dfine_${MODEL_SIZE}_obj2coco.pth\nFROM scratch\nARG MODEL_SIZE\nCOPY --from=build /dfine/output/dfine_${MODEL_SIZE}_obj2coco.onnx /dfine-${MODEL_SIZE}.onnx\nEOF\n```\n\n#### DEIMv2\n\n[DEIMv2](https://github.com/Intellindust-AI-Lab/DEIMv2) can be exported as ONNX by running the command below. Pretrained weights are available on Hugging Face for two backbone families:\n\n- **HGNetv2** (smaller/faster): `atto`, `femto`, `pico`, `n`\n- **DINOv3** (larger/more accurate): `s`, `m`, `l`, `x`\n\nSet `BACKBONE` and `MODEL_SIZE` in the first line to match your desired variant. Hugging Face model names use uppercase (e.g. `HGNetv2_N`, `DINOv3_S`), while config files use lowercase (e.g. `hgnetv2_n`, `dinov3_s`).\n\n```sh\ndocker build . --rm --build-arg BACKBONE=hgnetv2 --build-arg MODEL_SIZE=n --output . -f- <<'EOF'\nFROM python:3.11-slim AS build\nRUN apt-get update && apt-get install --no-install-recommends -y git libgl1 libglib2.0-0 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/\nWORKDIR /deimv2\nRUN git clone https://github.com/Intellindust-AI-Lab/DEIMv2.git .\n# Install CPU-only PyTorch first to avoid pulling CUDA variant\nRUN uv pip install --no-cache --system torch torchvision --index-url https://download.pytorch.org/whl/cpu\nRUN uv pip install --no-cache --system -r requirements.txt\nRUN uv pip install --no-cache --system onnx safetensors huggingface_hub\nRUN mkdir -p output\nARG BACKBONE\nARG MODEL_SIZE\n# Download from Hugging Face and convert safetensors to pth\nRUN python3 -c \"\\\nfrom huggingface_hub import hf_hub_download; \\\nfrom safetensors.torch import load_file; \\\nimport torch; \\\nbackbone = '${BACKBONE}'.replace('hgnetv2','HGNetv2').replace('dinov3','DINOv3'); \\\nsize = '${MODEL_SIZE}'.upper(); \\\nst = load_file(hf_hub_download('Intellindust/DEIMv2_' + backbone + '_' + size + '_COCO', 'model.safetensors')); \\\ntorch.save({'model': st}, 'output/deimv2.pth')\"\nRUN sed -i \"s/data = torch.rand(2/data = torch.rand(1/\" tools/deployment/export_onnx.py\n# HuggingFace safetensors omits frozen constants that the model constructor initializes\nRUN sed -i \"s/cfg.model.load_state_dict(state)/cfg.model.load_state_dict(state, strict=False)/\" tools/deployment/export_onnx.py\nRUN python3 tools/deployment/export_onnx.py -c configs/deimv2/deimv2_${BACKBONE}_${MODEL_SIZE}_coco.yml -r output/deimv2.pth\nFROM scratch\nARG BACKBONE\nARG MODEL_SIZE\nCOPY --from=build /deimv2/output/deimv2.onnx /deimv2_${BACKBONE}_${MODEL_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `CPU`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ---------------------------------- |\n| **Object Detection Model Type** | `dfine` |\n| **Object detection model input width** | `640` |\n| **Object detection model input height** | `640` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/dfine-s.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n ov:\n type: openvino\n device: CPU\n\nmodel:\n model_type: dfine\n width: 640\n height: 640\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/dfine-s.onnx\n labelmap_path: /labelmap/coco-80.txt"
}
]
},
"appleSilicon": {
"title": "Apple Silicon",
"models": [
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": true,
"download": "YOLOv9 model can be exported as ONNX using the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=t` and `IMG_SIZE=320` in the first line to the [model size](https://github.com/WongKinYiu/yolov9#performance) you would like to convert (available model sizes are `t`, `s`, `m`, `c`, and `e`, common image sizes are `320` and `640`).\n\n```sh\ndocker build . --build-arg MODEL_SIZE=t --build-arg IMG_SIZE=320 --output . -f- <<'EOF'\nFROM python:3.11 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y cmake libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /bin/\nWORKDIR /yolov9\nADD https://github.com/WongKinYiu/yolov9.git .\nRUN uv pip install --system -r requirements.txt\nRUN uv pip install --system onnx==1.18.0 onnxruntime onnx-simplifier==0.4.* onnxscript\nARG MODEL_SIZE\nARG IMG_SIZE\nADD https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-${MODEL_SIZE}-converted.pt yolov9-${MODEL_SIZE}.pt\nRUN 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\nRUN python3 export.py --weights ./yolov9-${MODEL_SIZE}.pt --imgsz ${IMG_SIZE} --simplify --include onnx\nFROM scratch\nARG MODEL_SIZE\nARG IMG_SIZE\nCOPY --from=build /yolov9/yolov9-${MODEL_SIZE}.onnx /yolov9-${MODEL_SIZE}-${IMG_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://host.docker.internal:5555`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize set during model export) |\n| **Object detection model input height** | `320` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/yolo.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n apple-silicon:\n type: zmq\n endpoint: tcp://host.docker.internal:5555\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize set during model export\n height: 320 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/yolo.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolo-legacy",
"label": "YOLO (v3, v4, v7)",
"recommended": false,
"download": "To export as ONNX:\n\n```sh\ngit clone https://github.com/NateMeyer/tensorrt_demos\ncd tensorrt_demos/yolo\n./download_yolo.sh\npython3 yolo_to_onnx.py -m yolov7-320\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://host.docker.internal:5555`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize set during model export) |\n| **Object detection model input height** | `320` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/yolo.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n apple-silicon:\n type: zmq\n endpoint: tcp://host.docker.internal:5555\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize set during model export\n height: 320 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/yolo.onnx\n labelmap_path: /labelmap/coco-80.txt"
}
]
},
"onnx": {
"title": "ONNX",
"models": [
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": true,
"download": "YOLOv9 model can be exported as ONNX using the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=t` and `IMG_SIZE=320` in the first line to the [model size](https://github.com/WongKinYiu/yolov9#performance) you would like to convert (available model sizes are `t`, `s`, `m`, `c`, and `e`, common image sizes are `320` and `640`).\n\n```sh\ndocker build . --build-arg MODEL_SIZE=t --build-arg IMG_SIZE=320 --output . -f- <<'EOF'\nFROM python:3.11 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y cmake libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /bin/\nWORKDIR /yolov9\nADD https://github.com/WongKinYiu/yolov9.git .\nRUN uv pip install --system -r requirements.txt\nRUN uv pip install --system onnx==1.18.0 onnxruntime onnx-simplifier==0.4.* onnxscript\nARG MODEL_SIZE\nARG IMG_SIZE\nADD https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-${MODEL_SIZE}-converted.pt yolov9-${MODEL_SIZE}.pt\nRUN 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\nRUN python3 export.py --weights ./yolov9-${MODEL_SIZE}.pt --imgsz ${IMG_SIZE} --simplify --include onnx\nFROM scratch\nARG MODEL_SIZE\nARG IMG_SIZE\nCOPY --from=build /yolov9/yolov9-${MODEL_SIZE}.onnx /yolov9-${MODEL_SIZE}-${IMG_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize set during model export) |\n| **Object detection model input height** | `320` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/yolo.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n onnx:\n type: onnx\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize set during model export\n height: 320 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/yolo.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "rfdetr",
"label": "RF-DETR",
"recommended": false,
"download": "RF-DETR can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=Nano` in the first line to `Nano`, `Small`, or `Medium` size.\n\n```sh\ndocker build . --build-arg MODEL_SIZE=Nano --rm --output . -f- <<'EOF'\nFROM python:3.12 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /bin/\nWORKDIR /rfdetr\nRUN uv pip install --system rfdetr[onnxexport] torch==2.8.0 onnx==1.19.1 transformers==4.57.6 onnxscript\nARG MODEL_SIZE\nRUN python3 -c \"from rfdetr import RFDETR${MODEL_SIZE}; x = RFDETR${MODEL_SIZE}(resolution=320); x.export(simplify=True)\"\nFROM scratch\nARG MODEL_SIZE\nCOPY --from=build /rfdetr/output/inference_model.onnx /rfdetr-${MODEL_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| --------------------------------------- | --------------------------------- |\n| **Object Detection Model Type** | `rfdetr` |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/rfdetr.onnx` |",
"yaml": "detectors:\n onnx:\n type: onnx\n\nmodel:\n model_type: rfdetr\n width: 320\n height: 320\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/rfdetr.onnx"
},
{
"key": "yolonas",
"label": "YOLO-NAS",
"recommended": false,
"download": "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).\n\n:::warning\n\nThe 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\n\n:::\n\nThe 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.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------------- |\n| **Object Detection Model Type** | `yolonas` |\n| **Object detection model input width** | `320` (should match whatever was set in notebook) |\n| **Object detection model input height** | `320` (should match whatever was set in notebook) |\n| **Model Input Pixel Color Format** | `bgr` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Custom object detector model path** | `/config/yolo_nas_s.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n onnx:\n type: onnx\n\nmodel:\n model_type: yolonas\n width: 320 # <--- should match whatever was set in notebook\n height: 320 # <--- should match whatever was set in notebook\n input_pixel_format: bgr\n input_tensor: nchw\n path: /config/yolo_nas_s.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolox",
"label": "YOLOX",
"recommended": false,
"download": "YOLOx models can be downloaded [from the YOLOx repo](https://github.com/Megvii-BaseDetection/YOLOX/tree/main/demo/ONNXRuntime).",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolox` |\n| **Object detection model input width** | `416` (should match the imgsize set during model export) |\n| **Object detection model input height** | `416` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float_denorm` |\n| **Custom object detector model path** | `/config/model_cache/yolox_tiny.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n onnx:\n type: onnx\n\nmodel:\n model_type: yolox\n width: 416 # <--- should match the imgsize set during model export\n height: 416 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float_denorm\n path: /config/model_cache/yolox_tiny.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "dfine",
"label": "D-FINE / DEIMv2",
"recommended": false,
"download": "#### Downloading D-FINE Model\n\nD-FINE can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=s` in the first line to `s`, `m`, or `l` size.\n\n```sh\ndocker build . --build-arg MODEL_SIZE=s --output . -f- <<'EOF'\nFROM python:3.11 AS build\nRUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/\nWORKDIR /dfine\nRUN git clone https://github.com/Peterande/D-FINE.git .\nRUN uv pip install --system -r requirements.txt\nRUN uv pip install --system onnx onnxruntime onnxsim onnxscript\n# Create output directory and download checkpoint\nRUN mkdir -p output\nARG MODEL_SIZE\nRUN wget https://github.com/Peterande/storage/releases/download/dfinev1.0/dfine_${MODEL_SIZE}_obj2coco.pth -O output/dfine_${MODEL_SIZE}_obj2coco.pth\n# Modify line 58 of export_onnx.py to change batch size to 1\nRUN sed -i '58s/data = torch.rand(.*)/data = torch.rand(1, 3, 640, 640)/' tools/deployment/export_onnx.py\nRUN python3 tools/deployment/export_onnx.py -c configs/dfine/objects365/dfine_hgnetv2_${MODEL_SIZE}_obj2coco.yml -r output/dfine_${MODEL_SIZE}_obj2coco.pth\nFROM scratch\nARG MODEL_SIZE\nCOPY --from=build /dfine/output/dfine_${MODEL_SIZE}_obj2coco.onnx /dfine-${MODEL_SIZE}.onnx\nEOF\n```\n\n#### Downloading DEIMv2 Model\n\n[DEIMv2](https://github.com/Intellindust-AI-Lab/DEIMv2) can be exported as ONNX by running the command below. Pretrained weights are available on Hugging Face for two backbone families:\n\n- **HGNetv2** (smaller/faster): `atto`, `femto`, `pico`, `n`\n- **DINOv3** (larger/more accurate): `s`, `m`, `l`, `x`\n\nSet `BACKBONE` and `MODEL_SIZE` in the first line to match your desired variant. Hugging Face model names use uppercase (e.g. `HGNetv2_N`, `DINOv3_S`), while config files use lowercase (e.g. `hgnetv2_n`, `dinov3_s`).\n\n```sh\ndocker build . --rm --build-arg BACKBONE=hgnetv2 --build-arg MODEL_SIZE=n --output . -f- <<'EOF'\nFROM python:3.11-slim AS build\nRUN apt-get update && apt-get install --no-install-recommends -y git libgl1 libglib2.0-0 && rm -rf /var/lib/apt/lists/*\nCOPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/\nWORKDIR /deimv2\nRUN git clone https://github.com/Intellindust-AI-Lab/DEIMv2.git .\n# Install CPU-only PyTorch first to avoid pulling CUDA variant\nRUN uv pip install --no-cache --system torch torchvision --index-url https://download.pytorch.org/whl/cpu\nRUN uv pip install --no-cache --system -r requirements.txt\nRUN uv pip install --no-cache --system onnx safetensors huggingface_hub\nRUN mkdir -p output\nARG BACKBONE\nARG MODEL_SIZE\n# Download from Hugging Face and convert safetensors to pth\nRUN python3 -c \"\\\nfrom huggingface_hub import hf_hub_download; \\\nfrom safetensors.torch import load_file; \\\nimport torch; \\\nbackbone = '${BACKBONE}'.replace('hgnetv2','HGNetv2').replace('dinov3','DINOv3'); \\\nsize = '${MODEL_SIZE}'.upper(); \\\nst = load_file(hf_hub_download('Intellindust/DEIMv2_' + backbone + '_' + size + '_COCO', 'model.safetensors')); \\\ntorch.save({'model': st}, 'output/deimv2.pth')\"\nRUN sed -i \"s/data = torch.rand(2/data = torch.rand(1/\" tools/deployment/export_onnx.py\n# HuggingFace safetensors omits frozen constants that the model constructor initializes\nRUN sed -i \"s/cfg.model.load_state_dict(state)/cfg.model.load_state_dict(state, strict=False)/\" tools/deployment/export_onnx.py\nRUN python3 tools/deployment/export_onnx.py -c configs/deimv2/deimv2_${BACKBONE}_${MODEL_SIZE}_coco.yml -r output/deimv2.pth\nFROM scratch\nARG BACKBONE\nARG MODEL_SIZE\nCOPY --from=build /deimv2/output/deimv2.onnx /deimv2_${BACKBONE}_${MODEL_SIZE}.onnx\nEOF\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------- |\n| **Object Detection Model Type** | `dfine` |\n| **Object detection model input width** | `640` |\n| **Object detection model input height** | `640` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/dfine_m_obj2coco.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n onnx:\n type: onnx\n\nmodel:\n model_type: dfine\n width: 640\n height: 640\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/dfine_m_obj2coco.onnx\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolo-legacy",
"label": "YOLO (v3, v4, v7)",
"recommended": false,
"download": "To export as ONNX:\n\n```sh\ngit clone https://github.com/NateMeyer/tensorrt_demos\ncd tensorrt_demos/yolo\n./download_yolo.sh\npython3 yolo_to_onnx.py -m yolov7-320\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (should match the imgsize set during model export) |\n| **Object detection model input height** | `320` (should match the imgsize set during model export) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Custom object detector model path** | `/config/model_cache/yolo.onnx` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n onnx:\n type: onnx\n\nmodel:\n model_type: yolo-generic\n width: 320 # <--- should match the imgsize set during model export\n height: 320 # <--- should match the imgsize set during model export\n input_tensor: nchw\n input_dtype: float\n path: /config/model_cache/yolo.onnx\n labelmap_path: /labelmap/coco-80.txt"
}
]
},
"cpu": {
"title": "CPU",
"models": [
{
"key": "ssd",
"label": "MobileNet v2",
"recommended": true,
"download": "A TensorFlow Lite model is provided in the container at `/cpu_model.tflite` and is used by this detector type by default. To provide your own model, bind mount the file into the container and provide the path with `model.path`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **CPU** from the detector type dropdown and click **Add**. Configure the number of threads and click **Add** again to add additional CPU detectors as needed (one per camera is recommended).\n\n| Field | Value |\n| ----------------- | ----- |\n| **Detector type** | `cpu` |\n| **Num threads** | `3` |",
"yaml": "detectors:\n cpu1:\n type: cpu\n num_threads: 3"
}
]
},
"deepstack": {
"title": "DeepStack / CodeProject.AI",
"models": [
{
"key": "yolo",
"label": "YOLO",
"recommended": true,
"download": "This detector runs object detection over the network against a CodeProject.AI or DeepStack server, so no model is downloaded into Frigate itself. Visit the [CodeProject.AI official website](https://www.codeproject.com/Articles/5322557/CodeProject-AI-Server-AI-the-easy-way) to download and install the AI server on your preferred device (e.g. Raspberry Pi, Nvidia Jetson, or other compatible hardware) before configuring the detector.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **DeepStack** from the detector type dropdown and click **Add**. Set the API URL to point to your CodeProject.AI server (e.g., `http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection`).\n\n| Field | Value |\n| ------------- | ---------------------------------------------------------------------- |\n| **API URL** | `http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection` |\n| **API Timeout** | `0.1` (seconds) |",
"yaml": "detectors:\n deepstack:\n api_url: http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection\n type: deepstack\n api_timeout: 0.1 # seconds"
}
]
},
"memryx": {
"title": "MemryX",
"models": [
{
"key": "yolonas",
"label": "YOLO-NAS",
"recommended": true,
"download": "The [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model included in this detector is downloaded automatically and compiled to DFP with [mx_nc](https://developer.memryx.com/2p1/tools/neural_compiler.html#usage).\n\n**Note:** The default model for the MemryX detector is YOLO-NAS 320x320.\n\nThe input size for **YOLO-NAS** can be set to either **320x320** (default) or **640x640**.\n\n- The default size of **320x320** is optimized for lower CPU usage and faster inference times.\n\nMemryX `.dfp` models are automatically downloaded at runtime, if enabled, to the container at `/memryx_models/model_folder/`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------------- |\n| **Object Detection Model Type** | `yolonas` |\n| **Object detection model input width** | `320` (can be set to `640` for higher resolution) |\n| **Object detection model input height** | `320` (can be set to `640` for higher resolution) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n memx0:\n type: memryx\n device: PCIe:0\n\nmodel:\n model_type: yolonas\n width: 320 # (Can be set to 640 for higher resolution)\n height: 320 # (Can be set to 640 for higher resolution)\n input_tensor: nchw\n input_dtype: float\n labelmap_path: /labelmap/coco-80.txt\n # Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.\n # path: /config/yolonas.zip\n # The .zip file must contain:\n # \u251c\u2500\u2500 yolonas.dfp (a file ending with .dfp)\n # \u2514\u2500\u2500 yolonas_post.onnx (optional; only if the model includes a cropped post-processing network)"
},
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": false,
"download": "The YOLOv9s model included in this detector is downloaded from [the original GitHub](https://github.com/WongKinYiu/yolov9) and compiled to DFP with [mx_nc](https://developer.memryx.com/2p1/tools/neural_compiler.html#usage).\n\nMemryX `.dfp` models are automatically downloaded at runtime, if enabled, to the container at `/memryx_models/model_folder/`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------------- |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` (can be set to `640` for higher resolution) |\n| **Object detection model input height** | `320` (can be set to `640` for higher resolution) |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n memx0:\n type: memryx\n device: PCIe:0\n\nmodel:\n model_type: yolo-generic\n width: 320 # (Can be set to 640 for higher resolution)\n height: 320 # (Can be set to 640 for higher resolution)\n input_tensor: nchw\n input_dtype: float\n labelmap_path: /labelmap/coco-80.txt\n # Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.\n # path: /config/yolov9.zip\n # The .zip file must contain:\n # \u251c\u2500\u2500 yolov9.dfp (a file ending with .dfp)"
},
{
"key": "yolox",
"label": "YOLOX",
"recommended": false,
"download": "The model is sourced from the [OpenCV Model Zoo](https://github.com/opencv/opencv_zoo) and precompiled to DFP.\n\nMemryX `.dfp` models are automatically downloaded at runtime, if enabled, to the container at `/memryx_models/model_folder/`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ----------------------- |\n| **Object Detection Model Type** | `yolox` |\n| **Object detection model input width** | `640` |\n| **Object detection model input height** | `640` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float_denorm` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n memx0:\n type: memryx\n device: PCIe:0\n\nmodel:\n model_type: yolox\n width: 640\n height: 640\n input_tensor: nchw\n input_dtype: float_denorm\n labelmap_path: /labelmap/coco-80.txt\n # Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.\n # path: /config/yolox.zip\n # The .zip file must contain:\n # \u251c\u2500\u2500 yolox.dfp (a file ending with .dfp)"
},
{
"key": "ssd",
"label": "SSDLite MobileNet v2",
"recommended": false,
"download": "The model is sourced from the [OpenMMLab Model Zoo](https://mmdeploy-oss.openmmlab.com/model/mmdet-det/ssdlite-e8679f.onnx) and has been converted to DFP.\n\nMemryX `.dfp` models are automatically downloaded at runtime, if enabled, to the container at `/memryx_models/model_folder/`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ----------------------- |\n| **Object Detection Model Type** | `ssd` |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input D Type** | `float` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n memx0:\n type: memryx\n device: PCIe:0\n\nmodel:\n model_type: ssd\n width: 320\n height: 320\n input_tensor: nchw\n input_dtype: float\n labelmap_path: /labelmap/coco-80.txt\n # Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.\n # path: /config/ssdlite_mobilenet.zip\n # The .zip file must contain:\n # \u251c\u2500\u2500 ssdlite_mobilenet.dfp (a file ending with .dfp)\n # \u2514\u2500\u2500 ssdlite_mobilenet_post.onnx (optional; only if the model includes a cropped post-processing network)"
}
]
},
"tensorrt": {
"title": "TensorRT",
"models": [
{
"key": "yolo-legacy",
"label": "YOLO (v3, v4, v7)",
"recommended": true,
"download": "The model used for TensorRT must be preprocessed on the same hardware platform that it will run on, so Frigate generates the `.trt` model file on-device at startup. Processed models are stored in the `/config/model_cache` folder.\n\nBy default no models are generated. Set the `YOLO_MODELS` environment variable in Docker to one or more comma-separated model names (from the available `yolov3`/`yolov4`/`yolov7` models) and each one will be generated on startup if the corresponding `{model}.trt` file is not already present in `model_cache` (delete it to force regeneration). On Jetson devices with DLAs (Xavier or Orin), append `-dla` to a model name to generate a DLA model. If your GPU does not support FP16 operations, pass `USE_FP16=False` to disable it.\n\nAn example `docker-compose.yml` fragment that converts the `yolov7-320` and `yolov7x-640` models:\n\n```yml\nfrigate:\n environment:\n - YOLO_MODELS=yolov7-320,yolov7x-640\n - USE_FP16=false\n```",
"ui": "Navigate to **Settings > System > Detectors and model** and select **TensorRT** from the detector type dropdown and click **Add**, then set the device to `0` (the default GPU index). Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ------------------------------------------------------------ |\n| **Custom object detector model path** | `/config/model_cache/tensorrt/yolov7-320.trt` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |\n| **Model Input Tensor Shape** | `nchw` |\n| **Model Input Pixel Color Format** | `rgb` |\n| **Object detection model input width** | `320` (MUST match the chosen model, e.g., yolov7-320 -> 320) |\n| **Object detection model input height** | `320` (MUST match the chosen model, e.g., yolov7-320 -> 320) |",
"yaml": "detectors:\n tensorrt:\n type: tensorrt\n device: 0 #This is the default, select the first GPU\n\nmodel:\n path: /config/model_cache/tensorrt/yolov7-320.trt\n labelmap_path: /labelmap/coco-80.txt\n input_tensor: nchw\n input_pixel_format: rgb\n width: 320 # MUST match the chosen model i.e yolov7-320 -> 320, yolov4-416 -> 416\n height: 320 # MUST match the chosen model i.e yolov7-320 -> 320 yolov4-416 -> 416"
}
]
},
"synaptics": {
"title": "Synaptics",
"models": [
{
"key": "ssd",
"label": "SSD MobileNet",
"recommended": true,
"download": "A synap model is provided in the container at `/mobilenet.synap` and is used by this detector type by default. The model comes from the [Synap-release Github](https://github.com/synaptics-astra/synap-release/tree/v1.5.0/models/dolphin/object_detection/coco/model/mobilenet224_full80).",
"ui": "Navigate to **Settings > System > Detectors and model** and select **Synaptics** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ---------------------------- |\n| **Custom object detector model path** | `/synaptics/mobilenet.synap` |\n| **Object detection model input width** | `224` |\n| **Object detection model input height** | `224` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors: # required\n synap_npu: # required\n type: synaptics # required\n\nmodel: # required\n path: /synaptics/mobilenet.synap # required\n width: 224 # required\n height: 224 # required\n input_tensor: nhwc # default value (optional. If you change the model, it is required)\n labelmap_path: /labelmap/coco-80.txt # required"
}
]
},
"rknn": {
"title": "RKNN",
"models": [
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": true,
"download": "If no custom model is provided, the RKNN detector downloads a default model from GitHub on first startup. Once cached, the model works fully offline. All models are automatically downloaded and stored in the folder `config/model_cache/rknn_cache`. After upgrading Frigate, you should remove older models to free up space.\n\nYou can also provide your own `.rknn` model. You should not save your own models in the `rknn_cache` folder, store them directly in the `model_cache` folder or another subfolder. To convert a model to `.rknn` format see the `rknn-toolkit2` (requires a x86 machine). Note, that there is only post-processing for the supported models.",
"ui": "Navigate to **Settings > System > Detectors and model** and, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | -------------------------------------------------- |\n| **Custom object detector model path** | `frigate-fp16-yolov9-t` (or other yolov9 variants) |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "model: # required\n # name of model (will be automatically downloaded) or path to your own .rknn model file\n # possible values are:\n # - frigate-fp16-yolov9-t\n # - frigate-fp16-yolov9-s\n # - frigate-fp16-yolov9-m\n # - frigate-fp16-yolov9-c\n # - frigate-fp16-yolov9-e\n # your yolo_model.rknn\n path: frigate-fp16-yolov9-t\n model_type: yolo-generic\n width: 320\n height: 320\n input_tensor: nhwc\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolonas",
"label": "YOLO-NAS",
"recommended": false,
"download": "If no custom model is provided, the RKNN detector downloads a default model from GitHub on first startup. Once cached, the model works fully offline. All models are automatically downloaded and stored in the folder `config/model_cache/rknn_cache`. After upgrading Frigate, you should remove older models to free up space.\n\nYou can also provide your own `.rknn` model. You should not save your own models in the `rknn_cache` folder, store them directly in the `model_cache` folder or another subfolder. To convert a model to `.rknn` format see the `rknn-toolkit2` (requires a x86 machine). Note, that there is only post-processing for the supported models.\n\n**Note:** 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",
"ui": "Navigate to **Settings > System > Detectors and model** and, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ----------------------------------------------------------------------- |\n| **Custom object detector model path** | `deci-fp16-yolonas_s` (or `deci-fp16-yolonas_m`, `deci-fp16-yolonas_l`) |\n| **Object Detection Model Type** | `yolonas` |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input Pixel Color Format** | `bgr` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "model: # required\n # name of model (will be automatically downloaded) or path to your own .rknn model file\n # possible values are:\n # - deci-fp16-yolonas_s\n # - deci-fp16-yolonas_m\n # - deci-fp16-yolonas_l\n # your yolonas_model.rknn\n path: deci-fp16-yolonas_s\n model_type: yolonas\n width: 320\n height: 320\n input_pixel_format: bgr\n input_tensor: nhwc\n labelmap_path: /labelmap/coco-80.txt"
},
{
"key": "yolox",
"label": "YOLOx",
"recommended": false,
"download": "If no custom model is provided, the RKNN detector downloads a default model from GitHub on first startup. Once cached, the model works fully offline. All models are automatically downloaded and stored in the folder `config/model_cache/rknn_cache`. After upgrading Frigate, you should remove older models to free up space.\n\nYou can also provide your own `.rknn` model. You should not save your own models in the `rknn_cache` folder, store them directly in the `model_cache` folder or another subfolder. To convert a model to `.rknn` format see the `rknn-toolkit2` (requires a x86 machine). Note, that there is only post-processing for the supported models.",
"ui": "Navigate to **Settings > System > Detectors and model** and, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ---------------------------------------------- |\n| **Custom object detector model path** | `rock-i8-yolox_nano` (or other yolox variants) |\n| **Object Detection Model Type** | `yolox` |\n| **Object detection model input width** | `416` |\n| **Object detection model input height** | `416` |\n| **Model Input Tensor Shape** | `nhwc` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "model: # required\n # name of model (will be automatically downloaded) or path to your own .rknn model file\n # possible values are:\n # - rock-i8-yolox_nano\n # - rock-i8-yolox_tiny\n # - rock-fp16-yolox_nano\n # - rock-fp16-yolox_tiny\n # your yolox_model.rknn\n path: rock-i8-yolox_nano\n model_type: yolox\n width: 416\n height: 416\n input_tensor: nhwc\n labelmap_path: /labelmap/coco-80.txt"
}
]
},
"axengine": {
"title": "AXEngine",
"models": [
{
"key": "yolov9",
"label": "YOLOv9",
"recommended": true,
"download": "A yolov9 axmodel is provided in the container at `/axmodels` and is used by this detector type by default. The AXEngine detector downloads its default model from HuggingFace on first startup; once cached, the model works fully offline.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **AXEngine NPU** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:\n\n| Field | Value |\n| ---------------------------------------- | ----------------------- |\n| **Custom object detector model path** | `frigate-yolov9-tiny` |\n| **Object Detection Model Type** | `yolo-generic` |\n| **Object detection model input width** | `320` |\n| **Object detection model input height** | `320` |\n| **Model Input D Type** | `int` |\n| **Model Input Pixel Color Format** | `bgr` |\n| **Label map for custom object detector** | `/labelmap/coco-80.txt` |",
"yaml": "detectors:\n axengine:\n type: axengine\n\nmodel:\n path: frigate-yolov9-tiny\n model_type: yolo-generic\n width: 320\n height: 320\n input_dtype: int\n input_pixel_format: bgr\n labelmap_path: /labelmap/coco-80.txt"
}
]
},
"degirumAiServer": {
"title": "DeGirum AI Server",
"models": [
{
"key": "ai-server-inference",
"label": "AI Server Inference",
"recommended": true,
"download": "Launch a DeGirum AI server as a Docker container, then point the detector at it. Add this to your `docker-compose.yml`:\n\n```yaml\ndegirum_detector:\n container_name: degirum\n image: degirum/aiserver:latest\n privileged: true\n ports:\n - \"8778:8778\"\n```\n\nSet `location` to the server's service name, container name, or `host:port`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **DeGirum** from the detector type dropdown and click **Add**.\n\n| Field | Value |\n| --- | --- |\n| **Location** | `degirum` |\n| **Zoo** | `degirum/public` |\n| **Token** | your AI Hub token (optional for the public zoo) |\n",
"yaml": "degirum_detector:\n type: degirum\n location: degirum\n zoo: degirum/public\n token: dg_example_token\n"
}
]
},
"degirumLocal": {
"title": "DeGirum Local",
"models": [
{
"key": "local-inference",
"label": "Local Inference",
"recommended": true,
"download": "Run hardware directly inside the Frigate container with `@local`, removing the AI server hop. The matching device runtime (e.g. the Hailo runtime) must be installed in the container; confirm it with `degirum sys-info`.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **DeGirum** from the detector type dropdown and click **Add**.\n\n| Field | Value |\n| --- | --- |\n| **Location** | `@local` |\n| **Zoo** | `degirum/public` |\n| **Token** | your AI Hub token (optional for the public zoo) |\n",
"yaml": "degirum_detector:\n type: degirum\n location: @local\n zoo: degirum/public\n token: dg_example_token\n"
}
]
},
"degirumCloud": {
"title": "DeGirum AI Hub Cloud",
"models": [
{
"key": "ai-hub-cloud-inference",
"label": "AI Hub Cloud Inference",
"recommended": true,
"download": "Run inferences on DeGirum's [AI Hub](https://hub.degirum.com) cloud with `@cloud`. Sign up, create an access token, and set it as `token`. Network latency may require lowering your detection fps.",
"ui": "Navigate to **Settings > System > Detectors and model** and select **DeGirum** from the detector type dropdown and click **Add**.\n\n| Field | Value |\n| --- | --- |\n| **Location** | `@cloud` |\n| **Zoo** | `degirum/public` |\n| **Token** | your AI Hub token (optional for the public zoo) |\n",
"yaml": "degirum_detector:\n type: degirum\n location: @cloud\n zoo: degirum/public\n token: dg_example_token\n"
}
]
}
}
+1 -1
View File
@@ -78,7 +78,7 @@ cameras:
### Configuring Minimum Volume
The audio detector uses volume levels in the same way that motion in a camera feed is used for object detection. This means that Frigate will not run audio detection unless the audio volume is above the configured level in order to reduce resource usage. Audio levels can vary widely between camera models so it is important to run tests to see what volume levels are. The Debug view in the Frigate UI has an Audio tab for cameras that have the `audio` role assigned where a graph and the current levels are is displayed. The `min_volume` parameter should be set to the minimum the `RMS` level required to run audio detection.
The audio detector uses volume levels in the same way that motion in a camera feed is used for object detection. This means that Frigate will not run audio detection unless the audio volume is above the configured level in order to reduce resource usage. Audio levels can vary widely between camera models so it is important to run tests to see what volume levels are. The [Debug view](/usage/live#the-single-camera-view) in the Frigate UI has an Audio tab for cameras that have the `audio` role assigned where a graph and the current levels are is displayed. The `min_volume` parameter should be set to the minimum the `RMS` level required to run audio detection.
:::tip
+1 -1
View File
@@ -141,7 +141,7 @@ Changing the secret will invalidate current tokens.
## Proxy configuration
Frigate can be configured to leverage features of common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth.
Frigate can be configured to leverage features of common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth. Frigate does not implement OIDC, SAML, or LDAP natively; as an NVR focused on recording and object detection, it relies on robust, battle-tested proxies to handle those protocols and passes the authenticated user and role through via headers (see below).
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.
+1 -1
View File
@@ -161,7 +161,7 @@ Every PTZ camera is different, so autotracking may not perform ideally in every
The object tracker in Frigate estimates the motion of the PTZ so that tracked objects are preserved when the camera moves. In most cases 5 fps is sufficient, but if you plan to track faster moving objects, you may want to increase this slightly. Higher frame rates (> 10fps) will only slow down Frigate and the motion estimator and may lead to dropped frames, especially if you are using experimental zooming.
A fast [detector](object_detectors.md) is recommended. CPU detectors will not perform well or won't work at all. You can watch Frigate's debug viewer for your camera to see a thicker colored box around the object currently being autotracked.
A fast [detector](object_detectors.md) is recommended. CPU detectors will not perform well or won't work at all. You can watch Frigate's [debug viewer](/usage/live#the-single-camera-view) for your camera to see a thicker colored box around the object currently being autotracked.
![Autotracking Debug View](/img/autotracking-debug.gif)
+2 -2
View File
@@ -126,12 +126,12 @@ birdseye:
### Sorting cameras in the Birdseye view
It is possible to override the order of cameras that are being shown in the Birdseye view. The order is set at the camera level.
It is possible to override the order of cameras that are being shown in the Birdseye view. The order is set at the camera level (when using YAML).
<ConfigTabs>
<TabItem value="ui">
Navigate to <NavPath path="Settings > Camera configuration > Birdseye" /> for each camera and set the **Position** field to control the display order.
Navigate to <NavPath path="Settings > System > Birdseye" /> and in the **Camera order** field, use the drag handle next to each camera name to control the display order.
</TabItem>
<TabItem value="yaml">
+24 -7
View File
@@ -3,6 +3,8 @@ id: camera_specific
title: Camera Specific Configurations
---
import NavPath from "@site/src/components/NavPath";
:::note
This page makes use of presets of FFmpeg args. For more information on presets, see the [FFmpeg Presets](/configuration/ffmpeg_presets) page.
@@ -148,11 +150,11 @@ WEB Digest Algorithm - MD5
Reolink has many different camera models with inconsistently supported features and behavior. The below table shows a summary of various features and recommendations.
| Camera Resolution | Camera Generation | Recommended Stream Type | Additional Notes |
| ----------------- | ------------------------- | --------------------------------- | ----------------------------------------------------------------------- |
| 5MP or lower | All | http-flv | Stream is h264 |
| 6MP or higher | Latest (ex: Duo3, CX-8##) | http-flv with ffmpeg 8.0, or rtsp | This uses the new http-flv-enhanced over H265 which requires ffmpeg 8.0 |
| 6MP or higher | Older (ex: RLC-8##) | rtsp | |
| Camera Resolution | Camera Generation | Recommended Stream Type | Additional Notes |
| ----------------- | ------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------- |
| 5MP or lower | All | http-flv | Stream is h264 |
| 6MP or higher | Latest (ex: Duo3, CX-8##) | http-flv with ffmpeg 8.0, or rtsp | This uses the new http-flv-enhanced over H265 which requires ffmpeg 8.0 (Frigate's default) |
| 6MP or higher | Older (ex: RLC-8##) | rtsp | |
Frigate works much better with newer reolink cameras that are setup with the below options:
@@ -161,6 +163,21 @@ If available, recommended settings are:
- `On, fluency first` this sets the camera to CBR (constant bit rate)
- `Interframe Space 1x` this sets the iframe interval to the same as the frame rate
#### Setup via the Add Camera Wizard
The Add Camera Wizard is the recommended way to add a standard Reolink camera. Before starting, make sure HTTP is enabled in the camera's advanced network settings — the wizard uses the camera's HTTP API to determine its resolution and choose the recommended stream type from the table above.
1. Click **Add Camera** in <NavPath path="Settings > Global configuration > Camera management" />.
2. Choose **Manual selection** as the stream detection method and select **Reolink** as the camera brand.
3. The wizard queries the camera and automatically uses an http-flv stream for cameras 5MP and lower, or an RTSP stream for higher resolution cameras.
4. In the validation step, enable **Use stream compatibility mode** for http-flv streams when the wizard recommends it.
If you use the **Probe camera** method instead, the discovered stream URLs will be RTSP. For Reolink cameras where http-flv is recommended, the wizard will show a warning in the validation step.
The wizard covers standard single-camera setups. For two way talk, cameras connected through a Reolink NVR, or audio transcoding for WebRTC live view, configure the camera manually as shown below.
#### Manual configuration
According to [this discussion](https://github.com/blakeblackshear/frigate/issues/3235#issuecomment-1135876973), the http video streams seem to be the most reliable for Reolink.
Cameras connected via a Reolink NVR can be connected with the http stream, use `channel[0..15]` in the stream url for the additional channels.
@@ -225,11 +242,12 @@ cameras:
roles:
- detect
```
</details>
### Unifi Protect Cameras
:::note
:::note
Unifi G5s cameras and newer need a Unifi Protect server to enable rtsps stream, it's not posible to enable it in standalone mode.
@@ -269,7 +287,6 @@ Some community members have found better performance on Wyze cameras by using an
To use a USB camera (webcam) with Frigate, the recommendation is to use go2rtc's [FFmpeg Device](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg-device) support:
- Preparation outside of Frigate:
- Get USB camera path. Run `v4l2-ctl --list-devices` to get a listing of locally-connected cameras available. (You may need to install `v4l-utils` in a way appropriate for your Linux distribution). In the sample configuration below, we use `video=0` to correlate with a detected device path of `/dev/video0`
- Get USB camera formats & resolutions. Run `ffmpeg -f v4l2 -list_formats all -i /dev/video0` to get an idea of what formats and resolutions the USB Camera supports. In the sample configuration below, we use a width of 1024 and height of 576 in the stream and detection settings based on what was reported back.
- If using Frigate in a container (e.g. Docker on TrueNAS), ensure you have USB Passthrough support enabled, along with a specific Host Device (`/dev/video0`) + Container Device (`/dev/video0`) listed.
+1 -1
View File
@@ -194,7 +194,7 @@ Camera groups let you organize cameras together with a shared name and icon, mak
<ConfigTabs>
<TabItem value="ui">
On the Live dashboard, press the **+** icon in the main navigation to add a new camera group. Configure the group name, select which cameras to include, choose an icon, and set the display order.
On the Live dashboard, press the **pencil icon** in the main navigation to add a new camera group. Configure the group name, select which cameras to include, choose an icon, and set the display order.
</TabItem>
<TabItem value="yaml">
+1 -1
View File
@@ -206,7 +206,7 @@ Start with the [Usage](#usage) section and re-read the [Model Requirements](#mod
1. Ensure `person` is being _detected_. A `person` will automatically be scanned by Frigate for a face. Any detected faces will appear in the Recent Recognitions tab in the Frigate UI's Face Library.
If you are using a Frigate+ or `face` detecting model:
- Watch the debug view (Settings --> Debug) to ensure that `face` is being detected along with `person`.
- Watch the [debug view](/usage/live#the-single-camera-view) to ensure that `face` is being detected along with `person`.
- You may need to adjust the `min_score` for the `face` object if faces are not being detected.
If you are **not** using a Frigate+ or `face` detecting model:
+39 -39
View File
@@ -7,27 +7,27 @@ import ConfigTabs from "@site/src/components/ConfigTabs";
import TabItem from "@theme/TabItem";
import NavPath from "@site/src/components/NavPath";
Some presets of FFmpeg args are provided by default to make the configuration easier. All presets can be seen in [this file](https://github.com/blakeblackshear/frigate/blob/master/frigate/ffmpeg_presets.py).
Frigate ships with a set of FFmpeg presets to keep your configuration short and readable. Each preset expands to a longer list of FFmpeg arguments at runtime. You can see exactly what every preset expands to in [this file](https://github.com/blakeblackshear/frigate/blob/master/frigate/ffmpeg_presets.py).
### Hwaccel Presets
In the config file you reference a preset by its name (for example, `preset-vaapi`). In the UI, the same preset is shown with a friendly label (for example, **VAAPI (Intel/AMD GPU)**). Both refer to the same thing — the tables below list the config name alongside the label you'll see in the UI.
It is highly recommended to use hwaccel presets in the config. These presets not only replace the longer args, but they also give Frigate hints of what hardware is available and allows Frigate to make other optimizations using the GPU such as when encoding the birdseye restream or when scaling a stream that has a size different than the native stream size.
### Hwaccel (Hardware Acceleration) Presets
See [the hwaccel docs](/configuration/hardware_acceleration_video.md) for more info on how to setup hwaccel for your GPU / iGPU.
Hardware acceleration arguments tell FFmpeg to decode your camera's video stream on a GPU or integrated graphics chip instead of the CPU, which dramatically lowers CPU usage. Using a preset is highly recommended. Beyond replacing a long list of arguments, each preset also tells Frigate what hardware is available so it can offload additional work to the GPU — for example, encoding the Birdseye restream or scaling a stream whose resolution differs from the camera's native size.
| Preset | Usage | Other Notes |
| --------------------- | ------------------------------ | ----------------------------------------------------- |
| preset-rpi-64-h264 | 64 bit Rpi with h264 stream | |
| preset-rpi-64-h265 | 64 bit Rpi with h265 stream | |
| preset-vaapi | Intel & AMD VAAPI | Check hwaccel docs to ensure correct driver is chosen |
| preset-intel-qsv-h264 | Intel QSV with h264 stream | If issues occur recommend using vaapi preset instead |
| preset-intel-qsv-h265 | Intel QSV with h265 stream | If issues occur recommend using vaapi preset instead |
| preset-nvidia | Nvidia GPU | |
| preset-jetson-h264 | Nvidia Jetson with h264 stream | |
| preset-jetson-h265 | Nvidia Jetson with h265 stream | |
| preset-rkmpp | Rockchip MPP | Use image with \*-rk suffix and privileged mode |
See [the hardware acceleration docs](/configuration/hardware_acceleration_video.md) for details on setting up hardware acceleration for your GPU / iGPU, then select the preset that matches your hardware.
Select the appropriate hwaccel preset for your hardware.
| Preset (YAML config) | UI Label | Usage | Notes |
| --------------------- | ----------------------- | --------------------------------- | --------------------------------------------------------------- |
| preset-rpi-64-h264 | Raspberry Pi (H.264) | 64-bit Raspberry Pi, H.264 stream | |
| preset-rpi-64-h265 | Raspberry Pi (H.265) | 64-bit Raspberry Pi, H.265 stream | |
| preset-vaapi | VAAPI (Intel/AMD GPU) | Intel or AMD GPU via VAAPI | Check the hwaccel docs to ensure the correct driver is selected |
| preset-intel-qsv-h264 | Intel QuickSync (H.264) | Intel QuickSync, H.264 stream | If you have issues, use the VAAPI preset instead |
| preset-intel-qsv-h265 | Intel QuickSync (H.265) | Intel QuickSync, H.265 stream | If you have issues, use the VAAPI preset instead |
| preset-nvidia | NVIDIA GPU | NVIDIA GPU | |
| preset-jetson-h264 | NVIDIA Jetson (H.264) | NVIDIA Jetson, H.264 stream | |
| preset-jetson-h265 | NVIDIA Jetson (H.265) | NVIDIA Jetson, H.265 stream | |
| preset-rkmpp | Rockchip RKMPP | Rockchip MPP | Use an image with the `-rk` suffix and run in privileged mode |
<ConfigTabs>
<TabItem value="ui">
@@ -53,25 +53,25 @@ cameras:
### Input Args Presets
Input args presets help make the config more readable and handle use cases for different types of streams to ensure maximum compatibility.
Input arguments are passed to FFmpeg before your camera source and control how Frigate connects to and reads the stream — the transport protocol, timeouts, reconnection behavior, and how the stream is probed. The right input args ensure a reliable connection and maximum compatibility for each type of stream.
See [the camera specific docs](/configuration/camera_specific.md) for more info on non-standard cameras and recommendations for using them in Frigate.
See [the camera-specific docs](/configuration/camera_specific.md) for more on non-standard cameras and recommendations for using them in Frigate.
| Preset | Usage | Other Notes |
| -------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------ |
| preset-http-jpeg-generic | HTTP Live Jpeg | Recommend restreaming live jpeg instead |
| preset-http-mjpeg-generic | HTTP Mjpeg Stream | Recommend restreaming mjpeg stream instead |
| preset-http-reolink | Reolink HTTP-FLV Stream | Only for reolink http, not when restreaming as rtsp |
| preset-rtmp-generic | RTMP Stream | |
| preset-rtsp-generic | RTSP Stream | This is the default when nothing is specified |
| preset-rtsp-restream | RTSP Stream from restream | Use for rtsp restream as source for frigate |
| preset-rtsp-restream-low-latency | RTSP Stream from restream | Use for rtsp restream as source for frigate to lower latency, may cause issues with some cameras |
| preset-rtsp-udp | RTSP Stream via UDP | Use when camera is UDP only |
| preset-rtsp-blue-iris | Blue Iris RTSP Stream | Use when consuming a stream from Blue Iris |
| Preset (config) | UI Label | Usage | Notes |
| -------------------------------- | ----------------------------------------- | --------------------------- | ------------------------------------------------------------------------------- |
| preset-http-jpeg-generic | HTTP JPEG (Generic) | HTTP live JPEG | Restreaming the live JPEG is recommended instead |
| preset-http-mjpeg-generic | HTTP MJPEG (Generic) | HTTP MJPEG stream | Restreaming the MJPEG stream is recommended instead |
| preset-http-reolink | HTTP - Reolink Cameras | Reolink HTTP-FLV stream | Only for Reolink HTTP, not when restreaming as RTSP |
| preset-rtmp-generic | RTMP (Generic) | RTMP stream | |
| preset-rtsp-generic | RTSP (Generic) | RTSP stream | The default when no input args are specified |
| preset-rtsp-restream | RTSP - Restream from go2rtc | RTSP stream from a restream | Use when a go2rtc restream is the source for Frigate |
| preset-rtsp-restream-low-latency | RTSP - Restream from go2rtc (Low Latency) | RTSP stream from a restream | Lowers latency for a go2rtc restream source; may cause issues with some cameras |
| preset-rtsp-udp | RTSP - UDP | RTSP stream over UDP | Use when the camera only supports UDP |
| preset-rtsp-blue-iris | RTSP - Blue Iris | Blue Iris RTSP stream | Use when consuming a stream from Blue Iris |
:::warning
It is important to be mindful of input args when using restream because you can have a mix of protocols. `http` and `rtmp` presets cannot be used with `rtsp` streams. For example, when using a reolink cam with the rtsp restream as a source for record the preset-http-reolink will cause a crash. In this case presets will need to be set at the stream level. See the example below.
Be mindful of input arguments when restreaming, because you can end up with a mix of protocols. The `http` and `rtmp` presets cannot be used with `rtsp` streams. For example, using a Reolink camera with an RTSP restream as the recording source while `preset-http-reolink` is applied will cause a crash. In cases like this, set the preset at the stream level instead. See the example below.
:::
@@ -96,13 +96,13 @@ cameras:
### Output Args Presets
Output args presets help make the config more readable and handle use cases for different types of streams to ensure consistent recordings.
Output arguments are passed to FFmpeg after your camera source and control how recordings are written — which codecs are used and whether audio and video are copied as-is or re-encoded. The right output args ensure consistent, playable recordings for each type of stream.
| Preset | Usage | Other Notes |
| -------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| preset-record-generic | Record WITHOUT audio | If your camera doesn't have audio, or if you don't want to record audio, use this option |
| preset-record-generic-audio-copy | Record WITH original audio | Use this to enable audio in recordings |
| preset-record-generic-audio-aac | Record WITH transcoded aac audio | This is the default when no option is specified. Use it to transcode audio to AAC. If the source is already in AAC format, use preset-record-generic-audio-copy instead to avoid unnecessary re-encoding |
| preset-record-mjpeg | Record an mjpeg stream | Recommend restreaming mjpeg stream instead |
| preset-record-jpeg | Record live jpeg | Recommend restreaming live jpeg instead |
| preset-record-ubiquiti | Record ubiquiti stream with audio | Recordings with ubiquiti non-standard audio |
| Preset (config) | UI Label | Usage | Notes |
| -------------------------------- | ------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| preset-record-generic | Record (Generic, no audio) | Record without audio | Use this if your camera has no audio, or if you don't want to record audio |
| preset-record-generic-audio-copy | Record (Generic + Copy Audio) | Record with the original audio | Use this to keep the camera's audio in recordings without re-encoding |
| preset-record-generic-audio-aac | Record (Generic + Audio to AAC) | Record with audio transcoded to AAC | The default when no output args are specified. Transcodes audio to AAC. If the source is already AAC, use `preset-record-generic-audio-copy` to avoid re-encoding |
| preset-record-mjpeg | Record - MJPEG Cameras | Record an MJPEG stream | Restreaming the MJPEG stream is recommended instead |
| preset-record-jpeg | Record - JPEG Cameras | Record a live JPEG | Restreaming the live JPEG is recommended instead |
| preset-record-ubiquiti | Record - Ubiquiti Cameras | Record a Ubiquiti stream with audio | Handles Ubiquiti's non-standard audio format |
+7 -8
View File
@@ -27,13 +27,12 @@ Running Generative AI models on CPU is not recommended, as high inference times
You must use a vision-capable model with Frigate. The following models are recommended for local deployment:
| Model | Notes |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qwen3-vl` | Strong visual and situational understanding, enhanced ability to identify smaller objects and interactions with object. |
| `qwen3.5` | Strong situational understanding, but missing DeepStack from qwen3-vl leading to worse performance for identifying objects in people's hand and other small details. |
| `gemma4` | Strong situational understanding, sometimes resorts to more vague terms like 'interacts' instead of assigning a specific action. |
| `Intern3.5VL` | Relatively fast with good vision comprehension |
| `gemma3` | Slower model with good vision and temporal understanding |
| Model | Notes |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qwen3-vl` | Strong visual and situational understanding, enhanced ability to identify smaller objects and interactions with object. |
| `qwen3.5` | Strong situational understanding, but missing DeepStack from qwen3-vl leading to worse performance for identifying objects in people's hand and other small details. |
| `qwen3.6` | Strong situational understanding, similar to qwen3-vl |
| `gemma4` | Strong situational understanding, sometimes resorts to more vague terms like 'interacts' instead of assigning a specific action. |
:::info
@@ -294,7 +293,7 @@ Other HTTP options are available, see the [python-genai documentation](https://g
### OpenAI
OpenAI does not have a free tier for their API. With the release of gpt-4o, pricing has been reduced and each generation should cost fractions of a cent if you choose to go this route.
OpenAI does not have a free tier for their API.
#### Supported Models
@@ -17,8 +17,6 @@ Some types of hardware acceleration are detected and used automatically, but you
- Check the logs: A message will either say that hardware acceleration was automatically detected, or there will be a warning that no hardware acceleration was automatically detected
- If hardware acceleration is specified in the config, verification can be done by ensuring the logs are free from errors. There is no CPU fallback for hardware acceleration.
:::info
Frigate supports presets for optimal hardware accelerated video decoding:
**AMD**
@@ -49,14 +47,10 @@ Frigate supports presets for optimal hardware accelerated video decoding:
Depending on your system, these presets may not be compatible, and you may need to use manual hwaccel args to take advantage of your hardware. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro
:::
## Intel-based CPUs
Frigate can utilize most Intel integrated GPUs and Arc GPUs to accelerate video decoding.
:::info
**Recommended hwaccel Preset**
| CPU Generation | Intel Driver | Recommended Preset | Notes |
@@ -68,8 +62,6 @@ Frigate can utilize most Intel integrated GPUs and Arc GPUs to accelerate video
| Intel Arc A-series | iHD / Xe | preset-intel-qsv-\* | |
| Intel Arc B-series | iHD / Xe | preset-intel-qsv-\* | Requires host kernel 6.12+ |
:::
:::note
The default driver is `iHD`. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file or [in the `config.yml` for HA App users](advanced/system.md#environment_vars).
@@ -671,7 +671,7 @@ lpr:
3. 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.
- Watch the [Debug view](/usage/live#the-single-camera-view) 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.
@@ -680,6 +680,7 @@ lpr:
- You may need to adjust your `detection_threshold` if your plates are not being detected.
4. Ensure the characters on detected plates are being _recognized_.
- Check the **Plate recognition** inference time in Enrichment metrics (<NavPath path="System metrics > Enrichments" />). High inference times (> 100ms) could lead to poor recognition results, especially for dedicated LPR cameras where the plate crosses the frame quickly.
- 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).
+4
View File
@@ -434,3 +434,7 @@ When your browser runs into problems playing back your camera streams, it will l
```
The same applies to your `record` stream: if its aspect ratio differs from your `detect` stream, your recordings will appear in a different shape than the live view. For consistent framing across live view and recordings, use the same aspect ratio for all of a camera's streams (the resolution can still differ).
9. **Why does Frigate prefer MSE over WebRTC for live view?**
Frigate prefers MSE because it delivers a better out-of-the-box experience than WebRTC on nearly every axis that matters for a security camera system. MSE is an open standard optimized and supported by all modern browsers, works without any extra configuration (WebRTC requires port forwarding and candidate setup, and lacks H.265 support in some browsers), and requires no internet access for NAT traversal. More importantly, MSE runs over TCP, so every frame arrives and is decoded in order, so nothing is ever silently skipped. WebRTC optimizes for latency over UDP by discarding late or incomplete frames, which works against you on cellular or spotty Wi-Fi: you can end up with frozen video, visual corruption, or gaps in the feed without ever knowing you missed something. Frigate's enhanced MSE player has adaptive speed playback and has been tuned for latency and connection robustness that meets or exceeds WebRTC, so you get near-real-time playback with a guarantee that when the video plays, every frame is actually there - which, for an NVR whose whole purpose is letting you see what happened, matters more than shaving fractions of a second off a latency number. That's why Frigate defaults to MSE and reserves WebRTC for cases that require it, like two-way talk.
+24 -2
View File
@@ -7,9 +7,11 @@ import ConfigTabs from "@site/src/components/ConfigTabs";
import TabItem from "@theme/TabItem";
import NavPath from "@site/src/components/NavPath";
Frigate has two kinds of masks: motion masks and object filter masks. Both are narrow tools for fine-tuning, **not for hiding an area from Frigate**. Masks should be used sparingly; in most cases where users reach for one, a [zone](zones.md) with [`required_zones`](zones.md#restricting-alerts-and-detections-to-specific-zones) is the right tool instead. See [Which tool do I need?](#which-tool-do-i-need) and [Common mistakes](#common-mistakes) below if you're new to Frigate's mask behavior.
## Motion masks
Motion masks are used to prevent unwanted types of motion from triggering detection. Try watching the Debug feed (Settings --> Debug) with `Motion Boxes` enabled to see what may be regularly detected as motion. For example, you want to mask out your timestamp, the sky, rooftops, etc. Keep in mind that this mask only prevents motion from being detected and does not prevent objects from being detected if object detection was started due to motion in unmasked areas. Motion is also used during object tracking to refine the object detection area in the next frame. _Over-masking will make it more difficult for objects to be tracked._
Motion masks are used to prevent unwanted types of motion from triggering detection. Try watching the [Debug view](/usage/live#the-single-camera-view) with `Motion Boxes` enabled to see what may be regularly detected as motion. For example, you want to mask out your timestamp, the sky, rooftops, etc. Keep in mind that this mask only prevents motion from being detected and does not prevent objects from being detected if object detection was started due to motion in unmasked areas. Motion is also used during object tracking to refine the object detection area in the next frame. _Over-masking will make it more difficult for objects to be tracked._
See [further clarification](#further-clarification) below on why you may not want to use a motion mask.
@@ -21,7 +23,16 @@ Object filter masks can be used to filter out stubborn false positives in fixed
![object mask](/img/bottom-center-mask.jpg)
## Creating masks
## Which tool do I need?
| What you're trying to do | Recommended tool | How it works |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Only get alerts/detections for activity in the areas you care about, ignoring activity elsewhere (e.g., alert when someone enters your yard, but not when they walk past on the sidewalk) | A [zone](zones.md) combined with [`required_zones`](zones.md#restricting-alerts-and-detections-to-specific-zones) | Frigate keeps detecting and tracking activity everywhere in the frame, but a review item is only created once the bottom-center of an object's bounding box enters a required zone. |
| Stop a stubborn false positive at a specific fixed spot (e.g., a tree base that keeps being detected as a person) | An **object filter mask** for that object type | Any detection of that object type whose bounding-box bottom-center lands inside the mask is treated as a false positive and discarded. |
| Ignore motion in an area that obviously isn't an object of interest (e.g., the camera timestamp, sky, flags, treetops swaying) | A **motion mask** | Motion inside the mask is ignored when deciding whether to run object detection. Objects can still be detected in a motion masked area if motion elsewhere in the frame triggers detection. |
| Stop tracking an object type altogether on this camera (e.g., you never care about cats) | Remove the object from the camera's [`objects.track`](objects.md) list | Frigate skips this object type entirely on this camera, regardless of where it appears. |
## Using the mask creator
<ConfigTabs>
<TabItem value="ui">
@@ -124,3 +135,14 @@ This is what `required_zones` are for. You should define a zone (remember this i
> Maybe my specific situation just warrants this. I've just been having a hard time understanding the relevance of this information - it seems to be that it's exactly what would be expected when "masking out" an area of ANY image.
That may be the case for you. Frigate will definitely work harder tracking people on the sidewalk to make sure it doesn't miss anyone who steps foot on your stoop. The trade off with the way you have it now is slower recognition of objects and potential misses. That may be acceptable based on your needs. Also, if your resolution is low enough on the detect stream, your regions may already be so big that they grab the entire object anyway.
## Common mistakes
**"I added a motion mask to ignore my driveway/sidewalk."**
A motion mask doesn't hide an area from Frigate. Objects can still be detected and tracked inside a masked area. The mask only stops motion _in that area_ from triggering object detection. If you want activity on the sidewalk to never produce a review item, define a [zone](zones.md) over the area you DO care about (your stoop, your driveway) and add it to [`required_zones`](zones.md#restricting-alerts-and-detections-to-specific-zones). Frigate will still see people on the sidewalk, but it won't create an alert until they cross into the zone.
**"I added an object filter mask because I don't care about cars in my yard."**
Object filter masks are for stubborn false positives at fixed locations, not for filtering whole areas or whole object types. If you only want alerts when a car enters the driveway, use a [zone](zones.md) with [`required_zones`](zones.md#restricting-alerts-and-detections-to-specific-zones). If you don't care about a whole object type on this camera, remove it from [`objects.track`](objects.md).
**"I masked everything except a thin strip on my stoop."**
Heavy masking hurts tracking. Frigate uses motion near a tracked object's previous bounding box to decide where to look in the next frame; with most of the frame masked, an object walking from an unmasked area into a masked one effectively disappears and gets picked up as a "new" object when it reappears. For example: someone walks down your sidewalk, stops under a tree (masked area) to tie their shoe, then continues. Frigate sees that as two separate people and can create two separate review items. Because Frigate needs several consecutive frames above the confidence threshold to commit to a detection, each re-appearance can also delay or miss alerts. Use [`required_zones`](zones.md#restricting-alerts-and-detections-to-specific-zones) for "only alert me about this spot" and leave the surrounding area unmasked so tracking stays intact.
+1 -1
View File
@@ -11,7 +11,7 @@ import NavPath from "@site/src/components/NavPath";
Frigate uses motion detection as a first line check to see if there is anything happening in the frame worth checking with object detection.
Once motion is detected, it tries to group up nearby areas of motion together in hopes of identifying a rectangle in the image that will capture the area worth inspecting. These are the red "motion boxes" you see in the debug viewer.
Once motion is detected, it tries to group up nearby areas of motion together in hopes of identifying a rectangle in the image that will capture the area worth inspecting. These are the red "motion boxes" you see in the [debug viewer](/usage/live#the-single-camera-view).
## The Goal
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -7,7 +7,7 @@ import ConfigTabs from "@site/src/components/ConfigTabs";
import TabItem from "@theme/TabItem";
import NavPath from "@site/src/components/NavPath";
There are several types of object filters that can be used to reduce false positive rates.
There are several types of object filters that can be used to reduce [false positive](/frigate/glossary#false-positive) rates.
## Object Scores
@@ -144,8 +144,8 @@ cameras:
### Zones
[Required zones](/configuration/zones.md) can be a great tool to reduce false positives that may be detected in the sky or other areas that are not of interest. The required zones will only create tracked objects for objects that enter the zone.
[Required zones](/configuration/zones.md#restricting-alerts-and-detections-to-specific-zones) can be a great tool to reduce false positives that may be detected in the sky or other areas that are not of interest. The required zones will only create tracked objects for objects that enter the zone.
### Object Masks
[Object Filter Masks](/configuration/masks) are a last resort but can be useful when false positives are in the relatively same place but can not be filtered due to their size or shape. Object filter masks can be configured in <NavPath path="Settings > Camera configuration > Masks / Zones" />.
[Object Filter Masks](/configuration/masks#object-filter-masks) are a last resort but can be useful when false positives are in the relatively same place but can not be filtered due to their size or shape. Object filter masks can be configured in <NavPath path="Settings > Camera configuration > Masks / Zones" />.
+6
View File
@@ -235,3 +235,9 @@ When you delete a base zone or mask in the Frigate UI, any profile overrides for
### Why are some settings missing when I configure a profile override?
Fields that require a Frigate restart to take effect cannot be overridden by profiles, since profiles are applied at runtime without restarting. Those fields are hidden when editing a profile override and can only be changed on the base configuration.
### Can I schedule profiles to be enabled or disabled at certain times?
Not within Frigate itself. Frigate is an NVR, not an automation platform, so it intentionally does not include a scheduler for activating profiles. Instead, activate profiles from an automation platform that already handles time- and event-based triggers well, such as [Home Assistant](https://www.home-assistant.io/) or [Node-RED](https://nodered.org/). These integrate with Frigate and give you far more robust and flexible scheduling than a built-in scheduler could.
If you prefer something lightweight, a simple script driven by a cron job that toggles profiles on a schedule works too.
+1 -1
View File
@@ -23,7 +23,7 @@ In 0.14 and later, all of that is bundled into a single review item which starts
## Alerts and Detections
Not every segment of video captured by Frigate may be of the same level of interest to you. Video of people who enter your property may be a different priority than those walking by on the sidewalk. For this reason, Frigate categorizes review items as _alerts_ and _detections_. By default, all person and car objects are considered alerts. You can refine categorization of your review items by configuring required zones for them.
Not every segment of video captured by Frigate may be of the same level of interest to you. Video of people who enter your property may be a different priority than those walking by on the sidewalk. For this reason, Frigate categorizes review items as _alerts_ and _detections_. By default, all person and car objects are considered alerts. You can refine categorization of your review items by configuring [required zones](/configuration/zones#restricting-alerts-and-detections-to-specific-zones) for them.
:::note
+6 -6
View File
@@ -18,7 +18,7 @@ Zones cannot have the same name as a camera. If desired, a single zone can inclu
Zones can be toggled on or off without removing them from the configuration. Disabled zones are completely ignored at runtime - objects will not be tracked for zone presence, and zones will not appear in the debug view. This is useful for temporarily disabling a zone during certain seasons or times of day without modifying the configuration.
During testing, enable the Zones option for the Debug view of your camera (Settings --> Debug) so you can adjust as needed. The zone line will increase in thickness when any object enters the zone.
During testing, enable the Zones option for the [Debug view](/usage/live#the-single-camera-view) of your camera so you can adjust as needed. The zone line will increase in thickness when any object enters the zone.
## Creating a Zone
@@ -61,7 +61,7 @@ Navigate to <NavPath path="Settings > Camera configuration > Review" />.
| Field | Description |
| ---------------------------------- | ----------------------------------------------------------------------------------------- |
| **Alerts config > Required zones** | Zones that an object must enter to be considered an alert; leave empty to allow any zone. |
| **Alerts config > Required zones** | Set to `entire_yard` so an object must enter that zone to be considered an alert; leave empty to allow alerts anywhere in the frame. |
</TabItem>
<TabItem value="yaml">
@@ -82,7 +82,7 @@ cameras:
</TabItem>
</ConfigTabs>
You may also want to filter detections to only be created when an object enters a secondary area of interest. For example, to trigger alerts when an object enters the inner area of the yard but detections when an object enters the edge of the yard:
You may also want to filter detections to only be created when an object enters a secondary area of interest. For example, to trigger alerts when an object enters the inner area of the yard (an `inner_yard` zone) but detections when an object enters the edge of the yard (an `edge_yard` zone):
<ConfigTabs>
<TabItem value="ui">
@@ -91,8 +91,8 @@ Navigate to <NavPath path="Settings > Camera configuration > Review" />.
| Field | Description |
| -------------------------------------- | -------------------------------------------------------------------------------------------- |
| **Alerts config > Required zones** | Zones that an object must enter to be considered an alert; leave empty to allow any zone. |
| **Detections config > Required zones** | Zones that an object must enter to be considered a detection; leave empty to allow any zone. |
| **Alerts config > Required zones** | Set to `inner_yard` so an object must enter that zone to be considered an alert; leave empty to allow alerts anywhere in the frame. |
| **Detections config > Required zones** | Set to `edge_yard` so an object must enter that zone to be considered a detection; leave empty to allow detections anywhere in the frame. |
</TabItem>
<TabItem value="yaml">
@@ -121,7 +121,7 @@ cameras:
### Restricting snapshots to specific zones
To only save snapshots when an object enters a specific zone:
To only save snapshots when an object enters a specific zone, for example an `entire_yard` zone:
<ConfigTabs>
<TabItem value="ui">
+1 -1
View File
@@ -31,7 +31,7 @@ Larger resolutions **do** improve performance if the objects are very small in t
:::warning
Most users who raise `detect.fps` above the default don't need to. Increasing it consumes more CPU/GPU (detection load scales directly with the frame rate) while providing **no benefit to tracking** once objects are already being followed smoothly. Leave it at **5** unless you have a specific scene that fails the test below, and confirm any change actually helps in the debug view.
Most users who raise `detect.fps` above the default don't need to. Increasing it consumes more CPU/GPU (detection load scales directly with the frame rate) while providing **no benefit to tracking** once objects are already being followed smoothly. Leave it at **5** unless you have a specific scene that fails the test below, and confirm any change actually helps in the [debug view](/usage/live#the-single-camera-view).
:::
+1 -1
View File
@@ -15,7 +15,7 @@ A property detected on an [object](#object) that exists alongside its [label](#l
## Bounding Box
A box returned by the object detection [model](#model) that outlines a detected [object](#object) in the frame. In the Debug view, bounding boxes are colored by object [label](#label).
A box returned by the object detection [model](#model) that outlines a detected [object](#object) in the frame. In the [Debug view](/usage/live#the-single-camera-view), bounding boxes are colored by object [label](#label).
### Bounding Box Colors
+4 -4
View File
@@ -68,26 +68,26 @@ Frigate supports multiple different detectors that work on different types of ha
**AMD**
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD Discrete GPUs to provide efficient object detection
- [Supports limited model architectures](../../configuration/object_detectors#rocm-supported-models)
- [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector)
- Runs best on discrete AMD GPUs
**Apple Silicon**
- [Apple Silicon](#apple-silicon): Apple Silicon is usable on all M1 and newer Apple Silicon devices to provide efficient and fast object detection
- [Supports primarily ssdlite and mobilenet model architectures](../../configuration/object_detectors#apple-silicon-supported-models)
- [Supports primarily ssdlite and mobilenet model architectures](../../configuration/object_detectors#apple-silicon-detector)
- Runs well with any size models including large
- Runs via ZMQ proxy which adds some latency, only recommended for local connection
**Intel**
- [OpenVino](#openvino---intel): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel NPUs to provide efficient object detection.
- [Supports majority of model architectures](../../configuration/object_detectors#openvino-supported-models)
- [Supports majority of model architectures](../../configuration/object_detectors#openvino-detector)
- Runs best with tiny, small, or medium models
**Nvidia**
- [Nvidia GPU](#nvidia-gpus): Nvidia GPUs can provide efficient object detection.
- [Supports majority of model architectures via ONNX](../../configuration/object_detectors#onnx-supported-models)
- [Supports majority of model architectures via ONNX](../../configuration/object_detectors#onnx)
- Runs well with any size models including large
- <CommunityBadge /> [Jetson](#nvidia-jetson): Jetson devices are supported via the TensorRT or ONNX detectors when running Jetpack 6.
+1 -1
View File
@@ -305,7 +305,7 @@ Restart Frigate and you should start seeing detections for `person`. If you want
### Step 5: Setup motion masks
Now that you have optimized your configuration for decoding the video stream, you will want to check to see where to implement motion masks. Click on the camera from the main dashboard, then select the gear icon in the top right, enable Debug View, and finally enable the switch for Motion Boxes. Watch for areas that continuously trigger unwanted motion to be detected. Common areas to mask include camera timestamps and trees that frequently blow in the wind. The goal is to avoid wasting object detection cycles looking at these areas.
Now that you have optimized your configuration for decoding the video stream, you will want to check to see where to implement motion masks. Click on the camera from the main dashboard, then select the gear icon in the top right, enable the [Debug view](/usage/live#the-single-camera-view), and finally enable the switch for Motion Boxes. Watch for areas that continuously trigger unwanted motion to be detected. Common areas to mask include camera timestamps and trees that frequently blow in the wind. The goal is to avoid wasting object detection cycles looking at these areas.
Use the mask editor to draw polygon masks directly on the camera feed. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and set up a motion mask over the area. More information about masks can be found [here](../configuration/masks.md).
@@ -31,6 +31,10 @@ This is a fork (with fixed errors and new features) of [original Double Take](ht
[Frigate Notify](https://github.com/0x2142/frigate-notify) is a simple app designed to send notifications from Frigate to your favorite platforms. Intended to be used with standalone Frigate installations - Home Assistant not required, MQTT is optional but recommended.
## [Frigate Notify Alert](https://github.com/Sysoev86/frigate-notify-alert)
[Frigate Notify Alert](https://github.com/Sysoev86/frigate-notify-alert) sends Frigate events to Telegram as a photo + video media group. It supports multiple camera groups (each notifying its own chat), optional zone filtering (notify only when an object enters a chosen zone), and in-chat buttons to pause notifications for a set time. Works with standalone Frigate over MQTT; Home Assistant not required.
## [Frigate Snap-Sync](https://github.com/thequantumphysicist/frigate-snap-sync/)
[Frigate Snap-Sync](https://github.com/thequantumphysicist/frigate-snap-sync/) is a program that works in tandem with Frigate. It responds to Frigate when a snapshot or a review is made (and more can be added), and uploads them to one or more remote server(s) of your choice.
+4 -3
View File
@@ -44,7 +44,7 @@ Choosing the right detector for your hardware is the single most important facto
### Understanding Detector Performance
Frigate uses motion detection as a first-line check before running expensive object detection, as explained in the [motion detection documentation](../configuration/motion_detection). When motion is detected, Frigate creates a "region" (the green boxes in the debug viewer) and sends it to the detector. The detector's inference speed determines how many detections per second your system can handle.
Frigate uses motion detection as a first-line check before running expensive object detection, as explained in the [motion detection documentation](../configuration/motion_detection). When motion is detected, Frigate creates a "region" (the green boxes in the [debug viewer](/usage/live#the-single-camera-view)) and sends it to the detector. The detector's inference speed determines how many detections per second your system can handle.
**Calculating Detector Capacity:** Your detector has a finite capacity measured in detections per second. With an inference speed of 10ms, your detector can handle approximately 100 detections per second (1000ms / 10ms = 100).If your cameras collectively require more than this capacity, you'll experience delays, missed detections, or the system will fall behind.
@@ -58,7 +58,6 @@ When a single detector cannot keep up with your camera count, some detector type
For detailed instructions on configuring multiple detectors, see the [Object Detectors documentation](../configuration/object_detectors).
**When to add a second detector:**
- Skipped FPS is consistently > 0 even during normal activity
@@ -70,4 +69,6 @@ The model you use significantly impacts detector performance. Frigate provides d
**Model Size Trade-offs:**
- Smaller models (320x320): Faster inference, Frigate is specifically optimized for a 320x320 size model.
- Larger models (640x640): Slower inference, can sometimes have higher accuracy on very large objects that take up a majority of the frame.
- Larger models (640x640): Slower inference, can sometimes have higher accuracy on very large objects that take up a majority of the frame.
For more detail on picking the right size, see [Choosing a model size](../configuration/object_detectors.md#choosing-a-model-size).
+1 -1
View File
@@ -109,7 +109,7 @@ cameras:
2. Add the temporary camera to `config/config.yml` (example above). Use a unique name such as `test` or `replay_camera` so it's easy to remove later.
- If you're debugging a specific camera, copy the settings from that camera (frame rate, model/enrichment settings, zones, etc.) into the temporary camera so the replay closely matches the original environment. Leave `record` and `snapshots` disabled unless you are specifically debugging recording or snapshot behavior.
3. Restart Frigate.
4. Observe the Debug view in the UI and logs as the clip is replayed. Watch detections, zones, or any feature you're looking to debug, and note any errors in the logs to reproduce the issue.
4. Observe the [Debug view](/usage/live#the-single-camera-view) in the UI and logs as the clip is replayed. Watch detections, zones, or any feature you're looking to debug, and note any errors in the logs to reproduce the issue.
5. Iterate on camera or enrichment settings (model, fps, zones, filters) and re-check the replay until the behavior is resolved.
6. Remove the temporary camera from your config after debugging to avoid spurious telemetry or recordings.
+31 -2
View File
@@ -49,9 +49,9 @@ This almost always means that the width/height defined for your camera are not c
These messages in the logs are expected in certain situations. Frigate checks the integrity of the recordings before storing. Occasionally these cached files will be invalid and cleaned up automatically.
### "On connect called"
### "MQTT connected" repeats in the logs
If you see repeated "On connect called" messages in your logs, check for another instance of Frigate. This happens when multiple Frigate containers are trying to connect to MQTT with the same `client_id`.
If you see repeated "MQTT connected" messages in your logs, check for another instance of Frigate. This happens when multiple Frigate containers are trying to connect to MQTT with the same `client_id`.
### Error: Database Is Locked
@@ -124,3 +124,32 @@ cameras:
width: 1280
height: 720
```
### Why does Frigate keep creating new tracked objects for my parked car?
Stationary tracking is designed to _prevent_ this — a parked car should remain a single tracked object rather than generating new ones. If you're repeatedly getting new tracked objects for the same car, it's likely that Frigate is losing the object and re-detecting it as a new one.
Open one of the tracked objects in Explore → **Tracking Details**. If the detection scores are low (< 70% or so), the model isn't confident the parked car is a car. This is common with the free [COCO-trained](https://cocodataset.org/#explore) object detection models on steep/top-down angles, partially occluded cars, foliage, or low-light footage. When detections fall below `min_score` for too many frames the tracker loses the object, and the next confident frame creates a brand new one.
What helps:
- **Improve the view** — even a small angle change that gets more of the car visible could lift scores enough to stabilize tracking.
- **Use a more accurate model** — switching from `mobiledet` to `yolov9`, or stepping up to a larger variant like `yolov9-s` over `yolov9-t`, can help (at the cost of inference time, and still on the COCO dataset). The biggest gains usually come from fine-tuning a model on images from your own cameras so it learns your specific scene. [Frigate+](https://frigate.video/plus) is a paid option that does this - models are trained on security-camera footage and can be fine-tuned on images you submit from your own setup.
- **Don't set `detect -> stationary -> max_frames` for `car`** — it artificially ends tracking and forces re-detection as a new object. See [Stationary Objects](../configuration/stationary_objects.md).
- **Restrict alerts to the areas you care about** with `required_zones` — see [Zones](../configuration/zones.md#restricting-alerts-and-detections-to-specific-zones). Make sure those zones use the default `loitering_time: 0` unless you specifically want the review item to stay open until the car leaves.
- **Filter impossible locations** with [object filter masks](../configuration/masks.md#object-filter-masks) if cars are being detected on rooftops, treetops, etc.
See [Object Filters](../configuration/object_filters.md) for more on tuning `min_score` and `threshold` — note that raising them too high will make this exact problem worse.
### How do I correct Frigate when it detects something as the wrong object?
Frigate's object detection relies on a machine learning [model](../frigate/glossary.md#model), and the free [COCO-trained](https://cocodataset.org/#explore) models that ship with Frigate can misidentify objects in scenes they weren't trained on. There are two ways to handle this, depending on whether you want to _teach_ the model or just _suppress_ the bad result.
**Train or fine-tune a model with your own images.** The most durable fix is to improve the model itself. The biggest gains usually come from fine-tuning a model on images from your own cameras so it learns your specific scene. Some tools are freely available, and [Frigate+](https://frigate.video/plus) is a paid option that does this - models are trained on security-camera footage and can be fine-tuned on images you submit from your own setup. When Frigate mislabels something, open the tracked object in Explore, select the **Snapshot** tab, and use **Submit to Frigate+** to send the example with the correct label (or mark it as a [false positive](../frigate/glossary.md#false-positive)). Once you've submitted examples and [requested a model](../plus/first_model.md), the retrained model will be more accurate for your cameras. See [Submitting examples to Frigate+](../integrations/plus.md#submit-examples) for the full workflow.
**Suppress the misidentification with filters.** You can use filters to stop a specific false positive from being tracked:
- Tune `min_score` / `threshold`, or add `min_area` / `max_area` / `min_ratio` / `max_ratio` filters — see [Object Filters](../configuration/object_filters.md).
- If the false positive is always in the same fixed spot (like a statue or mailbox that reads as a person), add an [object filter mask](../configuration/masks.md#object-filter-masks) over that location.
Filters and masks only hide the incorrect result - they don't teach Frigate what the object actually is. For that, fine-tune your own model or use Frigate+.
+44 -1
View File
@@ -10,4 +10,47 @@ title: GPU Errors
Some users have reported issues using some Intel iGPUs with OpenVINO, where the GPU would not be detected. This error can be caused by various problems, so it is important to ensure the configuration is setup correctly. Some solutions users have noted:
- In some cases users have noted that an HDMI dummy plug was necessary to be plugged into the motherboard's HDMI port.
- When mixing an Intel iGPU with Nvidia GPU, the devices can be mixed up between `/dev/dri/renderD128` and `/dev/dri/renderD129` so it is important to confirm the correct device, or map the entire `/dev/dri` directory into the Frigate container.
- When mixing an Intel iGPU with Nvidia GPU, the devices can be mixed up between `/dev/dri/renderD128` and `/dev/dri/renderD129` so it is important to confirm the correct device, or map the entire `/dev/dri` directory into the Frigate container.
## Intel/AMD GPU
### Hardware acceleration is not being used
For VAAPI or QSV to work, the GPU's render device must be passed through to the Frigate container. Intel and AMD GPUs expose this as a render node under `/dev/dri`, usually `/dev/dri/renderD128`. If it is not passed through, hardware acceleration is unavailable — ffmpeg fails to initialize it (for example `Failed to open the drm device` or `No VA display found for device`) and GPU usage stays at zero while CPU usage remains high.
Pass the render device through when starting the container. With `docker compose`:
```yaml
services:
frigate:
devices:
- /dev/dri/renderD128:/dev/dri/renderD128 # Intel / AMD GPU, update for your hardware
```
Or with `docker run`, add `--device /dev/dri/renderD128`. See the [installation docs](/frigate/installation) for a complete example.
If it still isn't working after passing the device through:
- **Confirm the render node exists and is the correct one.** Run `ls /dev/dri` on the host — you should see one or more `renderD12X` entries. Systems with more than one GPU (an Intel iGPU plus a discrete GPU) can expose both `/dev/dri/renderD128` and `/dev/dri/renderD129`, and the numbering is not guaranteed. Pass through the correct node, or map the entire directory (`/dev/dri:/dev/dri`, or `--device /dev/dri`) so all render nodes are available.
- **Check device permissions.** The Frigate process must be able to access the render node. This is usually automatic when the container runs as root (the default), but nested setups such as an unprivileged Proxmox/LXC container often require making the device accessible on the host (for example, a world-readable render node) or running the container privileged. Note that running Frigate inside an LXC is not officially supported — see the [installation docs](/frigate/installation#proxmox) for details.
### Failed to download frame: -5
When using VAAPI or QSV hardware acceleration, ffmpeg may crash and restart periodically with a signature like this in the `ffmpeg.<camera>.detect` log:
```
[AVHWFramesContext @ 0x...] Failed to sync surface ... (operation failed).
[hwdownload @ 0x...] Failed to download frame: -5.
[vf#0:0 @ 0x...] Error while filtering: Input/output error
[vf#0:0 @ 0x...] Task finished with error code: -5 (Input/output error)
[frigate.video] <camera>: Unable to read frames from ffmpeg process.
```
This is a hardware frame synchronization failure between ffmpeg and the GPU driver, not a Frigate bug. It comes from how a specific camera stream interacts with the GPU's decode and scaling path, so it is highly dependent on your hardware, driver, and stream. Frigate's automatic hardware acceleration detection is a best-guess effort, so the fix is usually to tune the configuration for your specific hardware and camera. The solutions below are ordered from most to least likely to help:
- **Switch between the VAAPI and QSV presets.** On Intel Gen 12 and newer iGPUs, `preset-intel-qsv-h264` / `preset-intel-qsv-h265` is often more stable than the auto-detected `preset-vaapi`. See the [hardware acceleration docs](/configuration/hardware_acceleration_video.md#intel-based-cpus) for the recommended preset for your Intel generation.
- **Try a different VAAPI driver.** The default driver is `iHD`. On older Intel CPUs, `LIBVA_DRIVER_NAME=i965` can be more stable; on AMD GPUs use `LIBVA_DRIVER_NAME=radeonsi`. See [the hardware acceleration docs](/configuration/hardware_acceleration_video.md#intel-based-cpus) for how to set the driver.
- **Use a codec that decodes more reliably.** H.265/HEVC streams may trigger this error far more often than H.264 depending on your CPU generation. If your camera exposes a separate sub-stream, assign an H.264 stream to the `detect` role. Cameras that output full-range YUV (for example some Hikvision models) are especially prone to it.
- **Match the detect resolution to the stream resolution.** When the `detect` resolution differs from the stream, Frigate inserts a GPU scaling filter (`scale_vaapi`), which is where these surface-sync failures can often originate. Set the `detect` `width` and `height` to match the exact resolution of the stream assigned the `detect` role.
- **Match the detect `fps` to the camera stream.** Aggressively dropping frames (for example `detect` `fps: 1` on a stream that runs at 15 fps) can cause timing mismatches in the GPU's frame buffer. Lower the sub-stream's frame rate on the camera itself instead of dropping most frames in Frigate.
- **Fall back to software decoding.** If none of the above resolve it, remove the preset for that camera (`hwaccel_args: []`). Hardware decoding is only an optimization — on a capable CPU, software-decoding a low-resolution sub-stream is inexpensive and gives a stable detect pipeline.
+6
View File
@@ -9,6 +9,12 @@ import NavPath from "@site/src/components/NavPath";
This page describes how to _use_ the Explore view. For how the underlying features are _configured_, see [Semantic Search](/configuration/semantic_search) and [Generative AI descriptions](/configuration/genai/genai_objects).
:::tip
If you just want to quickly see what happened on your cameras, it's recommended to use [Review](/usage/review) rather than Explore. Review groups overlapping and adjacent activity on a camera into **review items** and sorts them into Alerts, Detections, and Motion, so you can scan and play back footage in a few clicks instead of sifting through individual objects. Reach for Explore when you need to find a _specific_ tracked object after the fact — by label, time, zone, or description.
:::
## Browsing tracked objects
The default view shows your most recent tracked objects grouped into rows by label — _Person_, _Car_, _Dog_, and so on — each row labeled with the object type and a count. The arrow at the end of a row opens the full, filterable grid for that label.
+1 -1
View File
@@ -29,7 +29,7 @@ If you see **"No recordings found for this time"**, the most common causes are:
A toggle (a drawer on mobile) switches the side panel between three modes:
- **Timeline** — a scrubbable vertical timeline of the selected camera, annotated with a motion line, review-item markers, and gaps where no recording exists.
- **Timeline** — a scrubbable vertical timeline of the selected camera. Horizontal lines down the center represent motion, with longer lines indicating more motion at that moment. Review items are marked as shaded areas (**red** for alerts, **orange** for detections), and sections with no colored background are times when no recording exists.
- **Events** — a scrollable list of the camera's review items for the time range; clicking one seeks the player to it.
- **Detail** — the [tracking details inspector](#the-detail-view) for the objects in view.
+2 -2
View File
@@ -13,7 +13,7 @@ This page describes how to _use_ the Live view. For how to _configure_ live stre
The default **All Cameras** dashboard shows every camera, with a filmstrip of recent **alerts** scrolling across the top. Clicking an alert opens it in [Review](/usage/review); each card also has a check button to mark it reviewed without leaving the dashboard. Only **alerts** appear in the filmstrip — to suppress a label or zone from showing there, configure it as a detection instead (see [Alerts and Detections](/configuration/review#alerts-and-detections)).
By default Frigate uses **smart streaming**: a camera's image updates roughly once per minute while nothing is happening, and switches to a full live stream the moment activity is detected. This conserves bandwidth and resources. You can change this per camera or per group (see [Streaming settings](#streaming-settings-and-the-right-click-menu) below), and the behavior is explained in detail under [Live view technologies](/configuration/live#live-view-technologies).
By default Frigate uses **smart streaming**: a camera's image updates roughly once per minute while nothing is happening, and switches to a full live stream the moment activity is detected. This conserves bandwidth and resources. You can change this for each camera when using a camera group (see [Streaming settings](#streaming-settings-and-the-right-click-menu) below), and the behavior is explained in detail under [Live view technologies](/configuration/live#live-view-technologies).
On mobile, a toggle in the header switches between a **grid** layout and a single-column **list** layout. On desktop a **fullscreen** button is available in the lower-right corner.
@@ -24,7 +24,7 @@ The icon rail (top-left on desktop, a horizontal strip on mobile) switches betwe
- The **home** icon is the **All Cameras** dashboard, which shows every camera enabled for the dashboard.
- Each **camera group** you create appears as its own icon. Selecting a group shows only that group's cameras.
Camera groups are useful for organizing cameras by location (for example, _Front of House_ or _Backyard_) and for giving each group its own dashboard layout and streaming preferences.
Camera groups are useful for organizing cameras by location (for example, _Front of House_ or _Backyard_) and for giving each group its own dashboard layout and camera streaming preferences.
You can also view [Birdseye](/configuration/birdseye) on the dashboard, or open it directly at `http://<frigate_host>:5000/#birdseye`. Clicking a camera inside the Birdseye view jumps to that camera's live feed.
+1
View File
@@ -29,6 +29,7 @@
"docusaurus-plugin-openapi-docs": "^4.5.1",
"docusaurus-theme-openapi-docs": "^4.5.1",
"js-yaml": "^4.1.1",
"marked": "^16.4.2",
"prism-react-renderer": "^2.4.1",
"raw-loader": "^4.0.2",
"react": "^18.3.1",
@@ -0,0 +1,171 @@
import React, { useState } from "react";
import CodeBlock from "@theme/CodeBlock";
import ConfigTabs from "@site/src/components/ConfigTabs";
import TabItem from "@theme/TabItem";
import { marked } from "marked";
import styles from "./styles.module.css";
marked.setOptions({ gfm: true });
/**
* @typedef {Object} Model
* @property {string} key
* @property {string} label
* @property {boolean} recommended
* @property {string} download Markdown for the "download the model" step.
* @property {string} ui Markdown for the Frigate UI configuration step.
* @property {string} yaml Raw YAML for the configuration step.
*/
// Render a markdown string to React nodes. Fenced code blocks become Docusaurus
// CodeBlock components (so they get syntax highlighting and a copy button);
// everything else is marked-parsed to HTML.
function renderBlocks(md, keyPrefix) {
if (!md.trim()) return [];
const tokens = marked.lexer(md);
const nodes = [];
let buffer = [];
let idx = 0;
const flush = () => {
if (buffer.length) {
buffer.links = tokens.links;
nodes.push(
<div
key={`${keyPrefix}-h${idx++}`}
dangerouslySetInnerHTML={{ __html: marked.parser(buffer) }}
/>,
);
buffer = [];
}
};
tokens.forEach((token) => {
if (token.type === "code") {
flush();
const language = (token.lang || "text").split(/\s+/)[0];
nodes.push(
<CodeBlock key={`${keyPrefix}-c${idx++}`} language={language}>
{token.text}
</CodeBlock>,
);
} else {
buffer.push(token);
}
});
flush();
return nodes;
}
// marked does not understand Docusaurus admonitions (:::warning ... :::), so
// render those blocks ourselves and render everything around them normally.
function renderMarkdown(md) {
if (!md) return null;
const admonition = /:::(\w+)[ \t]*([^\n]*)\n([\s\S]*?)\n:::/g;
const nodes = [];
let lastIndex = 0;
let match;
let k = 0;
while ((match = admonition.exec(md)) !== null) {
nodes.push(...renderBlocks(md.slice(lastIndex, match.index), `seg${k}`));
const [, type, title, body] = match;
const heading = (title || type).trim();
nodes.push(
<div
key={`adm${k}`}
className={`${styles.admonition} ${styles[`admonition_${type}`] || ""}`}
>
<div className={styles.admonitionTitle}>{heading}</div>
{renderBlocks(body, `adm${k}`)}
</div>,
);
lastIndex = admonition.lastIndex;
k++;
}
nodes.push(...renderBlocks(md.slice(lastIndex), `seg${k}`));
return nodes;
}
function Markdown({ children }) {
return <div className={styles.markdown}>{renderMarkdown(children)}</div>;
}
function RecommendedBadge() {
return <span className={styles.recommendedBadge}>Recommended</span>;
}
/**
* @param {{ models: Model[] }} props
*/
export default function ModelConfigDropdown({ models }) {
const [selectedModelIndex, setSelectedModelIndex] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const selectedModel = models[selectedModelIndex];
const hasChoices = models.length > 1;
const handleModelSelect = (index) => {
setSelectedModelIndex(index);
setIsOpen(false);
};
return (
<div className={styles.wrapper}>
<div className={styles.panel}>
<div className={styles.step}>
<h4 className={styles.stepTitle}>Step 1 — Choose a model</h4>
<div
className={`${styles.dropdown} ${isOpen ? styles.open : ""} ${
hasChoices ? "" : styles.static
}`}
onClick={hasChoices ? () => setIsOpen(!isOpen) : undefined}
>
<div className={styles.dropdownContent}>
<span className={styles.modelName}>
{selectedModel.label}
{selectedModel.recommended && <RecommendedBadge />}
</span>
{hasChoices && (
<span className={styles.arrow}>{isOpen ? "▲" : "▼"}</span>
)}
</div>
</div>
{isOpen && hasChoices && (
<div className={styles.menu}>
{models.map((model, index) => (
<div
key={model.key}
className={`${styles.menuItem} ${
index === selectedModelIndex ? styles.menuItemActive : ""
}`}
onClick={() => handleModelSelect(index)}
>
{model.label}
{model.recommended && <RecommendedBadge />}
</div>
))}
</div>
)}
</div>
<div className={styles.step}>
<h4 className={styles.stepTitle}>Step 2 — Download the model</h4>
<Markdown>{selectedModel.download}</Markdown>
</div>
<div className={styles.step}>
<h4 className={styles.stepTitle}>Step 3 — Configure the detector</h4>
<ConfigTabs>
<TabItem value="ui">
<Markdown>{selectedModel.ui}</Markdown>
</TabItem>
<TabItem value="yaml">
<CodeBlock language="yaml">{selectedModel.yaml}</CodeBlock>
</TabItem>
</ConfigTabs>
</div>
</div>
</div>
);
}
@@ -0,0 +1,275 @@
/* ===================================================================
ModelConfigDropdown — styles
=================================================================== */
.wrapper {
margin: 1.5rem 0;
}
/* --- Dropdown button --- */
.dropdown {
display: inline-block;
width: 360px;
max-width: 100%;
text-align: left;
border: 1px solid var(--ifm-color-emphasis-400);
border-radius: 8px;
background: var(--ifm-background-color);
cursor: pointer;
transition:
border-color 0.2s,
box-shadow 0.2s;
}
[data-theme="light"] .dropdown {
border: 1px solid #d0d7de;
background: #fff;
}
[data-theme="dark"] .dropdown {
border: 1px solid var(--ifm-color-emphasis-300);
background: #21262d;
}
.dropdown:hover {
border-color: var(--ifm-color-primary);
box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest);
}
[data-theme="dark"] .dropdown:hover {
box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest);
}
.dropdown.open {
border-color: var(--ifm-color-primary);
box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest);
}
[data-theme="dark"] .dropdown.open {
border-color: var(--ifm-color-primary);
}
/* Single-model detectors render the label without a clickable menu. */
.dropdown.static {
cursor: default;
}
.dropdown.static:hover {
border-color: var(--ifm-color-emphasis-400);
box-shadow: none;
}
[data-theme="light"] .dropdown.static:hover {
border-color: #d0d7de;
}
[data-theme="dark"] .dropdown.static:hover {
border-color: var(--ifm-color-emphasis-300);
}
.dropdownContent {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 0.8rem 1rem;
}
/* --- Model menu --- */
.menu {
margin-top: 0.25rem;
width: 360px;
max-width: 100%;
border: 1px solid var(--ifm-color-emphasis-400);
border-radius: 8px;
overflow: hidden;
background: var(--ifm-background-color);
}
[data-theme="light"] .menu {
border: 1px solid #d0d7de;
background: #fff;
}
[data-theme="dark"] .menu {
border: 1px solid var(--ifm-color-emphasis-300);
background: #21262d;
}
.menuItem {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1rem;
cursor: pointer;
font-size: 0.95rem;
color: var(--ifm-font-color-base);
transition: background 0.15s;
}
.menuItem:not(:last-child) {
border-bottom: 1px solid var(--ifm-color-emphasis-200);
}
.menuItem:hover {
background: var(--ifm-color-emphasis-100);
}
.menuItemActive {
font-weight: var(--ifm-font-weight-semibold);
background: var(--ifm-color-primary-lightest);
}
[data-theme="dark"] .menuItem:hover {
background: #2b3139;
}
[data-theme="dark"] .menuItemActive {
background: #2b3139;
}
.modelName {
font-weight: var(--ifm-font-weight-semibold);
color: var(--ifm-font-color-base);
font-size: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
.recommendedBadge {
display: inline-block;
background: var(--ifm-color-success);
color: #fff;
font-size: 0.7rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.arrow {
font-size: 0.7rem;
color: var(--ifm-font-color-secondary);
transition: transform 0.2s;
}
.dropdown.open .arrow {
transform: rotate(180deg);
}
/* --- Panel --- */
.panel {
margin-top: 0.5rem;
border: 1px solid var(--ifm-color-emphasis-400);
border-radius: 8px;
overflow: hidden;
background: var(--ifm-background-color);
}
[data-theme="light"] .panel {
border: 1px solid #d0d7de;
background: #fff;
}
[data-theme="dark"] .panel {
border: 1px solid var(--ifm-color-emphasis-300);
background: #21262d;
}
/* --- Steps --- */
.step {
padding: 1rem;
}
.step:not(:last-child) {
border-bottom: 1px solid var(--ifm-color-emphasis-200);
}
.stepTitle {
margin: 0 0 0.75rem 0;
font-size: 1rem;
font-weight: 600;
color: var(--ifm-font-color-base);
}
/* Rendered markdown (download + Frigate UI instructions). */
.markdown {
font-size: 0.9rem;
line-height: 1.6;
}
.markdown > :last-child {
margin-bottom: 0;
}
.markdown a {
color: var(--ifm-color-primary);
text-decoration: underline;
text-underline-offset: 2px;
}
.markdown table {
display: table;
width: 100%;
margin: 0.75rem 0;
font-size: 0.85rem;
}
/* Docusaurus-style admonitions rendered from markdown. */
.admonition {
margin: 0.75rem 0;
padding: 0.75rem 1rem;
border-left: 4px solid var(--ifm-color-info);
border-radius: 4px;
background: var(--ifm-color-info-contrast-background);
font-size: 0.85rem;
}
.admonition > :last-child {
margin-bottom: 0;
}
.admonitionTitle {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.75rem;
margin-bottom: 0.4rem;
color: var(--ifm-color-info);
}
.admonition_warning {
border-left-color: var(--ifm-color-warning);
background: var(--ifm-color-warning-contrast-background);
}
.admonition_warning .admonitionTitle {
color: var(--ifm-color-warning-dark);
}
.admonition_danger {
border-left-color: var(--ifm-color-danger);
background: var(--ifm-color-danger-contrast-background);
}
.admonition_danger .admonitionTitle {
color: var(--ifm-color-danger-dark);
}
.admonition_tip {
border-left-color: var(--ifm-color-success);
background: var(--ifm-color-success-contrast-background);
}
.admonition_tip .admonitionTitle {
color: var(--ifm-color-success-dark);
}
+14
View File
@@ -7393,6 +7393,13 @@ components:
required:
- value
title: CameraSetBody
ChaptersEnum:
type: string
enum:
- none
- recording_segments
- review_items
title: ChaptersEnum
ChatCompletionRequest:
properties:
messages:
@@ -8119,6 +8126,13 @@ components:
- type: 'null'
title: Export case ID
description: ID of the export case to assign this export to
chapters:
anyOf:
- $ref: '#/components/schemas/ChaptersEnum'
- type: 'null'
title: Chapter mode
description: Optional chapter metadata to embed in the export. When
omitted, the camera's configured export chapter mode is used.
type: object
title: ExportRecordingsBody
ExportRecordingsCustomBody:
+2 -3
View File
@@ -4,7 +4,6 @@ import multiprocessing as mp
import signal
import sys
import threading
from typing import Union
import ruamel.yaml
from pydantic import ValidationError
@@ -54,7 +53,7 @@ def main() -> None:
print("*************************************************************\n")
# Attempt to get the original config file for line number tracking
config_path = find_config_file()
with open(config_path, "r") as f:
with open(config_path) as f:
yaml_config = ruamel.yaml.YAML()
yaml_config.preserve_quotes = True
full_config = yaml_config.load(f)
@@ -68,7 +67,7 @@ def main() -> None:
try:
for i, part in enumerate(error_path):
key: Union[int, str] = (
key: int | str = (
int(part) if isinstance(part, str) and part.isdigit() else part
)
+34 -34
View File
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta
from functools import reduce
from io import StringIO
from pathlib import Path as FilePath
from typing import Any, Dict, List, Optional
from typing import Any
import aiofiles
import ruamel.yaml
@@ -113,7 +113,7 @@ def version():
@router.get("/stats", dependencies=[Depends(allow_any_authenticated())])
def stats(
request: Request,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
stats_data = request.app.stats_emitter.get_latest_stats()
@@ -164,7 +164,7 @@ def metrics(request: Request):
# Retrieve the latest statistics and update the Prometheus metrics
stats = request.app.stats_emitter.get_latest_stats()
# query DB for count of events by camera, label
event_counts: List[Dict[str, Any]] = (
event_counts: list[dict[str, Any]] = (
Event.select(Event.camera, Event.label, fn.Count())
.group_by(Event.camera, Event.label)
.dicts()
@@ -250,7 +250,7 @@ async def genai_probe(body: GenAIProbeBody):
asyncio.to_thread(client.list_models),
timeout=_PROBE_OUTER_TIMEOUT_SECONDS,
)
except asyncio.TimeoutError:
except TimeoutError:
return JSONResponse(
content={"success": False, "message": "Probe timed out"},
)
@@ -374,7 +374,7 @@ def config(request: Request):
if model_path:
model_json_path = FilePath(model_path).with_suffix(".json")
try:
with open(model_json_path, "r") as f:
with open(model_json_path) as f:
model_plus_data = json.load(f)
config["model"]["plus"] = model_plus_data
except FileNotFoundError:
@@ -502,7 +502,7 @@ def config_raw():
status_code=404,
)
with open(config_file, "r") as f:
with open(config_file) as f:
raw_config = f.read()
f.close()
@@ -807,7 +807,7 @@ def config_set(request: Request, body: AppConfigSetBody):
try:
with lock:
with open(config_file, "r") as f:
with open(config_file) as f:
old_raw_config = f.read()
try:
@@ -854,7 +854,7 @@ def config_set(request: Request, body: AppConfigSetBody):
update_yaml_file_bulk(config_file, updates)
# validate the updated config
with open(config_file, "r") as f:
with open(config_file) as f:
new_raw_config = f.read()
try:
@@ -1028,16 +1028,16 @@ def nvinfo():
)
async def logs(
service: str = Path(enum=["frigate", "nginx", "go2rtc"]),
download: Optional[str] = None,
stream: Optional[bool] = False,
start: Optional[int] = 0,
end: Optional[int] = None,
download: str | None = None,
stream: bool | None = False,
start: int | None = 0,
end: int | None = None,
):
"""Get logs for the requested service (frigate/nginx/go2rtc)"""
def download_logs(service_location: str):
try:
file = open(service_location, "r")
file = open(service_location)
contents = file.read()
file.close()
return JSONResponse(jsonable_encoder(contents))
@@ -1052,7 +1052,7 @@ async def logs(
"""Asynchronously stream log lines."""
buffer = ""
try:
async with aiofiles.open(file_path, "r") as file:
async with aiofiles.open(file_path) as file:
await file.seek(0, 2)
while True:
line = await file.readline()
@@ -1090,7 +1090,7 @@ async def logs(
# For full logs initially
try:
async with aiofiles.open(service_location, "r") as file:
async with aiofiles.open(service_location) as file:
contents = await file.read()
total_lines, log_lines = process_logs(contents, service, start, end)
@@ -1231,7 +1231,7 @@ def get_media_sync_status(job_id: str):
@router.get("/labels", dependencies=[Depends(allow_any_authenticated())])
def get_labels(
camera: str = "",
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
try:
if camera:
@@ -1263,8 +1263,8 @@ def get_labels(
@router.get("/sub_labels", dependencies=[Depends(allow_any_authenticated())])
def get_sub_labels(
split_joined: Optional[int] = None,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
split_joined: int | None = None,
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
try:
events = (
@@ -1351,8 +1351,8 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
)
def get_recognized_license_plates(
split_joined: Optional[int] = None,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
split_joined: int | None = None,
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
try:
query = (
@@ -1393,8 +1393,8 @@ def get_recognized_license_plates(
def timeline(
camera: str = "all",
limit: int = 100,
source_id: Optional[str] = None,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
source_id: str | None = None,
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
clauses = []
@@ -1408,20 +1408,20 @@ def timeline(
]
if camera != "all":
clauses.append((Timeline.camera == camera))
clauses.append(Timeline.camera == camera)
if source_id:
source_ids = [sid.strip() for sid in source_id.split(",")]
if len(source_ids) == 1:
clauses.append((Timeline.source_id == source_ids[0]))
clauses.append(Timeline.source_id == source_ids[0])
else:
clauses.append((Timeline.source_id.in_(source_ids)))
clauses.append(Timeline.source_id.in_(source_ids))
# Enforce per-camera access control
clauses.append((Timeline.camera << allowed_cameras))
clauses.append(Timeline.camera << allowed_cameras)
if len(clauses) == 0:
clauses.append((True))
clauses.append(True)
timeline = (
Timeline.select(*selected_columns)
@@ -1437,7 +1437,7 @@ def timeline(
@router.get("/timeline/hourly", dependencies=[Depends(allow_any_authenticated())])
def hourly_timeline(
params: AppTimelineHourlyQueryParameters = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
"""Get hourly summary for timeline."""
cameras = params.cameras
@@ -1454,23 +1454,23 @@ def hourly_timeline(
if cameras != "all":
camera_list = cameras.split(",")
clauses.append((Timeline.camera << camera_list))
clauses.append(Timeline.camera << camera_list)
# Enforce per-camera access control
clauses.append((Timeline.camera << allowed_cameras))
clauses.append(Timeline.camera << allowed_cameras)
if labels != "all":
label_list = labels.split(",")
clauses.append((Timeline.data["label"] << label_list))
clauses.append(Timeline.data["label"] << label_list)
if before:
clauses.append((Timeline.timestamp < before))
clauses.append(Timeline.timestamp < before)
if after:
clauses.append((Timeline.timestamp > after))
clauses.append(Timeline.timestamp > after)
if len(clauses) == 0:
clauses.append((True))
clauses.append(True)
timeline = (
Timeline.select(
+22 -15
View File
@@ -11,7 +11,6 @@ import secrets
import time
from datetime import datetime
from pathlib import Path
from typing import List, Optional
from urllib.parse import parse_qs, urlparse
from fastapi import APIRouter, Depends, HTTPException, Request, Response
@@ -254,7 +253,14 @@ rateLimiter = RateLimiter()
def get_remote_addr(request: Request):
route = list(reversed(request.headers.get("x-forwarded-for").split(",")))
# fall back to the direct TCP peer when no proxy chain is present
direct_addr = request.client.host if request.client else None
forwarded_for = request.headers.get("x-forwarded-for")
if not forwarded_for:
return direct_addr or "127.0.0.1"
route = list(reversed(forwarded_for.split(",")))
logger.debug(f"IP Route: {[r for r in route]}")
trusted_proxies = []
for proxy in request.app.frigate_config.auth.trusted_proxies:
@@ -291,13 +297,8 @@ def get_remote_addr(request: Request):
logger.debug(f"First untrusted IP: {str(ip)}")
return str(ip)
# if there wasn't anything in the route, just return the default
remote_addr = None
if hasattr(request, "remote_addr"):
remote_addr = request.remote_addr
return remote_addr or "127.0.0.1"
# every hop in the route was trusted, so fall back to the direct peer
return direct_addr or "127.0.0.1"
def _cleanup_first_load_seen() -> None:
@@ -388,7 +389,7 @@ def verify_password(password, password_hash):
return secrets.compare_digest(password_hash, compare_hash)
def validate_password_strength(password: str) -> tuple[bool, Optional[str]]:
def validate_password_strength(password: str) -> tuple[bool, str | None]:
"""
Validate password strength.
@@ -416,6 +417,12 @@ def create_encoded_jwt(user, role, expiration, secret):
def set_jwt_cookie(response: Response, cookie_name, encoded_jwt, expiration, secure):
# TODO: ideally this would set secure as well, but that requires TLS
# SameSite is intentionally left unset (browsers default to Lax). Setting
# SameSite=Lax/Strict would stop the cookie from being sent in cross-origin
# iframes, breaking embedded views such as the Home Assistant Frigate card.
# CSRF is instead mitigated by requiring a custom X-CSRF-TOKEN header, which
# cross-origin pages cannot set without a CORS preflight that Frigate never
# grants (see check_csrf in api/fastapi_app.py).
response.set_cookie(
key=cookie_name,
value=encoded_jwt,
@@ -437,7 +444,7 @@ async def get_current_user(request: Request):
return {"username": username, "role": role}
def require_role(required_roles: List[str]):
def require_role(required_roles: list[str]):
async def role_checker(request: Request):
proxy_config: ProxyConfig = request.app.frigate_config.proxy
config_roles = list(request.app.frigate_config.auth.roles.keys())
@@ -1075,7 +1082,7 @@ async def update_role(
async def require_camera_access(
camera_name: Optional[str] = None,
camera_name: str | None = None,
request: Request = None,
):
"""Dependency to enforce camera access based on user role."""
@@ -1140,8 +1147,8 @@ GO2RTC_STREAM_PROXY_PATHS = frozenset(
def deny_response_for_go2rtc_stream(
original_url: Optional[str], role: Optional[str], request: Request
) -> Optional[int]:
original_url: str | None, role: str | None, request: Request
) -> int | None:
"""Block role-restricted users from go2rtc live streams they cannot access.
Returns 403 when any `src` stream named in `original_url` resolves to a
@@ -1186,7 +1193,7 @@ def deny_response_for_go2rtc_stream(
async def require_go2rtc_stream_access(
stream_name: Optional[str] = None,
stream_name: str | None = None,
request: Request = None,
):
"""Dependency to enforce go2rtc stream access based on owning camera access."""
+19 -5
View File
@@ -74,7 +74,7 @@ def _is_valid_host(host: str) -> bool:
@router.get("/go2rtc/streams", dependencies=[Depends(allow_any_authenticated())])
async def go2rtc_streams(request: Request):
r = requests.get("http://127.0.0.1:1984/api/streams")
r = await asyncio.to_thread(requests.get, "http://127.0.0.1:1984/api/streams")
if not r.ok:
logger.error("Failed to fetch streams from go2rtc")
return JSONResponse(
@@ -147,6 +147,19 @@ def go2rtc_camera_stream(request: Request, stream_name: str):
)
def go2rtc_add_stream(request: Request, stream_name: str, src: str = ""):
"""Add or update a go2rtc stream configuration."""
if src and is_restricted_go2rtc_source(src):
logger.warning(
"Rejected go2rtc stream '%s' with restricted source type (echo/expr/exec)",
stream_name,
)
return JSONResponse(
content={
"success": False,
"message": "Restricted stream source type",
},
status_code=400,
)
try:
params = {"name": stream_name}
if src:
@@ -1174,14 +1187,14 @@ async def delete_camera(
try:
with lock:
with open(config_file, "r") as f:
with open(config_file) as f:
old_raw_config = f.read()
try:
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
with open(config_file, "r") as f:
with open(config_file) as f:
data = yaml.load(f)
# Remove camera from config
@@ -1210,7 +1223,7 @@ async def delete_camera(
with open(config_file, "w") as f:
yaml.dump(data, f)
with open(config_file, "r") as f:
with open(config_file) as f:
new_raw_config = f.read()
try:
@@ -1272,7 +1285,8 @@ async def delete_camera(
# Best-effort go2rtc stream removal
try:
requests.delete(
await asyncio.to_thread(
requests.delete,
"http://127.0.0.1:1984/api/streams",
params={"src": camera_name},
timeout=5,
+10 -11
View File
@@ -7,7 +7,7 @@ import operator
import time
from datetime import datetime
from functools import reduce
from typing import Any, Optional
from typing import Any
import cv2
from fastapi import APIRouter, Body, Depends, HTTPException, Request
@@ -201,7 +201,7 @@ async def _execute_search_objects(
# Return it as-is for the LLM
return response
except Exception as e:
logger.error(f"Error executing search_objects: {e}", exc_info=True)
logger.exception(f"Error executing search_objects: {e}")
return JSONResponse(
content={
"success": False,
@@ -611,7 +611,7 @@ async def _execute_get_live_context(
return result
except Exception as e:
logger.error(f"Error executing get_live_context: {e}", exc_info=True)
logger.exception(f"Error executing get_live_context: {e}")
return {
"error": "Error getting live context",
}
@@ -621,7 +621,7 @@ async def _get_live_frame_image_url(
request: Request,
camera: str,
allowed_cameras: list[str],
) -> Optional[str]:
) -> str | None:
"""
Fetch the current live frame for a camera as a base64 data URL.
@@ -801,7 +801,7 @@ async def _execute_start_camera_watch(
zones=zones,
)
except RuntimeError as e:
logger.error("Failed to start VLM watch job: %s", e, exc_info=True)
logger.exception("Failed to start VLM watch job: %s", e)
return {"error": "Failed to start VLM watch job."}
return {
@@ -979,7 +979,7 @@ def _execute_get_recap(
return {"events": events}
except Exception as e:
logger.error("Error executing get_recap: %s", e, exc_info=True)
logger.exception("Error executing get_recap: %s", e)
return {"error": "Failed to fetch recap data."}
@@ -1072,13 +1072,12 @@ async def _execute_pending_tools(
}
)
except Exception as e:
logger.error(
logger.exception(
"Error executing tool %s (id: %s): %s. Arguments: %s",
tool_name,
tool_call_id,
e,
json.dumps(tool_args),
exc_info=True,
)
error_content = json.dumps({"error": f"Tool execution failed: {str(e)}"})
tool_calls_out.append(
@@ -1186,7 +1185,7 @@ async def chat_completion(
async def stream_body_llm():
nonlocal conversation, stream_iterations
def _emit_chain(extra: Optional[list[dict[str, Any]]] = None):
def _emit_chain(extra: list[dict[str, Any]] | None = None):
# Return the full conversation (including the system message) so
# the client persists and replays it verbatim next turn.
chain = conversation + (extra or [])
@@ -1414,7 +1413,7 @@ async def chat_completion(
)
except Exception as e:
logger.error(f"Error in chat completion: {e}", exc_info=True)
logger.exception(f"Error in chat completion: {e}")
return JSONResponse(
content={
"error": "An error occurred while processing your request.",
@@ -1477,7 +1476,7 @@ async def start_vlm_monitor(
username=request.headers.get("remote-user", ""),
)
except RuntimeError as e:
logger.error("Failed to start VLM watch job: %s", e, exc_info=True)
logger.exception("Failed to start VLM watch job: %s", e)
return JSONResponse(
content={"success": False, "message": "Failed to start VLM watch job."},
status_code=409,
+11 -10
View File
@@ -9,8 +9,9 @@ loop state — all inputs and outputs are plain data.
import logging
import math
import time
from collections.abc import Generator
from datetime import datetime
from typing import Any, Dict, Generator, List, Optional
from typing import Any
from frigate.embeddings.util import ZScoreNormalization
from frigate.models import Event
@@ -30,7 +31,7 @@ def chunk_content(content: str, chunk_size: int = 80) -> Generator[str, None, No
if not content:
return
words = content.split(" ")
current: List[str] = []
current: list[str] = []
current_len = 0
for w in words:
current.append(w)
@@ -44,8 +45,8 @@ def chunk_content(content: str, chunk_size: int = 80) -> Generator[str, None, No
def format_events_with_local_time(
events_list: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
events_list: list[dict[str, Any]],
) -> list[dict[str, Any]]:
"""Add human-readable local start/end times to each event for the LLM."""
result = []
for evt in events_list:
@@ -84,9 +85,9 @@ def distance_to_score(distance: float, stats: ZScoreNormalization) -> float:
def fuse_scores(
visual_score: Optional[float],
description_score: Optional[float],
) -> Optional[float]:
visual_score: float | None,
description_score: float | None,
) -> float | None:
"""Weighted fusion of visual and description similarity scores.
If one side is missing (e.g., no description embedding for this event),
@@ -102,7 +103,7 @@ def fuse_scores(
return VISUAL_WEIGHT * visual_score + DESCRIPTION_WEIGHT * description_score
def parse_iso_to_timestamp(value: Optional[str]) -> Optional[float]:
def parse_iso_to_timestamp(value: str | None) -> float | None:
"""Parse an ISO-8601 string as server-local time -> unix timestamp.
Mirrors the parsing _execute_search_objects uses so both tools accept the
@@ -119,9 +120,9 @@ def parse_iso_to_timestamp(value: Optional[str]) -> Optional[float]:
return None
def hydrate_event(event: Event, score: Optional[float] = None) -> Dict[str, Any]:
def hydrate_event(event: Event, score: float | None = None) -> dict[str, Any]:
"""Convert an Event row into the dict shape returned by find_similar_objects."""
data: Dict[str, Any] = {
data: dict[str, Any] = {
"id": event.id,
"camera": event.camera,
"label": event.label,
@@ -1,12 +1,10 @@
from typing import Optional
from pydantic import BaseModel
class AppTimelineHourlyQueryParameters(BaseModel):
cameras: Optional[str] = "all"
labels: Optional[str] = "all"
after: Optional[float] = None
before: Optional[float] = None
limit: Optional[int] = 200
timezone: Optional[str] = "utc"
cameras: str | None = "all"
labels: str | None = "all"
after: float | None = None
before: float | None = None
limit: int | None = 200
timezone: str | None = "utc"
@@ -1,28 +1,26 @@
from typing import Optional
from pydantic import BaseModel, Field
DEFAULT_TIME_RANGE = "00:00,24:00"
class EventsQueryParams(BaseModel):
camera: Optional[str] = "all"
cameras: Optional[str] = "all"
label: Optional[str] = "all"
labels: Optional[str] = "all"
sub_label: Optional[str] = "all"
sub_labels: Optional[str] = "all"
attributes: Optional[str] = "all"
zone: Optional[str] = "all"
zones: Optional[str] = "all"
limit: Optional[int] = 100
after: Optional[float] = None
before: Optional[float] = None
time_range: Optional[str] = DEFAULT_TIME_RANGE
has_clip: Optional[int] = None
has_snapshot: Optional[int] = None
in_progress: Optional[int] = None
include_thumbnails: Optional[int] = Field(
camera: str | None = "all"
cameras: str | None = "all"
label: str | None = "all"
labels: str | None = "all"
sub_label: str | None = "all"
sub_labels: str | None = "all"
attributes: str | None = "all"
zone: str | None = "all"
zones: str | None = "all"
limit: int | None = 100
after: float | None = None
before: float | None = None
time_range: str | None = DEFAULT_TIME_RANGE
has_clip: int | None = None
has_snapshot: int | None = None
in_progress: int | None = None
include_thumbnails: int | None = Field(
1,
description=(
"Deprecated. Thumbnail data is no longer included in the response. "
@@ -30,25 +28,25 @@ class EventsQueryParams(BaseModel):
),
deprecated=True,
)
favorites: Optional[int] = None
min_score: Optional[float] = None
max_score: Optional[float] = None
min_speed: Optional[float] = None
max_speed: Optional[float] = None
recognized_license_plate: Optional[str] = "all"
is_submitted: Optional[int] = None
min_length: Optional[float] = None
max_length: Optional[float] = None
event_id: Optional[str] = None
sort: Optional[str] = None
timezone: Optional[str] = "utc"
favorites: int | None = None
min_score: float | None = None
max_score: float | None = None
min_speed: float | None = None
max_speed: float | None = None
recognized_license_plate: str | None = "all"
is_submitted: int | None = None
min_length: float | None = None
max_length: float | None = None
event_id: str | None = None
sort: str | None = None
timezone: str | None = "utc"
class EventsSearchQueryParams(BaseModel):
query: Optional[str] = None
event_id: Optional[str] = None
search_type: Optional[str] = "thumbnail"
include_thumbnails: Optional[int] = Field(
query: str | None = None
event_id: str | None = None
search_type: str | None = "thumbnail"
include_thumbnails: int | None = Field(
1,
description=(
"Deprecated. Thumbnail data is no longer included in the response. "
@@ -56,28 +54,28 @@ class EventsSearchQueryParams(BaseModel):
),
deprecated=True,
)
limit: Optional[int] = 50
cameras: Optional[str] = "all"
labels: Optional[str] = "all"
sub_labels: Optional[str] = "all"
attributes: Optional[str] = "all"
zones: Optional[str] = "all"
after: Optional[float] = None
before: Optional[float] = None
time_range: Optional[str] = DEFAULT_TIME_RANGE
has_clip: Optional[bool] = None
has_snapshot: Optional[bool] = None
is_submitted: Optional[bool] = None
timezone: Optional[str] = "utc"
min_score: Optional[float] = None
max_score: Optional[float] = None
min_speed: Optional[float] = None
max_speed: Optional[float] = None
recognized_license_plate: Optional[str] = "all"
sort: Optional[str] = None
limit: int | None = 50
cameras: str | None = "all"
labels: str | None = "all"
sub_labels: str | None = "all"
attributes: str | None = "all"
zones: str | None = "all"
after: float | None = None
before: float | None = None
time_range: str | None = DEFAULT_TIME_RANGE
has_clip: bool | None = None
has_snapshot: bool | None = None
is_submitted: bool | None = None
timezone: str | None = "utc"
min_score: float | None = None
max_score: float | None = None
min_speed: float | None = None
max_speed: float | None = None
recognized_license_plate: str | None = "all"
sort: str | None = None
class EventsSummaryQueryParams(BaseModel):
timezone: Optional[str] = "utc"
has_clip: Optional[int] = None
has_snapshot: Optional[int] = None
timezone: str | None = "utc"
has_clip: int | None = None
has_snapshot: int | None = None
@@ -1,5 +1,4 @@
from enum import Enum
from typing import Optional
from pydantic import BaseModel
@@ -17,33 +16,33 @@ class Extension(str, Enum):
class MediaLatestFrameQueryParams(BaseModel):
bbox: Optional[int] = None
timestamp: Optional[int] = None
zones: Optional[int] = None
mask: Optional[int] = None
motion: Optional[int] = None
paths: Optional[int] = None
regions: Optional[int] = None
quality: Optional[int] = 70
height: Optional[int] = None
store: Optional[int] = None
bbox: int | None = None
timestamp: int | None = None
zones: int | None = None
mask: int | None = None
motion: int | None = None
paths: int | None = None
regions: int | None = None
quality: int | None = 70
height: int | None = None
store: int | None = None
class MediaEventsSnapshotQueryParams(BaseModel):
download: Optional[bool] = False
timestamp: Optional[int] = None
bbox: Optional[int] = None
crop: Optional[int] = None
height: Optional[int] = None
quality: Optional[int] = None
download: bool | None = False
timestamp: int | None = None
bbox: int | None = None
crop: int | None = None
height: int | None = None
quality: int | None = None
class MediaMjpegFeedQueryParams(BaseModel):
fps: int = 3
height: int = 360
bbox: Optional[int] = None
timestamp: Optional[int] = None
zones: Optional[int] = None
mask: Optional[int] = None
motion: Optional[int] = None
regions: Optional[int] = None
bbox: int | None = None
timestamp: int | None = None
zones: int | None = None
mask: int | None = None
motion: int | None = None
regions: int | None = None
@@ -1,21 +1,19 @@
from typing import Optional, Union
from pydantic import BaseModel
from pydantic.json_schema import SkipJsonSchema
class MediaRecordingsSummaryQueryParams(BaseModel):
timezone: str = "utc"
cameras: Optional[str] = "all"
cameras: str | None = "all"
class MediaRecordingsAvailabilityQueryParams(BaseModel):
cameras: str = "all"
before: Union[float, SkipJsonSchema[None]] = None
after: Union[float, SkipJsonSchema[None]] = None
before: float | SkipJsonSchema[None] = None
after: float | SkipJsonSchema[None] = None
scale: int = 30
class RecordingsDeleteQueryParams(BaseModel):
keep: Optional[str] = None
cameras: Optional[str] = "all"
keep: str | None = None
cameras: str | None = "all"
@@ -1,13 +1,11 @@
from typing import Optional
from pydantic import BaseModel, Field
from frigate.events.types import RegenerateDescriptionEnum
class RegenerateQueryParameters(BaseModel):
source: Optional[RegenerateDescriptionEnum] = RegenerateDescriptionEnum.thumbnails
force: Optional[bool] = Field(
source: RegenerateDescriptionEnum | None = RegenerateDescriptionEnum.thumbnails
force: bool | None = Field(
default=False,
description="Force (re)generating the description even if GenAI is disabled for this camera.",
)
@@ -1,5 +1,3 @@
from typing import Union
from pydantic import BaseModel
from pydantic.json_schema import SkipJsonSchema
@@ -10,11 +8,11 @@ class ReviewQueryParams(BaseModel):
cameras: str = "all"
labels: str = "all"
zones: str = "all"
reviewed: Union[int, SkipJsonSchema[None]] = None
limit: Union[int, SkipJsonSchema[None]] = None
severity: Union[SeverityEnum, SkipJsonSchema[None]] = None
before: Union[float, SkipJsonSchema[None]] = None
after: Union[float, SkipJsonSchema[None]] = None
reviewed: int | SkipJsonSchema[None] = None
limit: int | SkipJsonSchema[None] = None
severity: SeverityEnum | SkipJsonSchema[None] = None
before: float | SkipJsonSchema[None] = None
after: float | SkipJsonSchema[None] = None
class ReviewSummaryQueryParams(BaseModel):
@@ -26,6 +24,6 @@ class ReviewSummaryQueryParams(BaseModel):
class ReviewActivityMotionQueryParams(BaseModel):
cameras: str = "all"
before: Union[float, SkipJsonSchema[None]] = None
after: Union[float, SkipJsonSchema[None]] = None
before: float | SkipJsonSchema[None] = None
after: float | SkipJsonSchema[None] = None
scale: int = 30
+8 -8
View File
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional
from typing import Any
from pydantic import BaseModel, Field
@@ -8,26 +8,26 @@ from frigate.config import GenAIProviderEnum
class AppConfigSetBody(BaseModel):
requires_restart: int = 1
update_topic: str | None = None
config_data: Optional[Dict[str, Any]] = None
config_data: dict[str, Any] | None = None
skip_save: bool = False
class GenAIProbeBody(BaseModel):
provider: GenAIProviderEnum
api_key: Optional[str] = None
base_url: Optional[str] = None
provider_options: Dict[str, Any] = Field(default_factory=dict)
api_key: str | None = None
base_url: str | None = None
provider_options: dict[str, Any] = Field(default_factory=dict)
class AppPutPasswordBody(BaseModel):
password: str
old_password: Optional[str] = None
old_password: str | None = None
class AppPostUsersBody(BaseModel):
username: str
password: str
role: Optional[str] = "viewer"
role: str | None = "viewer"
class AppPostLoginBody(BaseModel):
@@ -47,7 +47,7 @@ class MediaSyncBody(BaseModel):
dry_run: bool = Field(
default=True, description="If True, only report orphans without deleting them"
)
media_types: List[str] = Field(
media_types: list[str] = Field(
default=["all"],
description="Types of media to sync: 'all', 'event_snapshots', 'event_thumbnails', 'review_thumbnails', 'previews', 'exports', 'recordings'",
)
@@ -1,5 +1,3 @@
from typing import List, Optional
from pydantic import BaseModel, Field, model_validator
MAX_BATCH_EXPORT_ITEMS = 50
@@ -9,18 +7,18 @@ class BatchExportItem(BaseModel):
camera: str = Field(title="Camera name")
start_time: float = Field(title="Start time")
end_time: float = Field(title="End time")
image_path: Optional[str] = Field(
image_path: str | None = Field(
default=None,
title="Existing thumbnail path",
description="Optional existing image to use as the export thumbnail",
)
friendly_name: Optional[str] = Field(
friendly_name: str | None = Field(
default=None,
title="Friendly name",
max_length=256,
description="Optional friendly name for this specific export item",
)
client_item_id: Optional[str] = Field(
client_item_id: str | None = Field(
default=None,
title="Client item ID",
max_length=128,
@@ -29,13 +27,13 @@ class BatchExportItem(BaseModel):
class BatchExportBody(BaseModel):
items: List[BatchExportItem] = Field(
items: list[BatchExportItem] = Field(
title="Items",
min_length=1,
max_length=MAX_BATCH_EXPORT_ITEMS,
description="List of export items. Each item has its own camera and time range.",
)
export_case_id: Optional[str] = Field(
export_case_id: str | None = Field(
default=None,
title="Export case ID",
max_length=30,
@@ -44,13 +42,13 @@ class BatchExportBody(BaseModel):
"existing case is temporarily admin-only until case-level ACLs exist."
),
)
new_case_name: Optional[str] = Field(
new_case_name: str | None = Field(
default=None,
title="New case name",
max_length=100,
description="Name of a new export case to create when export_case_id is omitted",
)
new_case_description: Optional[str] = Field(
new_case_description: str | None = Field(
default=None,
title="New case description",
description="Optional description for a newly created export case",
+6 -6
View File
@@ -1,6 +1,6 @@
"""Chat API request models."""
from typing import Any, Optional
from typing import Any
from pydantic import BaseModel, Field
@@ -11,7 +11,7 @@ class ChatMessage(BaseModel):
role: str = Field(
description="Message role: 'user', 'assistant', 'system', or 'tool'"
)
content: Optional[Any] = Field(
content: Any | None = Field(
default=None,
description=(
"Message content. Usually a string, but may be a multimodal content "
@@ -19,13 +19,13 @@ class ChatMessage(BaseModel):
"request tool calls."
),
)
tool_call_id: Optional[str] = Field(
tool_call_id: str | None = Field(
default=None, description="For tool messages, the ID of the tool call"
)
name: Optional[str] = Field(
name: str | None = Field(
default=None, description="For tool messages, the tool name"
)
tool_calls: Optional[list[dict[str, Any]]] = Field(
tool_calls: list[dict[str, Any]] | None = Field(
default=None,
description=(
"For assistant messages replayed from prior turns, the OpenAI-format "
@@ -52,7 +52,7 @@ class ChatCompletionRequest(BaseModel):
default=False,
description="If true, stream the final assistant response in the body as newline-delimited JSON.",
)
enable_thinking: Optional[bool] = Field(
enable_thinking: bool | None = Field(
default=None,
description=(
"Per-request thinking toggle. None means use the provider default. "
@@ -1,5 +1,3 @@
from typing import Dict, List, Tuple
from pydantic import BaseModel, Field
@@ -12,14 +10,14 @@ class AudioTranscriptionBody(BaseModel):
class DeleteFaceImagesBody(BaseModel):
ids: List[str] = Field(
ids: list[str] = Field(
description="List of image filenames to delete from the face folder"
)
class GenerateStateExamplesBody(BaseModel):
model_name: str = Field(description="Name of the classification model")
cameras: Dict[str, Tuple[float, float, float, float]] = Field(
cameras: dict[str, tuple[float, float, float, float]] = Field(
description="Dictionary mapping camera names to normalized crop coordinates in [x1, y1, x2, y2] format (values 0-1)"
)
+13 -17
View File
@@ -1,5 +1,3 @@
from typing import List, Optional, Union
from pydantic import BaseModel, Field
from frigate.config.classification import TriggerType
@@ -7,49 +5,47 @@ from frigate.config.classification import TriggerType
class EventsSubLabelBody(BaseModel):
subLabel: str = Field(title="Sub label", max_length=100)
subLabelScore: Optional[float] = Field(
subLabelScore: float | None = Field(
title="Score for sub label", default=None, gt=0.0, le=1.0
)
camera: Optional[str] = Field(
title="Camera this object is detected on.", default=None
)
camera: str | None = Field(title="Camera this object is detected on.", default=None)
class EventsLPRBody(BaseModel):
recognizedLicensePlate: str = Field(
title="Recognized License Plate", max_length=100
)
recognizedLicensePlateScore: Optional[float] = Field(
recognizedLicensePlateScore: float | None = Field(
title="Score for recognized license plate", default=None, gt=0.0, le=1.0
)
class EventsAttributesBody(BaseModel):
attributes: List[str] = Field(
attributes: list[str] = Field(
title="Selected classification attributes for the event",
default_factory=list,
)
class EventsDescriptionBody(BaseModel):
description: Union[str, None] = Field(title="The description of the event")
description: str | None = Field(title="The description of the event")
class EventsCreateBody(BaseModel):
sub_label: Optional[str] = None
score: Optional[float] = 0
duration: Optional[int] = 30
include_recording: Optional[bool] = True
draw: Optional[dict] = {}
pre_capture: Optional[int] = None
sub_label: str | None = None
score: float | None = 0
duration: int | None = 30
include_recording: bool | None = True
draw: dict | None = {}
pre_capture: int | None = None
class EventsEndBody(BaseModel):
end_time: Optional[float] = None
end_time: float | None = None
class EventsDeleteBody(BaseModel):
event_ids: List[str] = Field(title="The event IDs to delete")
event_ids: list[str] = Field(title="The event IDs to delete")
class SubmitPlusBody(BaseModel):
+1 -3
View File
@@ -1,7 +1,5 @@
"""Request bodies for bulk export operations."""
from typing import Optional
from pydantic import BaseModel, Field, conlist, constr
@@ -17,7 +15,7 @@ class ExportBulkReassignBody(BaseModel):
# List of export IDs with at least one element and each element with at least one char
ids: conlist(constr(min_length=1), min_length=1)
export_case_id: Optional[str] = Field(
export_case_id: str | None = Field(
default=None,
max_length=30,
description="Case ID to assign to, or null to unassign from current case",
+3 -5
View File
@@ -1,5 +1,3 @@
from typing import Optional
from pydantic import BaseModel, Field
@@ -7,7 +5,7 @@ class ExportCaseCreateBody(BaseModel):
"""Request body for creating a new export case."""
name: str = Field(max_length=100, description="Friendly name of the export case")
description: Optional[str] = Field(
description: str | None = Field(
default=None, description="Optional description of the export case"
)
@@ -15,11 +13,11 @@ class ExportCaseCreateBody(BaseModel):
class ExportCaseUpdateBody(BaseModel):
"""Request body for updating an existing export case."""
name: Optional[str] = Field(
name: str | None = Field(
default=None,
max_length=100,
description="Updated friendly name of the export case",
)
description: Optional[str] = Field(
description: str | None = Field(
default=None, description="Updated description of the export case"
)
@@ -1,23 +1,32 @@
from typing import Optional, Union
from pydantic import BaseModel, Field
from pydantic.json_schema import SkipJsonSchema
from frigate.record.export import PlaybackSourceEnum
from frigate.record.export import (
ChaptersEnum,
PlaybackSourceEnum,
)
class ExportRecordingsBody(BaseModel):
source: PlaybackSourceEnum = Field(
default=PlaybackSourceEnum.recordings, title="Playback source"
)
name: Optional[str] = Field(title="Friendly name", default=None, max_length=256)
image_path: Union[str, SkipJsonSchema[None]] = None
export_case_id: Optional[str] = Field(
name: str | None = Field(title="Friendly name", default=None, max_length=256)
image_path: str | SkipJsonSchema[None] = None
export_case_id: str | None = Field(
default=None,
title="Export case ID",
max_length=30,
description="ID of the export case to assign this export to",
)
chapters: ChaptersEnum | None = Field(
default=None,
title="Chapter mode",
description=(
"Optional chapter metadata to embed in the export. When omitted, "
"the camera's configured export chapter mode is used."
),
)
class ExportRecordingsCustomBody(BaseModel):
@@ -25,19 +34,19 @@ class ExportRecordingsCustomBody(BaseModel):
default=PlaybackSourceEnum.recordings, title="Playback source"
)
name: str = Field(title="Friendly name", default=None, max_length=256)
image_path: Union[str, SkipJsonSchema[None]] = None
export_case_id: Optional[str] = Field(
image_path: str | SkipJsonSchema[None] = None
export_case_id: str | None = Field(
default=None,
title="Export case ID",
max_length=30,
description="ID of the export case to assign this export to",
)
ffmpeg_input_args: Optional[str] = Field(
ffmpeg_input_args: str | None = Field(
default=None,
title="FFmpeg input arguments",
description="Custom FFmpeg input arguments. If not provided, defaults to timelapse input args.",
)
ffmpeg_output_args: Optional[str] = Field(
ffmpeg_output_args: str | None = Field(
default=None,
title="FFmpeg output arguments",
description="Custom FFmpeg output arguments. If not provided, defaults to timelapse output args.",
+4 -4
View File
@@ -1,6 +1,6 @@
"""Chat API response models."""
from typing import Any, Optional
from typing import Any
from pydantic import BaseModel, Field
@@ -17,14 +17,14 @@ class ChatMessageResponse(BaseModel):
"""A message in the chat response."""
role: str = Field(description="Message role")
content: Optional[str] = Field(
content: str | None = Field(
default=None, description="Message content (None if tool calls present)"
)
reasoning: Optional[str] = Field(
reasoning: str | None = Field(
default=None,
description="Separated reasoning/thinking trace if the model emitted one",
)
tool_calls: Optional[list[ToolCallInvocation]] = Field(
tool_calls: list[ToolCallInvocation] | None = Field(
default=None, description="Tool calls if LLM wants to call tools"
)
@@ -1,9 +1,7 @@
from typing import Dict, List, Optional
from pydantic import BaseModel, Field, RootModel
class FacesResponse(RootModel[Dict[str, List[str]]]):
class FacesResponse(RootModel[dict[str, list[str]]]):
"""Response model for the get_faces endpoint.
Returns a mapping of face names to lists of image filenames.
@@ -17,7 +15,7 @@ class FacesResponse(RootModel[Dict[str, List[str]]]):
}
"""
root: Dict[str, List[str]] = Field(
root: dict[str, list[str]] = Field(
default_factory=dict,
description="Dictionary mapping face names to lists of image filenames",
)
@@ -30,9 +28,9 @@ class FaceRecognitionResponse(BaseModel):
"""
success: bool = Field(description="Whether the face recognition was successful")
score: Optional[float] = Field(
score: float | None = Field(
default=None, description="Confidence score of the recognition (0-1)"
)
face_name: Optional[str] = Field(
face_name: str | None = Field(
default=None, description="The recognized face name if successful"
)
+9 -9
View File
@@ -1,4 +1,4 @@
from typing import Any, Optional
from typing import Any
from pydantic import BaseModel, ConfigDict
@@ -6,20 +6,20 @@ from pydantic import BaseModel, ConfigDict
class EventResponse(BaseModel):
id: str
label: str
sub_label: Optional[str]
sub_label: str | None
camera: str
start_time: float
end_time: Optional[float]
false_positive: Optional[bool]
end_time: float | None
false_positive: bool | None
zones: list[str]
thumbnail: Optional[str]
thumbnail: str | None
has_clip: bool
has_snapshot: bool
retain_indefinitely: bool
plus_id: Optional[str]
model_hash: Optional[str]
detector_type: Optional[str]
model_type: Optional[str]
plus_id: str | None
model_hash: str | None
detector_type: str | None
model_type: str | None
data: dict[str, Any]
model_config = ConfigDict(protected_namespaces=())
@@ -1,5 +1,3 @@
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -8,7 +6,7 @@ class ExportCaseModel(BaseModel):
id: str = Field(description="Unique identifier for the export case")
name: str = Field(description="Friendly name of the export case")
description: Optional[str] = Field(
description: str | None = Field(
default=None, description="Optional description of the export case"
)
created_at: float = Field(
@@ -19,4 +17,4 @@ class ExportCaseModel(BaseModel):
)
ExportCasesResponse = List[ExportCaseModel]
ExportCasesResponse = list[ExportCaseModel]
+20 -20
View File
@@ -1,4 +1,4 @@
from typing import Any, List, Optional
from typing import Any
from pydantic import BaseModel, Field
@@ -15,7 +15,7 @@ class ExportModel(BaseModel):
in_progress: bool = Field(
description="Whether the export is currently being processed"
)
export_case_id: Optional[str] = Field(
export_case_id: str | None = Field(
default=None, description="ID of the export case this export belongs to"
)
@@ -25,10 +25,10 @@ class StartExportResponse(BaseModel):
success: bool = Field(description="Whether the export was started successfully")
message: str = Field(description="Status or error message")
export_id: Optional[str] = Field(
export_id: str | None = Field(
default=None, description="The export ID if successfully started"
)
status: Optional[str] = Field(
status: str | None = Field(
default=None,
description="Queue status for the export job",
)
@@ -38,24 +38,24 @@ class BatchExportResultModel(BaseModel):
"""Per-item result for a batch export request."""
camera: str = Field(description="Camera name for this export attempt")
export_id: Optional[str] = Field(
export_id: str | None = Field(
default=None,
description="The export ID when the export was successfully queued",
)
success: bool = Field(description="Whether the export was successfully queued")
status: Optional[str] = Field(
status: str | None = Field(
default=None,
description="Queue status for this camera export",
)
error: Optional[str] = Field(
error: str | None = Field(
default=None,
description="Validation or queueing error for this item, if any",
)
item_index: Optional[int] = Field(
item_index: int | None = Field(
default=None,
description="Zero-based index of this result within the request items list",
)
client_item_id: Optional[str] = Field(
client_item_id: str | None = Field(
default=None,
description="Opaque client-supplied item identifier echoed from the request",
)
@@ -64,12 +64,12 @@ class BatchExportResultModel(BaseModel):
class BatchExportResponse(BaseModel):
"""Response model for starting an export batch."""
export_case_id: Optional[str] = Field(
export_case_id: str | None = Field(
default=None,
description="Export case ID associated with the batch",
)
export_ids: List[str] = Field(description="Export IDs successfully queued")
results: List[BatchExportResultModel] = Field(
export_ids: list[str] = Field(description="Export IDs successfully queued")
results: list[BatchExportResultModel] = Field(
description="Per-item batch export results"
)
@@ -81,29 +81,29 @@ class ExportJobModel(BaseModel):
job_type: str = Field(description="Job type")
status: str = Field(description="Current job status")
camera: str = Field(description="Camera associated with this export job")
name: Optional[str] = Field(
name: str | None = Field(
default=None,
description="Friendly name for the export",
)
export_case_id: Optional[str] = Field(
export_case_id: str | None = Field(
default=None,
description="ID of the export case this export belongs to",
)
request_start_time: float = Field(description="Requested export start time")
request_end_time: float = Field(description="Requested export end time")
start_time: Optional[float] = Field(
start_time: float | None = Field(
default=None,
description="Unix timestamp when execution started",
)
end_time: Optional[float] = Field(
end_time: float | None = Field(
default=None,
description="Unix timestamp when execution completed",
)
error_message: Optional[str] = Field(
error_message: str | None = Field(
default=None,
description="Error message for failed jobs",
)
results: Optional[dict[str, Any]] = Field(
results: dict[str, Any] | None = Field(
default=None,
description="Result metadata for completed jobs",
)
@@ -117,7 +117,7 @@ class ExportJobModel(BaseModel):
)
ExportJobsResponse = List[ExportJobModel]
ExportJobsResponse = list[ExportJobModel]
ExportsResponse = List[ExportModel]
ExportsResponse = list[ExportModel]
@@ -1,5 +1,3 @@
from typing import List
from pydantic import BaseModel, Field
@@ -13,5 +11,5 @@ class PreviewModel(BaseModel):
end: float = Field(description="Unix timestamp when the preview ends")
PreviewsResponse = List[PreviewModel]
PreviewFramesResponse = List[str]
PreviewsResponse = list[PreviewModel]
PreviewFramesResponse = list[str]
+1 -2
View File
@@ -1,5 +1,4 @@
from datetime import datetime
from typing import Dict
from pydantic import BaseModel, Json
@@ -34,7 +33,7 @@ class DayReview(BaseModel):
class ReviewSummaryResponse(BaseModel):
last24Hours: Last24HoursReview
root: Dict[str, DayReview]
root: dict[str, DayReview]
class ReviewActivityMotionResponse(BaseModel):
+68 -73
View File
@@ -10,7 +10,6 @@ import random
import string
from functools import reduce
from pathlib import Path
from typing import List
from urllib.parse import unquote
import numpy as np
@@ -97,7 +96,7 @@ def _build_attribute_filter_clause(attributes: str):
)
def events(
params: EventsQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
camera = params.camera
cameras = params.cameras
@@ -171,7 +170,7 @@ def events(
]
if camera != "all":
clauses.append((Event.camera == camera))
clauses.append(Event.camera == camera)
if cameras != "all":
requested = set(cameras.split(","))
@@ -181,11 +180,11 @@ def events(
camera_list = list(filtered)
else:
camera_list = allowed_cameras
clauses.append((Event.camera << camera_list))
clauses.append(Event.camera << camera_list)
if labels != "all":
label_list = labels.split(",")
clauses.append((Event.label << label_list))
clauses.append(Event.label << label_list)
if sub_labels != "all":
# use matching so joined sub labels are included
@@ -196,24 +195,24 @@ def events(
if "None" in filtered_sub_labels:
filtered_sub_labels.remove("None")
sub_label_clauses.append((Event.sub_label.is_null()))
sub_label_clauses.append(Event.sub_label.is_null())
for label in filtered_sub_labels:
lowered = label.lower()
sub_label_clauses.append(
(fn.LOWER(Event.sub_label.cast("text")) == lowered)
fn.LOWER(Event.sub_label.cast("text")) == lowered
) # include exact matches (case-insensitive)
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
sub_label_clauses.append(
(fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*")
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
)
sub_label_clauses.append(
(fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*")
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
)
sub_label_clause = reduce(operator.or_, sub_label_clauses)
clauses.append((sub_label_clause))
clauses.append(sub_label_clause)
if attributes != "all":
# Custom classification results are stored as data[model_name] = result_value
@@ -257,19 +256,19 @@ def events(
if "None" in filtered_zones:
filtered_zones.remove("None")
zone_clauses.append((Event.zones.length() == 0))
zone_clauses.append(Event.zones.length() == 0)
for zone in filtered_zones:
zone_clauses.append((Event.zones.cast("text") % f'*"{zone}"*'))
zone_clauses.append(Event.zones.cast("text") % f'*"{zone}"*')
zone_clause = reduce(operator.or_, zone_clauses)
clauses.append((zone_clause))
clauses.append(zone_clause)
if after:
clauses.append((Event.start_time > after))
clauses.append(Event.start_time > after)
if before:
clauses.append((Event.start_time < before))
clauses.append(Event.start_time < before)
if time_range != DEFAULT_TIME_RANGE:
# get timezone arg to ensure browser times are used
@@ -289,62 +288,60 @@ def events(
# should use or operator
if time_after > time_before:
clauses.append(
(
reduce(
operator.or_,
[(start_hour_fun > time_after), (start_hour_fun < time_before)],
)
reduce(
operator.or_,
[(start_hour_fun > time_after), (start_hour_fun < time_before)],
)
)
# all other cases should be and operator
else:
clauses.append((start_hour_fun > time_after))
clauses.append((start_hour_fun < time_before))
clauses.append(start_hour_fun > time_after)
clauses.append(start_hour_fun < time_before)
if has_clip is not None:
clauses.append((Event.has_clip == has_clip))
clauses.append(Event.has_clip == has_clip)
if has_snapshot is not None:
clauses.append((Event.has_snapshot == has_snapshot))
clauses.append(Event.has_snapshot == has_snapshot)
if in_progress is not None:
clauses.append((Event.end_time.is_null(in_progress)))
clauses.append(Event.end_time.is_null(in_progress))
if include_thumbnails:
selected_columns.append(Event.thumbnail)
if favorites:
clauses.append((Event.retain_indefinitely == favorites))
clauses.append(Event.retain_indefinitely == favorites)
if max_score is not None:
clauses.append((Event.data["score"] <= max_score))
clauses.append(Event.data["score"] <= max_score)
if min_score is not None:
clauses.append((Event.data["score"] >= min_score))
clauses.append(Event.data["score"] >= min_score)
if max_speed is not None:
clauses.append((Event.data["average_estimated_speed"] <= max_speed))
clauses.append(Event.data["average_estimated_speed"] <= max_speed)
if min_speed is not None:
clauses.append((Event.data["average_estimated_speed"] >= min_speed))
clauses.append(Event.data["average_estimated_speed"] >= min_speed)
if min_length is not None:
clauses.append(((Event.end_time - Event.start_time) >= min_length))
clauses.append((Event.end_time - Event.start_time) >= min_length)
if max_length is not None:
clauses.append(((Event.end_time - Event.start_time) <= max_length))
clauses.append((Event.end_time - Event.start_time) <= max_length)
if is_submitted is not None:
if is_submitted == 0:
clauses.append((Event.plus_id.is_null()))
clauses.append(Event.plus_id.is_null())
elif is_submitted > 0:
clauses.append((Event.plus_id != ""))
clauses.append(Event.plus_id != "")
if event_id is not None:
clauses.append((Event.id == event_id))
clauses.append(Event.id == event_id)
if len(clauses) == 0:
clauses.append((True))
clauses.append(True)
if sort:
if sort == "score_asc":
@@ -387,7 +384,7 @@ def events(
)
def events_explore(
limit: int = 10,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
# get distinct labels for all events
distinct_labels = (
@@ -515,7 +512,7 @@ async def event_ids(ids: str, request: Request):
def events_search(
request: Request,
params: EventsSearchQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
query = params.query
search_type = params.search_type
@@ -595,12 +592,12 @@ def events_search(
filtered = requested.intersection(allowed_cameras)
if not filtered:
return JSONResponse(content=[])
event_filters.append((Event.camera << list(filtered)))
event_filters.append(Event.camera << list(filtered))
else:
event_filters.append((Event.camera << allowed_cameras))
event_filters.append(Event.camera << allowed_cameras)
if labels != "all":
event_filters.append((Event.label << labels.split(",")))
event_filters.append(Event.label << labels.split(","))
if sub_labels != "all":
# use matching so joined sub labels are included
@@ -611,23 +608,23 @@ def events_search(
if "None" in filtered_sub_labels:
filtered_sub_labels.remove("None")
sub_label_clauses.append((Event.sub_label.is_null()))
sub_label_clauses.append(Event.sub_label.is_null())
for label in filtered_sub_labels:
lowered = label.lower()
sub_label_clauses.append(
(fn.LOWER(Event.sub_label.cast("text")) == lowered)
fn.LOWER(Event.sub_label.cast("text")) == lowered
) # include exact matches (case-insensitive)
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
sub_label_clauses.append(
(fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*")
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
)
sub_label_clauses.append(
(fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*")
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
)
event_filters.append((reduce(operator.or_, sub_label_clauses)))
event_filters.append(reduce(operator.or_, sub_label_clauses))
if attributes != "all":
# Custom classification results are stored as data[model_name] = result_value
@@ -641,12 +638,12 @@ def events_search(
if "None" in filtered_zones:
filtered_zones.remove("None")
zone_clauses.append((Event.zones.length() == 0))
zone_clauses.append(Event.zones.length() == 0)
for zone in filtered_zones:
zone_clauses.append((Event.zones.cast("text") % f'*"{zone}"*'))
zone_clauses.append(Event.zones.cast("text") % f'*"{zone}"*')
event_filters.append((reduce(operator.or_, zone_clauses)))
event_filters.append(reduce(operator.or_, zone_clauses))
if recognized_license_plate != "all":
filtered_recognized_license_plates = recognized_license_plate.split(",")
@@ -674,43 +671,43 @@ def events_search(
)
recognized_license_plate_clause = reduce(operator.or_, clauses_for_plates)
event_filters.append((recognized_license_plate_clause))
event_filters.append(recognized_license_plate_clause)
if after:
event_filters.append((Event.start_time > after))
event_filters.append(Event.start_time > after)
if before:
event_filters.append((Event.start_time < before))
event_filters.append(Event.start_time < before)
if has_clip is not None:
event_filters.append((Event.has_clip == has_clip))
event_filters.append(Event.has_clip == has_clip)
if has_snapshot is not None:
event_filters.append((Event.has_snapshot == has_snapshot))
event_filters.append(Event.has_snapshot == has_snapshot)
if is_submitted is not None:
if is_submitted == 0:
event_filters.append((Event.plus_id.is_null()))
event_filters.append(Event.plus_id.is_null())
elif is_submitted > 0:
event_filters.append((Event.plus_id != ""))
event_filters.append(Event.plus_id != "")
if min_score is not None and max_score is not None:
event_filters.append((Event.data["score"].between(min_score, max_score)))
event_filters.append(Event.data["score"].between(min_score, max_score))
else:
if min_score is not None:
event_filters.append((Event.data["score"] >= min_score))
event_filters.append(Event.data["score"] >= min_score)
if max_score is not None:
event_filters.append((Event.data["score"] <= max_score))
event_filters.append(Event.data["score"] <= max_score)
if min_speed is not None and max_speed is not None:
event_filters.append(
(Event.data["average_estimated_speed"].between(min_speed, max_speed))
Event.data["average_estimated_speed"].between(min_speed, max_speed)
)
else:
if min_speed is not None:
event_filters.append((Event.data["average_estimated_speed"] >= min_speed))
event_filters.append(Event.data["average_estimated_speed"] >= min_speed)
if max_speed is not None:
event_filters.append((Event.data["average_estimated_speed"] <= max_speed))
event_filters.append(Event.data["average_estimated_speed"] <= max_speed)
if time_range != DEFAULT_TIME_RANGE:
tz_name = params.timezone
@@ -728,17 +725,15 @@ def events_search(
# should use or operator
if time_after > time_before:
event_filters.append(
(
reduce(
operator.or_,
[(start_hour_fun > time_after), (start_hour_fun < time_before)],
)
reduce(
operator.or_,
[(start_hour_fun > time_after), (start_hour_fun < time_before)],
)
)
# all other cases should be and operator
else:
event_filters.append((start_hour_fun > time_after))
event_filters.append((start_hour_fun < time_before))
event_filters.append(start_hour_fun > time_after)
event_filters.append(start_hour_fun < time_before)
# Perform semantic search
search_results = {}
@@ -894,7 +889,7 @@ def events_search(
@router.get("/events/summary", dependencies=[Depends(allow_any_authenticated())])
def events_summary(
params: EventsSummaryQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
tz_name = params.timezone
has_clip = params.has_clip
@@ -903,13 +898,13 @@ def events_summary(
clauses = []
if has_clip is not None:
clauses.append((Event.has_clip == has_clip))
clauses.append(Event.has_clip == has_clip)
if has_snapshot is not None:
clauses.append((Event.has_snapshot == has_snapshot))
clauses.append(Event.has_snapshot == has_snapshot)
if len(clauses) == 0:
clauses.append((True))
clauses.append(True)
time_range_query = (
Event.select(
+47 -23
View File
@@ -7,8 +7,8 @@ import string
import time
import zipfile
from collections import deque
from collections.abc import Iterator
from pathlib import Path
from typing import Iterator, List, Optional
import psutil
from fastapi import APIRouter, Depends, Query, Request
@@ -68,6 +68,7 @@ from frigate.jobs.export import (
from frigate.models import Export, ExportCase, Previews, Recordings
from frigate.record.export import (
DEFAULT_TIME_LAPSE_FFMPEG_ARGS,
ChaptersEnum,
PlaybackSourceEnum,
validate_ffmpeg_args,
)
@@ -88,7 +89,7 @@ def _generate_export_id(camera_name: str) -> str:
def _create_export_case_record(
name: str,
description: Optional[str],
description: str | None,
) -> ExportCase:
now = datetime.datetime.fromtimestamp(time.time())
return ExportCase.create(
@@ -100,7 +101,7 @@ def _create_export_case_record(
)
def _validate_camera_name(request: Request, camera_name: str) -> Optional[JSONResponse]:
def _validate_camera_name(request: Request, camera_name: str) -> JSONResponse | None:
if camera_name and request.app.frigate_config.cameras.get(camera_name):
return None
@@ -110,7 +111,7 @@ def _validate_camera_name(request: Request, camera_name: str) -> Optional[JSONRe
)
def _validate_export_case(export_case_id: Optional[str]) -> Optional[JSONResponse]:
def _validate_export_case(export_case_id: str | None) -> JSONResponse | None:
if export_case_id is None:
return None
@@ -126,8 +127,17 @@ def _validate_export_case(export_case_id: Optional[str]) -> Optional[JSONRespons
def _sanitize_existing_image(
image_path: Optional[str],
) -> tuple[Optional[str], Optional[JSONResponse]]:
image_path: str | None,
) -> tuple[str | None, JSONResponse | None]:
# sanitize_filepath normalizes "\" to "/" but leaves ".." intact, so a path
# like "clips\..\..\etc/passwd" passes the CLIPS_DIR prefix check yet still
# escapes the directory once resolved. A valid snapshot path never uses "..".
if image_path and ".." in image_path:
return None, JSONResponse(
content={"success": False, "message": "Invalid image path"},
status_code=400,
)
existing_image = sanitize_filepath(image_path) if image_path else None
if existing_image and not existing_image.startswith(CLIPS_DIR):
@@ -144,7 +154,7 @@ def _validate_export_source(
start_time: float,
end_time: float,
playback_source: PlaybackSourceEnum,
) -> Optional[str]:
) -> str | None:
if playback_source == PlaybackSourceEnum.recordings:
recordings_count = (
Recordings.select()
@@ -247,13 +257,14 @@ def _build_export_job(
camera_name: str,
start_time: float,
end_time: float,
friendly_name: Optional[str],
existing_image: Optional[str],
friendly_name: str | None,
existing_image: str | None,
playback_source: PlaybackSourceEnum,
export_case_id: Optional[str],
ffmpeg_input_args: Optional[str] = None,
ffmpeg_output_args: Optional[str] = None,
export_case_id: str | None,
ffmpeg_input_args: str | None = None,
ffmpeg_output_args: str | None = None,
cpu_fallback: bool = False,
chapters: ChaptersEnum | None = None,
) -> ExportJob:
return ExportJob(
id=_generate_export_id(camera_name),
@@ -267,6 +278,7 @@ def _build_export_job(
ffmpeg_input_args=ffmpeg_input_args,
ffmpeg_output_args=ffmpeg_output_args,
cpu_fallback=cpu_fallback,
chapters=chapters,
)
@@ -290,11 +302,11 @@ def _export_case_to_dict(case: ExportCase) -> dict[str, object]:
Returns a list of exports ordered by date (most recent first).""",
)
def get_exports(
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
export_case_id: Optional[str] = None,
cameras: Optional[str] = Query(default="all"),
start_date: Optional[float] = None,
end_date: Optional[float] = None,
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
export_case_id: str | None = None,
cameras: str | None = Query(default="all"),
start_date: float | None = None,
end_date: float | None = None,
):
query = Export.select().where(Export.camera << allowed_cameras)
@@ -410,7 +422,7 @@ def _unique_archive_name(export: Export, used: set[str]) -> str:
return candidate
def _stream_case_archive(exports: List[Export]) -> Iterator[bytes]:
def _stream_case_archive(exports: list[Export]) -> Iterator[bytes]:
"""Yield bytes of a zip archive built from the given exports' mp4 files."""
buffer = _StreamingZipBuffer()
used_names: set[str] = set()
@@ -454,7 +466,7 @@ def _stream_case_archive(exports: List[Export]) -> Iterator[bytes]:
)
def download_export_case(
case_id: str,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
try:
case = ExportCase.get(ExportCase.id == case_id)
@@ -568,7 +580,7 @@ def delete_export_case(case_id: str, request: Request, delete_exports: bool = Fa
)
def get_active_export_jobs(
request: Request,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
jobs = list_active_export_jobs(request.app.frigate_config)
return JSONResponse(
@@ -610,7 +622,7 @@ async def get_export_job_status(export_id: str, request: Request):
def export_recordings_batch(
request: Request,
body: BatchExportBody,
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
current_user: dict = Depends(get_current_user),
):
if isinstance(current_user, JSONResponse):
@@ -650,7 +662,7 @@ def export_recordings_batch(
# Sanitize each item's image_path up front. A bad path in any item
# kills the whole request, consistent with single-export behavior.
sanitized_images: list[Optional[str]] = []
sanitized_images: list[str | None] = []
for item in body.items:
existing_image, image_validation_error = _sanitize_existing_image(
item.image_path
@@ -701,7 +713,7 @@ def export_recordings_batch(
export_case_id = export_case.id
export_ids: list[str] = []
results: list[dict[str, Optional[str] | bool | int]] = []
results: list[dict[str, str | None | bool | int]] = []
for index, item in enumerate(body.items):
if index in item_errors:
results.append(
@@ -725,6 +737,9 @@ def export_recordings_batch(
sanitized_images[index],
PlaybackSourceEnum.recordings,
export_case_id,
chapters=request.app.frigate_config.cameras[
item.camera
].record.export.chapters,
)
try:
start_export_job(request.app.frigate_config, export_job)
@@ -803,6 +818,14 @@ def export_recording(
export_case_id = body.export_case_id
# a chapters value in the request body overrides the camera's export config
camera_config = request.app.frigate_config.cameras[camera_name]
chapters = (
body.chapters
if body.chapters is not None
else camera_config.record.export.chapters
)
# Attaching to an existing case requires admin. Single-export for
# cameras the user can access is otherwise non-admin; we only gate
# the case-attachment side effect.
@@ -839,6 +862,7 @@ def export_recording(
existing_image,
playback_source,
export_case_id,
chapters=chapters,
)
try:
start_export_job(request.app.frigate_config, export_job)
+3 -4
View File
@@ -1,7 +1,6 @@
import asyncio
import logging
import re
from typing import Optional
from fastapi import Depends, FastAPI, Request
from fastapi.responses import JSONResponse
@@ -64,7 +63,7 @@ class RemoteUserPlugin(Plugin):
def create_fastapi_app(
frigate_config: FrigateConfig,
database: SqliteQueueDatabase,
embeddings: Optional[EmbeddingsContext],
embeddings: EmbeddingsContext | None,
detected_frames_processor,
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
@@ -72,8 +71,8 @@ def create_fastapi_app(
event_metadata_updater: EventMetadataPublisher,
config_publisher: CameraConfigUpdatePublisher,
replay_manager: DebugReplayManager,
dispatcher: Optional[Dispatcher] = None,
profile_manager: Optional[ProfileManager] = None,
dispatcher: Dispatcher | None = None,
profile_manager: ProfileManager | None = None,
enforce_default_admin: bool = True,
):
logger.info("Starting FastAPI app")
+39 -27
View File
@@ -7,7 +7,7 @@ import math
import os
import subprocess as sp
import time
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from pathlib import Path as FilePath
from typing import Any
from urllib.parse import unquote
@@ -60,6 +60,19 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=[Tags.media])
def _resolve_cache_age(max_cache_age: int) -> int:
"""Return max_cache_age as an int.
When a media handler is invoked directly by another handler instead of
through its route, FastAPI doesn't resolve the Query() default and
max_cache_age arrives as the Query object; fall back to its int default.
"""
if isinstance(max_cache_age, int):
return max_cache_age
return max_cache_age.default
@router.get("/{camera_name}", dependencies=[Depends(require_camera_access)])
async def mjpeg_feed(
request: Request,
@@ -301,10 +314,8 @@ async def get_snapshot_from_recording(
Recordings.start_time,
)
.where(
(
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
.where(Recordings.camera == camera_name)
.order_by(Recordings.start_time.desc())
@@ -322,10 +333,8 @@ async def get_snapshot_from_recording(
Recordings.start_time,
)
.where(
(
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
.where(Recordings.camera == camera_name)
.order_by(Recordings.start_time.desc())
@@ -385,10 +394,7 @@ async def submit_recording_snapshot_to_plus(
Recordings.start_time,
)
.where(
(
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
(frame_time >= Recordings.start_time) & (frame_time <= Recordings.end_time)
)
.where(Recordings.camera == camera_name)
.order_by(Recordings.start_time.desc())
@@ -413,7 +419,9 @@ async def submit_recording_snapshot_to_plus(
)
nd = cv2.imdecode(np.frombuffer(image_data, dtype=np.int8), cv2.IMREAD_COLOR)
request.app.frigate_config.plus_api.upload_image(nd, camera_name)
await asyncio.to_thread(
request.app.frigate_config.plus_api.upload_image, nd, camera_name
)
return JSONResponse(
content={
@@ -704,7 +712,7 @@ async def vod_hour(
):
parts = year_month.split("-")
start_date = (
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=timezone.utc)
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC)
- datetime.now(pytz.timezone(tz_name.replace(",", "/"))).utcoffset()
)
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
@@ -936,7 +944,7 @@ async def event_thumbnail(
thumbnail_bytes,
media_type=extension.get_mime_type(),
headers={
"Cache-Control": f"private, max-age={max_cache_age}"
"Cache-Control": f"private, max-age={_resolve_cache_age(max_cache_age)}"
if event_complete
else "no-store",
},
@@ -1270,14 +1278,14 @@ async def event_preview(request: Request, event_id: str):
end_ts = start_ts + (
min(event.end_time - event.start_time, 20) if event.end_time else 20
)
return preview_gif(request, event.camera, start_ts, end_ts)
return await preview_gif(request, event.camera, start_ts, end_ts)
@router.get(
"/{camera_name}/start/{start_ts}/end/{end_ts}/preview.gif",
dependencies=[Depends(require_camera_access)],
)
def preview_gif(
async def preview_gif(
request: Request,
camera_name: str,
start_ts: float,
@@ -1340,7 +1348,8 @@ def preview_gif(
"-",
]
process = sp.run(
process = await asyncio.to_thread(
sp.run,
ffmpeg_cmd,
capture_output=True,
)
@@ -1419,7 +1428,8 @@ def preview_gif(
"-",
]
process = sp.run(
process = await asyncio.to_thread(
sp.run,
ffmpeg_cmd,
input=str.encode("\n".join(selected_previews)),
capture_output=True,
@@ -1438,7 +1448,7 @@ def preview_gif(
gif_bytes,
media_type="image/gif",
headers={
"Cache-Control": f"private, max-age={max_cache_age}",
"Cache-Control": f"private, max-age={_resolve_cache_age(max_cache_age)}",
"Content-Type": "image/gif",
},
)
@@ -1448,7 +1458,7 @@ def preview_gif(
"/{camera_name}/start/{start_ts}/end/{end_ts}/preview.mp4",
dependencies=[Depends(require_camera_access)],
)
def preview_mp4(
async def preview_mp4(
request: Request,
camera_name: str,
start_ts: float,
@@ -1528,7 +1538,8 @@ def preview_mp4(
path,
]
process = sp.run(
process = await asyncio.to_thread(
sp.run,
ffmpeg_cmd,
capture_output=True,
)
@@ -1604,7 +1615,8 @@ def preview_mp4(
path,
]
process = sp.run(
process = await asyncio.to_thread(
sp.run,
ffmpeg_cmd,
input=str.encode("\n".join(selected_previews)),
capture_output=True,
@@ -1619,7 +1631,7 @@ def preview_mp4(
headers = {
"Content-Description": "File Transfer",
"Cache-Control": f"private, max-age={max_cache_age}",
"Cache-Control": f"private, max-age={_resolve_cache_age(max_cache_age)}",
"Content-Type": "video/mp4",
"Content-Length": str(os.path.getsize(path)),
# nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
@@ -1657,9 +1669,9 @@ async def review_preview(
)
if format == "gif":
return preview_gif(request, review.camera, start_ts, end_ts)
return await preview_gif(request, review.camera, start_ts, end_ts)
else:
return preview_mp4(request, review.camera, start_ts, end_ts)
return await preview_mp4(request, review.camera, start_ts, end_ts)
@router.get(
+15 -16
View File
@@ -14,7 +14,6 @@ from __future__ import annotations
import logging
import os
from enum import Enum
from typing import Optional
from urllib.parse import unquote, urlparse
from peewee import DoesNotExist
@@ -42,7 +41,7 @@ class MediaAuthResolution(str, Enum):
UNKNOWN = "unknown"
def extract_path(original_url: Optional[str]) -> Optional[str]:
def extract_path(original_url: str | None) -> str | None:
"""Return the decoded path component of nginx's `X-Original-URL` header.
nginx forwards the *raw* request URI (with `..` segments intact) via
@@ -72,8 +71,8 @@ def extract_path(original_url: Optional[str]) -> Optional[str]:
def resolve_media_uri(
uri: str, frigate_config: Optional[FrigateConfig] = None
) -> tuple[MediaAuthResolution, Optional[str]]:
uri: str, frigate_config: FrigateConfig | None = None
) -> tuple[MediaAuthResolution, str | None]:
"""Classify a URI and return the owning camera if applicable.
`frigate_config` is used to disambiguate clip/review filenames whose
@@ -100,7 +99,7 @@ def resolve_media_uri(
def _resolve_recording(
parts: list[str],
) -> tuple[MediaAuthResolution, Optional[str]]:
) -> tuple[MediaAuthResolution, str | None]:
# /recordings → neutral
# /recordings/{date} → neutral
# /recordings/{date}/{hour} → multi-camera listing
@@ -113,8 +112,8 @@ def _resolve_recording(
def _resolve_clip(
parts: list[str], frigate_config: Optional[FrigateConfig]
) -> tuple[MediaAuthResolution, Optional[str]]:
parts: list[str], frigate_config: FrigateConfig | None
) -> tuple[MediaAuthResolution, str | None]:
# /clips → multi-camera listing
# /clips/thumbs/{cam}/... → camera
# /clips/previews/{cam}/... → camera
@@ -159,8 +158,8 @@ def _resolve_clip(
def _longest_prefix_camera(
stem: str, frigate_config: Optional[FrigateConfig]
) -> Optional[str]:
stem: str, frigate_config: FrigateConfig | None
) -> str | None:
if frigate_config is None:
return None
for cam in sorted(frigate_config.cameras.keys(), key=len, reverse=True):
@@ -170,8 +169,8 @@ def _longest_prefix_camera(
def _camera_from_clip_filename(
filename: str, frigate_config: Optional[FrigateConfig]
) -> Optional[str]:
filename: str, frigate_config: FrigateConfig | None
) -> str | None:
"""Match a flat clip filename `{camera}-{event_id}[-clean].{ext}` against
configured camera names. Longest-prefix wins so camera names containing
hyphens (e.g. `front-door`) resolve correctly.
@@ -182,8 +181,8 @@ def _camera_from_clip_filename(
def _camera_from_thumb_filename(
filename: str, frigate_config: Optional[FrigateConfig]
) -> Optional[str]:
filename: str, frigate_config: FrigateConfig | None
) -> str | None:
"""Match a review thumbnail filename `thumb-{camera}-{review_id}.webp`."""
if not filename.startswith("thumb-"):
return None
@@ -194,7 +193,7 @@ def _camera_from_thumb_filename(
def _resolve_export(
parts: list[str],
) -> tuple[MediaAuthResolution, Optional[str]]:
) -> tuple[MediaAuthResolution, str | None]:
# /exports → multi-camera listing
# /exports/{filename}.mp4 → camera (DB lookup by exact path)
if len(parts) == 1:
@@ -240,8 +239,8 @@ def is_role_restricted(role: str, frigate_config: FrigateConfig) -> bool:
def deny_response_for_media_uri(
original_url: Optional[str], role: Optional[str], frigate_config: FrigateConfig
) -> Optional[int]:
original_url: str | None, role: str | None, frigate_config: FrigateConfig
) -> int | None:
"""Decide whether the current role should be blocked from `original_url`.
Returns an HTTP status code (403) when access should be denied, or `None`
+8 -8
View File
@@ -1,7 +1,7 @@
"""Motion search API for detecting changes within a region of interest."""
import logging
from typing import Any, List, Optional
from typing import Any
from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse
@@ -26,7 +26,7 @@ class MotionSearchRequest(BaseModel):
start_time: float = Field(description="Start timestamp for the search range")
end_time: float = Field(description="End timestamp for the search range")
polygon_points: List[List[float]] = Field(
polygon_points: list[list[float]] = Field(
description="List of [x, y] normalized coordinates (0-1) defining the ROI polygon"
)
threshold: int = Field(
@@ -87,12 +87,12 @@ class MotionSearchStatusResponse(BaseModel):
success: bool
message: str
status: str # "queued", "running", "success", "failed", or "cancelled"
results: Optional[List[MotionSearchResult]] = None
total_frames_processed: Optional[int] = None
error_message: Optional[str] = None
metrics: Optional[MotionSearchMetricsResponse] = None
scanning_timestamp: Optional[float] = None
progress: Optional[float] = None
results: list[MotionSearchResult] | None = None
total_frames_processed: int | None = None
error_message: str | None = None
metrics: MotionSearchMetricsResponse | None = None
scanning_timestamp: float | None = None
progress: float | None = None
@router.post(
+2 -2
View File
@@ -4,7 +4,7 @@ import bisect
import logging
import os
import threading
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
import pytz
from fastapi import APIRouter, Depends, HTTPException
@@ -125,7 +125,7 @@ def preview_hour(
"""Get all mp4 previews relevant for time period given the timezone"""
parts = year_month.split("-")
start_date = (
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=timezone.utc)
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC)
- datetime.now(pytz.timezone(tz_name.replace(",", "/"))).utcoffset()
)
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
+12 -15
View File
@@ -5,7 +5,6 @@ import logging
from datetime import datetime, timedelta
from functools import reduce
from pathlib import Path
from typing import List
from urllib.parse import unquote
from fastapi import APIRouter, Depends, Request
@@ -63,7 +62,7 @@ def get_recordings_storage_usage(request: Request):
def all_recordings_summary(
request: Request,
params: MediaRecordingsSummaryQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
"""Returns true/false by day indicating if recordings exist"""
@@ -263,18 +262,18 @@ async def recordings(
async def no_recordings(
request: Request,
params: MediaRecordingsAvailabilityQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
"""Get time ranges with no recordings."""
cameras = params.cameras
if cameras != "all":
requested = set(unquote(cameras).split(","))
filtered = requested.intersection(allowed_cameras)
if not filtered:
return JSONResponse(content=[])
cameras = ",".join(filtered)
camera_list = list(requested.intersection(allowed_cameras))
else:
cameras = allowed_cameras
camera_list = list(allowed_cameras)
if not camera_list:
return JSONResponse(content=[])
before = params.before or datetime.datetime.now().timestamp()
after = (
@@ -283,12 +282,10 @@ async def no_recordings(
)
scale = params.scale
clauses = [(Recordings.end_time >= after) & (Recordings.start_time <= before)]
if cameras != "all":
camera_list = cameras.split(",")
clauses.append((Recordings.camera << camera_list))
else:
camera_list = allowed_cameras
clauses = [
(Recordings.end_time >= after) & (Recordings.start_time <= before),
(Recordings.camera << camera_list),
]
# Get recording start times
data: list[Recordings] = (
@@ -367,7 +364,7 @@ async def delete_recordings(
start: float = PathParam(..., description="Start timestamp (unix)"),
end: float = PathParam(..., description="End timestamp (unix)"),
params: RecordingsDeleteQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
"""Delete recordings in the specified time range."""
if start >= end:
+10 -11
View File
@@ -4,7 +4,6 @@ import datetime
import logging
from functools import reduce
from pathlib import Path
from typing import List
import pandas as pd
from fastapi import APIRouter, Request
@@ -51,7 +50,7 @@ router = APIRouter(tags=[Tags.review])
async def review(
params: ReviewQueryParams = Depends(),
current_user: dict = Depends(get_current_user),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
if isinstance(current_user, JSONResponse):
return current_user
@@ -83,7 +82,7 @@ async def review(
camera_list = list(filtered)
else:
camera_list = allowed_cameras
clauses.append((ReviewSegment.camera << camera_list))
clauses.append(ReviewSegment.camera << camera_list)
if labels != "all":
# use matching so segments with multiple labels
@@ -106,12 +105,12 @@ async def review(
for zone in filtered_zones:
zone_clauses.append(
(ReviewSegment.data["zones"].cast("text") % f'*"{zone}"*')
ReviewSegment.data["zones"].cast("text") % f'*"{zone}"*'
)
clauses.append(reduce(operator.or_, zone_clauses))
if severity:
clauses.append((ReviewSegment.severity == severity))
clauses.append(ReviewSegment.severity == severity)
# Join with UserReviewStatus to get per-user review status
review_query = (
@@ -204,7 +203,7 @@ async def review_ids(request: Request, ids: str):
async def review_summary(
params: ReviewSummaryQueryParams = Depends(),
current_user: dict = Depends(get_current_user),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
if isinstance(current_user, JSONResponse):
return current_user
@@ -227,7 +226,7 @@ async def review_summary(
camera_list = list(filtered)
else:
camera_list = allowed_cameras
clauses.append((ReviewSegment.camera << camera_list))
clauses.append(ReviewSegment.camera << camera_list)
if labels != "all":
# use matching so segments with multiple labels
@@ -328,7 +327,7 @@ async def review_summary(
camera_list = list(filtered)
else:
camera_list = allowed_cameras
clauses.append((ReviewSegment.camera << camera_list))
clauses.append(ReviewSegment.camera << camera_list)
if labels != "all":
# use matching so segments with multiple labels
@@ -584,7 +583,7 @@ def delete_reviews(body: ReviewModifyMultipleBody):
)
def motion_activity(
params: ReviewActivityMotionQueryParams = Depends(),
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
allowed_cameras: list[str] = Depends(get_allowed_cameras_for_filter),
):
"""Get motion and audio activity."""
cameras = params.cameras
@@ -597,7 +596,7 @@ def motion_activity(
scale = params.scale
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
clauses.append((Recordings.motion > 0))
clauses.append(Recordings.motion > 0)
if cameras != "all":
requested = set(cameras.split(","))
@@ -608,7 +607,7 @@ def motion_activity(
else:
camera_list = list(allowed_cameras)
clauses.append((Recordings.camera << camera_list))
clauses.append(Recordings.camera << camera_list)
data: list[Recordings] = (
Recordings.select(
+4 -4
View File
@@ -4,11 +4,11 @@ import multiprocessing as mp
import os
import secrets
import shutil
from collections.abc import Callable
from multiprocessing import Queue
from multiprocessing.managers import DictProxy, SyncManager
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Callable, Optional
import psutil
import uvicorn
@@ -95,7 +95,7 @@ class FrigateApp:
self, config: FrigateConfig, manager: SyncManager, stop_event: MpEvent
) -> None:
self.metrics_manager = manager
self.audio_process: Optional[mp.Process] = None
self.audio_process: mp.Process | None = None
self.stop_event = stop_event
self.detection_queue: Queue = mp.Queue()
self.detectors: dict[str, ObjectDetectProcess] = {}
@@ -120,8 +120,8 @@ class FrigateApp:
)
self.ptz_metrics: dict[str, PTZMetrics] = {}
self.processes: dict[str, int] = {}
self.embeddings: Optional[EmbeddingsContext] = None
self.profile_manager: Optional[ProfileManager] = None
self.embeddings: EmbeddingsContext | None = None
self.profile_manager: ProfileManager | None = None
self.config = config
def ensure_dirs(self) -> None:
+47 -1
View File
@@ -6,13 +6,18 @@ import logging
import random
import string
from collections import Counter
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
from frigate.comms.event_metadata_updater import (
EventMetadataPublisher,
EventMetadataTypeEnum,
)
from frigate.config import CameraConfig, FrigateConfig
from frigate.config.camera.updater import (
CameraConfigUpdateEnum,
CameraConfigUpdateSubscriber,
)
logger = logging.getLogger(__name__)
@@ -29,6 +34,11 @@ class CameraActivityManager:
self.zone_all_object_counts: dict[str, Counter] = {}
self.zone_active_object_counts: dict[str, Counter] = {}
self.all_zone_labels: dict[str, set[str]] = {}
self.config_subscriber = CameraConfigUpdateSubscriber(
config,
config.cameras,
[CameraConfigUpdateEnum.zones, CameraConfigUpdateEnum.objects],
)
for camera_config in config.cameras.values():
if not camera_config.enabled_in_config:
@@ -56,7 +66,40 @@ class CameraActivityManager:
else camera_config.objects.track
)
def __rebuild_zone_labels(self) -> None:
"""Rebuild zone label tracking after a runtime zones/objects change."""
new_zone_labels: dict[str, set[str]] = {}
for camera_config in self.config.cameras.values():
if not camera_config.enabled_in_config or camera_config.name is None:
continue
for zone, zone_config in camera_config.zones.items():
new_zone_labels.setdefault(zone, set()).update(
zone_config.objects
if zone_config.objects
else camera_config.objects.track
)
# drop counters for zones that no longer exist
for zone in list(self.zone_all_object_counts.keys()):
if zone not in new_zone_labels:
self.zone_all_object_counts.pop(zone, None)
self.zone_active_object_counts.pop(zone, None)
# ensure counters exist for new zones so the first count is published
for zone in new_zone_labels:
self.zone_all_object_counts.setdefault(zone, Counter())
self.zone_active_object_counts.setdefault(zone, Counter())
self.all_zone_labels = new_zone_labels
def update_activity(self, new_activity: dict[str, dict[str, Any]]) -> None:
updated_topics = self.config_subscriber.check_for_updates()
if "zones" in updated_topics or "objects" in updated_topics:
self.__rebuild_zone_labels()
all_objects: list[dict[str, Any]] = []
for camera in new_activity.keys():
@@ -161,6 +204,9 @@ class CameraActivityManager:
self.publish(f"{camera}/all", sum(list(all_objects.values())))
self.publish(f"{camera}/all/active", sum(list(active_objects.values())))
def stop(self) -> None:
self.config_subscriber.stop()
class AudioActivityManager:
def __init__(
+2 -1
View File
@@ -5,7 +5,8 @@ import logging
import os
import threading
from collections import defaultdict
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
import cv2
import numpy as np
+2 -1
View File
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
class Communicator(ABC):
+6 -4
View File
@@ -3,8 +3,8 @@
import datetime
import json
import logging
from collections.abc import Iterable
from typing import Any, Callable, Optional, cast
from collections.abc import Callable, Iterable
from typing import Any, cast
from frigate.camera import PTZMetrics
from frigate.camera.activity_manager import AudioActivityManager, CameraActivityManager
@@ -97,7 +97,7 @@ class Dispatcher:
"notifications": self._on_global_notification_command,
"profile": self._on_profile_command,
}
self.profile_manager: Optional[ProfileManager] = None
self.profile_manager: ProfileManager | None = None
for comm in self.comms:
comm.subscribe(self._receive)
@@ -106,7 +106,7 @@ class Dispatcher:
(comm for comm in communicators if isinstance(comm, WebPushClient)), None
)
def _receive(self, topic: str, payload: Any) -> Optional[Any]:
def _receive(self, topic: str, payload: Any) -> Any | None:
"""Handle receiving of payload from communicators."""
def handle_camera_command(
@@ -397,6 +397,8 @@ class Dispatcher:
comm.publish(topic, payload, retain)
def stop(self) -> None:
self.camera_activity.stop()
for comm in self.comms:
comm.stop()
+2 -1
View File
@@ -1,8 +1,9 @@
"""Facilitates communication between processes."""
import logging
from collections.abc import Callable
from enum import Enum
from typing import Any, Callable
from typing import Any
import zmq
+2 -1
View File
@@ -3,8 +3,9 @@
import logging
import multiprocessing as mp
import threading
from collections.abc import Callable
from multiprocessing.synchronize import Event as MpEvent
from typing import Any, Callable
from typing import Any
import zmq
+25 -5
View File
@@ -1,6 +1,7 @@
import logging
import threading
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
import paho.mqtt.client as mqtt
from paho.mqtt.enums import CallbackAPIVersion
@@ -41,6 +42,18 @@ class MqttClient(Communicator):
self.publish("available", "stopped", retain=True)
self.client.disconnect()
def _notifications_enabled_in_config(self) -> bool:
"""Whether notifications are configured globally or on any camera.
Notifications can be enabled per camera with the global config left
disabled, so the global topics must consider both (matching how
app.py decides to create the WebPushClient).
"""
return self.config.notifications.enabled_in_config or any(
cam.enabled and cam.notifications.enabled_in_config
for cam in self.config.cameras.values()
)
def _set_initial_topics(self) -> None:
"""Set initial state topics."""
for camera_name, camera in self.config.cameras.items():
@@ -157,7 +170,7 @@ class MqttClient(Communicator):
retain=True,
)
if self.config.notifications.enabled_in_config:
if self._notifications_enabled_in_config():
self.publish(
"notifications/state",
"ON" if self.config.notifications.enabled else "OFF",
@@ -202,8 +215,8 @@ class MqttClient(Communicator):
logger.error("Unable to connect to MQTT server: MQTT Not authorized")
else:
logger.error(
"Unable to connect to MQTT server: Connection refused. Error code: "
+ reason_code.getName()
"Unable to connect to MQTT server: Connection refused. Error code: %s",
reason_code.getName(),
)
self.connected = True
@@ -256,6 +269,7 @@ class MqttClient(Communicator):
"review_detections",
"object_descriptions",
"review_descriptions",
"notifications",
]
for name in self.config.cameras.keys():
@@ -265,6 +279,12 @@ class MqttClient(Communicator):
self.on_mqtt_command,
)
# notifications suspend doesn't follow the /set topic pattern
self.client.message_callback_add(
f"{self.mqtt_config.topic_prefix}/{name}/notifications/suspend",
self.on_mqtt_command,
)
if self.config.cameras[name].onvif.host:
self.client.message_callback_add(
f"{self.mqtt_config.topic_prefix}/{name}/ptz",
@@ -289,7 +309,7 @@ class MqttClient(Communicator):
self.on_mqtt_command,
)
if self.config.notifications.enabled_in_config:
if self._notifications_enabled_in_config():
self.client.message_callback_add(
f"{self.mqtt_config.topic_prefix}/notifications/set",
self.on_mqtt_command,
+1 -1
View File
@@ -146,7 +146,7 @@ class RuntimeStatePersistence:
if not os.path.exists(self._path):
return {}
try:
with open(self._path, "r") as f:
with open(self._path) as f:
data = json.load(f)
except (OSError, json.JSONDecodeError):
logger.exception(
+2 -1
View File
@@ -6,9 +6,10 @@ import logging
import os
import queue
import threading
from collections.abc import Callable
from dataclasses import dataclass
from multiprocessing.synchronize import Event as MpEvent
from typing import Any, Callable
from typing import Any
from py_vapid import Vapid01
from pywebpush import WebPusher
+41 -8
View File
@@ -4,7 +4,8 @@ import errno
import json
import logging
import threading
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
from wsgiref.simple_server import make_server
from ws4py.server.wsgirefserver import (
@@ -72,11 +73,16 @@ _WS_VIEWER_TOPICS = frozenset(
}
)
# Camera-scoped command topics a camera-authorized (non-admin) user may send.
_WS_CAMERA_COMMAND_TOPICS = frozenset({"ptz"})
def _check_ws_authorization(
topic: str,
role_header: str | None,
separator: str,
roles_config: dict[str, list[str]] | None = None,
camera_names: set[str] | None = None,
) -> bool:
"""Check if a WebSocket message is authorized.
@@ -84,6 +90,10 @@ def _check_ws_authorization(
topic: The message topic.
role_header: The HTTP_REMOTE_ROLE header value, or None.
separator: The role separator character from proxy config.
roles_config: The auth.roles mapping (role -> allowed cameras), used to
authorize camera-scoped commands for non-admin users.
camera_names: All configured camera names, used to resolve a role's
allowed cameras.
Returns:
True if authorized, False if blocked.
@@ -93,16 +103,33 @@ def _check_ws_authorization(
return False
# No role header: default to viewer (fail-closed)
if role_header is None:
return topic in _WS_VIEWER_TOPICS
roles = [r.strip() for r in role_header.split(separator)] if role_header else []
# Check if any role is admin
roles = [r.strip() for r in role_header.split(separator)]
# Admin can send anything
if "admin" in roles:
return True
# Non-admin: only viewer topics allowed
return topic in _WS_VIEWER_TOPICS
# Read-only topics any authenticated user can send
if topic in _WS_VIEWER_TOPICS:
return True
# Camera-scoped command like "<camera>/ptz": allow when the user's role(s)
# grant access to that camera.
parts = topic.split("/")
if (
roles_config is not None
and len(parts) == 2
and parts[1] in _WS_CAMERA_COMMAND_TOPICS
):
allowed: set[str] = set()
# No role header maps to the default viewer role (e.g. proxy-only setups)
for role in roles or ["viewer"]:
allowed.update(
User.get_allowed_cameras(role, roles_config, camera_names or set())
)
return parts[0] in allowed
return False
# ---- Outbound filtering ---------------------------------------------------
@@ -449,6 +476,8 @@ class WebSocketClient(Communicator):
class _WebSocketHandler(WebSocket):
receiver = self._dispatcher
role_separator = self.config.proxy.separator or ","
roles_config = self.config.auth.roles
camera_names = set(self.config.cameras.keys())
def received_message(self, message: WebSocket.received_message) -> None: # type: ignore[name-defined]
try:
@@ -470,7 +499,11 @@ class WebSocketClient(Communicator):
self.environ.get("HTTP_REMOTE_ROLE") if self.environ else None
)
if self.environ is not None and not _check_ws_authorization(
topic, role_header, self.role_separator
topic,
role_header,
self.role_separator,
self.roles_config,
self.camera_names,
):
logger.warning(
"Blocked unauthorized WebSocket message: topic=%s, role=%s",
+4 -6
View File
@@ -1,5 +1,3 @@
from typing import Dict, List, Optional
from pydantic import Field, field_validator, model_validator
from .base import FrigateBaseModel
@@ -41,7 +39,7 @@ class AuthConfig(FrigateBaseModel):
description="When a session is within this many seconds of expiring, refresh it back to full length.",
ge=30,
)
failed_login_rate_limit: Optional[str] = Field(
failed_login_rate_limit: str | None = Field(
default=None,
title="Failed login limits",
description="Rate limiting rules for failed login attempts to reduce brute-force attacks.",
@@ -57,12 +55,12 @@ class AuthConfig(FrigateBaseModel):
title="Hash iterations",
description="Number of PBKDF2-SHA256 iterations to use when hashing user passwords.",
)
roles: Dict[str, List[str]] = Field(
roles: dict[str, list[str]] = Field(
default_factory=dict,
title="Role mappings",
description="Map roles to camera lists. An empty list grants access to all cameras for the role.",
)
admin_first_time_login: Optional[bool] = Field(
admin_first_time_login: bool | None = Field(
default=False,
title="First-time admin flag",
description=(
@@ -72,7 +70,7 @@ class AuthConfig(FrigateBaseModel):
@field_validator("roles")
@classmethod
def validate_roles(cls, v: Dict[str, List[str]]) -> Dict[str, List[str]]:
def validate_roles(cls, v: dict[str, list[str]]) -> dict[str, list[str]]:
# Ensure role names are valid (alphanumeric with underscores)
for role in v.keys():
if not role.replace("_", "").isalnum():
+2 -4
View File
@@ -1,5 +1,3 @@
from typing import Optional
from pydantic import Field
from frigate.const import AUDIO_MIN_CONFIDENCE
@@ -43,12 +41,12 @@ class AudioConfig(FrigateBaseModel):
title="Listen types",
description="List of audio event types to detect (for example: bark, fire_alarm, speech, yell).",
)
filters: Optional[dict[str, AudioFilterConfig]] = Field(
filters: dict[str, AudioFilterConfig] | None = Field(
None,
title="Audio filters",
description="Per-audio-type filter settings such as confidence thresholds used to reduce false positives.",
)
enabled_in_config: Optional[bool] = Field(
enabled_in_config: bool | None = Field(
None,
title="Original audio state",
description="Indicates whether audio detection was originally enabled in the static config file.",
+1 -2
View File
@@ -1,5 +1,4 @@
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
@@ -35,7 +34,7 @@ class BirdseyeLayoutConfig(FrigateBaseModel):
ge=1.0,
le=5.0,
)
max_cameras: Optional[int] = Field(
max_cameras: int | None = Field(
default=None,
title="Max cameras",
description="Maximum number of cameras to display at once in Birdseye; shows the most recent cameras.",
+4 -5
View File
@@ -1,6 +1,5 @@
import os
from enum import Enum
from typing import Optional
from pydantic import Field, PrivateAttr, model_validator
@@ -51,14 +50,14 @@ class CameraTypeEnum(str, Enum):
class CameraConfig(FrigateBaseModel):
name: Optional[str] = Field(
name: str | None = Field(
None,
title="Camera name",
description="Camera name is required",
pattern=REGEX_CAMERA_NAME,
)
friendly_name: Optional[str] = Field(
friendly_name: str | None = Field(
None,
title="Friendly name",
description="Camera friendly name used in the Frigate UI",
@@ -180,7 +179,7 @@ class CameraConfig(FrigateBaseModel):
title="Camera UI",
description="Display ordering and visibility for this camera in the UI. Ordering affects the default dashboard. For more granular control, use camera groups.",
)
webui_url: Optional[str] = Field(
webui_url: str | None = Field(
None,
title="Camera URL",
description="URL to visit the camera directly from system page",
@@ -196,7 +195,7 @@ class CameraConfig(FrigateBaseModel):
title="Zones",
description="Zones allow you to define a specific area of the frame so you can determine whether or not an object is within a particular area.",
)
enabled_in_config: Optional[bool] = Field(
enabled_in_config: bool | None = Field(
default=None,
title="Original camera state",
description="Keep track of original state of camera.",
+7 -9
View File
@@ -1,5 +1,3 @@
from typing import Optional
from pydantic import Field, model_validator
from ..base import FrigateBaseModel
@@ -8,7 +6,7 @@ __all__ = ["DetectConfig", "StationaryConfig", "StationaryMaxFramesConfig"]
class StationaryMaxFramesConfig(FrigateBaseModel):
default: Optional[int] = Field(
default: int | None = Field(
default=None,
title="Default max frames",
description="Default maximum frames to track a stationary object before stopping.",
@@ -22,13 +20,13 @@ class StationaryMaxFramesConfig(FrigateBaseModel):
class StationaryConfig(FrigateBaseModel):
interval: Optional[int] = Field(
interval: int | None = Field(
default=None,
title="Stationary interval",
description="How often (in frames) to run a detection check to confirm a stationary object.",
gt=0,
)
threshold: Optional[int] = Field(
threshold: int | None = Field(
default=None,
title="Stationary threshold",
description="Number of frames with no position change required to mark an object as stationary.",
@@ -52,12 +50,12 @@ class DetectConfig(FrigateBaseModel):
title="Enable object detection",
description="Enable or disable object detection for all cameras; can be overridden per-camera.",
)
height: Optional[int] = Field(
height: int | None = Field(
default=None,
title="Detect height",
description="Height (pixels) of frames used for the detect stream; leave empty to use the native stream resolution.",
)
width: Optional[int] = Field(
width: int | None = Field(
default=None,
title="Detect width",
description="Width (pixels) of frames used for the detect stream; leave empty to use the native stream resolution.",
@@ -67,13 +65,13 @@ class DetectConfig(FrigateBaseModel):
title="Detect FPS",
description="Desired frames per second to run detection on; lower values reduce CPU usage (recommended value is 5, only set higher - at most 10 - if tracking extremely fast moving objects).",
)
min_initialized: Optional[int] = Field(
min_initialized: int | None = Field(
default=None,
title="Minimum initialization frames",
description="Number of consecutive detection hits required before creating a tracked object. Increase to reduce false initializations. Default value is fps divided by 2.",
ge=2,
)
max_disappeared: Optional[int] = Field(
max_disappeared: int | None = Field(
default=None,
title="Maximum disappeared frames",
description="Number of frames without a detection before a tracked object is considered gone.",
+8 -9
View File
@@ -1,5 +1,4 @@
from enum import Enum
from typing import Union
from pydantic import Field, field_validator
@@ -33,12 +32,12 @@ DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT = [
class FfmpegOutputArgsConfig(FrigateBaseModel):
detect: Union[str, list[str]] = Field(
detect: str | list[str] = Field(
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
title="Detect output arguments",
description="Default output arguments for detect role streams.",
)
record: Union[str, list[str]] = Field(
record: str | list[str] = Field(
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
title="Record output arguments",
description="Default output arguments for record role streams.",
@@ -51,17 +50,17 @@ class FfmpegConfig(FrigateBaseModel):
title="FFmpeg path",
description='Path to the FFmpeg binary to use or a version alias ("7.0" or "8.0").',
)
global_args: Union[str, list[str]] = Field(
global_args: str | list[str] = Field(
default=FFMPEG_GLOBAL_ARGS_DEFAULT,
title="FFmpeg global arguments",
description="Global arguments passed to FFmpeg processes.",
)
hwaccel_args: Union[str, list[str]] = Field(
hwaccel_args: str | list[str] = Field(
default="auto",
title="Hardware acceleration arguments",
description="Hardware acceleration arguments for FFmpeg. Provider-specific presets are recommended.",
)
input_args: Union[str, list[str]] = Field(
input_args: str | list[str] = Field(
default=FFMPEG_INPUT_ARGS_DEFAULT,
title="Input arguments",
description="Input arguments applied to FFmpeg input streams.",
@@ -112,17 +111,17 @@ class CameraInput(FrigateBaseModel):
title="Input roles",
description="Roles for this input stream.",
)
global_args: Union[str, list[str]] = Field(
global_args: str | list[str] = Field(
default_factory=list,
title="FFmpeg global arguments",
description="FFmpeg global arguments for this input stream.",
)
hwaccel_args: Union[str, list[str]] = Field(
hwaccel_args: str | list[str] = Field(
default_factory=list,
title="Hardware acceleration arguments",
description="Hardware acceleration arguments for this input stream.",
)
input_args: Union[str, list[str]] = Field(
input_args: str | list[str] = Field(
default_factory=list,
title="Input arguments",
description="Input arguments specific to this stream.",
+3 -3
View File
@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Optional
from typing import Any
from pydantic import Field
@@ -26,12 +26,12 @@ class GenAIRoleEnum(str, Enum):
class GenAIConfig(FrigateBaseModel):
"""Primary GenAI Config to define GenAI Provider."""
api_key: Optional[EnvString] = Field(
api_key: EnvString | None = Field(
default=None,
title="API key",
description="API key required by some providers (can also be set via environment variables).",
)
base_url: Optional[str] = Field(
base_url: str | None = Field(
default=None,
title="Base URL",
description="Base URL for self-hosted or compatible providers (for example an Ollama instance).",
+1 -3
View File
@@ -1,5 +1,3 @@
from typing import Dict
from pydantic import Field
from ..base import FrigateBaseModel
@@ -8,7 +6,7 @@ __all__ = ["CameraLiveConfig"]
class CameraLiveConfig(FrigateBaseModel):
streams: Dict[str, str] = Field(
streams: dict[str, str] = Field(
default_factory=list,
title="Live stream names",
description="Mapping of configured stream names to restream/go2rtc names used for live playback.",
+9 -9
View File
@@ -1,6 +1,6 @@
"""Mask configuration for motion and object masks."""
from typing import Any, Optional, Union
from typing import Any
from pydantic import Field, field_serializer
@@ -12,7 +12,7 @@ __all__ = ["MotionMaskConfig", "ObjectMaskConfig"]
class MotionMaskConfig(FrigateBaseModel):
"""Configuration for a single motion mask."""
friendly_name: Optional[str] = Field(
friendly_name: str | None = Field(
default=None,
title="Friendly name",
description="A friendly name for this motion mask used in the Frigate UI",
@@ -22,13 +22,13 @@ class MotionMaskConfig(FrigateBaseModel):
title="Enabled",
description="Enable or disable this motion mask",
)
coordinates: Union[str, list[str]] = Field(
coordinates: str | list[str] = Field(
default="",
title="Coordinates",
description="Ordered x,y coordinates defining the motion mask polygon used to include/exclude areas.",
)
raw_coordinates: Union[str, list[str]] = ""
enabled_in_config: Optional[bool] = Field(
raw_coordinates: str | list[str] = ""
enabled_in_config: bool | None = Field(
default=None, title="Keep track of original state of motion mask."
)
@@ -50,7 +50,7 @@ class MotionMaskConfig(FrigateBaseModel):
class ObjectMaskConfig(FrigateBaseModel):
"""Configuration for a single object mask."""
friendly_name: Optional[str] = Field(
friendly_name: str | None = Field(
default=None,
title="Friendly name",
description="A friendly name for this object mask used in the Frigate UI",
@@ -60,13 +60,13 @@ class ObjectMaskConfig(FrigateBaseModel):
title="Enabled",
description="Enable or disable this object mask",
)
coordinates: Union[str, list[str]] = Field(
coordinates: str | list[str] = Field(
default="",
title="Coordinates",
description="Ordered x,y coordinates defining the object mask polygon used to include/exclude areas.",
)
raw_coordinates: Union[str, list[str]] = ""
enabled_in_config: Optional[bool] = Field(
raw_coordinates: str | list[str] = ""
enabled_in_config: bool | None = Field(
default=None, title="Keep track of original state of object mask."
)
+7 -7
View File
@@ -1,4 +1,4 @@
from typing import Any, Optional
from typing import Any
from pydantic import Field, field_serializer
@@ -28,7 +28,7 @@ class MotionConfig(FrigateBaseModel):
ge=0.3,
le=1.0,
)
skip_motion_threshold: Optional[float] = Field(
skip_motion_threshold: float | None = Field(
default=None,
title="Skip motion threshold",
description="If set to a value between 0.0 and 1.0, and more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Leave unset (None) to disable this feature.",
@@ -40,7 +40,7 @@ class MotionConfig(FrigateBaseModel):
title="Improve contrast",
description="Apply contrast improvement to frames before motion analysis to help detection.",
)
contour_area: Optional[int] = Field(
contour_area: int | None = Field(
default=10,
title="Contour area",
description="Minimum contour area in pixels required for a motion contour to be counted.",
@@ -55,12 +55,12 @@ class MotionConfig(FrigateBaseModel):
title="Frame alpha",
description="Alpha value used when blending frames for motion preprocessing.",
)
frame_height: Optional[int] = Field(
frame_height: int | None = Field(
default=100,
title="Frame height",
description="Height in pixels to scale frames to when computing motion.",
)
mask: dict[str, Optional[MotionMaskConfig]] = Field(
mask: dict[str, MotionMaskConfig | None] = Field(
default_factory=dict,
title="Mask coordinates",
description="Ordered x,y coordinates defining the motion mask polygon used to include/exclude areas.",
@@ -70,12 +70,12 @@ class MotionConfig(FrigateBaseModel):
title="MQTT off delay",
description="Seconds to wait after last motion before publishing an MQTT 'off' state.",
)
enabled_in_config: Optional[bool] = Field(
enabled_in_config: bool | None = Field(
default=None,
title="Original motion state",
description="Indicates whether motion detection was enabled in the original static configuration.",
)
raw_mask: dict[str, Optional[MotionMaskConfig]] = Field(
raw_mask: dict[str, MotionMaskConfig | None] = Field(
default_factory=dict, exclude=True
)
+2 -4
View File
@@ -1,5 +1,3 @@
from typing import Optional
from pydantic import Field
from ..base import FrigateBaseModel
@@ -13,7 +11,7 @@ class NotificationConfig(FrigateBaseModel):
title="Enable notifications",
description="Enable or disable notifications for all cameras; can be overridden per-camera.",
)
email: Optional[str] = Field(
email: str | None = Field(
default=None,
title="Notification email",
description="Email address used for push notifications or required by certain notification providers.",
@@ -24,7 +22,7 @@ class NotificationConfig(FrigateBaseModel):
title="Cooldown period",
description="Cooldown (seconds) between notifications to avoid spamming recipients.",
)
enabled_in_config: Optional[bool] = Field(
enabled_in_config: bool | None = Field(
default=None,
title="Original notifications state",
description="Indicates whether notifications were enabled in the original static configuration.",
+11 -11
View File
@@ -1,4 +1,4 @@
from typing import Any, Optional, Union
from typing import Any
from pydantic import Field, PrivateAttr, field_serializer, field_validator
@@ -12,12 +12,12 @@ DEFAULT_TRACKED_OBJECTS = ["person"]
class FilterConfig(FrigateBaseModel):
min_area: Union[int, float] = Field(
min_area: int | float = Field(
default=0,
title="Minimum object area",
description="Minimum bounding box area (pixels or percentage) required for this object type. Can be pixels (int) or percentage (float between 0.000001 and 0.99).",
)
max_area: Union[int, float] = Field(
max_area: int | float = Field(
default=24000000,
title="Maximum object area",
description="Maximum bounding box area (pixels or percentage) allowed for this object type. Can be pixels (int) or percentage (float between 0.000001 and 0.99).",
@@ -42,12 +42,12 @@ class FilterConfig(FrigateBaseModel):
title="Minimum confidence",
description="Minimum single-frame detection confidence required for the object to be counted.",
)
mask: dict[str, Optional[ObjectMaskConfig]] = Field(
mask: dict[str, ObjectMaskConfig | None] = Field(
default_factory=dict,
title="Filter mask",
description="Polygon coordinates defining where this filter applies within the frame.",
)
raw_mask: dict[str, Optional[ObjectMaskConfig]] = Field(
raw_mask: dict[str, ObjectMaskConfig | None] = Field(
default_factory=dict, exclude=True
)
@@ -68,7 +68,7 @@ class GenAIObjectTriggerConfig(FrigateBaseModel):
title="Send on end",
description="Send a request to GenAI when the tracked object ends.",
)
after_significant_updates: Optional[int] = Field(
after_significant_updates: int | None = Field(
default=None,
title="Early GenAI trigger",
description="Send a request to GenAI after a specified number of significant updates for the tracked object.",
@@ -98,12 +98,12 @@ class GenAIObjectConfig(FrigateBaseModel):
description="Per-object prompts to customize GenAI outputs for specific labels.",
)
objects: Union[str, list[str]] = Field(
objects: str | list[str] = Field(
default_factory=list,
title="GenAI objects",
description="List of object labels to send to GenAI by default.",
)
required_zones: Union[str, list[str]] = Field(
required_zones: str | list[str] = Field(
default_factory=list,
title="Required zones",
description="Zones that must be entered for objects to qualify for GenAI description generation.",
@@ -118,7 +118,7 @@ class GenAIObjectConfig(FrigateBaseModel):
title="GenAI triggers",
description="Defines when frames should be sent to GenAI (on end, after updates, etc.).",
)
enabled_in_config: Optional[bool] = Field(
enabled_in_config: bool | None = Field(
default=None,
title="Original GenAI state",
description="Indicates whether GenAI was enabled in the original static config.",
@@ -144,12 +144,12 @@ class ObjectConfig(FrigateBaseModel):
title="Object filters",
description="Filters applied to detected objects to reduce false positives (area, ratio, confidence).",
)
mask: dict[str, Optional[ObjectMaskConfig]] = Field(
mask: dict[str, ObjectMaskConfig | None] = Field(
default_factory=dict,
title="Object mask",
description="Mask polygon used to prevent object detection in specified areas.",
)
raw_mask: dict[str, Optional[ObjectMaskConfig]] = Field(
raw_mask: dict[str, ObjectMaskConfig | None] = Field(
default_factory=dict, exclude=True
)
genai: GenAIObjectConfig = Field(
+5 -6
View File
@@ -1,5 +1,4 @@
from enum import Enum
from typing import Optional, Union
from pydantic import Field, field_validator
@@ -59,12 +58,12 @@ class PtzAutotrackConfig(FrigateBaseModel):
title="Return timeout",
description="Wait this many seconds after losing tracking before returning camera to preset position.",
)
movement_weights: Optional[Union[str, list[str]]] = Field(
movement_weights: str | list[str] | None = Field(
default_factory=list,
title="Movement weights",
description="Calibration values automatically generated by camera calibration. Do not modify manually.",
)
enabled_in_config: Optional[bool] = Field(
enabled_in_config: bool | None = Field(
default=None,
title="Original autotrack state",
description="Internal field to track whether autotracking was enabled in configuration.",
@@ -102,12 +101,12 @@ class OnvifConfig(FrigateBaseModel):
title="ONVIF port",
description="Port number for the ONVIF service.",
)
user: Optional[EnvString] = Field(
user: EnvString | None = Field(
default=None,
title="ONVIF username",
description="Username for ONVIF authentication; some devices require admin user for ONVIF.",
)
password: Optional[EnvString] = Field(
password: EnvString | None = Field(
default=None,
title="ONVIF password",
description="Password for ONVIF authentication.",
@@ -117,7 +116,7 @@ class OnvifConfig(FrigateBaseModel):
title="Disable TLS verify",
description="Skip TLS verification and disable digest auth for ONVIF (unsafe; use in safe networks only).",
)
profile: Optional[str] = Field(
profile: str | None = Field(
default=None,
title="ONVIF profile",
description="Specific ONVIF media profile to use for PTZ control, matched by token or name. If not set, the first profile with valid PTZ configuration is selected automatically.",
+13 -15
View File
@@ -1,7 +1,5 @@
"""Camera profile configuration for named config overrides."""
from typing import Optional
from ..base import FrigateBaseModel
from ..classification import (
CameraFaceRecognitionConfig,
@@ -29,16 +27,16 @@ class CameraProfileConfig(FrigateBaseModel):
explicitly-set fields are used as overrides via exclude_unset.
"""
enabled: Optional[bool] = None
audio: Optional[AudioConfig] = None
birdseye: Optional[BirdseyeCameraConfig] = None
detect: Optional[DetectConfig] = None
face_recognition: Optional[CameraFaceRecognitionConfig] = None
lpr: Optional[CameraLicensePlateRecognitionConfig] = None
motion: Optional[MotionConfig] = None
notifications: Optional[NotificationConfig] = None
objects: Optional[ObjectConfig] = None
record: Optional[RecordConfig] = None
review: Optional[ReviewConfig] = None
snapshots: Optional[SnapshotsConfig] = None
zones: Optional[dict[str, ZoneConfig]] = None
enabled: bool | None = None
audio: AudioConfig | None = None
birdseye: BirdseyeCameraConfig | None = None
detect: DetectConfig | None = None
face_recognition: CameraFaceRecognitionConfig | None = None
lpr: CameraLicensePlateRecognitionConfig | None = None
motion: MotionConfig | None = None
notifications: NotificationConfig | None = None
objects: ObjectConfig | None = None
record: RecordConfig | None = None
review: ReviewConfig | None = None
snapshots: SnapshotsConfig | None = None
zones: dict[str, ZoneConfig] | None = None

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