Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub d413634ab9 Update tensorflow-cpu requirement in /docker/main
Updates the requirements on [tensorflow-cpu](https://github.com/tensorflow/tensorflow) to permit the latest version.
- [Release notes](https://github.com/tensorflow/tensorflow/releases)
- [Changelog](https://github.com/tensorflow/tensorflow/blob/master/RELEASE.md)
- [Commits](https://github.com/tensorflow/tensorflow/compare/v2.19.0-rc0...v2.21.0)

---
updated-dependencies:
- dependency-name: tensorflow-cpu
  dependency-version: 2.21.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-14 11:33:46 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bb6c2e98eb Bump docker/login-action from 3.5.0 to 4.6.0 (#23872)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.5.0 to 4.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/184bdaa0721073962dff0199f1fb9940f07167d1...dbcb813823bdd20940b903addbd779551569679f)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 17:39:20 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0570a9c4eb Bump actions/checkout from 6 to 7 (#23510)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 17:36:45 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ced95a1a31 Bump actions/setup-python from 5.4.0 to 7.0.0 (#23768)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.4.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.4.0...v7.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 17:27:51 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1f340f389f Bump actions/setup-node from 6 to 7 (#23715)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 17:27:44 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
94422ff24f Update click requirement from ==8.1.* to ==8.5.* in /docker/main (#23067)
Updates the requirements on [click](https://github.com/pallets/click) to permit the latest version.
- [Release notes](https://github.com/pallets/click/releases)
- [Changelog](https://github.com/pallets/click/blob/main/CHANGES.md)
- [Commits](https://github.com/pallets/click/compare/8.1.0...8.5.0)

---
updated-dependencies:
- dependency-name: click
  dependency-version: 8.3.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 17:13:38 -05:00
06ff2ced5d bump cryptography to 46 and pin py-vapid to 1.9.4 (#24292)
Co-authored-by: t <t@t>
2026-09-13 15:52:19 -06:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
35b9c4978d Bump python-multipart from 0.0.26 to 0.0.31 in /docker/main (#23497)
Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.26 to 0.0.31.
- [Release notes](https://github.com/Kludex/python-multipart/releases)
- [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kludex/python-multipart/compare/0.0.26...0.0.31)

---
updated-dependencies:
- dependency-name: python-multipart
  dependency-version: 0.0.31
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:50 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2106d10e2e Update faster-whisper requirement in /docker/main (#23075)
Updates the requirements on [faster-whisper](https://github.com/SYSTRAN/faster-whisper) to permit the latest version.
- [Release notes](https://github.com/SYSTRAN/faster-whisper/releases)
- [Commits](https://github.com/SYSTRAN/faster-whisper/compare/v1.1.0...v1.2.1)

---
updated-dependencies:
- dependency-name: faster-whisper
  dependency-version: 1.2.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:45 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8b8c90ee1a Update aiofiles requirement from ==24.1.* to ==25.1.* in /docker/main (#23074)
Updates the requirements on [aiofiles](https://github.com/Tinche/aiofiles) to permit the latest version.
- [Release notes](https://github.com/Tinche/aiofiles/releases)
- [Changelog](https://github.com/Tinche/aiofiles/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Tinche/aiofiles/compare/v24.1.0...v25.1.0)

---
updated-dependencies:
- dependency-name: aiofiles
  dependency-version: 25.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:41 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d462aeb068 Update uvicorn requirement from ==0.35.* to ==0.46.* in /docker/main (#23073)
Updates the requirements on [uvicorn](https://github.com/Kludex/uvicorn) to permit the latest version.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/uvicorn/compare/0.35.0...0.46.0)

---
updated-dependencies:
- dependency-name: uvicorn
  dependency-version: 0.46.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:37 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
828ecdd0ea Update joserfc requirement from ==1.2.* to ==1.6.* in /docker/main (#23071)
Updates the requirements on [joserfc](https://github.com/authlib/joserfc) to permit the latest version.
- [Release notes](https://github.com/authlib/joserfc/releases)
- [Changelog](https://github.com/authlib/joserfc/blob/main/docs/changelog.rst)
- [Commits](https://github.com/authlib/joserfc/compare/1.2.0...1.6.4)

---
updated-dependencies:
- dependency-name: joserfc
  dependency-version: 1.6.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:33 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d5e3fed130 Update requests requirement from ==2.32.* to ==2.33.* in /docker/main (#23068)
Updates the requirements on [requests](https://github.com/psf/requests) to permit the latest version.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.0...v2.33.1)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:28 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3e7cb396e2 Update pyzmq requirement from ==26.2.* to ==27.1.* in /docker/main (#23064)
Updates the requirements on [pyzmq](https://github.com/zeromq/pyzmq) to permit the latest version.
- [Release notes](https://github.com/zeromq/pyzmq/releases)
- [Commits](https://github.com/zeromq/pyzmq/compare/v26.2.0...v27.1.0)

---
updated-dependencies:
- dependency-name: pyzmq
  dependency-version: 27.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:24 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d71d573edf Update pyclipper requirement from ==1.3.* to ==1.4.* in /docker/main (#23063)
Updates the requirements on [pyclipper](https://github.com/fonttools/pyclipper) to permit the latest version.
- [Release notes](https://github.com/fonttools/pyclipper/releases)
- [Commits](https://github.com/fonttools/pyclipper/compare/1.3.0...1.4.0)

---
updated-dependencies:
- dependency-name: pyclipper
  dependency-version: 1.4.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 16:16:20 -05:00
4b71cb76dd bump i18next-http-backend to 4.0.2 (#24290)
Co-authored-by: t <t@t>
2026-09-13 15:05:41 -06:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cf59d9ad56 Bump browserslist from 4.23.3 to 4.28.9 in /web (#24287)
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.23.3 to 4.28.9.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.23.3...4.28.9)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-version: 4.28.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:27:19 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
94b37ceb6e Bump nanoid from 3.3.11 to 3.3.19 in /web (#24286)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.11 to 3.3.19.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.11...3.3.19)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-version: 3.3.19
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:27:14 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ea84b1be82 Bump js-yaml from 4.1.1 to 4.3.2 in /web (#24273)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.3.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.3.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:27:09 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
105b7bb79d Bump micromatch from 4.0.5 to 4.0.8 in /web (#23840)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-version: 4.0.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:27:05 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c46b099e14 Bump braces from 3.0.2 to 3.0.3 in /web (#23839)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-version: 3.0.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:59 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
05ebab80b6 Bump form-data from 4.0.5 to 4.0.6 in /web (#23532)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.5 to 4.0.6.
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.5...v4.0.6)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:53 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6d2bf59aa6 Bump tmp from 0.2.5 to 0.2.7 in /web (#23334)
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.5 to 0.2.7.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.5...v0.2.7)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:48 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1dd5ec312b Bump eslint-plugin-vitest-globals from 1.5.0 to 1.6.1 in /web (#23058)
Bumps [eslint-plugin-vitest-globals](https://github.com/saqqdy/eslint-plugin-vitest-globals) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/saqqdy/eslint-plugin-vitest-globals/releases)
- [Changelog](https://github.com/saqqdy/eslint-plugin-vitest-globals/blob/master/CHANGELOG.md)
- [Commits](https://github.com/saqqdy/eslint-plugin-vitest-globals/compare/1.5.0...1.6.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-vitest-globals
  dependency-version: 1.6.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:42 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cd8a159cc6 Bump postcss from 8.5.8 to 8.5.12 in /web (#23051)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.8 to 8.5.12.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.12)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.12
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:37 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3da100af62 Bump react-icons from 5.5.0 to 5.6.0 in /web (#23072)
Bumps [react-icons](https://github.com/react-icons/react-icons) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.5.0...v5.6.0)

---
updated-dependencies:
- dependency-name: react-icons
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:32 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d25dbb69e1 Bump uuid and @rjsf/shadcn in /web (#23120)
Bumps [uuid](https://github.com/uuidjs/uuid) to 14.0.0 and updates ancestor dependency [@rjsf/shadcn](https://github.com/rjsf-team/react-jsonschema-form). These dependencies need to be updated together.


Updates `uuid` from 13.0.0 to 14.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v13.0.0...v14.0.0)

Updates `@rjsf/shadcn` from 6.4.1 to 6.5.2
- [Release notes](https://github.com/rjsf-team/react-jsonschema-form/releases)
- [Changelog](https://github.com/rjsf-team/react-jsonschema-form/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rjsf-team/react-jsonschema-form/compare/6.4.1...6.5.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: indirect
- dependency-name: "@rjsf/shadcn"
  dependency-version: 6.5.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:27 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
049263eb42 Bump axios from 1.13.6 to 1.18.0 in /web (#23782)
Bumps [axios](https://github.com/axios/axios) from 1.13.6 to 1.18.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.6...v1.18.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:22 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9182d07f12 Bump react-router-dom from 6.30.3 to 6.30.6 in /web (#24282)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.30.3 to 6.30.6.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.30.6/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.30.6/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-version: 6.30.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 15:26:18 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8d652ce6e4 Bump @ai-sdk/provider-utils, @ai-sdk/react and ai in /docs (#24284)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
Bumps [@ai-sdk/provider-utils](https://github.com/vercel/ai/tree/HEAD/packages/provider-utils), [@ai-sdk/react](https://github.com/vercel/ai/tree/HEAD/packages/react) and [ai](https://github.com/vercel/ai/tree/HEAD/packages/ai). These dependencies needed to be updated together.

Updates `@ai-sdk/provider-utils` from 3.0.19 to 3.0.37
- [Release notes](https://github.com/vercel/ai/releases)
- [Changelog](https://github.com/vercel/ai/blob/@ai-sdk/provider-utils@3.0.37/packages/provider-utils/CHANGELOG.md)
- [Commits](https://github.com/vercel/ai/commits/@ai-sdk/provider-utils@3.0.37/packages/provider-utils)

Updates `@ai-sdk/react` from 2.0.113 to 2.0.260
- [Release notes](https://github.com/vercel/ai/releases)
- [Changelog](https://github.com/vercel/ai/blob/@ai-sdk/react@2.0.260/packages/react/CHANGELOG.md)
- [Commits](https://github.com/vercel/ai/commits/@ai-sdk/react@2.0.260/packages/react)

Updates `ai` from 5.0.111 to 5.0.257
- [Release notes](https://github.com/vercel/ai/releases)
- [Changelog](https://github.com/vercel/ai/blob/ai@5.0.257/packages/ai/CHANGELOG.md)
- [Commits](https://github.com/vercel/ai/commits/ai@5.0.257/packages/ai)

---
updated-dependencies:
- dependency-name: "@ai-sdk/provider-utils"
  dependency-version: 3.0.37
  dependency-type: indirect
- dependency-name: "@ai-sdk/react"
  dependency-version: 2.0.260
  dependency-type: indirect
- dependency-name: ai
  dependency-version: 5.0.257
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:58:35 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9056696ee5 Bump fast-uri from 3.1.2 to 3.1.7 in /web (#24283)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.7.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.7)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:57:45 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
94c9cdbe9b Bump colord from 2.9.3 to 2.10.0 in /docs (#24281)
Bumps [colord](https://github.com/omgovich/colord) from 2.9.3 to 2.10.0.
- [Release notes](https://github.com/omgovich/colord/releases)
- [Changelog](https://github.com/omgovich/colord/blob/master/CHANGELOG.md)
- [Commits](https://github.com/omgovich/colord/commits/v2.10)

---
updated-dependencies:
- dependency-name: colord
  dependency-version: 2.10.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:54:44 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
95bdc232d3 Bump fast-uri from 3.1.4 to 3.1.7 in /docs (#24280)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.7.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.7)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:48:41 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8d648756d2 Bump baseline-browser-mapping from 2.9.6 to 2.11.23 in /docs (#24276)
Bumps [baseline-browser-mapping](https://github.com/web-platform-dx/baseline-browser-mapping) from 2.9.6 to 2.11.23.
- [Release notes](https://github.com/web-platform-dx/baseline-browser-mapping/releases)
- [Commits](https://github.com/web-platform-dx/baseline-browser-mapping/compare/v2.9.6...v2.11.23)

---
updated-dependencies:
- dependency-name: baseline-browser-mapping
  dependency-version: 2.11.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:48:37 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
89cc76680c Bump joi from 17.13.3 to 17.13.8 in /docs (#24275)
Bumps [joi](https://github.com/hapijs/joi) from 17.13.3 to 17.13.8.
- [Commits](https://github.com/hapijs/joi/compare/v17.13.3...v17.13.8)

---
updated-dependencies:
- dependency-name: joi
  dependency-version: 17.13.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:48:33 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fc57dc3b36 Bump svgo from 3.3.2 to 3.3.5 in /docs (#24274)
Bumps [svgo](https://github.com/svg/svgo) from 3.3.2 to 3.3.5.
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](https://github.com/svg/svgo/compare/v3.3.2...v3.3.5)

---
updated-dependencies:
- dependency-name: svgo
  dependency-version: 3.3.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:48:29 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
97a2697587 Bump js-yaml from 4.1.1 to 4.3.2 in /docs (#24277)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.3.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.3.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:48:25 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
021c12d707 Bump ws in /docs (#23830)
Bumps  and [ws](https://github.com/websockets/ws). These dependencies needed to be updated together.

Updates `ws` from 7.5.10 to 7.5.13
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.10...7.5.13)

Updates `ws` from 8.18.3 to 8.21.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.10...7.5.13)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 7.5.13
  dependency-type: indirect
- dependency-name: ws
  dependency-version: 8.21.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:32:09 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4cdcb51571 Bump postcss from 8.5.6 to 8.5.23 in /docs (#23813)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.6 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.6...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:32:05 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c2b9e1222f Bump body-parser from 1.20.4 to 1.20.6 in /docs (#23812)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.4 to 1.20.6.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.4...1.20.6)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-version: 1.20.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:32:00 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
eaad3cdad7 Bump fast-uri from 3.1.2 to 3.1.4 in /docs (#23811)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.4.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:31:56 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cce23c1983 Bump immutable from 5.1.5 to 5.1.9 in /docs (#23797)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.5 to 5.1.9.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.5...v5.1.9)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 5.1.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:31:50 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c5e9ff6f67 Bump websocket-driver from 0.7.4 to 0.7.5 in /docs (#23729)
Bumps [websocket-driver](https://github.com/faye/websocket-driver-node) from 0.7.4 to 0.7.5.
- [Changelog](https://github.com/faye/websocket-driver-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-driver-node/compare/0.7.4...0.7.5)

---
updated-dependencies:
- dependency-name: websocket-driver
  dependency-version: 0.7.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:31:42 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
177e0b73c0 Bump @babel/plugin-transform-modules-systemjs in /docs (#23148)
Bumps [@babel/plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-modules-systemjs) from 7.28.5 to 7.29.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.4/packages/babel-plugin-transform-modules-systemjs)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-modules-systemjs"
  dependency-version: 7.29.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:31:38 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
54d2c99f6f Bump follow-redirects from 1.15.11 to 1.16.0 in /docs (#22892)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.11 to 1.16.0.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.11...v1.16.0)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:31:35 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
54c63c485a Bump brace-expansion in /docs (#22682)
Bumps  and [brace-expansion](https://github.com/juliangruber/brace-expansion). These dependencies needed to be updated together.

Updates `brace-expansion` from 1.1.12 to 1.1.13
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.13)

Updates `brace-expansion` from 2.0.2 to 2.0.3
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.13)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
- dependency-name: brace-expansion
  dependency-version: 2.0.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:31:31 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7627008c11 Bump mermaid from 11.12.2 to 11.16.1 in /docs (#23935)
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 11.12.2 to 11.16.1.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Commits](https://github.com/mermaid-js/mermaid/compare/mermaid@11.12.2...mermaid@11.16.1)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-version: 11.16.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:30:48 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0987024856 Bump webpack-dev-server from 5.2.2 to 5.2.6 in /docs (#23819)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 5.2.2 to 5.2.6.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/v5.2.6/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v5.2.2...v5.2.6)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-version: 5.2.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 12:29:55 -05:00
Josh HawkinsandGitHub a9eb286db9 Tweaks (#24260)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* update versions in discussion templates

* make /run writable by the runtime user under docker's user
2026-09-12 17:07:02 -05:00
Hosted WeblateandJosh Hawkins 9af22e0c2f Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 9f50aae966 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/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-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 9b0c06afda Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/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-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 87d04687de Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 138eb65ac8 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins d418ba4a8d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
11bbb2d45e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Translated using Weblate (Slovak)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Slovak)

Currently translated at 73.1% (79 of 108 strings)

Translated using Weblate (Slovak)

Currently translated at 98.7% (237 of 240 strings)

Translated using Weblate (Slovak)

Currently translated at 99.8% (499 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Miroslav Kravec <kravec.miroslav@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins fd31941089 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 292cc648a2 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 078b1e6bb3 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
d2e5de3863 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Translated using Weblate (Finnish)

Currently translated at 34.4% (172 of 500 strings)

Translated using Weblate (Finnish)

Currently translated at 29.7% (55 of 185 strings)

Translated using Weblate (Finnish)

Currently translated at 40.2% (27 of 67 strings)

Translated using Weblate (Finnish)

Currently translated at 36.1% (39 of 108 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Tobbana <tobbana@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fi/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fi/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 4717db79a0 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 2cfdb6cb34 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-player
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
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
8846495e16 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Translated using Weblate (French)

Currently translated at 21.5% (172 of 800 strings)

Translated using Weblate (French)

Currently translated at 59.9% (284 of 474 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jl3r <jl3r@proton.me>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/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-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins c639e58dad Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins aab79dc4cd Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translation: Frigate NVR/components-player
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 33f8caf31e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 0f86a00afa Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 853d8b0c07 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins de5c18af38 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins acafd8712c Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
b5106863df Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Translated using Weblate (Hebrew)

Currently translated at 54.6% (59 of 108 strings)

Translated using Weblate (Hebrew)

Currently translated at 96.0% (48 of 50 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Hebrew)

Currently translated at 49.3% (639 of 1295 strings)

Translated using Weblate (Hebrew)

Currently translated at 98.7% (237 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nick Burdilov <nick.bordilov0405@gmail.com>
Co-authored-by: Nimrod Milo <nimrod@honeybook.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/he/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 7e69bc2dc2 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins eb5e166ee6 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 48fc9f2083 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins fa78baef94 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 0d3ece336f Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
5a9f1153c5 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Translated using Weblate (Czech)

Currently translated at 88.8% (96 of 108 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: KZeliop <kzeliop@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 2a1c087234 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
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-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins dfaf3ebe1b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins bd0ab2a71c Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 7ff5ff717c Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-player
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
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 3d5f14e79e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 6a0e8a851b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins e31592ab2b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
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-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 74fb3111dc Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 7108d24d8b Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 07c15079a3 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 5e8c70f24c Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 590b0c9d2d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
f26f9c467a Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Translated using Weblate (Portuguese (Brazil))

Currently translated at 50.0% (400 of 800 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (500 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marco Cordeiro <marcoecordeiro@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
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/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
b29fb3431b Translated using Weblate (Tamil)
Currently translated at 16.6% (1 of 6 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Tamil)

Currently translated at 0.2% (1 of 474 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (74 of 74 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/ta/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-recording
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins c3750ffc80 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 75d22a436c Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins a466d46476 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-live
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins 6aacb47c34 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/
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/components-filter
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-12 16:32:31 -05:00
Hosted WeblateandJosh Hawkins c5093e6668 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Update translation files

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

Update translation files

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

Update translation files

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
2026-09-12 16:32:31 -05:00
Josh HawkinsandGitHub 9cba1c2963 remove deepstack detector and all references to it (#24259)
the 0.18 release notes indicated this was being removed in 0.19
2026-09-12 16:20:50 -05:00
Josh HawkinsandGitHub d59c28e53a Add command menu to frontend (#24256)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* add command menu to quickly jump between pages, cameras, settings, and quick actions

* access tweaks
2026-09-12 10:57:17 -06:00
Josh HawkinsandGitHub 4861a668b1 Add error boundary to frontend (#24255)
* add error boundary

on frontend errors, show a page-level recovery panel and a separate compact strip for the sidebar/status/bottombar so failures there don't take down the rest of the page

* tweaks

* fix test
2026-09-12 10:32:22 -06:00
Josh HawkinsandGitHub dcfe5307c9 fix CI smoke test (#24254) 2026-09-12 10:12:57 -06:00
Josh HawkinsandNicolas Mowen 37f338d5d6 fix save_attempts trimming (#24246) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 7bc32fd4c9 Refactor Notices and System Health pane (#24243)
* refactor notices

* show startup message for enrichments in health pane

* tweaks
2026-09-12 07:30:04 -06:00
Nicolas Mowen 5cef6823a6 Revamp Face Recognition (#24236)
* Rename face model to face recognizer

* Refactor face detection into own module

* Return face and face landmarks

* Align faces with 5 points instead of just the eyes

* Add landmark validation to throw out images which do not fit a landmark

* Fix circular import and lock face detector for concurrent runs across threads

* Fix mypy
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen e3029beba5 Report skipped detections in status bar (#24223)
* report skipped detections in the system notices pane

* revert notices and move to status bar

* shorten string
2026-09-12 07:30:04 -06:00
Nicolas Mowen f9347967eb Add ability to manually run Review Descriptions from the UI / API (#24222)
* Add ability to manually run Review Descriptions from the UI / API

* Fix not handling None type for call
2026-09-12 07:30:04 -06:00
Nicolas Mowen 82be9fff5e Support main+sub stream exports (#24193)
* Support multi resolution exports

* Fix decoder text

* Add dropdown and ability to select export stream selection

* Fix for review comments

* Fix mypy

* Cleanup wording
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 70ce193e09 Improve System Health pane (#24188)
* build out system health pane

* tweaks

* fixes

* fix notice link so it opens the correct camera

* tweak language
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 6d33b31bc6 show disk space reclaimed by media sync (#24189) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen d6b93301e2 Miscellaneous fixes (#24179)
* rename migration

* use ASC composite index for event camera and start_time

the planner still picks for newest-first single-camera queries but doesn't slow down full per-camera scans on a cold cache

* pause playback while the timeline range handles are up

* reduce multi camera seeded export range to 30m

allows both handlebars to fit within most desktop windows
2026-09-12 07:30:04 -06:00
11b4d34f93 Misc backend performance improvements (#23244)
* Add additional indicies on event and review tables.  Every events or timeline endpoint filters on event start time and camera, this should speed things up by avoiding a range scan on the table.

* Rewrite to use a CTE to leverage speedups by using sqllite internal optimization to do a single query instead of a starter query to get distinct labels and a subsequent loop of querys per distinct event labels.

Frigate is currently shipping sqlite 3.46.1, which is above the minimum version 3.25 needed for CTEs.

* Collapse a few sequential queries into a single one.

* Use peewee instead of rw sql for the CTE query.

* Slightly simplify review logic and avoid duplicating the json response for empty review IDs.

* Rerun ruff formatting.

* Remove 2x unnecessary index on reviewsegment, remove reference to prior code implementation in comment in event.py

* Editor fail, re-ruff format.

* Remove the CTE and restore the generator with sub-queries, which is more performance (thanks Nick and Blake for testing against your larger DB!)

* Update peewee index migration description

* Add on_conflict_ignore, replacing the try/catch/pass on IntegrityError

* Add a testcase for validating that on_conflict_ignore bypasses what was formerly an IntegrityError

* Change testcase to clarify that it covers the peewee behavior of on_conflict_ignore

---------

Co-authored-by: Greg <{ID}+{username}@users.noreply.github.com>
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen f3a31e2fb4 Add a notice registry and System Health tab (#24178)
* add a notice registry and System Health tab

Problems Frigate detects on its own (ffmpeg crash loops, stuck detectors, failed model downloads, recordings deleted before their retention period) only ever existed as log lines. This adds a `NoticeRegistry` in the main process backed by two tables, an `update_notice` IPC topic so producers in other processes can reach it through the dispatcher, an admin-only API and websocket topic, and a Health tab that lists them. Kinds declare their own mode, severity, and category in one place: state notices are resolved by their producer, event notices are dismissed by the user.

* treat a prerelease as behind its final release

* fix notices clearing early

* rename menu items and update docs

* don't resolve the update notice on a failed version lookup
2026-09-12 07:30:04 -06:00
Nicolas Mowen af60d2db48 GenAI Chat Improvements (#24173)
* Initial tool approval implementation

* Cleanups and fixes

* Improve robustness of loading case
2026-09-12 07:30:04 -06:00
Nicolas Mowen 76708e7fa6 Add options for review prompt style (#24166)
* Add script for testing genai review prompts

* Add option for prompt styling

* Add tests

* Update docs
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 6bd6d3383f fix hardware stats crash when audio transcription is enabled (#24164) 2026-09-12 07:30:04 -06:00
Li XingyuandNicolas Mowen 8cd36416e2 Avoid killing recovered detector during restart (#24146) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 9eef369dd0 fix latched loading spinner after cancelling a timeline selection (#24162)
isLoading and isBuffering only clear on playback progress, and scrubbing holds the player paused, so a source rebuild during a timeline selection left them set with nothing able to clear them. Cancelling made them visible as a spinner over an already-loaded frame, which stayed until the next manual seek. Leaving a scrub now clears them when the source is loaded and the element holds a frame.
2026-09-12 07:30:04 -06:00
Nicolas Mowen d183f03fee Dynamically install and load detector dependencies (#24156)
* Dynamically install and load detector dependencies

* Cleanup

* Cleanup
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 53b04f44ef Fix preview players at the hour rollover (#24157)
* fix preview players at the hour rollover

* clean up
2026-09-12 07:30:04 -06:00
Nicolas Mowen 6ae8050974 Refactor Hardware Stats (#24150)
* Refactor hardware stats to have consolidated ffmpeg, detector, and enrichments running.

* Cleanup hardware access that is not passed into the container

* Remove network stats from hardware refactor
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 8370e205da add network isolation docs (#24148) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 9416e74aeb fix frozen time bounds on the recordings API (#24121)
`after` and `before` defaulted to `datetime.now()` in the function signature, so they were evaluated once at import. Requests that omitted them got a window ending at process start. They now resolve in the handler.
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen e2da7aae99 Add a deny option for the proxy default role (#24145)
* backend

* frontend

* docs

* fix none default role casing and name reserved roles in the error

* reserve every casing of none as a role name
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 41bc1a5844 Container security hardening (phase 4) (#24140)
* Support read-only rootfs with self-signed certs in /config/tls

* Support read-only rootfs in s6 and pre-compile bytecode

* Assert read-only rootfs support in CI

* Document hardened read-only deployment

* keep certsync's cert selection identical to nginx's

* note the uid trade-off in user: mode

* fail fast when EXTRA_GROUPS or a missing media volume meets read_only

* keep nosuid and nodev on the /run tmpfs

* support read_only in the default mode

* don't take go2rtc down when the homekit file isn't writable

* lead with the hardware consequence of switching to user:

* refuse to write TLS material through a symlink as root

* note that memryx writes models to the root filesystem

* certsync watches whichever cert path nginx loaded
2026-09-12 07:30:04 -06:00
Nicolas Mowen b99c87f272 Fix NPU turbo key and priviledges set (#24138) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 3be59c9c18 Container security hardening (phase 3, breaking) (#24081)
* Run the frigate service as the frigate user

* Run go2rtc as its own restricted user

* Run nginx as the frigate user with writable state in /tmp/nginx

* Disable bandwidth stats gracefully when not running as root

* Hand TensorRT model cache ownership to the runtime user

* Document non-root operation and per-hardware device access

* Create /media/frigate after the ownership sweep

* Assert non-root services, JWT migration, and escape hatch in CI

* only write the sweep sentinel when a media volume is mounted

* tolerate homekit config chown failures in the go2rtc run script

* chown the s6 log pipe so non-root nginx can reopen /dev/stdout

* set HOME to /config for non-root services

* run smoke nginx -t and the write probe as the runtime user

* re-own the nginx shm cache on service restart

* discard stdout for the unprivileged smoke nginx -t

* unwrap hard-wrapped prose in the installation docs

* report progress during the ownership sweep

* document EXTRA_GROUPS as the only device access path for dropped services

* expand the non-root device access docs with diagnosis steps and udev rules

* document network storage ownership and the remaining detector hardware

* skip lost+found during the ownership sweep

* hand /tmp/cache to the runtime user before services start

* make bundled models readable by the runtime user

* reload nginx by signaling the master instead of parsing its config as root

* harden root writes into unprivileged-owned paths

Restrict the sweep sentinel to a mount at or below /media/frigate so a
parent /media mount cannot bless a later-shadowed volume. Rebuild
/tmp/nginx root-owned each start so root's cp and tempio writes cannot
follow a symlink an unprivileged nginx planted in the previous run.

* collapse the duplicated sentinel comment

* add a service-runs-as-root helper for granular root services

* validate FRIGATE_ROOT_SERVICES and fail fast on unknown names

* let services listed in FRIGATE_ROOT_SERVICES skip the privilege drop

* record the root-services mode in the sentinel and sweep small trees each boot

* cache the runtime ids in the ownership helper

* chown recordings, previews, and exports to the runtime user at create

* chown the database files after init

* recommend FRIGATE_ROOT_SERVICES in the bandwidth stats warning

* assert granular root services in CI

* document FRIGATE_ROOT_SERVICES

* own every directory level created for a recording segment

* clear the cached runtime ids when ownership tests finish

* skip missing media paths in the per-boot ownership sweep

* clarify granular root services docs

* clean up

* install acl for device access grants

* grant runtime users access to mapped device nodes at boot

* assert device access grants in CI

* document automatic device access grants

* stop telling users device access needs host side setup

* clarify the non-root docs

* link the migration script to the repo

* group the manual device setup under one section

* harden against symlink attacks

/config is owned by the unprivileged runtime user after the ownership sweep, so root operations on files there could be redirected by a planted symlink.

- go2rtc HomeKit setup: replace the root yq/jq normalization and chown with an O_NOFOLLOW helper (prepare_homekit.py), so a symlink at go2rtc_homekit.yml can't redirect a root write or chown onto another file
- go2rtc binary override: ignore /config/go2rtc whenever the service runs as root, so a planted binary can't exec as root under FRIGATE_ROOT_SERVICES
- sweep sentinel: read and write it through safe-sentinel, which trusts only a root-owned regular file and never follows a symlink, so it can't be forged to skip the migration or symlinked to clobber a root file
- ownership sweep: chown with -execdir so a parent directory swapped for a symlink mid-walk can't redirect the chown out of the volume
- validate inputs: restrict DEVICE_ACL_PATHS to /dev, require nonzero numeric EXTRA_GROUPS, and reject PUID/PGID that collide with the go2rtc ids
- docs: correct the TLS key ownership note to match what actually happens

* tweak docs

* stop the ownership sweep chasing entries other mechanisms own

* keep custom binaries out of root services only under granular root
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 1693415375 switch nginx-vod-module to the maintained dio-az fork (#24123)
The `v1.x` line is the same muxed fMP4 code as Kaltura's 1.31 with fixes backported, so the mapping JSON, manifest routes, and ffmpeg consumers are unchanged. The `MAX_CLIPS` patch applies as-is and the HEVC workaround is rewritten for the fork's reformatted source.

`vod_hls_version 6` is now explicit because the fork replaced Kaltura's automatic version calculation with a directive that defaults to 4 and only warns when fmp4 needs 6, so playlists were being stamped `EXT-X-VERSION:4` while carrying `EXT-X-MAP`. The `error_page 502 =404` hack is gone: https://github.com/kaltura/nginx-vod-module/issues/468 is a `vod_mode remote` bug and we're `mapped`, so those 502s were really `_vod_response` returning 404 upstream. The fork maps that through now, and the hack was also turning real 5xx into "no recordings".
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen aaf18b81b2 Fix LPR vehicle message for multiple models (#24119)
* use the camera's model for the lpr vehicle check

`FrigateConfig.model` became `models[]` in the detector refactor, so this didn't compile on 0.19. Each camera has its own detector/model pair now, so the check resolves the camera's scene with `getModelForCamera` instead of looking at every model.

* use camera config model

* fix export test
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 98706482f5 Add Apple compatibility switch to the camera wizard (#24115)
* add apple compatibility switch to the camera wizard

* don't require every record stream to be h265
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen fb8ab56c41 Add onboarding wizard for new installations (#24102)
* add onboarding wizard for new users

* resolve hwaccel per camera and clarify recording retention

The hwaccel step listed every preset Frigate ships, so an Intel box was offered Raspberry Pi and Rockchip decoding, and the codec specific presets (`preset-intel-qsv-h264` vs `-h265`) were offered as global values that break as soon as two cameras use different codecs. `/hardware/hwaccel` now returns the decoding families the probed hardware can actually use, each carrying a preset per codec, and the wizard resolves the family against the detect stream codec the camera wizard already probed: one global `ffmpeg.hwaccel_args` when every camera agrees, per-camera `cameras.<name>.ffmpeg.hwaccel_args` when they don't. The global stays on `auto` in that case so cameras added later still resolve at startup. A gen13+ Intel machine keeps its QuickSync recommendation with mixed h264 and h265 cameras instead of dropping to vaapi.

The recording step's "Days to retain recordings" only wrote alert and detection retention, and the storage estimate under it assumed continuous recording. It now asks what to record in plain language, writes `record.continuous.days` to match, shows the estimate only for continuous, and drops the spinner arrows on the number input.

* clean up

* add light/dark mode icon switcher

* use yml as default config file extension when not found

* i18n tweaks

* gate the setup wizard on cameras instead of a config key

* render setup wizard steps by key

* share the setup wizard e2e helpers and mock users

* add an account step to the setup wizard

* add setup wizard account step e2e coverage

* cover the account step's restart behavior

* button consistency

* fix test

* docs

* fixes
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 96013a0487 Fix 500 when an event thumbnail file is empty (#24110)
* fix 500 when an event thumbnail file is empty

* fix test
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 0aa086eefd Fix inconsistent export download filenames (#24111)
* fix inconsistent export download filenames

Zip entries in a case download were named from `Export.name`, the friendly display name, while an individual download uses the file name on disk. The two have always been formatted differently, so one export came out as `front_door_20260823_020615-20260823_020734_abc123.mp4` on its own and `front door 2026-08-23 020615 2026-08-23 020734.mp4` inside a zip. Zip entries now use the on-disk file name, and renaming an export renames its file, so there's only one name to download under. The rename is blocked while ffmpeg still holds the file.

* cap filename length and catch duplicate names

* fix export rename and stop blocking the event loop

* move the rename rollback off the event loop

* no awaits
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 53fb6c8daa Recording fixes (#24072)
* pin genai review frames to the main stream

* retain previews as long as either stream has recordings

* watch sub stream recording health separately from main

* reject record_sub on the same input as record and document the role

* derive recording paths from the cache segment timestamp

Recording paths carry one second of resolution, but since sub stream recording start times are resolved to fractional wall clock, anchored to the cache file mtime and chained to the previous segment's end. A stream cutting segments faster than once a second resolves consecutive segments into the same second, so two rows collide on the unique path index and the batch insert fails. The cache segment name is unique per camera stream and second by construction because ffmpeg names segments with strftime, so the recording path is now built from that timestamp while the row keeps the resolved start time. This also restores the path semantics from before sub stream recording, when start times came straight from the cache filename.

Nothing derives times from recording paths: playback offsets, stream switching, and export all use the row's start time, which is unchanged, and the recordings sync matches files by exact path string.

* keep the rest of a recording batch when one row conflicts

* only publish record_sub status when a sub stream is configured

* don't shadow camera_cfg when publishing empty cache streams

* back off restarts when a recording stream goes stale

* give the shared sub stream grace on any capture thread reset

* include segment details in recording discard warnings
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 40793b8478 Container security hardening (phase 2) (#24068)
* Create frigate and go2rtc runtime users in the image

* Add single fix-ownership helper for volume permission migration

* Add init-usermod oneshot for PUID and PGID remapping

* Chown newly created runtime directories to the frigate user

* Run sentinel-guarded ownership sweep during prepare

* Add host-side volume permission migration script

* Guard log directory ownership for user-mode startup

* Fall back to plain s6-log when running without root

* Assert PUID remapping and sweep sentinel in CI smoke test

* Skip the ownership sweep in the devcontainer

* Pin FRIGATE_RUN_AS_ROOT in ownership tests

* Do not record the sweep as complete when a chown failed

* Validate PUID and PGID in the migration script

* Treat a failed ownership scan as an incomplete sweep

* Reject PUID and PGID of 0 during remapping

* Handle symlinks, dry runs, and sentinel write failures in the sweep

* Treat an absent sweep root as an incomplete sweep
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen c004d0a1c9 Container security hardening (phase 1) (#24061)
* Verify s6-overlay downloads against pinned checksums

* Verify go2rtc download against pinned checksums

The v1.9.14 release publishes no checksums file, just the bare per-platform binaries, so these digests come from a one-time fetch rather than upstream. That pins the artifact against later substitution, which is the realistic threat for a version we stay on for months, but it does not verify the original download. The stage moves from `ADD --link` to a script because `ADD --checksum` can't express an architecture-dependent URL.

* Verify main image downloads against pinned checksums

Covers everything the main image downloads on the default path: tempio, the hailort runtime tarball and wheel, the six ffmpeg builds, the libedgetpu deb, and the thirteen Intel driver debs. The hailort tarball was streamed straight into `tar`, which can't be verified before extraction, so it downloads to `/tmp` first. The three ffmpeg blocks per arch collapse into one `install_ffmpeg` helper since they only differed by URL and install dir, and the Intel debs go through a `fetch_intel_deb` helper for the same reason.

The Intel debs are the ones that mattered most here. They're installed as root with `dpkg` on the default amd64 path and had no verification at all. compute-runtime publishes a `ww<week>.sum` asset with every release and npu-driver published `checksum.sha256` on v1.19.0, so those eight digests came from upstream rather than from us. intel-graphics-compiler and level-zero publish none, so those five and everything else here come from a one-time fetch, which pins the artifact against later substitution but doesn't verify the original download. The comment above the map says which is which and how to refresh them, since npu-driver has stopped publishing sums since v1.19.0 and that provenance won't survive the next bump.

Still unpinned: `get-pip.py`, which is a rolling URL where a digest would just break the build on pypa's next edit, and the per-variant artifacts for Axera, Synaptics, and Jetson. apt repositories are out of scope since apt already verifies signatures.

* Restrict generated TLS key permissions

OpenSSL 3.x already writes the key at 600 on its own, so this pins the guarantee rather than fixing an observed leak: the mode no longer depends on the openssl version or the umask the service happens to start with. Only the generated pair is touched. User-mounted certs take the other branch and are never chmod'd, which matters when they're mounted read-only.

* Add security headers and server_tokens off

Adds `X-Content-Type-Options: nosniff` and `Referrer-Policy: strict-origin-when-cross-origin`, and turns off nginx version disclosure.

No `X-Frame-Options` and no CSP `frame-ancestors`. HA's Webpage card and iframe panels frame Frigate's own address cross-origin, and either header would break them silently with nothing in Frigate's logs to explain it. Ingress is same-origin and would survive `SAMEORIGIN`, but Frigate can't tell the two apart from inside the container. `security_headers.conf` is a plain file in the image rather than a generated one, so anyone who does want framing restrictions can bind-mount it.

`add_header` doesn't inherit into a block that declares its own, so the include goes in per block, all nine of them, including the four nested static-asset locations that serve the JS bundles. Those are the ones nosniff actually matters for.

The run script now reads `get_nginx_settings.py` once into a variable instead of shelling out per template. That script imports the frigate config machinery, which is noticeable on an SBC.

Not fixed here: `listen.conf` is included at server level and carries `Strict-Transport-Security`, so those same nine blocks already drop HSTS under TLS today. Folding it into this file would change existing TLS behavior on nine paths, so it needs its own PR.

* Restrict go2rtc config file permissions

* Log failed login attempts with source address

Failed logins returned a bare 401 and left nothing behind, so credential stuffing was invisible unless you were already watching nginx access logs. Both failure branches now log a warning with the attempted username and the client address.

The address comes from `get_remote_addr()`, the same helper the login rate limiter keys on, so the two agree on who the client is and the trusted-proxy handling is consistent. Logging a raw `x-forwarded-for` instead would let an attacker forge the source address in the very log line meant to catch them.

The response is unchanged and identical either way. Which factor failed is only visible in the log, never to the client, and the password is never logged.

* Recommend least-privilege container options in install docs

The compose generator pushed `privileged: true` into every file it produced, no matter what hardware you picked, and it's the default tab on the install page so it's what most people copy. It now emits `security_opt: no-new-privileges:true` instead, and only adds `privileged: true` for hardware that actually needs it, with the reason inline. MemryX is the only one today, since it needs to reach the max-manager. Rockchip and Synaptics only want privileged during initial setup and their documented end state is device mappings, so neither gets it.

`no-new-privileges` merges into the same `security_opt` block as any device-specific entries, so Rockchip still gets its `apparmor=unconfined` and `systempaths=unconfined` without a duplicate key.

The static example now has `privileged` commented out, and there's a short section on the options worth adding, with a note that `cap_drop: ALL` breaks `telemetry.stats.network_bandwidth` since nethogs needs NET_ADMIN/NET_RAW.

* Add amd64 container smoke test to CI

Boots the built amd64 image against a minimal config and asserts the two security headers, that the Server header no longer carries a version, that no frame-ancestors is present, that nginx accepts its own config, and the two file modes. This is also the harness the rest of the hardening work extends.

The two negative assertions are written as `if grep; then exit 1; fi` rather than `! grep`. Bash exempts a negated command from `set -e`, so the `!` form would have passed even with the version and frame-ancestors both present, which is the opposite of what a regression net is for.
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 396a2156b2 Tweaks (#24067)
* improve keyframes messages

* don't pad the labelmap with unknown

`load_labels()` prefilled 91 `unknown` entries before reading the label file, so any model with fewer than 91 classes kept that padding in `merged_labelmap` and `unknown` showed up as a selectable object type in the objects settings UI. The padding only existed so `RemoteObjectDetector.detect` could index the labelmap without a KeyError, and it didn't even cover the empty-file case or Frigate+, which never had a prefill. Both lookups now skip class ids the labelmap doesn't name and warn once per id.
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 853840dfd4 Add secrets.yaml and unify variable substitution sources (#24044)
* add secrets.yaml and merge substitution sources by precedence

FRIGATE_ENV_VARS was built once at import from container env and /run/secrets, and the environment_vars validator overwrote it unconditionally, so the block beat the deployment and nothing could be re-read. Sources are now separate dicts merged lowest to highest (environment_vars, secrets.yaml, container env, credentials directory), re-read at the top of every parse, and a collision warns once naming the winner. An undefined {FRIGATE_*} raises a ValueError subclass so pydantic reports the field instead of a KeyError traceback.

* use the shared substitution namespace in go2rtc config

The generator rebuilt the namespace itself from os.environ and a hardcoded /run/secrets, so it never saw environment_vars or CREDENTIALS_DIRECTORY, and str.format made any stray brace fatal. It now installs the FRIGATE_ names from environment_vars and substitutes streams the same way every other field does.

* read the exec override from an import time snapshot

environment_vars is exported into os.environ, and is_go2rtc_arbitrary_exec_allowed read os.environ live, so the config file could enable exec sources. Snapshot the variable at import, which runs before any config is loaded.

* docs

* clarify docs
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen c9c6a923ef add recognized plate picker to lpr known plates in settings (#24059) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen a16ca285f7 fix clip download deadlock from unread ffmpeg stderr (#24032)
ffmpeg's stderr was piped but never read, so recording segments that generate more than 64 KB of ffmpeg warnings blocked ffmpeg mid-write, stranding the streaming thread and its anyio threadpool token for good. Enough of those and every sync endpoint stops responding until restart. The trigger is how noisy the segments are, not how long the clip is.

Send stderr to a temp file instead, and guarantee ffmpeg teardown and playlist cleanup on every exit path, including client disconnect.

Also fixes two bugs the deadlock hid: the failure branch was unreachable because returncode is None mid-loop, so the playlist file leaked and ffmpeg's logs were never reported. Playlist files now get a unique name so concurrent requests for one range cannot delete each other's input.

Extracts the terminate helper motion search already had into frigate/util/ffmpeg.py, now shared by both streaming call sites.
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 46796fe9ef fix the model lookup KeyError for cameras added at runtime (#24026) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 257a05a7e2 Add import/export for camera group layouts and per-camera streaming settings (#24025)
* add import/export for camera group layouts and streaming settings

Camera group layouts and per-camera streaming settings are stored in the browser's IndexedDB, so they are tied to a single browser on a single device. Users with more than one device have to rebuild every group layout and re-pick every camera's stream settings by hand, and clearing browser data loses the work.

Add a Backup & Restore card to Settings > UI Settings that exports these settings to a JSON file and imports that file on another device. Import shows a confirmation dialog with per-section counts, switches for layouts, streaming settings, and UI preferences, and warnings about camera groups or cameras in the file that are not on this server.

Server-side storage is deliberately avoided. These are per-device presentation settings: a layout arranged for a desktop is wrong on a tablet, and continuous full-resolution streams that are free on a wired LAN are not on a phone. An explicit file moves settings only when the user chooses to move them.

Implementation notes:

- web/src/utils/uiSettingsTransfer.ts owns a registry of transferable IndexedDB keys. Each entry records whether the key is user-namespaced, matching which persistence hook wrote it, plus a zod schema for its value.
- Only registry-known keys are ever written, and only when their value passes that schema. The file format deliberately lets unknown keys survive parsing, so this filter is what prevents a hand-edited file from writing arbitrary storage keys or out-of-range values.
- Export falls back to the legacy un-namespaced key, because the username migration runs lazily on first mount of each owning hook.
- Streaming settings merge per group rather than replacing the whole map, so groups configured only on the receiving device survive.
- Import writes storage and then reloads, because useUserPersistence reads a key only on mount and StreamingSettingsProvider would otherwise write its stale in-memory state back over the import.
- playbackBandwidthEstimate, frigate-search-history, and live-layout are excluded: the first two are measurements and user data rather than preferences, and live-layout's default is derived from the device.

* merge imported streaming settings per camera instead of per group
2026-09-12 07:30:04 -06:00
Nicolas Mowen 27a40a507b Implement UI for managing multiple models (#24023)
* Implement hardware detection and UI management

* Cleanup Frigate+ detection

* Don't count model as changed

* Fixes for audio map error

* Add descriptions

* Enforce that all model must exist

* Fix hardware picking

* Docs fixes

* WebUI cleanup

* Cleanup handling of scenes

* UI refinement

* Cleanup recommended UI

* test fixews
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 3f20209339 Base emergency cleanup on the streams a camera is currently recording (#24022)
* gate emergency cleanup bandwidth on the streams a camera currently records

* settle bandwidth samples per stream instead of per camera

* fix mypy
2026-09-12 07:30:04 -06:00
Nicolas Mowen 8fe35ace3b Refactor detector and model management (#23995)
* Refactor detector and model management

* Fix model resolution field
2026-09-12 07:30:04 -06:00
Ersa Oktavian RamadanandNicolas Mowen fd76eb6c6f Add audio labelmap grouping (#24004)
Allow audio classes to be grouped under a shared configured label.

Keep audio overrides separate from object labels and retain only the highest-scoring grouped detection.

Refs #23967
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 6ba9dd92e4 Show main and sub stream usage separately in Storage Metrics (#24015)
* backend

* frontend

* docs

* test

* report null instead of 0 for a stream with no cached bandwidth sample
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen ad04101c76 Refactor MQTT (#24010)
* refactor mqtt so that Frigate owns the transport lifecycle instead of delegating it to paho

* release the shutdown barrier on worker crash and replay retained publishes the broker never acked

* collapse in-flight retained values by topic and release the shutdown barrier from a finally

* replay the outage buffer before the publish queue so newer values are not reverted
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen f4cf1e1539 Refactor birdseye activity modes as a list and add alerts/detections (#24012)
* backend

* tests

* frontend and i18n

* e2e test schema

* docs
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen e77155a4f1 Improve History's seek startup time and recordings query performance (#24011)
* serve a segment startup ladder so seeks begin playing sooner

nginx-vod was handed one 10s segment per recording file, so every playlist start had to download and decode a full segment before the first frame. Declare real keyframe data per clip and let nginx cut short leading segments from it.

- add vod_bootstrap_segment_durations 1000/2000/4000 so each playlist starts with 1s/2s/4s segments before settling at 10s
- emit real clip-relative keyFrameDurations (plus firstKeyFrameOffset when nonzero) from the recording keyframe index; rows without an index keep the whole-clip declaration, the only safe cut without keyframe knowledge
- drop the manifest's segment_duration field, which was always inert: nginx-vod parses only camelCase segmentDuration
- rebuild the player source at the seek target, quantized to a 10s grid, so the ladder applies to every seek and seek URLs stay repeatable for nginx's mapping and response caches
- route the seek model, in-range checks, and the stale-report guard through the source window rather than the chunk range
- bridge repositioning seeks (>2s from the last played timestamp) through the preview player and hold the release anchor one commit, so neither path paints a stale frame
- clear a pending loading timer before replacing it; an orphaned timer escaped onPlaying's clearTimeout and flashed loading mid-playback

* keep recordings queries on their indexes

Several recordings queries degraded into full scans or large sorts on big databases: the planner ignored index order, or the query shape gave it nothing tight to seek on. Reshape them into bounded seeks and add the composite index the per-stream lookups need.

- index recordings on (camera, stream_type, start_time DESC) and drop the (camera, stream_type) index it supersedes
- walk the recordings summary day by day with EXISTS probes and per-camera MIN/MAX seeks, skipping ahead over empty gaps instead of bucketing every row for the requested cameras
- run the summary endpoint on the event loop rather than the threadpool
- bound the unavailable-recordings query by start_time per camera and merge the results in Python
- bound the expire query's start_time so it seeks the retention window instead of scanning a camera's whole history
- enumerate deleted cameras with one index seek each rather than a camera NOT IN (...) scan
- compute bandwidth with segment_size filtered in a CASE projection; as a WHERE predicate it baited the planner into the (camera, segment_size) index plus a full sort of the camera's history
- fall back to a 1000-segment window when the recent 100 are all zero-size, so an ingest glitch doesn't report zero bandwidth
- limit the needs_refresh count instead of counting every segment
- cover sub-only and sparse calendar days, midnight-spanning day attribution, multi-camera gap merging, deleted-camera expiry, and zero-size segment runs

* fix mypy
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 50e7b76eb5 Enable PTZ control setup in the Add Camera Wizard (#23444)
* add ptz controls to camera via wizard when onvif has already been probed

* i18n

* add e2e test

* backend add and remove subscriber

* tweaks

* turn on switch by default if pan and/or tilt capability is available

* fix test
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 1498231eb9 Add sub stream recording with adaptive quality playback (#24009)
* add sub stream recording with adaptive quality playback

Optionally record a second, lower bitrate stream alongside the main
recording stream via a `record_sub` input role and `record.sub` config block, with its own retention windows.
Recordings rows now carry the stream type plus the media details needed to serve both streams from one manifest: video codec, audio presence, audio codec and rate, and a record-time keyframe index.

Playback resolves coverage across both streams and merges them into a single VOD sequence, falling back to a discontinuity manifest with per-clip init segments when the media signatures differ. The player exposes a quality selector, and an auto governor picks the stream from stall time, bandwidth, codec support, and the save-data hint.

* fix tests and i18n
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen f7c5500ea8 stop creating a config subscriber per capture thread (#24002) 2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen d78f6a7a98 Guard lookups when adding/deleting cameras at runtime (#23994)
* Guard object processor queue handlers against unknown cameras

* Skip embeddings post processing for removed cameras

* End review segments for removed cameras

* Drop queued autotracker moves for removed cameras

* Release tracked event thumbnails when skipping a removed camera

* Add locked accessors for camera states

* Read camera states through the processor accessors

* Guard output and recording paths against cameras not yet known

* Resolve camera state once in ONVIF, notification, and transcription paths
2026-09-12 07:30:04 -06:00
Ersa Oktavian RamadanandNicolas Mowen d6a18e79aa Refactor Birdseye activity types as composable booleans (#23940)
* Add combined motion and object Birdseye mode

Add a motion_objects mode that keeps Birdseye active when motion is detected or a confirmed tracked object is present, including stationary objects.

Wire the mode through configuration, runtime commands, API schemas, documentation, and UI labels. Exclude false-positive trackers and add regression coverage for Birdseye activation and MQTT validation.

* Refactor Birdseye activity types as booleans

Replace combination-specific Birdseye modes with composable boolean activity types for motion, active objects, stationary objects, and continuous display.

Preserve legacy single-mode configuration and MQTT inputs, support canonical comma-separated MQTT combinations, and allow scalar YAML values to be replaced by nested settings through the config API.

* Preserve OpenVINO config translations

Regenerate the configuration translations with the OpenVINO detector schema available so the unrelated production detector labels remain intact.

* Preserve partial Birdseye mode overrides

Allow an empty activity selection with a canonical NONE MQTT state so partial camera and profile overrides can disable inherited flags without failing validation.

Add regression coverage for camera and profile inheritance, document the NONE contract, and keep the generated schema fixture scoped to Birdseye.

* Address Birdseye activity review feedback

Move scalar mode compatibility into the 0.18-1 config migration and reject empty activity selections instead of publishing a NONE state.

Pass activity signals through a frozen dataclass, preserve existing active-object tracker behavior, and require confirmed stationary objects. Revert the generic YAML mutation and cover migration, inheritance, MQTT, and activation regressions.

* Move Birdseye migration to 0.19

Use the 0.19-0 configuration revision for converting scalar Birdseye modes to composable activity flags, and update the migration regression coverage accordingly.

* Remove Birdseye migration test

Drop the dedicated config migration test as requested during review while retaining the 0.19-0 migration implementation.
2026-09-12 07:30:04 -06:00
Josh HawkinsandNicolas Mowen 7841d41bea Fix birdseye layout overlap with mixed landscape/portrait cameras (#22917)
* fix birdseye layout calculation

replace the two pass layout with a single pass pixel space algorithm

* add test
2026-09-12 07:30:04 -06:00
Nicolas Mowen 15c41c9850 Don't require object type for parameter in categorized names tool 2026-09-12 07:30:04 -06:00
3dffa52eac Dynamically resolve Intel NPU (#23761)
* Add support for newer Intel NPU busy time counter

* Resolve Intel NPU device dynamically

---------

Co-authored-by: Filious Louis <1417132+fjlouis@users.noreply.github.com>
2026-09-12 07:30:04 -06:00
DoFabienandNicolas Mowen 8fe8e20d68 Improve recording timeline and VOD query performance (#23862)
* Improve recording timeline and VOD query performance

* Add recording query boundary tests
2026-09-12 07:30:04 -06:00
Nicolas Mowen b2345cdd0b GenAI Chat Prompt Refinements (#23864)
* Prompt refactoring and optimization

* Update spec
2026-09-12 07:30:04 -06:00
Nicolas Mowen c881b346a7 Update to 0.19 2026-09-12 07:30:04 -06:00
Dermot DuffyandGitHub 77a66e75c6 Fix Content-Type for webp, png, and jpeg files in /clips/ (#24208)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
2026-09-06 13:32:44 -06:00
Josh HawkinsandGitHub 4ac92dac72 Sync PWA status bar theme-color with resolved app theme (#24203)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* sync PWA status bar theme-color with resolved app theme

* update tags on login page too

* track system theme preference

* revert
2026-09-05 15:57:39 -06:00
6ceb370abb Translated using Weblate (Korean)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (500 of 500 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (500 of 500 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (500 of 500 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (49 of 49 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (26 of 26 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: ecomen <skymbj@naver.com>
Co-authored-by: sinfancy <yujsjs@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-groups/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ko/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/ko/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Groups
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-05 06:33:55 -05:00
dfefc5a64b Translated using Weblate (Swedish)
Currently translated at 64.6% (517 of 800 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (474 of 474 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jonatan Nyberg <nickwick@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sv/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
2026-09-05 06:33:55 -05:00
97dec9d046 Translated using Weblate (Hungarian)
Currently translated at 7.3% (35 of 474 strings)

Translated using Weblate (Hungarian)

Currently translated at 97.0% (485 of 500 strings)

Translated using Weblate (Hungarian)

Currently translated at 97.0% (485 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Laszlo Bana <banalac@yahoo.com>
Co-authored-by: Martin Rácz <raczmartinroland@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/hu/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/hu/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/audio
2026-09-05 06:33:55 -05:00
ffcadb440e Translated using Weblate (Portuguese)
Currently translated at 57.4% (62 of 108 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (499 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: cantarol CR <cr1996@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/pt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
2026-09-05 06:33:55 -05:00
a381a88b29 Translated using Weblate (Ukrainian)
Currently translated at 4.3% (35 of 800 strings)

Translated using Weblate (Ukrainian)

Currently translated at 9.7% (46 of 474 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: wkornilow <the.wkornilow@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/uk/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
2026-09-05 06:33:55 -05:00
Nicolas MowenandGitHub 9e74adf812 Guard against memory allocation in graph capture (#24192)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
2026-09-04 09:13:30 -06:00
Josh HawkinsandGitHub 287fc42404 Miscellaneous fixes (#24172)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* fix frigate+ submission state bleeding onto the next tracked object

* add Korean

* fix tests
2026-09-03 06:44:18 -05:00
b4d5035b79 Docs: fix Synaptics default model path and warn about v4l2m2m kernel Oops on GT-BE19000AI (#24008)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* Docs: fix Synaptics default model path in object detector docs

The model is installed at /synaptics/mobilenet.synap by docker/synaptics/Dockerfile,
matching the config examples in the same section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Docs: warn about kernel Oops with v4l2m2m hwaccel on ASUS GT-BE19000AI

Enabling the recommended h264_v4l2m2m hwaccel args on the GT-BE19000AI AI
board (SL1680, firmware kernel 5.15.140) triggers a LIST_POISON dereference
Oops in the vendor vpu driver during decoder teardown, requiring a reboot.
Observed and captured on real hardware. Also fixes a Synaptics typo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Docs: drop GT-BE19000AI hwaccel warning per review

Remove the device-specific v4l2m2m kernel Oops warning as requested by
maintainer review; upstream/vendor gotchas are not documented here. The
Synaptics typo fix and model path correction remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 10:39:30 -05:00
7497ef7506 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (49 of 49 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
0d5038c7a0 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
c71939acc2 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Traditional Han script))

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

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Traditional Han script))

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

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (129 of 129 strings)

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% (500 of 500 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (49 of 49 strings)

Translated using Weblate (Chinese (Traditional Han script))

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

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Chinese (Traditional Han script))

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

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (500 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: windasd <me@windasd.tw>
Co-authored-by: 蘭蘭露 <flandrescarlettw@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/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/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-02 08:24:47 -06:00
e983825781 Translated using Weblate (Korean)
Currently translated at 87.4% (437 of 500 strings)

Translated using Weblate (Korean)

Currently translated at 85.6% (428 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: ecomen <skymbj@naver.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ko/
Translation: Frigate NVR/audio
2026-09-02 08:24:47 -06:00
25681444d4 Translated using Weblate (French)
Currently translated at 21.3% (171 of 800 strings)

Translated using Weblate (French)

Currently translated at 59.7% (283 of 474 strings)

Translated using Weblate (French)

Currently translated at 65.4% (847 of 1295 strings)

Translated using Weblate (French)

Currently translated at 30.2% (26 of 86 strings)

Translated using Weblate (French)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (French)

Currently translated at 99.1% (238 of 240 strings)

Translated using Weblate (French)

Currently translated at 18.3% (147 of 800 strings)

Translated using Weblate (French)

Currently translated at 54.8% (260 of 474 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Thomas DAGET <tdaget@gmail.com>
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/config-cameras/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
47d25254e9 Translated using Weblate (Indonesian)
Currently translated at 59.4% (44 of 74 strings)

Translated using Weblate (Indonesian)

Currently translated at 94.4% (102 of 108 strings)

Co-authored-by: Akos <asep.tmt@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/id/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/id/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
2026-09-02 08:24:47 -06:00
3e6d60fcac Added translation using Weblate (Azerbaijani)
Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Azerbaijani)

Update translation files

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

Co-authored-by: Akus <orxanrecebov05@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/
Translation: Frigate NVR/common
2026-09-02 08:24:47 -06:00
77d6b0540d Translated using Weblate (Italian)
Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Gringo <ita.translations@tiscali.it>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/it/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
632af09f73 Translated using Weblate (Polish)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mieszko Stelmach <mieszko.stelmach@gmail.com>
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/config-validation/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pl/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-09-02 08:24:47 -06:00
10eb677e4c Translated using Weblate (Catalan)
Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (240 of 240 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/common/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
bc3e4a0b35 Translated using Weblate (Belarusian)
Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Uladz Maltsau <wldyslw@icloud.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/be/
Translation: Frigate NVR/common
2026-09-02 08:24:47 -06:00
08a13d955e Translated using Weblate (Romanian)
Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ro/
Translation: Frigate NVR/common
2026-09-02 08:24:47 -06:00
ac16decf05 Translated using Weblate (Estonian)
Currently translated at 30.8% (399 of 1295 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/et/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
6e106854e4 Translated using Weblate (German)
Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (German)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Viktor Stier <viktor-stier@gmx.de>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
c6688c8ce7 Translated using Weblate (Tamil)
Currently translated at 1.6% (1 of 60 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Tamil)

Currently translated at 5.0% (25 of 500 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ta/
Translation: Frigate NVR/audio
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
2026-09-02 08:24:47 -06:00
149362c77f Translated using Weblate (Lithuanian)
Currently translated at 1.2% (10 of 800 strings)

Translated using Weblate (Lithuanian)

Currently translated at 2.5% (12 of 474 strings)

Translated using Weblate (Lithuanian)

Currently translated at 42.8% (555 of 1295 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (240 of 240 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: MaBeniu <runnerm@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/lt/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-settings
2026-09-02 08:24:47 -06:00
Nicolas MowenandGitHub 2117b58aba Adjust audio striping (#24161) 2026-09-02 07:29:46 -06:00
Josh HawkinsandGitHub a529656a90 Fix jumping timeline handles in debug replay range selection (#24158)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* fix jumping timeline handles in debug replay range selection

The timeline mirrored the export range into local state that was never reset when the range cleared, so starting a second selection after cancelling one left the stale mirror disagreeing with the new range. The sync effect and the draggable position effect then rewrote each other's values every commit until React hit its update limit. The handles now write the range directly.

* add mobile test
2026-09-01 20:17:29 -05:00
Nicolas MowenandGitHub c1b56b51b0 Backport 0.19 iOS HLS fixes (#24147)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
2026-08-31 12:17:56 -05:00
Josh HawkinsandGitHub d37dff1b49 Docs update (#24131)
* fix incorrect backchannel docs

`#backchannel=0` is a native rtsp source param, not an ffmpeg one, and the troubleshooting warning had it listed as ffmpeg-only. Any `#` modifier on a bare `rtsp://` source turns the backchannel off unless the URL explicitly contains `#backchannel=1`, so a dedicated two-way talk stream can't carry `#video=h264` or anything else.

* add rotation faq
2026-08-29 07:01:11 -06:00
UladzislauandGitHub a745070b76 proper belarusian locale support (#24112)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
2026-08-28 07:08:54 -05:00
4ffe687c44 Translated using Weblate (Finnish)
Currently translated at 25.9% (48 of 185 strings)

Co-authored-by: Harri Avellan <hhamalai@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fi/
Translation: Frigate NVR/views-system
2026-08-28 07:01:48 -05:00
59269238ed Translated using Weblate (Swedish)
Currently translated at 64.6% (517 of 800 strings)

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/config-global/sv/
Translation: Frigate NVR/Config - Global
2026-08-28 07:01:48 -05:00
02da563712 Translated using Weblate (Dutch)
Currently translated at 97.1% (777 of 800 strings)

Translated using Weblate (Dutch)

Currently translated at 88.6% (420 of 474 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: InSaNiTy87 <M.vanderlinden@hotmail.nl>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/nl/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
2026-08-28 07:01:48 -05:00
a670972a4f Translated using Weblate (Indonesian)
Currently translated at 87.0% (94 of 108 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Naufal F <fadhlurrahmannf0812@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/id/
Translation: Frigate NVR/components-dialog
2026-08-28 07:01:48 -05:00
c4d46b7ed3 Translated using Weblate (Catalan)
Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (500 of 500 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/audio/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/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/objects/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/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-28 07:01:48 -05:00
baa1ba718c Translated using Weblate (Belarusian)
Currently translated at 100.0% (23 of 23 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (800 of 800 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (67 of 67 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (239 of 239 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (129 of 129 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (108 of 108 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (500 of 500 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (26 of 26 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (100 of 100 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (50 of 50 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (49 of 49 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (1295 of 1295 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Belarusian)

Currently translated at 10.0% (1 of 10 strings)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Belarusian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Uladz Maltsau <wldyslw@icloud.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-groups/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-validation/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/be/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/be/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Groups
Translation: Frigate NVR/Config - Validation
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-28 07:01:48 -05:00
359d44fc8f Translated using Weblate (Romanian)
Currently translated at 100.0% (1295 of 1295 strings)

Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/views-settings
2026-08-28 07:01:48 -05:00
80efea2554 Translated using Weblate (Russian)
Currently translated at 99.7% (798 of 800 strings)

Co-authored-by: Artem Vladimirov <artyomka71@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ru/
Translation: Frigate NVR/Config - Global
2026-08-28 07:01:48 -05:00
592775bd19 Translated using Weblate (Tamil)
Currently translated at 2.0% (2 of 100 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (10 of 10 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/ta/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ta/
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/views-live
2026-08-28 07:01:48 -05:00
aee6aa1845 Translated using Weblate (Lithuanian)
Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (25 of 25 strings)

Translated using Weblate (Lithuanian)

Currently translated at 1.1% (9 of 800 strings)

Translated using Weblate (Lithuanian)

Currently translated at 2.1% (10 of 474 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (185 of 185 strings)

Translated using Weblate (Lithuanian)

Currently translated at 42.7% (553 of 1295 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (67 of 67 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: MaBeniu <runnerm@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-groups/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/lt/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/lt/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/Config - Groups
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-08-28 07:01:48 -05:00
708 changed files with 24698 additions and 19470 deletions
-1
View File
@@ -55,7 +55,6 @@ Dahua
datasheet
debconf
deci
deepstack
defragment
devcontainer
DEVICEMAP
+2 -2
View File
@@ -26,8 +26,8 @@ body:
id: version
attributes:
label: Beta Version
description: Visible on the System Metrics page in the Web UI. Please include the full version including the build identifier (eg. 0.18.0-beta1, 0.18.0-8b72c7a, etc.)
placeholder: "0.18.0-beta1"
description: Visible on the System Metrics page in the Web UI. Please include the full version including the build identifier (eg. 0.19.0-beta1, 0.19.0-8b72c7a, etc.)
placeholder: "0.19.0-beta1"
validations:
required: true
- type: dropdown
+3 -1
View File
@@ -6,7 +6,9 @@ body:
value: |
Use this form to submit a reproducible bug in Frigate or Frigate's UI.
**⚠️ If you are running a beta version (0.18.0-beta or similar), please use the [Beta Support template](https://github.com/blakeblackshear/frigate/discussions/new?category=beta-support) instead.**
If you are running on Proxmox, please see the [Proxmox FAQ](https://github.com/blakeblackshear/frigate/discussions/23916) and reproduce the issue on a standard Docker install first (bare metal, or a VM running plain Debian/Ubuntu) before submitting here.
**⚠️ If you are running a beta version (0.19.0-beta or similar), please use the [Beta Support template](https://github.com/blakeblackshear/frigate/discussions/new?category=beta-support) instead.**
Before submitting your bug report, please ask the AI with the "Ask AI" button on the [official documentation site][ai] about your issue, [search the discussions][discussions], look at recent open and closed [pull requests][prs], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your bug has already been fixed by the developers or reported by the community.
+17 -15
View File
@@ -23,7 +23,7 @@ jobs:
name: AMD64 Build
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -49,7 +49,7 @@ jobs:
- amd64_build
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -144,9 +144,11 @@ jobs:
done'
- name: Assert device access grants
run: |
# a fake accelerator node created after boot, then the oneshot re-run
# a fake accelerator node created after boot, then the oneshot re-run.
# /command is on PATH only for s6-supervised services, and the
# with-contenv shebang resolves its execline helpers through PATH
docker exec frigate mknod /dev/apex_9 c 120 99
docker exec frigate /etc/s6-overlay/s6-rc.d/init-devices/run
docker exec frigate sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run'
acl=$(docker exec frigate getfacl -p /dev/apex_9)
echo "$acl"
echo "$acl" | grep -q "user:frigate:rw-"
@@ -154,7 +156,7 @@ jobs:
# the usb tree gets recursive grants plus a default ACL that
# newly created nodes inherit (the Coral re-enumeration path)
docker exec frigate sh -c 'mkdir -p /dev/bus/usb/001 && mknod /dev/bus/usb/001/002 c 189 1'
docker exec frigate /etc/s6-overlay/s6-rc.d/init-devices/run
docker exec frigate sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run'
docker exec frigate getfacl -p /dev/bus/usb/001 | grep -q "user:frigate:rwx"
docker exec frigate sh -c 'mknod /dev/bus/usb/001/099 c 189 98 && chmod 664 /dev/bus/usb/001/099'
inherited=$(docker exec frigate getfacl -p /dev/bus/usb/001/099)
@@ -171,7 +173,7 @@ jobs:
# hardware that is absent must stay silent: the literal table entries
# are not globs, so nullglob does not drop them and only an existence
# check keeps them from warning on every boot
out=$(docker exec frigate /etc/s6-overlay/s6-rc.d/init-devices/run)
out=$(docker exec frigate sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run')
echo "$out"
if echo "$out" | grep -q "WARN"; then
echo "grant warned about device nodes that do not exist"; exit 1
@@ -301,7 +303,7 @@ jobs:
# /run must allow exec: S6_READ_ONLY_ROOT has s6 copy its service
# scripts there and run them, and --tmpfs defaults to noexec
docker run -d --name frigate-ro --shm-size 256m \
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755,uid=1000,gid=1000 \
--user 1000:1000 \
--security-opt no-new-privileges:true \
-v /tmp/frigate-config-ro:/config \
@@ -395,7 +397,7 @@ jobs:
# setfacl under a read-only rootfs, which nothing else covers:
# init-devices exits early under --user, so that path is never reached
docker exec frigate-rod mknod /dev/apex_9 c 120 99
docker exec frigate-rod /etc/s6-overlay/s6-rc.d/init-devices/run
docker exec frigate-rod sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run'
docker exec frigate-rod getfacl -p /dev/apex_9 | grep -q "user:frigate:rw-"
docker rm -f frigate-rod
- name: "Assert switching that install to user: still starts"
@@ -403,7 +405,7 @@ jobs:
# the config dir above now holds a go2rtc-owned go2rtc_homekit.yml,
# which user: keeps readable but not writable (no supplementary groups)
docker run -d --name frigate-rod-user --shm-size 256m \
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755,uid=1000,gid=1000 \
--user 1000:1000 \
-v /tmp/frigate-config-rod:/config \
-v /tmp/frigate-media-rod:/media/frigate \
@@ -424,7 +426,7 @@ jobs:
name: ARM Build
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -459,7 +461,7 @@ jobs:
name: Jetson Jetpack 6
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -490,7 +492,7 @@ jobs:
- amd64_build
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -531,7 +533,7 @@ jobs:
- arm64_build
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -556,7 +558,7 @@ jobs:
- arm64_build
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU and Buildx
@@ -588,7 +590,7 @@ jobs:
with:
string: ${{ github.repository }}
- name: Log in to the Container registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with:
registry: ghcr.io
username: ${{ github.actor }}
+10 -10
View File
@@ -16,10 +16,10 @@ jobs:
name: Web - Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 20.x
- run: npm install
@@ -35,10 +35,10 @@ jobs:
name: Web - Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 20.x
- run: npm install
@@ -54,10 +54,10 @@ jobs:
name: Web - E2E Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 20.x
- run: npm install
@@ -86,11 +86,11 @@ jobs:
name: Python Checks
steps:
- name: Check out the repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v7.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Install requirements
@@ -109,10 +109,10 @@ jobs:
name: Python Tests
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 20.x
- name: Install devcontainer cli
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- id: lowercaseRepo
@@ -18,7 +18,7 @@ jobs:
with:
string: ${{ github.repository }}
- name: Log in to the Container registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with:
registry: ghcr.io
username: ${{ github.actor }}
+12 -12
View File
@@ -1,20 +1,20 @@
aiofiles == 24.1.*
click == 8.1.*
aiofiles == 25.1.*
click == 8.5.*
# FastAPI
aiohttp == 3.12.*
starlette == 0.47.*
starlette-context == 0.4.*
fastapi[standard-no-fastapi-cloud-cli] == 0.116.*
uvicorn == 0.35.*
uvicorn == 0.46.*
slowapi == 0.1.*
joserfc == 1.2.*
cryptography == 44.0.*
joserfc == 1.6.*
cryptography == 46.0.*
pathvalidate == 3.3.*
markupsafe == 3.0.*
python-multipart == 0.0.26
python-multipart == 0.0.31
# Classification Model Training
tensorflow == 2.19.* ; platform_machine == 'aarch64'
tensorflow-cpu == 2.19.* ; platform_machine == 'x86_64'
tensorflow-cpu == 2.21.* ; platform_machine == 'x86_64'
# General
mypy == 1.6.1
onvif-zeep-async == 4.0.*
@@ -26,10 +26,10 @@ psutil == 7.1.*
pydantic == 2.10.*
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
pytz == 2025.*
pyzmq == 26.2.*
pyzmq == 27.1.*
ruamel.yaml == 0.18.*
tzlocal == 5.2
requests == 2.32.*
requests == 2.33.*
types-requests == 2.32.*
norfair == 2.3.*
setproctitle == 1.3.*
@@ -51,10 +51,10 @@ google-genai == 1.58.*
ollama == 0.6.*
openai == 1.65.*
# push notifications
py-vapid == 1.9.*
py-vapid == 1.9.4
pywebpush == 2.0.*
# alpr
pyclipper == 1.3.*
pyclipper == 1.4.*
shapely == 2.0.*
rapidfuzz==3.12.*
# HailoRT
@@ -69,7 +69,7 @@ tflite_runtime @ https://github.com/frigate-nvr/TFlite-builds/releases/download/
tflite_runtime @ https://github.com/feranick/TFlite-builds/releases/download/v2.17.1/tflite_runtime-2.17.1-cp311-cp311-linux_aarch64.whl; platform_machine == 'aarch64'
# audio transcription
sherpa-onnx==1.12.*
faster-whisper==1.1.*
faster-whisper==1.2.*
librosa==0.11.*
soundfile==0.13.*
# Memory profiling
@@ -166,7 +166,9 @@ http {
include auth_request.conf;
types {
video/mp4 mp4;
image/jpeg jpg;
image/jpeg jpg jpeg;
image/png png;
image/webp webp;
}
expires 7d;
+1 -19
View File
@@ -824,24 +824,6 @@ cpu:
models:
- devices:
- cpu: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 > Detection models** and add a model. The CodeProject.AI server is not reported by the hardware probe, so set `devices` to `deepstack:http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection` in YAML.
| Field | Value |
| ------------- | ---------------------------------------------------------------------- |
| **API URL** | `http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection` |
| **API Timeout** | `0.1` (seconds) |
yaml: |-
models:
- devices:
- deepstack:http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection
memryx:
title: MemryX
models:
@@ -1033,7 +1015,7 @@ synaptics:
- 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).
download: A synap model is provided in the container at `/synaptics/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 > Detection models** and select **Synaptics NPU** from the **Hardware** dropdown. Then, on the same model, open the **Custom Model** tab and configure:
@@ -499,7 +499,7 @@ cameras:
## Synaptics
Hardware accelerated video de-/encoding is supported on Synpatics SL-series SoC.
Hardware accelerated video de-/encoding is supported on Synaptics SL-series SoC.
### Prerequisites
+8
View File
@@ -313,8 +313,16 @@ To remove root from the container entirely, add Docker's `user:`:
```yaml
user: "1000:1000" # NOT compatible with PUID/PGID, see the run modes table
tmpfs:
- /tmp:size=256m
- /tmp/cache:size=1000000000
- /run:exec,nosuid,nodev,mode=0755,uid=1000,gid=1000,size=16m # uid must match user:
```
`/run` has to be owned by that uid as well. s6 writes its runtime state there before anything else starts, and with no root in the container a root-owned `/run` stops it during init with `cannot create /run/test of writability`. Keep `uid` and `gid` in the tmpfs options matching `user:`, and don't carry that pair back into the default mode, where a root-owned `/run` is what keeps the unprivileged services out of s6's runtime state.
This only bites once root is genuinely gone. s6's init helper is setuid, so `user:` on its own still lets init regain root and correct `/run` itself. The `no-new-privileges:true` above is what blocks that, which is also what makes the `/run` ownership mandatory. Dropping it would hide the problem by handing init root again.
Two things change, and the first one will break a working install if you skip it. The startup device grants can't run, because there is no root left to run them, so every device you pass stops working until you grant that uid access yourself with `group_add:` or a udev rule; see [Manual setup](#manual-setup). Expect this to surface as a driver error rather than a permission error, like `No VA display found` from VAAPI. And every service then runs as that one uid, so go2rtc no longer gets its own restricted user. `/config` and `/media/frigate` have to be owned by that uid already, since Frigate never adjusts ownership in this mode. Switching an existing install over also leaves `/config/go2rtc_homekit.yml` owned by the go2rtc user, which this mode can't write; `chown` it to your uid or HomeKit pairing changes stop persisting. Frigate warns and starts either way.
This mode can also take `cap_drop: [ALL]`, which the default mode cannot: starting as root needs `CAP_CHOWN` for the ownership sweep, `CAP_SETUID` and `CAP_SETGID` to drop to the runtime user, and `CAP_FOWNER` for the device grants.
+1 -25
View File
@@ -362,7 +362,7 @@ Intel NPUs cannot be used under Home Assistant OS, which does not include the NP
:::warning
The network-based detectors (Deepstack and the Apple Silicon client) are being reworked. Their extra options no longer have a place in the config, so only the endpoint carried in the device string is honored right now: Deepstack ignores `api_key` and `api_timeout`, and the Apple Silicon client ignores `request_timeout_ms` and `linger_ms`. Anything else is dropped when your config is migrated.
The Apple Silicon detector client is being reworked. Its extra options no longer have a place in the config, so only the endpoint carried in the device string is honored right now, and `request_timeout_ms` and `linger_ms` are ignored. Anything else is dropped when your config is migrated.
:::
@@ -540,30 +540,6 @@ A TensorFlow Lite model is provided in the container at `/cpu_model.tflite` and
When using CPU detectors, you can add one CPU detector per camera. Adding more detectors than the number of cameras should not improve performance.
## Deepstack / CodeProject.AI Server Detector
:::warning
The network-based detectors (Deepstack and the Apple Silicon client) are being reworked. Their extra options no longer have a place in the config, so only the endpoint carried in the device string is honored right now: Deepstack ignores `api_key` and `api_timeout`, and the Apple Silicon client ignores `request_timeout_ms` and `linger_ms`. Anything else is dropped when your config is migrated.
:::
The Deepstack / CodeProject.AI Server detector for Frigate allows you to integrate Deepstack and CodeProject.AI object detection capabilities into Frigate. CodeProject.AI and DeepStack are open-source AI platforms that can be run on various devices such as the Raspberry Pi, Nvidia Jetson, and other compatible hardware. It is important to note that the integration is performed over the network, so the inference times may not be as fast as native Frigate detectors, but it still provides an efficient and reliable solution for object detection and tracking.
### Setup {#setup-deepstack}
To get started with CodeProject.AI, visit their [official website](https://www.codeproject.com/Articles/5322557/CodeProject-AI-Server-AI-the-easy-way) to follow the instructions to download and install the AI server on your preferred device. Detailed setup instructions for CodeProject.AI are outside the scope of the Frigate documentation.
To integrate CodeProject.AI into Frigate, configure the detector as follows:
### Configuration {#configuration-deepstack}
<ModelConfigDropdown detectorTitle="DeepStack" models={objectDetectorsModels.deepstack.models} />
Replace `<your_codeproject_ai_server_ip>` and `<port>` with the IP address and port of your CodeProject.AI server.
To verify that the integration is working correctly, start Frigate and observe the logs for any error messages related to CodeProject.AI. Additionally, you can check the Frigate web interface to see if the objects detected by CodeProject.AI are being displayed and tracked properly.
# Community Supported Detectors
## MemryX MX3
+1 -1
View File
@@ -450,7 +450,7 @@ For advanced use cases, the [custom export HTTP API](../integrations/api/export-
POST /export/custom/{camera_name}/start/{start_time}/end/{end_time}
```
The request body accepts `ffmpeg_input_args` and `ffmpeg_output_args` to control encoding, frame rate, filters, and other FFmpeg options. If neither is provided, Frigate defaults to time-lapse output settings (25x speed, 30 FPS).
The request body accepts `ffmpeg_input_args` and `ffmpeg_output_args` to control encoding, frame rate, filters, and other FFmpeg options. If neither is provided, Frigate defaults to time-lapse output settings (25x speed, 30 FPS) with audio removed (`-an`). When providing your own `ffmpeg_input_args`, include `-an` if you want audio stripped from the export.
The following example exports a time-lapse at 60x speed with 25 FPS:
+3 -1
View File
@@ -197,7 +197,7 @@ For cameras that support two-way talk, go2rtc will automatically establish an au
To prevent this, you must configure two separate stream instances:
1. One stream instance with `#backchannel=0` for Frigate's viewing, recording, and detection (prevents go2rtc from establishing the blocking backchannel)
2. A second stream instance without `#backchannel=0` for two-way talk functionality (can be used by Frigate's WebRTC viewer or other applications)
2. A second stream instance with no `#` parameters at all for two-way talk functionality (can be used by Frigate's WebRTC viewer or other applications)
Configuration example:
@@ -215,6 +215,8 @@ In this configuration:
- `front_door` stream is used by Frigate for viewing, recording, and detection. The `#backchannel=0` parameter prevents go2rtc from establishing the audio output backchannel, so it won't block two-way talk access.
- `front_door_twoway` stream is used for two-way talk functionality. This stream can be used by Frigate's WebRTC viewer when two-way talk is enabled, or by other applications (like Home Assistant Advanced Camera Card) that need access to the camera's audio output channel.
Any `#` parameter on a bare `rtsp://` source disables the backchannel unless the URL explicitly contains `#backchannel=1`. A two-way talk stream with something like `#video=h264` on it silently loses two-way audio, and Frigate will report that two-way talk is unavailable for that stream.
## Security: Restricted Stream Sources
For security reasons, the `echo:`, `expr:`, and `exec:` stream sources are disabled by default in go2rtc. These sources allow arbitrary command execution and can pose security risks if misconfigured.
@@ -142,10 +142,6 @@ When [notifications](/configuration/notifications) are enabled and users have re
If an [MQTT broker](/integrations/mqtt) is configured, Frigate maintains a connection to the broker's host and port. This is typically a local network connection, but will require internet if you use a cloud-hosted MQTT broker.
### DeepStack / CodeProject.AI
When using the [DeepStack detector plugin](/configuration/object_detectors), Frigate sends images to the configured API endpoint for inference. This is typically local but depends on where the service is hosted.
## WebRTC (STUN)
For [WebRTC live streaming](/configuration/live), Frigate uses STUN for NAT traversal:
+15 -1
View File
@@ -39,6 +39,20 @@ To do this efficiently the following setup is required:
When this is done correctly, the GPU will do the decoding and scaling which will result in a small increase in CPU usage but with better results.
### How can I rotate my camera's video feed?
Rotation is best done in the camera's firmware settings (usually called rotate, flip, or corridor mode) so the video arrives already rotated and no extra processing is needed. Check there first.
If your camera does not support rotation, go2rtc's ffmpeg module can rotate the stream with the `#rotate` parameter (`90`, `180`, `270`, or `-90`), but this is not recommended: rotation requires transcoding (re-encoding) the video, which significantly increases CPU usage, especially for high resolution streams.
```yaml
go2rtc:
streams:
my_camera: "ffmpeg:rtsp://user:password@192.168.1.10:554/stream#video=h264#hardware#rotate=90"
```
Point the camera's inputs at the restream as described in the [restream docs](/configuration/restream.md), and swap `detect -> width` and `detect -> height` to match the rotated resolution.
### My mjpeg stream or snapshots look green and crazy
This almost always means that the width/height defined for your camera are not correct. Double check the resolution with VLC or another player. Also make sure you don't have the width and height values backwards.
@@ -170,6 +184,6 @@ Filters and masks only hide the incorrect result - they don't teach Frigate what
### Where do I see problems Frigate has detected?
Open System > Health. The Notices list shows problems the backend has noticed on its own, such as a camera whose ffmpeg keeps crashing, a detector that had to be restarted, a model download that failed, or recordings being deleted before their retention period. Entries that describe a one-time event can be dismissed; entries that describe an ongoing condition clear themselves once it is fixed.
Open System > Health. The Notices list keeps a record of problems Frigate has found, and you can dismiss any entry to acknowledge it. Ongoing conditions, such as an offline camera or recordings deleted before their retention period, appear in the status bar for admins until they clear, and the status bar links to the Notices list while it has undismissed entries. On mobile, tap the warning icon in the bottom navigation bar to see them.
The Hardware section below the notices shows whether the detection hardware, hardware acceleration, and enrichment devices in your config were found and are being used, so a GPU that silently fell back to the CPU shows up as a warning. Run stream checks to probe every camera's streams for the same problems the camera wizard reports.
+4 -2
View File
@@ -78,7 +78,9 @@ go2rtc:
:::warning
The `#`-modifiers (`#video=`, `#audio=`, `#hardware`, `#backchannel=0`, …) **only take effect on a source that is prefixed with `ffmpeg:`**. Adding them to a bare `rtsp://…#audio=opus` source does nothing: go2rtc ignores them. Likewise, when a source references another stream by name (e.g. `ffmpeg:back#audio=aac`), the name must match the stream key **exactly** (it is case sensitive), or the transcode is silently never produced. This is the single most common configuration mistake. In the Frigate UI, the **Use compatibility mode (ffmpeg)** toggle adds the `ffmpeg:` prefix for you.
The transcoding modifiers (`#video=`, `#audio=`, `#hardware`, …) **only take effect on a source that is prefixed with `ffmpeg:`**. Adding them to a bare `rtsp://…#audio=opus` source does nothing: go2rtc ignores them. Likewise, when a source references another stream by name (e.g. `ffmpeg:back#audio=aac`), the name must match the stream key **exactly** (it is case sensitive), or the transcode is silently never produced. This is the single most common configuration mistake. In the Frigate UI, the **Use compatibility mode (ffmpeg)** toggle adds the `ffmpeg:` prefix for you.
A bare `rtsp://` source reads a different set of modifiers: `#backchannel=`, `#media=`, `#timeout=`, and `#transport=`. These do nothing on an `ffmpeg:` source. Adding **any** modifier to a bare `rtsp://` source also disables the camera's backchannel unless the URL explicitly contains `#backchannel=1`, so a stream dedicated to two-way talk should carry no modifiers at all.
:::
@@ -153,7 +155,7 @@ WebRTC is only attempted when MSE fails or when using a camera's two-way talk fe
- **Codec mismatch**: WebRTC cannot carry H.265 or AAC. The stream backing the WebRTC view must provide Opus (or PCMA/PCMU) audio and H.264 video. Add an `ffmpeg:back#audio=opus` source as shown above.
- **Port `8555` not reachable, or no candidates set**: WebRTC needs port `8555` (both TCP and UDP) open and a reachable candidate advertised. On Docker installs running on a custom/overlay network, go2rtc may advertise unreachable container IPs as ICE candidates; setting `webrtc.filters.candidates: []` and supplying only your host's LAN IP resolves this. See [WebRTC extra configuration](/configuration/live#webrtc-extra-configuration).
- **Two-way talk** additionally requires a secure context (HTTPS or the authenticated port `8971`, because browsers block microphone access on plain HTTP). The camera's RTSP backchannel must also be handled correctly: go2rtc seizes the backchannel by default, which blocks two-way audio for other consumers and can inject static. Disable it on the primary stream with `#backchannel=0` and use a separate dedicated stream for talk, as documented in [preventing go2rtc from blocking two-way audio](/configuration/restream#two-way-talk-restream).
- **Two-way talk** additionally requires a secure context (HTTPS or the authenticated port `8971`, because browsers block microphone access on plain HTTP). The camera's RTSP backchannel must also be handled correctly: go2rtc seizes the backchannel by default, which blocks two-way audio for other consumers and can inject static. Disable it on the primary stream with `#backchannel=0` and use a separate dedicated stream for talk, carrying no `#` modifiers of any kind, as documented in [preventing go2rtc from blocking two-way audio](/configuration/restream#two-way-talk-restream).
## High CPU usage
+554 -377
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -28,7 +28,7 @@
"clsx": "^2.1.1",
"docusaurus-plugin-openapi-docs": "^4.5.1",
"docusaurus-theme-openapi-docs": "^4.5.1",
"js-yaml": "^4.1.1",
"js-yaml": "^4.3.2",
"marked": "^16.4.2",
"prism-react-renderer": "^2.4.1",
"raw-loader": "^4.0.2",
+44 -4
View File
@@ -4171,13 +4171,13 @@ paths:
description: |-
**Access:** Admin role required.
Get the active notices, most severe first.
Get notices, most severe first.
Args:
include_dismissed: Also return event notices the user has dismissed
include_dismissed: Also return dismissed notices, for the history view
Returns:
The active notices
The notices
operationId: get_notices_notices_get
parameters:
- name: include_dismissed
@@ -4221,6 +4221,44 @@ paths:
security:
- frigateAdminAuth: []
x-required-role: admin
/notices/dismissed_checks:
get:
tags:
- Notices
summary: Get Dismissed Checks
description: |-
**Access:** Admin role required.
Get the dismissed config and stream check rows, newest first.
operationId: get_dismissed_checks_notices_dismissed_checks_get
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
security:
- frigateAdminAuth: []
x-required-role: admin
/notices/dismissed:
delete:
tags:
- Notices
summary: Purge Dismissed
description: |-
**Access:** Admin role required.
Delete every dismissed notice and check row so each can show again.
operationId: purge_dismissed_notices_dismissed_delete
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
security:
- frigateAdminAuth: []
x-required-role: admin
/notices/{notice_id}/dismiss:
post:
tags:
@@ -4229,7 +4267,9 @@ paths:
description: |-
**Access:** Admin role required.
Hide an event notice until it is raised again.
Hide a notice or a config or stream check row.
It stays hidden if the same problem happens again.
operationId: dismiss_notice_notices__notice_id__dismiss_post
parameters:
- name: notice_id
+56
View File
@@ -8,6 +8,7 @@ import logging
import os
import re
import secrets
import threading
import time
from datetime import datetime
from pathlib import Path
@@ -34,6 +35,7 @@ from frigate.api.media_auth import (
from frigate.config import AuthConfig, ProxyConfig
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
from frigate.models import User
from frigate.notices import raise_notice
logger = logging.getLogger(__name__)
@@ -253,6 +255,58 @@ class RateLimiter:
rateLimiter = RateLimiter()
# a failed login this long after the user's previous one opens a new burst
FAILED_LOGIN_BURST_GAP_S = 300
# the username comes from the request, so it is cut before it reaches a notice
MAX_NOTICE_USERNAME = 64
# unknown usernames are unbounded, so past this many open bursts a new one only
# reaches the log
MAX_OPEN_BURSTS = 100
class FailedLoginTracker:
"""Groups each user's failed logins into bursts, one notice per burst."""
def __init__(self) -> None:
self._lock = threading.Lock()
# user -> (burst start, last attempt), stalest attempt first
self._bursts: dict[str, tuple[int, float]] = {}
def record(self, user: str, now: float, *, known: bool = False) -> None:
"""Count a failed login toward the user's open burst, or open a new one.
Once MAX_OPEN_BURSTS are open, only a known user opens another.
"""
user = user[:MAX_NOTICE_USERNAME]
with self._lock:
# bursts that went quiet are over
while self._bursts:
stalest = next(iter(self._bursts))
if now - self._bursts[stalest][1] < FAILED_LOGIN_BURST_GAP_S:
break
del self._bursts[stalest]
if (
not known
and user not in self._bursts
and len(self._bursts) >= MAX_OPEN_BURSTS
):
return
start, _ = self._bursts.pop(user, (int(now), now))
self._bursts[user] = (start, now)
raise_notice("failed_login", scope=f"{user}:{start}", params={"user": user})
failed_logins = FailedLoginTracker()
def get_remote_addr(request: Request):
# fall back to the direct TCP peer when no proxy chain is present
@@ -880,6 +934,7 @@ def login(request: Request, body: AppPostLoginBody):
db_user: User = User.get_by_id(user)
except DoesNotExist:
logger.warning(f"Login failed for unknown user '{user}' from {remote_addr}")
failed_logins.record(user, time.time())
return JSONResponse(content={"message": "Login failed"}, status_code=401)
password_hash = db_user.password_hash
@@ -911,6 +966,7 @@ def login(request: Request, body: AppPostLoginBody):
logger.warning(
f"Login failed for user '{user}' (invalid password) from {remote_addr}"
)
failed_logins.record(user, time.time(), known=True)
return JSONResponse(content={"message": "Login failed"}, status_code=401)
+2 -1
View File
@@ -71,6 +71,7 @@ from frigate.jobs.export import (
from frigate.models import Export, ExportCase, Previews, Recordings
from frigate.record.export import (
DEFAULT_TIME_LAPSE_FFMPEG_ARGS,
DEFAULT_TIME_LAPSE_FFMPEG_INPUT_ARGS,
ChaptersEnum,
ExportStreamEnum,
PlaybackSourceEnum,
@@ -1087,7 +1088,7 @@ def export_recording_custom(
# Set default values if not provided (timelapse defaults)
if ffmpeg_input_args is None:
ffmpeg_input_args = ""
ffmpeg_input_args = DEFAULT_TIME_LAPSE_FFMPEG_INPUT_ARGS
if ffmpeg_output_args is None:
ffmpeg_output_args = DEFAULT_TIME_LAPSE_FFMPEG_ARGS
+27 -17
View File
@@ -7,7 +7,6 @@ from fastapi.responses import JSONResponse
from frigate.api.auth import require_role
from frigate.api.defs.tags import Tags
from frigate.notices.registry import DismissResult
logger = logging.getLogger(__name__)
@@ -16,13 +15,13 @@ router = APIRouter(tags=[Tags.notices])
@router.get("/notices", dependencies=[Depends(require_role(["admin"]))])
def get_notices(request: Request, include_dismissed: bool = False) -> JSONResponse:
"""Get the active notices, most severe first.
"""Get notices, most severe first.
Args:
include_dismissed: Also return event notices the user has dismissed
include_dismissed: Also return dismissed notices, for the history view
Returns:
The active notices
The notices
"""
return JSONResponse(
content=request.app.notice_registry.active(include_dismissed=include_dismissed)
@@ -35,26 +34,37 @@ def get_notice_stats(request: Request) -> JSONResponse:
return JSONResponse(content=request.app.notice_registry.stats())
@router.get(
"/notices/dismissed_checks", dependencies=[Depends(require_role(["admin"]))]
)
def get_dismissed_checks(request: Request) -> JSONResponse:
"""Get the dismissed config and stream check rows, newest first."""
return JSONResponse(content=request.app.notice_registry.dismissed_checks())
@router.delete("/notices/dismissed", dependencies=[Depends(require_role(["admin"]))])
def purge_dismissed(request: Request) -> JSONResponse:
"""Delete every dismissed notice and check row so each can show again."""
request.app.notice_registry.purge_dismissed()
return JSONResponse(
content={"success": True, "message": "Dismissed notices cleared"}
)
# model notice ids contain a slash, so the id is a path parameter
@router.post(
"/notices/{notice_id}/dismiss", dependencies=[Depends(require_role(["admin"]))]
"/notices/{notice_id:path}/dismiss",
dependencies=[Depends(require_role(["admin"]))],
)
def dismiss_notice(request: Request, notice_id: str) -> JSONResponse:
"""Hide an event notice until it is raised again."""
result = request.app.notice_registry.dismiss(notice_id)
"""Hide a notice or a config or stream check row.
if result == DismissResult.not_found:
It stays hidden if the same problem happens again.
"""
if not request.app.notice_registry.dismiss(notice_id):
return JSONResponse(
content={"success": False, "message": "Notice not found"},
status_code=404,
)
if result == DismissResult.not_dismissable:
return JSONResponse(
content={
"success": False,
"message": "This notice clears itself when the problem is fixed",
},
status_code=400,
)
return JSONResponse(content={"success": True, "message": "Notice dismissed"})
+9 -11
View File
@@ -73,6 +73,7 @@ from frigate.models import (
Trigger,
User,
)
from frigate.notices import install_registry
from frigate.notices.registry import NoticeRegistry
from frigate.object_detection.base import ObjectDetectProcess
from frigate.object_detection.util import detection_frame_size
@@ -310,10 +311,6 @@ class FrigateApp:
]
self.db.bind(models)
self.notice_registry = NoticeRegistry()
# producers confirm state notices again at startup if they still hold
self.notice_registry.mark_state_notices_unconfirmed()
def check_db_data_migrations(self) -> None:
# check if vacuum needs to be run
if not os.path.exists(f"{CONFIG_DIR}/.exports"):
@@ -325,6 +322,10 @@ class FrigateApp:
migrate_exports(self.config.ffmpeg, list(self.config.cameras.keys()))
def install_notice_registry(self) -> None:
self.notice_registry = NoticeRegistry()
install_registry(self.notice_registry)
def init_embeddings_client(self) -> None:
# Create a client for other processes to use
self.embeddings = EmbeddingsContext(self.db)
@@ -510,9 +511,7 @@ class FrigateApp:
self.record_cleanup.start()
def start_storage_maintainer(self) -> None:
self.storage_maintainer = StorageMaintainer(
self.config, self.stop_event, self.notice_registry
)
self.storage_maintainer = StorageMaintainer(self.config, self.stop_event)
self.storage_maintainer.start()
def start_stats_emitter(self) -> None:
@@ -524,16 +523,14 @@ class FrigateApp:
self.embeddings_metrics,
self.detectors,
self.processes,
self.storage_maintainer,
),
self.stop_event,
notice_registry=self.notice_registry,
)
self.stats_emitter.start()
def start_watchdog(self) -> None:
self.frigate_watchdog = FrigateWatchdog(
self.detectors, self.stop_event, self.notice_registry
)
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
# (attribute on self, key in self.processes, factory)
specs: list[tuple[str, str, Callable[[], FrigateProcess]]] = [
@@ -644,6 +641,7 @@ class FrigateApp:
self.init_embeddings_manager()
self.bind_database()
self.check_db_data_migrations()
self.install_notice_registry()
# Clean up any stale replay camera artifacts (filesystem + DB)
cleanup_replay_cameras()
+1 -14
View File
@@ -357,20 +357,7 @@ class Dispatcher:
return
try:
action = payload.get("action")
kind = payload.get("kind")
scope = payload.get("scope")
if not isinstance(kind, str):
logger.warning("Ignoring notice update without a kind")
elif action == "raise":
self.notice_registry.raise_notice(
kind, scope=scope, params=payload.get("params") or {}
)
elif action == "resolve":
self.notice_registry.resolve(kind, scope)
else:
logger.warning("Ignoring notice update with action %s", action)
self.notice_registry.apply(payload)
except Exception:
# a raise here would kill the REP thread for every process
logger.exception("Failed to apply notice update")
@@ -16,6 +16,7 @@ from frigate.config.classification import CustomClassificationConfig
from frigate.const import CLIPS_DIR, MODEL_CACHE_DIR
from frigate.log import suppress_stderr_during
from frigate.util.builtin import EventsPerSecond, InferenceSpeed, load_labels
from frigate.util.file import trim_oldest_files
from frigate.util.image import calculate_region
from frigate.util.object import box_overlaps
@@ -729,16 +730,4 @@ def write_classification_attempt(
file = os.path.join(folder, f"{event_id}-{timestamp}-{label}-{score}.webp")
os.makedirs(folder, exist_ok=True)
cv2.imwrite(file, frame)
# delete oldest face image if maximum is reached
try:
files = sorted(
filter(lambda f: f.endswith(".webp"), os.listdir(folder)),
key=lambda f: os.path.getctime(os.path.join(folder, f)),
reverse=True,
)
if len(files) > max_files:
os.unlink(os.path.join(folder, files[-1]))
except (FileNotFoundError, OSError):
pass
trim_oldest_files(folder, max_files)
+2 -11
View File
@@ -6,7 +6,6 @@ import json
import logging
import os
import shutil
from pathlib import Path
from typing import Any
import cv2
@@ -28,6 +27,7 @@ from frigate.data_processing.common.face.recognizer import (
)
from frigate.types import TrackedObjectUpdateTypesEnum
from frigate.util.builtin import EventsPerSecond, InferenceSpeed
from frigate.util.file import trim_oldest_files
from frigate.util.image import area
from frigate.util.path import safe_join, sanitize_path_component
@@ -489,13 +489,4 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
)
os.makedirs(folder, exist_ok=True)
cv2.imwrite(file, frame)
files = sorted(
filter(lambda f: f.endswith(".webp"), os.listdir(folder)),
key=lambda f: os.path.getctime(os.path.join(folder, f)),
reverse=True,
)
# delete oldest face image if maximum is reached
if len(files) > self.config.face_recognition.save_attempts:
Path(os.path.join(folder, files[-1])).unlink(missing_ok=True)
trim_oldest_files(folder, self.config.face_recognition.save_attempts)
+37 -26
View File
@@ -247,15 +247,20 @@ class CudaGraphRunner(BaseModelRunner):
EnrichmentModelTypeEnum.yolov9_license_plate.value,
]
# ORT performs two regular runs before it starts capturing, but on some
# driver / cuDNN combinations the arena still has to extend on the run that
# captures, and cudaMalloc is not allowed during capture. Running with
# capture disabled first keeps those allocations outside of the capture.
GRAPH_FREE_WARMUP_RUNS = 2
def __init__(self, session: ort.InferenceSession, cuda_device_id: int):
self._session = session
self._cuda_device_id = cuda_device_id
self._captured = False
self._prepared = False
self._io_binding: ort.IOBinding | None = None
self._input_name: str | None = None
self._output_names: list[str] | None = None
self._input_ortvalue: ort.OrtValue | None = None
self._output_ortvalues: ort.OrtValue | None = None
def get_input_names(self) -> list[str]:
"""Get input names for the model."""
@@ -265,35 +270,41 @@ class CudaGraphRunner(BaseModelRunner):
"""Get the input width of the model."""
return self._session.get_inputs()[0].shape[3]
def _prepare(self, input_name: str, tensor_input: np.ndarray) -> None:
"""Bind CUDA buffers and warm the session up with capture disabled."""
self._io_binding = self._session.io_binding()
self._input_name = input_name
self._output_names = [o.name for o in self._session.get_outputs()]
self._input_ortvalue = ort.OrtValue.ortvalue_from_numpy(
tensor_input, "cuda", self._cuda_device_id
)
self._io_binding.bind_ortvalue_input(self._input_name, self._input_ortvalue)
for name in self._output_names:
# Bind outputs to CUDA and allow ORT to allocate appropriately
self._io_binding.bind_output(name, "cuda", self._cuda_device_id)
# gpu_graph_id -1 disables capture and replay for the run
warmup_options = ort.RunOptions()
warmup_options.add_run_config_entry("gpu_graph_id", "-1")
for _ in range(self.GRAPH_FREE_WARMUP_RUNS):
self._session.run_with_iobinding(self._io_binding, warmup_options)
self._prepared = True
def run(self, input: dict[str, Any]):
# Extract the single tensor input (assuming one input)
input_name = list(input.keys())[0]
tensor_input = input[input_name]
tensor_input = np.ascontiguousarray(tensor_input)
tensor_input = np.ascontiguousarray(input[input_name])
if not self._captured:
# Prepare IOBinding with CUDA buffers and let ORT allocate outputs on device
self._io_binding = self._session.io_binding()
self._input_name = input_name
self._output_names = [o.name for o in self._session.get_outputs()]
if not self._prepared:
self._prepare(input_name, tensor_input)
else:
# Replay using updated input
self._input_ortvalue.update_inplace(tensor_input)
self._input_ortvalue = ort.OrtValue.ortvalue_from_numpy(
tensor_input, "cuda", self._cuda_device_id
)
self._io_binding.bind_ortvalue_input(self._input_name, self._input_ortvalue)
for name in self._output_names:
# Bind outputs to CUDA and allow ORT to allocate appropriately
self._io_binding.bind_output(name, "cuda", self._cuda_device_id)
# First IOBinding run to allocate, execute, and capture CUDA Graph
ro = ort.RunOptions()
self._session.run_with_iobinding(self._io_binding, ro)
self._captured = True
return self._io_binding.copy_outputs_to_cpu()
# Replay using updated input, copy results to CPU
self._input_ortvalue.update_inplace(tensor_input)
ro = ort.RunOptions()
self._session.run_with_iobinding(self._io_binding, ro)
return self._io_binding.copy_outputs_to_cpu()
-105
View File
@@ -1,105 +0,0 @@
import io
import logging
from typing import Literal
import numpy as np
import requests
from PIL import Image
from pydantic import ConfigDict, Field
from frigate.detectors.detection_api import DetectionApi
from frigate.detectors.detector_config import BaseDetectorConfig
logger = logging.getLogger(__name__)
DETECTOR_KEY = "deepstack"
class DeepstackDetectorConfig(BaseDetectorConfig):
"""DeepStack/CodeProject.AI detector that sends images to a remote DeepStack HTTP API for inference. Not recommended."""
model_config = ConfigDict(
title="DeepStack",
)
type: Literal[DETECTOR_KEY]
api_url: str = Field(
default="http://localhost:80/v1/vision/detection",
title="DeepStack API URL",
description="The URL of the DeepStack API.",
)
api_timeout: float = Field(
default=0.1,
title="DeepStack API timeout (in seconds)",
description="Maximum time allowed for a DeepStack API request.",
)
api_key: str = Field(
default="",
title="DeepStack API key (if required)",
description="Optional API key for authenticated DeepStack services.",
)
class DeepStack(DetectionApi):
type_key = DETECTOR_KEY
def __init__(self, detector_config: DeepstackDetectorConfig):
self.api_url = detector_config.api_url
self.api_timeout = detector_config.api_timeout
self.api_key = detector_config.api_key
self.labels = detector_config.model.merged_labelmap
self.session = requests.Session()
def get_label_index(self, label_value):
if label_value.lower() == "truck":
label_value = "car"
for index, value in self.labels.items():
if value == label_value.lower():
return index
return -1
def detect_raw(self, tensor_input):
image_data = np.squeeze(tensor_input).astype(np.uint8)
image = Image.fromarray(image_data)
self.w, self.h = image.size
with io.BytesIO() as output:
image.save(output, format="JPEG")
image_bytes = output.getvalue()
data = {"api_key": self.api_key}
try:
response = self.session.post(
self.api_url,
data=data,
files={"image": image_bytes},
timeout=self.api_timeout,
)
except requests.exceptions.RequestException as ex:
logger.error("Error calling deepstack API: %s", ex)
return np.zeros((20, 6), np.float32)
response_json = response.json()
detections = np.zeros((20, 6), np.float32)
if response_json.get("predictions") is None:
logger.debug(f"Error in parsing response json: {response_json}")
return detections
for i, detection in enumerate(response_json.get("predictions")):
logger.debug(f"Response: {detection}")
if detection["confidence"] < 0.4:
logger.debug("Break due to confidence < 0.4")
break
label = self.get_label_index(detection["label"])
if label < 0:
logger.debug("Break due to unknown label")
break
detections[i] = [
label,
float(detection["confidence"]),
detection["y_min"] / self.h,
detection["x_min"] / self.w,
detection["y_max"] / self.h,
detection["x_max"] / self.w,
]
return detections
+84
View File
@@ -0,0 +1,84 @@
"""Raise and resolve notices from any Frigate process or thread.
A notice records something worth seeing later, such as an event or a setup
problem only the backend can detect. A condition that comes and goes belongs in
stats and the status bar, and a config problem belongs in a settings message
flagged for health.
"""
import logging
import multiprocessing as mp
import threading
from typing import TYPE_CHECKING, Any
from frigate.comms.inter_process import InterProcessRequestor
from frigate.const import UPDATE_NOTICE
if TYPE_CHECKING:
from frigate.notices.registry import NoticeRegistry
logger = logging.getLogger(__name__)
_registry: "NoticeRegistry | None" = None
# zmq sockets are not thread safe, so every thread in a process shares one
# requestor behind a lock
_requestor: InterProcessRequestor | None = None
_requestor_lock = threading.Lock()
def install_registry(registry: "NoticeRegistry | None") -> None:
"""Send this process's notices straight to the registry.
Only the main process installs one. Children start from the forkserver,
so they never inherit it and send over IPC instead.
"""
global _registry
_registry = registry
def raise_notice(
kind: str, *, scope: str | None = None, params: dict[str, Any] | None = None
) -> None:
"""Record a notice, or count another occurrence of it. Never raises."""
_send({"action": "raise", "kind": kind, "scope": scope, "params": params or {}})
def resolve_notice(kind: str, scope: str | None = None) -> None:
"""Delete a notice once its problem is fixed. Never raises."""
_send({"action": "resolve", "kind": kind, "scope": scope})
def resolve_kind(kind: str) -> None:
"""Delete every notice of a kind. Never raises."""
_send({"action": "resolve_kind", "kind": kind})
def flush_notices() -> None:
"""Write repeats the registry held back. Only the main process has any."""
if _registry is None:
return
try:
_registry.flush()
except Exception:
logger.exception("Failed to flush notices")
def _send(update: dict[str, Any]) -> None:
global _requestor
try:
if _registry is not None:
_registry.apply(update)
elif mp.parent_process() is not None:
with _requestor_lock:
if _requestor is None:
_requestor = InterProcessRequestor()
_requestor.send_data(UPDATE_NOTICE, update)
else:
# the main process before startup installs the registry, or a test
logger.debug("No notice registry for %s", update["kind"])
except Exception:
logger.exception("Failed to update notice %s", update["kind"])
+187 -78
View File
@@ -1,49 +1,44 @@
"""Registry of active notices and per-kind occurrence counts."""
"""Registry of notices and per-kind occurrence counts."""
import logging
import threading
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Any
from frigate.const import REPLAY_CAMERA_PREFIX
from frigate.models import Notice, NoticeStats
from frigate.notices.types import (
NOTICE_KINDS,
SEVERITY_ORDER,
NoticeMode,
notice_id,
)
from frigate.notices.types import CHECK_KINDS, NOTICE_KINDS, SEVERITY_ORDER, notice_id
logger = logging.getLogger(__name__)
class DismissResult(str, Enum):
ok = "ok"
not_found = "not_found"
not_dismissable = "not_dismissable"
# last_seen value of a state notice that has not been raised since startup
UNCONFIRMED = 0
@dataclass
class _HeldRepeats:
kind: str
count: int
last_seen: float
params: dict[str, Any]
class NoticeRegistry:
"""Owns the notice tables. Lives in the main process only.
Producers in the main process call it directly. Other processes send an
update_notice request that the dispatcher forwards here. The FastAPI app
runs inside the main process too, so the API's dismiss shares this exact
object, its lock, and its listeners.
Producers call the functions in frigate.notices, which reach this object
directly in the main process and through the dispatcher everywhere else.
It holds no rules for particular kinds; those live in NOTICE_KINDS.
"""
def __init__(self) -> None:
self._lock = threading.Lock()
self._listeners: list[Callable[[], None]] = []
# row id -> repeats of a batch_repeats kind that flush() writes
self._held: dict[str, _HeldRepeats] = {}
def subscribe(self, listener: Callable[[], None]) -> None:
"""Call listener after every change to the active list."""
"""Call listener after every change to the notices."""
self._listeners.append(listener)
def _notify(self) -> None:
@@ -53,6 +48,26 @@ class NoticeRegistry:
except Exception:
logger.exception("Notice listener failed")
def apply(self, update: dict[str, Any]) -> None:
"""Apply one update message built by frigate.notices."""
kind = update.get("kind")
if not isinstance(kind, str):
logger.warning("Ignoring notice update without a kind")
return
match update.get("action"):
case "raise":
self.raise_notice(
kind, scope=update.get("scope"), params=update.get("params")
)
case "resolve":
self.resolve(kind, update.get("scope"))
case "resolve_kind":
self.resolve_kind(kind)
case action:
logger.warning("Ignoring notice update with action %s", action)
def raise_notice(
self,
kind: str,
@@ -60,7 +75,12 @@ class NoticeRegistry:
scope: str | None = None,
params: dict[str, Any] | None = None,
) -> None:
"""Insert or update a notice. See the counting rule in the spec."""
"""Insert a notice or count another occurrence of it.
A dismissed notice stays dismissed when it is raised again, unless its
kind sets reopen_at_count. A kind that should come back after a
dismissal gives each episode its own scope.
"""
definition = NOTICE_KINDS.get(kind)
if definition is None:
@@ -82,6 +102,8 @@ class NoticeRegistry:
existing = Notice.get_or_none(Notice.id == row_id)
if existing is None:
# repeats held for a row that has since been deleted are stale
self._held.pop(row_id, None)
Notice.create(
id=row_id,
kind=kind,
@@ -92,28 +114,48 @@ class NoticeRegistry:
count=1,
dismissed_at=None,
)
self._bump_occurrences(kind, now)
elif definition.mode == NoticeMode.event:
Notice.update(
count=Notice.count + 1,
last_seen=now,
params=params,
dismissed_at=None,
).where(Notice.id == row_id).execute()
self._bump_occurrences(kind, now)
self._bump_occurrences(kind, 1, now)
if definition.keep_latest is not None:
self._prune(kind, definition.keep_latest)
elif not definition.counts_repeats:
return
elif definition.batch_repeats:
held = self._held.setdefault(row_id, _HeldRepeats(kind, 0, now, params))
held.count += 1
held.last_seen = now
held.params = params
return
else:
# one episode counts once; a repeat with nothing new is free
confirmed = existing.last_seen != UNCONFIRMED
if confirmed and existing.params == params:
return
Notice.update(last_seen=now, params=params).where(
Notice.id == row_id
).execute()
self._write_repeats(existing, kind, 1, now, params)
self._notify()
def flush(self) -> None:
"""Write the repeats that batch_repeats kinds held back."""
with self._lock:
held, self._held = self._held, {}
written = False
for row_id, repeats in held.items():
row = Notice.get_or_none(Notice.id == row_id)
# resolved while its repeats waited
if row is None:
continue
self._write_repeats(
row,
repeats.kind,
repeats.count,
repeats.last_seen,
repeats.params,
)
written = True
if written:
self._notify()
def resolve(self, kind: str, scope: str | None = None) -> None:
"""Delete a notice if present. Safe to call when it is absent."""
with self._lock:
@@ -124,8 +166,16 @@ class NoticeRegistry:
if deleted:
self._notify()
def resolve_kind(self, kind: str) -> None:
"""Delete every notice of a kind."""
with self._lock:
deleted = Notice.delete().where(Notice.kind == kind).execute()
if deleted:
self._notify()
def resolve_camera(self, camera: str) -> None:
"""Drop every camera-category notice scoped to a camera being deleted."""
"""Drop the notices and check dismissals of a camera being deleted."""
camera_kinds = [
key
for key, definition in NOTICE_KINDS.items()
@@ -142,33 +192,82 @@ class NoticeRegistry:
.execute()
)
# a stream id names its camera first; a config id ends with camera.<name>
for check in self.dismissed_checks():
check_id = check["id"]
if check_id.startswith(f"stream:{camera}:") or (
check_id.startswith("config:")
and check_id.endswith(f":camera.{camera}")
):
Notice.delete_by_id(check_id)
if deleted:
self._notify()
def dismiss(self, row_id: str) -> DismissResult:
"""Hide an event notice until it is raised again."""
def purge_dismissed(self) -> int:
"""Delete every dismissed row so each can show again. Returns how many."""
with self._lock:
return int(
Notice.delete().where(Notice.dismissed_at.is_null(False)).execute()
)
def dismiss(self, row_id: str) -> bool:
"""Hide a notice or check row for good. Returns False for an unknown id."""
with self._lock:
existing = Notice.get_or_none(Notice.id == row_id)
if existing is None:
return DismissResult.not_found
# a check row gets a notice row only once it is dismissed
kind, _, scope = row_id.partition(":")
definition = NOTICE_KINDS.get(existing.kind)
if kind not in CHECK_KINDS or not scope:
return False
if definition is None or definition.mode == NoticeMode.state:
return DismissResult.not_dismissable
now = datetime.now().timestamp()
Notice.create(
id=row_id,
kind=kind,
scope=scope,
params={},
first_seen=now,
last_seen=now,
count=1,
dismissed_at=now,
)
return True
now = datetime.now().timestamp()
Notice.update(dismissed_at=now).where(Notice.id == row_id).execute()
if existing.dismissed_at is not None:
return True
if existing.kind not in NOTICE_KINDS:
return False
Notice.update(dismissed_at=datetime.now().timestamp()).where(
Notice.id == row_id
).execute()
NoticeStats.update(dismissals=NoticeStats.dismissals + 1).where(
NoticeStats.kind == existing.kind
).execute()
self._notify()
return DismissResult.ok
return True
def dismissed_checks(self) -> list[dict[str, Any]]:
"""Dismissed config and stream check rows, newest first."""
rows = (
Notice.select()
.where(Notice.kind.in_(list(CHECK_KINDS))) # type: ignore[call-arg, arg-type, misc]
.order_by(Notice.dismissed_at.desc())
)
return [{"id": row.id, "dismissed_at": row.dismissed_at} for row in rows]
def active(self, include_dismissed: bool = False) -> list[dict[str, Any]]:
"""The active notices, most severe first, then most recent first."""
"""Notices most severe first, then most recent first.
Args:
include_dismissed: Also return dismissed notices, for the history view
"""
rows = []
for row in Notice.select():
@@ -180,18 +279,15 @@ class NoticeRegistry:
if row.dismissed_at is not None and not include_dismissed:
continue
if definition.mode == NoticeMode.state and row.last_seen == UNCONFIRMED:
continue
rows.append(
{
"id": row.id,
"kind": row.kind,
"mode": definition.mode.value,
"severity": definition.severity.value,
"category": definition.category,
"scope": row.scope,
"params": row.params,
"link": definition.link_for(row.params),
"first_seen": row.first_seen,
"last_seen": row.last_seen,
"count": row.count,
@@ -208,7 +304,7 @@ class NoticeRegistry:
return rows
def stats(self) -> list[dict[str, Any]]:
"""Lifetime counts per kind, for the UI meta line and later analytics."""
"""Lifetime counts per kind, for later analytics."""
return [
{
"kind": row.kind,
@@ -223,41 +319,54 @@ class NoticeRegistry:
if row.kind in NOTICE_KINDS
]
def mark_state_notices_unconfirmed(self) -> None:
"""Hide state notices until their producer raises them again.
def _write_repeats(
self,
row: Notice,
kind: str,
count: int,
last_seen: float,
params: dict[str, Any],
) -> None:
# called with the lock held
fields: dict[str, Any] = {
"count": row.count + count,
"last_seen": last_seen,
"params": params,
}
reopen_at = NOTICE_KINDS[kind].reopen_at_count
Rows already unconfirmed from the previous run are deleted. Hiding
rather than deleting keeps one episode that spans a restart at one
occurrence.
"""
state_kinds = [
key
for key, definition in NOTICE_KINDS.items()
if definition.mode == NoticeMode.state
]
if reopen_at is not None and row.count < reopen_at <= row.count + count:
fields["dismissed_at"] = None
with self._lock:
Notice.delete().where(
Notice.kind.in_(state_kinds), # type: ignore[call-arg, arg-type, misc]
Notice.last_seen == UNCONFIRMED,
).execute()
Notice.update(last_seen=UNCONFIRMED).where(
Notice.kind.in_(state_kinds) # type: ignore[call-arg, arg-type, misc]
).execute()
Notice.update(**fields).where(Notice.id == row.id).execute()
self._bump_occurrences(kind, count, last_seen)
def _bump_occurrences(self, kind: str, now: float) -> None:
def _prune(self, kind: str, keep: int) -> None:
# called with the lock held
newest = (
Notice.select(Notice.id)
.where(Notice.kind == kind)
.order_by(Notice.first_seen.desc())
.limit(keep)
)
Notice.delete().where(
Notice.kind == kind,
Notice.id.not_in(newest), # type: ignore[call-arg, misc]
).execute()
def _bump_occurrences(self, kind: str, count: int, now: float) -> None:
# called with the lock held
stats = NoticeStats.get_or_none(NoticeStats.kind == kind)
if stats is None:
NoticeStats.create(
kind=kind,
occurrences=1,
occurrences=count,
dismissals=0,
first_seen=now,
last_seen=now,
)
else:
NoticeStats.update(
occurrences=NoticeStats.occurrences + 1, last_seen=now
occurrences=NoticeStats.occurrences + count, last_seen=now
).where(NoticeStats.kind == kind).execute()
+55 -17
View File
@@ -1,14 +1,8 @@
"""Notice kinds and the definitions that fix their mode, severity, and category."""
"""Notice kinds and the definitions that fix their severity and category."""
from dataclasses import dataclass
from enum import Enum
class NoticeMode(str, Enum):
"""Whether a producer resolves the notice itself or the user dismisses it."""
state = "state"
event = "event"
from typing import Any
class NoticeSeverity(str, Enum):
@@ -30,33 +24,77 @@ class NoticeKind:
Attributes:
key: Stable machine-readable name, also the translation key suffix
mode: state notices are resolved by their producer, event notices by the user
severity: error, warning, or info
category: what the scope names: camera, detector, and model kinds carry a scope
category: camera, detector, model, or system; a camera scope is a
camera name, and the UI shows it
link: app route or absolute URL for the row, filled in from params
counts_repeats: whether raising an existing notice counts another occurrence
batch_repeats: whether repeats wait in memory for the next flush
reopen_at_count: count at which a dismissed notice shows again
keep_latest: rows of this kind to keep; a new row drops the oldest
reportable: whether a future analytics reporter may send this kind's counts
"""
key: str
mode: NoticeMode
severity: NoticeSeverity
category: str
link: str | None = None
counts_repeats: bool = True
batch_repeats: bool = False
reopen_at_count: int | None = None
keep_latest: int | None = None
reportable: bool = True
def link_for(self, params: dict[str, Any]) -> str | None:
"""The link with params filled in, or None if a param is missing."""
if self.link is None:
return None
try:
return self.link.format(**params)
except (KeyError, IndexError, TypeError, ValueError):
return None
SCOPED_CATEGORIES = frozenset({"camera", "detector", "model"})
_KINDS = (
NoticeKind("ffmpeg_crash_loop", NoticeMode.state, NoticeSeverity.error, "camera"),
NoticeKind("detector_stuck", NoticeMode.event, NoticeSeverity.warning, "detector"),
NoticeKind(
"model_download_failed", NoticeMode.event, NoticeSeverity.error, "model"
"detector_stuck", NoticeSeverity.warning, "detector", link="/system#general"
),
NoticeKind("model_download_failed", NoticeSeverity.error, "model"),
NoticeKind(
"skipped_detections",
NoticeSeverity.warning,
"camera",
link="/system#cameras",
),
NoticeKind("shm_too_low", NoticeSeverity.warning, "system", link="/system#storage"),
# one row per user per burst; the login log lines carry the address
NoticeKind(
"failed_login",
NoticeSeverity.warning,
"system",
link="/logs",
batch_repeats=True,
reopen_at_count=5,
keep_latest=100,
),
# one row per release, so a dismissal lasts until the next release
NoticeKind(
"update_available",
NoticeSeverity.info,
"system",
link="https://github.com/blakeblackshear/frigate/releases/tag/v{version}",
counts_repeats=False,
keep_latest=1,
),
NoticeKind("retention_unmet", NoticeMode.state, NoticeSeverity.error, "storage"),
NoticeKind("update_available", NoticeMode.state, NoticeSeverity.info, "system"),
)
NOTICE_KINDS: dict[str, NoticeKind] = {kind.key: kind for kind in _KINDS}
# the Health tab builds config and stream check rows in the browser, so a notice
# row of these kinds only records a dismissal; its other fields are placeholders
CHECK_KINDS = frozenset({"config", "stream"})
def notice_id(kind: str, scope: str | None) -> str:
"""The row id for a kind and optional scope."""
+3 -2
View File
@@ -50,8 +50,9 @@ from frigate.util.time import is_current_hour
logger = logging.getLogger(__name__)
DEFAULT_TIME_LAPSE_FFMPEG_INPUT_ARGS = "-an"
DEFAULT_TIME_LAPSE_FFMPEG_ARGS = "-vf setpts=0.04*PTS -r 30"
TIMELAPSE_DATA_INPUT_ARGS = "-an -skip_frame nokey"
TIMELAPSE_DATA_INPUT_ARGS = "-skip_frame nokey"
# nginx-vod repackages each stream into fMP4 with a timescale derived from
# that stream's frame rate, and the concat demuxer rescales every input to
@@ -1193,7 +1194,7 @@ class RecordingExporter(threading.Thread):
parse_preset_hardware_acceleration_encode(
self.config.ffmpeg.ffmpeg_path,
hwaccel_args,
f"{self.ffmpeg_input_args} -an {ffmpeg_input}".strip(),
f"{self.ffmpeg_input_args} {ffmpeg_input}".strip(),
f"{self.ffmpeg_output_args} -movflags +faststart".strip(),
EncodeTypeEnum.timelapse,
)
+91 -10
View File
@@ -11,7 +11,7 @@ from typing import Any
from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import FrigateConfig
from frigate.const import FREQUENCY_STATS_POINTS
from frigate.notices.registry import NoticeRegistry
from frigate.notices import flush_notices, raise_notice, resolve_kind, resolve_notice
from frigate.stats.hardware import HardwareStats
from frigate.stats.prometheus import update_metrics
from frigate.stats.util import get_latest_version, is_newer_version, stats_snapshot
@@ -26,6 +26,46 @@ MAX_STATS_POINTS = 80
# how often to ask GitHub for the latest release
VERSION_REFRESH_S = 24 * 60 * 60
# a camera skipping at least this percent of its frames is falling behind
SKIPPED_DETECTIONS_PCT = 5
# for at least this long before it becomes a notice
SKIPPED_DETECTIONS_HOLD_S = 60
# detectors warm up after a start; the status bar waits this long too
STARTUP_GRACE_S = 120
class SkippedDetectionsTracker:
"""Finds cameras whose skipped share stays high long enough for a notice."""
def __init__(self) -> None:
self._since: dict[str, float] = {}
self._raised: set[str] = set()
def update(self, cameras: dict[str, dict[str, Any]], now: float) -> list[str]:
"""Return the cameras whose episode qualified on this sample."""
qualified: list[str] = []
# a removed camera that comes back starts a new episode
for camera in self._since.keys() - cameras.keys():
self._since.pop(camera)
self._raised.discard(camera)
for camera, camera_stats in cameras.items():
if camera_stats["skipped_pct"] < SKIPPED_DETECTIONS_PCT:
self._since.pop(camera, None)
self._raised.discard(camera)
continue
since = self._since.setdefault(camera, now)
if camera not in self._raised and now - since >= SKIPPED_DETECTIONS_HOLD_S:
self._raised.add(camera)
qualified.append(camera)
return qualified
class StatsEmitter(threading.Thread):
def __init__(
@@ -33,15 +73,18 @@ class StatsEmitter(threading.Thread):
config: FrigateConfig,
stats_tracking: StatsTrackingTypes,
stop_event: MpEvent,
notice_registry: NoticeRegistry | None = None,
):
super().__init__(name="frigate_stats_emitter")
self.config = config
self.stats_tracking = stats_tracking
self.stop_event = stop_event
self.notice_registry = notice_registry
self.hardware_stats = HardwareStats(config)
self.stats_history: list[dict[str, Any]] = []
self.skipped_detections = SkippedDetectionsTracker()
# the shm notice's params as last sent, so only a change is written
self._shm_checked = False
self._shm_params: dict[str, Any] | None = None
# create communication for stats
self.requestor = InterProcessRequestor()
@@ -134,9 +177,6 @@ class StatsEmitter(threading.Thread):
def _check_update_notice(self) -> None:
"""Raise or resolve the update notice from the tracked latest version."""
if self.notice_registry is None:
return
latest = self.stats_tracking["latest_frigate_version"]
# a failed lookup says nothing about whether an update exists
@@ -144,11 +184,9 @@ class StatsEmitter(threading.Thread):
return
if is_newer_version(VERSION, latest):
self.notice_registry.raise_notice(
"update_available", params={"version": latest}
)
raise_notice("update_available", scope=latest, params={"version": latest})
else:
self.notice_registry.resolve("update_available")
resolve_kind("update_available")
def _refresh_latest_version(self) -> None:
"""Refresh the latest release on a daemon thread so the request never stalls stats."""
@@ -166,6 +204,46 @@ class StatsEmitter(threading.Thread):
target=refresh, name="frigate_version_check", daemon=True
).start()
def _update_shm_notice(self, shm: dict[str, Any]) -> None:
"""Raise the shm notice while /dev/shm is smaller than the cameras need."""
params = (
{"total": shm["total"], "min": shm["min_shm"]}
if shm and shm["total"] < shm["min_shm"]
else None
)
# the first tick always sends, so a row the last run left is raised
# again or cleared
if self._shm_checked and params == self._shm_params:
return
self._shm_checked = True
self._shm_params = params
if params is None:
resolve_notice("shm_too_low")
else:
raise_notice("shm_too_low", params=params)
def _update_notices(self, stats: dict[str, Any], now: float) -> None:
"""Update notices based on current stats or time."""
# skipped detections
if stats["service"]["uptime"] >= STARTUP_GRACE_S:
for camera in self.skipped_detections.update(stats["cameras"], now):
raise_notice(
"skipped_detections",
scope=camera,
params={"pct": stats["cameras"][camera]["skipped_pct"]},
)
# shm too small for the cameras
self._update_shm_notice(stats["service"]["storage"]["/dev/shm"])
# repeats of batched kinds, such as failed logins
flush_notices()
# add any additional notice types here
def run(self) -> None:
time.sleep(10)
self._check_update_notice()
@@ -186,11 +264,14 @@ class StatsEmitter(threading.Thread):
)
self.stats_history.append(stats)
self.stats_history = self.stats_history[-MAX_STATS_POINTS:]
self._update_notices(stats, time.time())
if counter == 0:
self.requestor.send_data("stats", json.dumps(stats))
logger.debug("Finished stats collection")
# write the repeats held back since the last tick
flush_notices()
self.hardware_stats.stop()
logger.info("Exiting stats emitter...")
+28 -1
View File
@@ -6,7 +6,7 @@ import shutil
import time
from json import JSONDecodeError
from multiprocessing.managers import DictProxy
from typing import Any
from typing import TYPE_CHECKING, Any
import requests
from requests.exceptions import RequestException
@@ -24,6 +24,9 @@ from frigate.util.services import (
)
from frigate.version import VERSION
if TYPE_CHECKING:
from frigate.storage import StorageMaintainer
logger = logging.getLogger(__name__)
@@ -88,6 +91,7 @@ def stats_init(
embeddings_metrics: DataProcessorMetrics,
detectors: dict[str, ObjectDetectProcess],
processes: dict[str, int],
storage_maintainer: "StorageMaintainer | None" = None,
) -> StatsTrackingTypes:
stats_tracking: StatsTrackingTypes = {
"camera_metrics": camera_metrics,
@@ -97,6 +101,7 @@ def stats_init(
"latest_frigate_version": get_latest_version(config),
"last_updated": int(time.time()),
"processes": processes,
"storage_maintainer": storage_maintainer,
}
return stats_tracking
@@ -216,6 +221,18 @@ def embeddings_stats(
return stats
def skipped_percent(skipped_fps: float, camera_fps: float, enabled: bool) -> float:
"""Percent of a camera's frames dropped before detection.
camera_fps counts the dropped frames too. A disabled camera keeps its last
readings, so it reports zero.
"""
if not enabled or camera_fps <= 0:
return 0.0
return round(skipped_fps / camera_fps * 100, 1)
def stats_snapshot(
config: FrigateConfig,
stats_tracking: StatsTrackingTypes,
@@ -273,6 +290,11 @@ def stats_snapshot(
"camera_fps": round(camera_stats.camera_fps.value, 2),
"process_fps": round(camera_stats.process_fps.value, 2),
"skipped_fps": round(camera_stats.skipped_fps.value, 2),
"skipped_pct": skipped_percent(
camera_stats.skipped_fps.value,
current_fps,
config.cameras[name].enabled,
),
"detection_fps": round(camera_stats.detection_fps.value, 2),
"detection_enabled": config.cameras[name].detect.enabled,
"pid": pid,
@@ -301,12 +323,17 @@ def stats_snapshot(
if bandwidth_stats:
stats["bandwidth_usages"] = bandwidth_stats
storage_maintainer = stats_tracking.get("storage_maintainer")
stats["service"] = {
"uptime": (int(time.time()) - stats_tracking["started"]),
"version": VERSION,
"latest_version": stats_tracking["latest_frigate_version"],
"storage": {},
"last_updated": int(time.time()),
"retention_unmet": bool(
storage_maintainer and storage_maintainer.retention_unmet
),
}
for path in [RECORD_DIR, CLIPS_DIR, CACHE_DIR]:
+8 -21
View File
@@ -20,7 +20,6 @@ from frigate.const import (
STREAM_TYPE_SUB,
)
from frigate.models import Event, Recordings
from frigate.notices.registry import NoticeRegistry
from frigate.util.builtin import clear_and_unlink
logger = logging.getLogger(__name__)
@@ -35,17 +34,14 @@ BANDWIDTH_SAMPLE_TARGET = 50
class StorageMaintainer(threading.Thread):
"""Maintain frigates recording storage."""
def __init__(
self,
config: FrigateConfig,
stop_event: MpEvent,
notice_registry: NoticeRegistry | None = None,
) -> None:
def __init__(self, config: FrigateConfig, stop_event: MpEvent) -> None:
super().__init__(name="storage_maintainer")
self.config = config
self.stop_event = stop_event
self.notice_registry = notice_registry
self.camera_storage_stats: dict[str, dict] = {}
# read by stats; true while maintenance has to delete retained recordings
self.retention_unmet = False
self.config_subscriber = CameraConfigUpdateSubscriber(
self.config,
self.config.cameras,
@@ -350,19 +346,10 @@ class StorageMaintainer(threading.Thread):
# this file was not found so we must assume no space was cleaned up
pass
if self.notice_registry is not None:
self.notice_registry.raise_notice(
"retention_unmet",
params={
"needed_mb": round(float(hourly_bandwidth), 1),
"cleared_mb": round(float(deleted_segments_size), 1),
},
)
self.retention_unmet = True
else:
logger.info(f"Cleaned up {deleted_segments_size:.2f} MB of recordings")
if self.notice_registry is not None:
self.notice_registry.resolve("retention_unmet")
self.retention_unmet = False
logger.debug(f"Expiring {len(deleted_recordings)} recordings")
# delete up to 100,000 at a time
@@ -448,5 +435,5 @@ class StorageMaintainer(threading.Thread):
"Less than 1 hour of recording space left, running storage maintenance..."
)
self.reduce_storage_consumption()
elif self.notice_registry is not None:
self.notice_registry.resolve("retention_unmet")
else:
self.retention_unmet = False
+122 -2
View File
@@ -1,9 +1,16 @@
"""Tests for authentication endpoints."""
import os
from unittest.mock import patch
import unittest
from unittest.mock import ANY, patch
from frigate.api.auth import hash_password
from frigate.api.auth import (
FAILED_LOGIN_BURST_GAP_S,
MAX_NOTICE_USERNAME,
MAX_OPEN_BURSTS,
FailedLoginTracker,
hash_password,
)
from frigate.const import JWT_SECRET_ENV_VAR
from frigate.models import User
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
@@ -43,3 +50,116 @@ class TestHttpAuth(BaseTestHttp):
)
assert response.status_code == 401
assert any("Login failed" in m and "admin" in m for m in logs.output)
@patch.dict(os.environ, {JWT_SECRET_ENV_VAR: "test-secret"})
class TestFailedLoginNotices(BaseTestHttp):
def setUp(self):
super().setUp([User])
self.app = super().create_app()
User.insert(
username="admin",
password_hash=hash_password("correct-horse-battery", iterations=1000),
role="admin",
notification_tokens=[],
).execute()
tracker_patch = patch("frigate.api.auth.failed_logins")
self.failed_logins = tracker_patch.start()
self.addCleanup(tracker_patch.stop)
def tearDown(self):
User.delete().execute()
super().tearDown()
def _login(self, user: str, password: str) -> int:
with AuthTestClient(self.app) as client:
response = client.post("/login", json={"user": user, "password": password})
return response.status_code
def test_unknown_user_is_recorded(self):
self.assertEqual(self._login("ghost", "irrelevant"), 401)
self.failed_logins.record.assert_called_once_with("ghost", ANY)
def test_bad_password_is_recorded_as_known(self):
self.assertEqual(self._login("admin", "wrong"), 401)
self.failed_logins.record.assert_called_once_with("admin", ANY, known=True)
def test_success_is_not_recorded(self):
self.assertEqual(self._login("admin", "correct-horse-battery"), 200)
self.failed_logins.record.assert_not_called()
class TestFailedLoginTracker(unittest.TestCase):
def setUp(self):
self.tracker = FailedLoginTracker()
raise_patch = patch("frigate.api.auth.raise_notice")
self.raise_notice = raise_patch.start()
self.addCleanup(raise_patch.stop)
def _scopes(self) -> list[str]:
return [call.kwargs["scope"] for call in self.raise_notice.call_args_list]
def _fill(self, now: float) -> None:
for index in range(MAX_OPEN_BURSTS):
self.tracker.record(f"ghost{index}", now)
def test_attempts_inside_the_gap_share_a_burst(self):
for now in (1000.0, 1010.0, 1299.0):
self.tracker.record("admin", now)
self.raise_notice.assert_called_with(
"failed_login", scope="admin:1000", params={"user": "admin"}
)
self.assertEqual(self._scopes(), ["admin:1000"] * 3)
def test_each_user_gets_their_own_burst(self):
self.tracker.record("admin", 1000.0)
self.tracker.record("ghost", 1010.0)
self.tracker.record("admin", 1020.0)
self.assertEqual(self._scopes(), ["admin:1000", "ghost:1010", "admin:1000"])
def test_a_quiet_gap_opens_a_new_burst(self):
self.tracker.record("admin", 1000.0)
self.tracker.record("admin", 1000.0 + FAILED_LOGIN_BURST_GAP_S)
self.assertEqual(self._scopes(), ["admin:1000", "admin:1300"])
def test_the_gap_runs_from_the_latest_attempt(self):
for now in (1000.0, 1200.0, 1450.0):
self.tracker.record("admin", now)
self.assertEqual(self._scopes(), ["admin:1000"] * 3)
def test_quiet_users_are_forgotten(self):
self.tracker.record("ghost", 1000.0)
self.tracker.record("admin", 1000.0 + FAILED_LOGIN_BURST_GAP_S)
self.assertEqual(list(self.tracker._bursts), ["admin"])
def test_a_long_username_is_cut(self):
self.tracker.record("x" * 500, 1000.0)
user = self.raise_notice.call_args.kwargs["params"]["user"]
self.assertEqual(len(user), MAX_NOTICE_USERNAME)
def test_unknown_users_past_the_cap_get_no_notice(self):
self._fill(1000.0)
self.tracker.record("one-too-many", 1001.0)
self.tracker.record("ghost0", 1002.0)
self.assertEqual(self.raise_notice.call_count, MAX_OPEN_BURSTS + 1)
self.assertEqual(self._scopes()[-1], "ghost0:1000")
def test_known_users_skip_the_cap(self):
self._fill(1000.0)
self.tracker.record("admin", 1001.0, known=True)
self.assertEqual(self._scopes()[-1], "admin:1001")
def test_quiet_bursts_free_their_slots(self):
self._fill(1000.0)
self.tracker.record("late", 1000.0 + FAILED_LOGIN_BURST_GAP_S)
self.assertEqual(self._scopes()[-1], "late:1300")
+87 -15
View File
@@ -6,6 +6,8 @@ from frigate.models import Notice, NoticeStats
from frigate.notices.registry import NoticeRegistry
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
CONFIG_CHECK = "config:detect:fps-greater-than-five:camera.garage"
class TestHttpNotices(BaseTestHttp):
def setUp(self):
@@ -17,20 +19,32 @@ class TestHttpNotices(BaseTestHttp):
return AuthTestClient(self.app)
def test_get_notices_orders_by_severity(self):
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
self.registry.raise_notice("retention_unmet", params={"needed_mb": 1})
self.registry.raise_notice(
"detector_stuck", scope="ov", params={"detector": "ov"}
)
self.registry.raise_notice(
"model_download_failed",
scope="yolo/model.onnx",
params={"file": "model.onnx", "error": "timeout"},
)
with self.client() as client:
response = client.get("/notices")
self.assertEqual(response.status_code, 200)
self.assertEqual(
[n["id"] for n in response.json()], ["retention_unmet", "detector_stuck"]
[n["id"] for n in response.json()],
["model_download_failed:yolo/model.onnx", "detector_stuck:ov"],
)
self.assertEqual(response.json()[0]["severity"], "error")
self.assertEqual(
[n["link"] for n in response.json()], [None, "/system#general"]
)
def test_get_stats(self):
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
self.registry.raise_notice(
"detector_stuck", scope="ov", params={"detector": "ov"}
)
with self.client() as client:
response = client.get("/notices/stats")
@@ -39,15 +53,33 @@ class TestHttpNotices(BaseTestHttp):
self.assertEqual(response.json()[0]["kind"], "detector_stuck")
self.assertEqual(response.json()[0]["occurrences"], 1)
def test_dismiss_event(self):
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
def test_dismiss_hides_and_history_keeps_it(self):
self.registry.raise_notice(
"detector_stuck", scope="ov", params={"detector": "ov"}
)
with self.client() as client:
response = client.post("/notices/detector_stuck/dismiss")
response = client.post("/notices/detector_stuck:ov/dismiss")
listed = client.get("/notices").json()
history = client.get("/notices", params={"include_dismissed": True}).json()
self.assertEqual(response.status_code, 200)
self.assertEqual(listed, [])
self.assertEqual([n["id"] for n in history], ["detector_stuck:ov"])
self.assertIsNotNone(history[0]["dismissed_at"])
def test_dismiss_an_id_with_a_slash(self):
self.registry.raise_notice(
"model_download_failed", scope="yolo/model.onnx", params={}
)
with self.client() as client:
response = client.post(
"/notices/model_download_failed:yolo/model.onnx/dismiss"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(self.registry.active(), [])
def test_dismiss_unknown_is_404(self):
with self.client() as client:
@@ -55,14 +87,6 @@ class TestHttpNotices(BaseTestHttp):
self.assertEqual(response.status_code, 404)
def test_dismiss_state_is_400(self):
self.registry.raise_notice("retention_unmet", params={})
with self.client() as client:
response = client.post("/notices/retention_unmet/dismiss")
self.assertEqual(response.status_code, 400)
def test_requires_admin(self):
with TestClient(self.app) as client:
response = client.get(
@@ -70,3 +94,51 @@ class TestHttpNotices(BaseTestHttp):
)
self.assertEqual(response.status_code, 403)
def test_dismissed_checks_are_listed_once(self):
with self.client() as client:
first = client.post(f"/notices/{CONFIG_CHECK}/dismiss")
again = client.post(f"/notices/{CONFIG_CHECK}/dismiss")
listed = client.get("/notices/dismissed_checks").json()
history = client.get("/notices", params={"include_dismissed": True}).json()
self.assertEqual(first.status_code, 200)
self.assertEqual(again.status_code, 200)
self.assertEqual([check["id"] for check in listed], [CONFIG_CHECK])
self.assertIsNotNone(listed[0]["dismissed_at"])
# the Health tab builds the row itself, so it never comes back as a notice
self.assertEqual(history, [])
def test_dismissed_checks_require_admin(self):
with TestClient(self.app) as client:
response = client.get(
"/notices/dismissed_checks",
headers={"remote-user": "viewer", "remote-role": "viewer"},
)
self.assertEqual(response.status_code, 403)
def test_purge_dismissed_clears_notices_and_checks(self):
self.registry.raise_notice(
"detector_stuck", scope="ov", params={"detector": "ov"}
)
self.registry.dismiss("detector_stuck:ov")
self.registry.dismiss(CONFIG_CHECK)
with self.client() as client:
response = client.delete("/notices/dismissed")
history = client.get("/notices", params={"include_dismissed": True}).json()
checks = client.get("/notices/dismissed_checks").json()
self.assertEqual(response.status_code, 200)
self.assertEqual(history, [])
self.assertEqual(checks, [])
def test_purge_dismissed_requires_admin(self):
with TestClient(self.app) as client:
response = client.delete(
"/notices/dismissed",
headers={"remote-user": "viewer", "remote-role": "viewer"},
)
self.assertEqual(response.status_code, 403)
+1 -77
View File
@@ -6,11 +6,7 @@ from unittest.mock import MagicMock, patch
from frigate.config import FrigateConfig
from frigate.const import STREAM_TYPE_MAIN, STREAM_TYPE_SUB
from frigate.video.ffmpeg import (
CRASH_LOOP_RECOVERY_S,
CRASH_LOOP_RESTARTS,
CameraWatchdog,
)
from frigate.video.ffmpeg import CameraWatchdog
def build_watchdog(
@@ -228,75 +224,3 @@ class TestCameraWatchdogStreamHealth(unittest.TestCase):
assert watchdog.record_stale_threshold[STREAM_TYPE_MAIN] == 120
assert watchdog.record_stale_threshold[STREAM_TYPE_SUB] == 150
class TestCameraWatchdogCrashLoop(unittest.TestCase):
"""The crash loop notice follows restarts in the trailing hour."""
def _build_watchdog(self) -> CameraWatchdog:
return build_watchdog()
def _notice_payloads(self, watchdog: CameraWatchdog) -> list[dict]:
return [
call.args[1]
for call in watchdog.requestor.send_data.call_args_list
if call.args[0] == "update_notice"
]
def test_below_threshold_raises_nothing(self):
watchdog = self._build_watchdog()
now = datetime.now().timestamp()
watchdog.reconnect_timestamps.extend([now] * (CRASH_LOOP_RESTARTS - 1))
watchdog.capture_thread = MagicMock()
watchdog.capture_thread.is_alive.return_value = False
watchdog._update_crash_loop_notice(now)
self.assertEqual(self._notice_payloads(watchdog), [])
def test_threshold_raises_once_per_restart_count(self):
watchdog = self._build_watchdog()
now = datetime.now().timestamp()
watchdog.reconnect_timestamps.extend([now] * CRASH_LOOP_RESTARTS)
watchdog.capture_thread = MagicMock()
watchdog.capture_thread.is_alive.return_value = False
watchdog._update_crash_loop_notice(now)
watchdog._update_crash_loop_notice(now)
payloads = self._notice_payloads(watchdog)
self.assertEqual(len(payloads), 1)
self.assertEqual(payloads[0]["action"], "raise")
self.assertEqual(payloads[0]["kind"], "ffmpeg_crash_loop")
self.assertEqual(payloads[0]["scope"], "front_door")
self.assertEqual(payloads[0]["params"], {"restarts": CRASH_LOOP_RESTARTS})
def test_recovery_window_resolves(self):
watchdog = self._build_watchdog()
now = datetime.now().timestamp()
# the last restart is at `now`, so recovery is measured from there
watchdog.reconnect_timestamps.extend([now] * CRASH_LOOP_RESTARTS)
watchdog.capture_thread = MagicMock()
watchdog.capture_thread.is_alive.return_value = False
watchdog._update_crash_loop_notice(now)
watchdog.capture_thread.is_alive.return_value = True
watchdog._update_crash_loop_notice(now + CRASH_LOOP_RECOVERY_S - 1)
self.assertEqual(len(self._notice_payloads(watchdog)), 1)
watchdog._update_crash_loop_notice(now + CRASH_LOOP_RECOVERY_S + 1)
payloads = self._notice_payloads(watchdog)
self.assertEqual(len(payloads), 2)
self.assertEqual(payloads[1]["action"], "resolve")
self.assertFalse(watchdog.crash_loop_raised)
def test_disable_resolves(self):
watchdog = self._build_watchdog()
watchdog.crash_loop_raised = True
watchdog._resolve_crash_loop()
payloads = self._notice_payloads(watchdog)
self.assertEqual(payloads[-1]["action"], "resolve")
self.assertFalse(watchdog.crash_loop_raised)
+6 -6
View File
@@ -128,10 +128,10 @@ class TestMigrateModels(unittest.TestCase):
migrated = migrate_models(
{
"detectors": {
"ds": {
"type": "deepstack",
"api_url": "http://host:5000/v1/vision/detection",
"api_key": "secret",
"remote": {
"type": "zmq",
"endpoint": "tcp://host:5555",
"request_timeout_ms": 200,
}
}
}
@@ -139,9 +139,9 @@ class TestMigrateModels(unittest.TestCase):
self.assertEqual(
migrated["models"][0]["devices"],
["deepstack:http://host:5000/v1/vision/detection"],
["zmq:tcp://host:5555"],
)
self.assertTrue(any("api_key" in message for message in logs.output))
self.assertTrue(any("request_timeout_ms" in message for message in logs.output))
def test_mixed_detector_types_are_logged(self):
with self.assertLogs("frigate.util.config", level=logging.ERROR) as logs:
+48 -1
View File
@@ -1,10 +1,15 @@
"""Tests for ONNX Runtime session option selection."""
import unittest
from unittest.mock import MagicMock, patch
import numpy as np
import onnxruntime as ort
from frigate.detectors.detection_runners import get_ort_session_options
from frigate.detectors.detection_runners import (
CudaGraphRunner,
get_ort_session_options,
)
from frigate.detectors.detector_config import ModelTypeEnum
from frigate.embeddings.types import EnrichmentModelTypeEnum
@@ -39,3 +44,45 @@ class TestGetOrtSessionOptions(unittest.TestCase):
]:
with self.subTest(model_type=model_type):
self.assertIsNone(get_ort_session_options(model_type))
class TestCudaGraphRunner(unittest.TestCase):
"""CUDA graph capture fails if the arena has to allocate during capture, so
the session is warmed up with capture disabled before the first real run."""
def setUp(self):
self.session = MagicMock()
self.session.get_outputs.return_value = [MagicMock(name="output")]
self.io_binding = self.session.io_binding.return_value
self.input = {"images": np.zeros((1, 3, 320, 320), np.float32)}
def _annotations(self) -> list[str | None]:
"""Graph annotation id passed with each run, None when unset."""
annotations = []
for call in self.session.run_with_iobinding.call_args_list:
try:
annotations.append(call.args[1].get_run_config_entry("gpu_graph_id"))
except RuntimeError:
annotations.append(None)
return annotations
def test_first_run_warms_up_with_capture_disabled(self):
with patch.object(ort.OrtValue, "ortvalue_from_numpy"):
CudaGraphRunner(self.session, 0).run(self.input)
self.assertEqual(
self._annotations(),
["-1"] * CudaGraphRunner.GRAPH_FREE_WARMUP_RUNS + [None],
)
def test_later_runs_allow_capture(self):
with patch.object(ort.OrtValue, "ortvalue_from_numpy"):
runner = CudaGraphRunner(self.session, 0)
runner.run(self.input)
self.session.run_with_iobinding.reset_mock()
runner.run(self.input)
self.assertEqual(self._annotations(), [None])
runner._input_ortvalue.update_inplace.assert_called_once()
+15 -24
View File
@@ -518,7 +518,7 @@ class TestNoticeWiring(unittest.TestCase):
def setUp(self) -> None:
self.registry = MagicMock()
self.registry.active.return_value = [{"id": "retention_unmet"}]
self.registry.active.return_value = [{"id": "detector_stuck:ov"}]
config = MagicMock()
config.cameras = {}
@@ -540,37 +540,28 @@ class TestNoticeWiring(unittest.TestCase):
self.dispatcher._publish_notices
)
def test_raise_request_reaches_registry(self) -> None:
self.dispatcher._receive(
"update_notice",
{
"action": "raise",
"kind": "ffmpeg_crash_loop",
"scope": "front_door",
"params": {"restarts": 5},
},
)
def test_update_request_reaches_registry(self) -> None:
update = {
"action": "raise",
"kind": "model_download_failed",
"scope": "yolo/model.onnx",
"params": {"file": "model.onnx", "error": "timeout"},
}
self.registry.raise_notice.assert_called_once_with(
"ffmpeg_crash_loop", scope="front_door", params={"restarts": 5}
)
self.dispatcher._receive("update_notice", update)
def test_resolve_request_reaches_registry(self) -> None:
self.dispatcher._receive(
"update_notice",
{"action": "resolve", "kind": "retention_unmet", "scope": None},
)
self.registry.resolve.assert_called_once_with("retention_unmet", None)
self.registry.apply.assert_called_once_with(update)
def test_malformed_request_does_not_raise(self) -> None:
self.registry.raise_notice.side_effect = RuntimeError("boom")
self.registry.apply.side_effect = RuntimeError("boom")
self.dispatcher._receive("update_notice", "not a dict")
self.dispatcher._receive(
"update_notice", {"action": "raise", "kind": "x", "params": {}}
)
self.registry.apply.assert_called_once()
def test_publish_local_skips_mqtt(self) -> None:
mqtt = MagicMock(spec=MqttClient)
other = MagicMock()
@@ -587,7 +578,7 @@ class TestNoticeWiring(unittest.TestCase):
self.dispatcher._publish_notices()
self.dispatcher.publish_local.assert_called_once_with(
"notices", '[{"id": "retention_unmet"}]'
"notices", '[{"id": "detector_stuck:ov"}]'
)
def test_snapshot_includes_notices(self) -> None:
@@ -599,4 +590,4 @@ class TestNoticeWiring(unittest.TestCase):
self.dispatcher.publish_runtime_snapshot(publisher)
publisher.assert_any_call("notices", '[{"id": "retention_unmet"}]', False)
publisher.assert_any_call("notices", '[{"id": "detector_stuck:ov"}]', False)
+50
View File
@@ -1,5 +1,6 @@
import os
import tempfile
import time
from types import SimpleNamespace
from unittest import TestCase
from unittest.mock import patch
@@ -86,3 +87,52 @@ class TestFileUtils(TestCase):
pass
assert file_util.get_event_thumbnail_bytes(event) is None
class TestTrimOldestFiles(TestCase):
def _fill(self, folder: str, names: list[str]) -> None:
# a short gap keeps the ctime order deterministic
for name in names:
with open(os.path.join(folder, name), "wb"):
pass
time.sleep(0.01)
def test_trims_folder_already_over_limit(self):
"""Verify one call trims a folder that is far over the limit."""
with tempfile.TemporaryDirectory() as folder:
self._fill(folder, [f"{i:03d}.webp" for i in range(10)])
file_util.trim_oldest_files(folder, 4)
assert sorted(os.listdir(folder)) == [
"006.webp",
"007.webp",
"008.webp",
"009.webp",
]
def test_counts_every_listed_image_extension(self):
"""Verify the trim counts the same images the train listing shows."""
with tempfile.TemporaryDirectory() as folder:
self._fill(
folder, ["notes.txt", "a.jpg", "b.jpeg", "c.png", "d.webp", "e.webp"]
)
file_util.trim_oldest_files(folder, 2)
assert sorted(os.listdir(folder)) == ["d.webp", "e.webp", "notes.txt"]
def test_file_removed_during_scan_does_not_skip_trim(self):
"""Verify a file deleted between listing and stat still trims the rest."""
real_listdir = os.listdir
with tempfile.TemporaryDirectory() as folder:
self._fill(folder, [f"{i:03d}.webp" for i in range(10)])
with patch(
"os.listdir", side_effect=lambda p: real_listdir(p) + ["gone.webp"]
):
file_util.trim_oldest_files(folder, 4)
assert len(os.listdir(folder)) == 4
+26 -26
View File
@@ -13,13 +13,12 @@ class TestModelDownloadNotice(unittest.TestCase):
def setUp(self):
self.download_path = tempfile.mkdtemp()
downloader.last_download_error.clear()
def _notice_calls(self, requestor: MagicMock) -> list[dict]:
return [
call.args[1]
for call in requestor.send_data.call_args_list
if call.args[0] == "update_notice"
]
raise_patch = patch("frigate.util.downloader.raise_notice")
resolve_patch = patch("frigate.util.downloader.resolve_notice")
self.raise_notice = raise_patch.start()
self.resolve_notice = resolve_patch.start()
self.addCleanup(raise_patch.stop)
self.addCleanup(resolve_patch.stop)
def _downloader(self, download_func) -> ModelDownloader:
with patch("frigate.util.downloader.InterProcessRequestor"):
@@ -36,15 +35,16 @@ class TestModelDownloadNotice(unittest.TestCase):
with self.assertRaises(RuntimeError):
model_downloader._download_models()
notices = self._notice_calls(model_downloader.requestor)
self.assertEqual(len(notices), 1)
self.assertEqual(notices[0]["action"], "raise")
self.assertEqual(notices[0]["kind"], "model_download_failed")
self.assertEqual(notices[0]["scope"], "facedet/facedet.onnx")
self.assertEqual(
notices[0]["params"],
{"file": "facedet.onnx", "error": "HTTP 503 from upstream"},
self.raise_notice.assert_called_once_with(
"model_download_failed",
scope="facedet/facedet.onnx",
params={
"file": "facedet.onnx",
"model": "facedet",
"error": "HTTP 503 from upstream",
},
)
self.resolve_notice.assert_not_called()
def test_swallowing_download_that_leaves_no_file_reports(self):
target = os.path.join(self.download_path, "facedet.onnx")
@@ -62,10 +62,11 @@ class TestModelDownloadNotice(unittest.TestCase):
model_downloader = self._downloader(swallowing)
model_downloader._download_models()
notices = self._notice_calls(model_downloader.requestor)
self.assertEqual(len(notices), 1)
self.assertEqual(notices[0]["action"], "raise")
self.assertEqual(notices[0]["params"], {"file": "facedet.onnx", "error": "dns"})
self.raise_notice.assert_called_once()
self.assertEqual(
self.raise_notice.call_args.kwargs["params"],
{"file": "facedet.onnx", "model": "facedet", "error": "dns"},
)
self.assertFalse(os.path.exists(target))
def test_success_resolves(self):
@@ -76,10 +77,10 @@ class TestModelDownloadNotice(unittest.TestCase):
model_downloader = self._downloader(succeeding)
model_downloader._download_models()
notices = self._notice_calls(model_downloader.requestor)
self.assertEqual(len(notices), 1)
self.assertEqual(notices[0]["action"], "resolve")
self.assertEqual(notices[0]["scope"], "facedet/facedet.onnx")
self.raise_notice.assert_not_called()
self.resolve_notice.assert_called_once_with(
"model_download_failed", "facedet/facedet.onnx"
)
def test_a_sibling_downloader_only_resolves_its_own_files(self):
"""PaddleOCR, facedet and jina each spread one model_name over several
@@ -96,9 +97,8 @@ class TestModelDownloadNotice(unittest.TestCase):
sibling._download_models()
self.assertEqual(
[n["scope"] for n in self._notice_calls(sibling.requestor)],
["paddleocr-onnx/det.onnx"],
self.resolve_notice.assert_called_once_with(
"model_download_failed", "paddleocr-onnx/det.onnx"
)
+85
View File
@@ -0,0 +1,85 @@
"""Tests for raising and resolving notices through frigate.notices."""
import unittest
from unittest.mock import MagicMock, patch
from frigate import notices
class TestNoticeFacade(unittest.TestCase):
def tearDown(self):
notices.install_registry(None)
def test_the_installed_registry_applies_each_update(self):
registry = MagicMock()
notices.install_registry(registry)
notices.raise_notice("detector_stuck", scope="ov", params={"detector": "ov"})
notices.resolve_notice("detector_stuck", "ov")
notices.resolve_kind("update_available")
self.assertEqual(
[call.args[0] for call in registry.apply.call_args_list],
[
{
"action": "raise",
"kind": "detector_stuck",
"scope": "ov",
"params": {"detector": "ov"},
},
{"action": "resolve", "kind": "detector_stuck", "scope": "ov"},
{"action": "resolve_kind", "kind": "update_available"},
],
)
def test_a_child_process_sends_over_ipc(self):
with (
patch.object(notices.mp, "parent_process", return_value=MagicMock()),
patch.object(notices, "InterProcessRequestor") as requestor,
patch.object(notices, "_requestor", None),
):
notices.raise_notice("detector_stuck", scope="ov")
notices.resolve_notice("detector_stuck", "ov")
# one requestor serves every call in the process
requestor.assert_called_once_with()
send_data = requestor.return_value.send_data
self.assertEqual(send_data.call_count, 2)
self.assertEqual(
send_data.call_args_list[0].args,
(
"update_notice",
{
"action": "raise",
"kind": "detector_stuck",
"scope": "ov",
"params": {},
},
),
)
def test_the_main_process_without_a_registry_drops_updates(self):
with (
patch.object(notices.mp, "parent_process", return_value=None),
patch.object(notices, "InterProcessRequestor") as requestor,
):
notices.raise_notice("detector_stuck", scope="ov")
requestor.assert_not_called()
def test_a_failing_registry_is_logged_not_raised(self):
registry = MagicMock()
registry.apply.side_effect = RuntimeError("db")
notices.install_registry(registry)
with self.assertLogs("frigate.notices", level="ERROR"):
notices.raise_notice("detector_stuck", scope="ov")
def test_flush_reaches_only_an_installed_registry(self):
notices.flush_notices()
registry = MagicMock()
notices.install_registry(registry)
notices.flush_notices()
registry.flush.assert_called_once_with()
+298 -111
View File
@@ -3,51 +3,53 @@
import logging
import os
import unittest
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from frigate.models import Notice, NoticeStats
from frigate.notices.registry import DismissResult, NoticeRegistry
from frigate.notices.types import (
NOTICE_KINDS,
NoticeMode,
NoticeSeverity,
notice_id,
)
from frigate.notices.registry import NoticeRegistry
from frigate.notices.types import NOTICE_KINDS, NoticeKind, NoticeSeverity, notice_id
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
# kinds that switch on the lifecycle knobs, so the tests do not depend on the
# values the real catalog picks
BATCHED = NoticeKind(
"batched", NoticeSeverity.warning, "system", batch_repeats=True, reopen_at_count=3
)
PRUNED = NoticeKind("pruned", NoticeSeverity.info, "system", keep_latest=2)
TEST_KINDS = {kind.key: kind for kind in (BATCHED, PRUNED)}
class TestNoticeKinds(unittest.TestCase):
def test_every_kind_is_keyed_by_its_own_key(self):
for key, definition in NOTICE_KINDS.items():
self.assertEqual(key, definition.key)
def test_first_producers_are_defined(self):
self.assertEqual(NOTICE_KINDS["ffmpeg_crash_loop"].mode, NoticeMode.state)
self.assertEqual(
NOTICE_KINDS["ffmpeg_crash_loop"].severity, NoticeSeverity.error
)
self.assertEqual(NOTICE_KINDS["detector_stuck"].mode, NoticeMode.event)
self.assertEqual(
NOTICE_KINDS["detector_stuck"].severity, NoticeSeverity.warning
)
self.assertEqual(NOTICE_KINDS["model_download_failed"].mode, NoticeMode.event)
self.assertEqual(NOTICE_KINDS["retention_unmet"].mode, NoticeMode.state)
self.assertEqual(NOTICE_KINDS["update_available"].mode, NoticeMode.state)
self.assertEqual(NOTICE_KINDS["update_available"].severity, NoticeSeverity.info)
def test_notice_id_with_and_without_scope(self):
self.assertEqual(notice_id("retention_unmet", None), "retention_unmet")
self.assertEqual(notice_id("failed_login", None), "failed_login")
self.assertEqual(
notice_id("ffmpeg_crash_loop", "front_door"),
"ffmpeg_crash_loop:front_door",
notice_id("skipped_detections", "front_door"),
"skipped_detections:front_door",
)
def test_link_is_filled_in_from_params(self):
kind = NoticeKind(
"k", NoticeSeverity.info, "system", link="https://x/v{version}"
)
class TestNoticeRegistry(unittest.TestCase):
self.assertEqual(kind.link_for({"version": "0.19.1"}), "https://x/v0.19.1")
self.assertIsNone(kind.link_for({}))
def test_a_kind_without_a_link(self):
kind = NoticeKind("k", NoticeSeverity.info, "system")
self.assertIsNone(kind.link_for({"version": "0.19.1"}))
class RegistryTestCase(unittest.TestCase):
def setUp(self):
migrate_db = SqliteExtDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:]
@@ -70,132 +72,128 @@ class TestNoticeRegistry(unittest.TestCase):
except OSError:
pass
def _raise_at(self, timestamp: float, kind: str, **kwargs) -> None:
with patch("frigate.notices.registry.datetime") as mock_datetime:
mock_datetime.now.return_value.timestamp.return_value = timestamp
self.registry.raise_notice(kind, **kwargs)
class TestNoticeRegistry(RegistryTestCase):
def test_raise_inserts_and_counts_one_occurrence(self):
self.registry.raise_notice(
"ffmpeg_crash_loop", scope="front_door", params={"restarts": 5}
"skipped_detections", scope="front_door", params={"pct": 12.5}
)
active = self.registry.active()
self.assertEqual(len(active), 1)
self.assertEqual(active[0]["id"], "ffmpeg_crash_loop:front_door")
self.assertEqual(active[0]["severity"], "error")
self.assertEqual(active[0]["mode"], "state")
self.assertEqual(active[0]["id"], "skipped_detections:front_door")
self.assertEqual(active[0]["severity"], "warning")
self.assertEqual(active[0]["category"], "camera")
self.assertEqual(active[0]["scope"], "front_door")
self.assertEqual(active[0]["params"], {"restarts": 5})
self.assertEqual(active[0]["params"], {"pct": 12.5})
self.assertEqual(active[0]["link"], "/system#cameras")
self.assertEqual(active[0]["count"], 1)
self.assertEqual(self.registry.stats()[0]["occurrences"], 1)
self.listener.assert_called_once()
def test_state_re_raise_with_new_params_updates_without_counting(self):
def test_re_raise_counts_and_updates_params(self):
self.registry.raise_notice(
"ffmpeg_crash_loop", scope="front_door", params={"restarts": 5}
"skipped_detections", scope="front_door", params={"pct": 12.5}
)
self.listener.reset_mock()
self.registry.raise_notice(
"ffmpeg_crash_loop", scope="front_door", params={"restarts": 6}
"skipped_detections", scope="front_door", params={"pct": 8.0}
)
active = self.registry.active()
self.assertEqual(active[0]["count"], 1)
self.assertEqual(active[0]["params"], {"restarts": 6})
self.assertEqual(self.registry.stats()[0]["occurrences"], 1)
self.listener.assert_called_once()
self.assertEqual(active[0]["count"], 2)
self.assertEqual(active[0]["params"], {"pct": 8.0})
self.assertEqual(self.registry.stats()[0]["occurrences"], 2)
def test_state_re_raise_with_same_params_is_a_no_op(self):
def test_repeat_of_a_non_counting_kind_is_a_no_op(self):
self.registry.raise_notice(
"ffmpeg_crash_loop", scope="front_door", params={"restarts": 5}
"update_available", scope="0.19.1", params={"version": "0.19.1"}
)
before = self.registry.active()[0]["last_seen"]
self.listener.reset_mock()
self.registry.raise_notice(
"ffmpeg_crash_loop", scope="front_door", params={"restarts": 5}
"update_available", scope="0.19.1", params={"version": "0.19.1"}
)
self.assertEqual(self.registry.active()[0]["last_seen"], before)
self.assertEqual(self.registry.active()[0]["count"], 1)
self.assertEqual(self.registry.stats()[0]["occurrences"], 1)
self.listener.assert_not_called()
def test_event_re_raise_counts_and_undismisses(self):
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
self.assertEqual(self.registry.dismiss("detector_stuck"), DismissResult.ok)
self.assertEqual(self.registry.active(), [])
def test_dismissal_survives_a_re_raise(self):
self.registry.raise_notice("skipped_detections", scope="front_door", params={})
self.assertTrue(self.registry.dismiss("skipped_detections:front_door"))
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
# no scope here on purpose: a global event id is still valid
active = self.registry.active()
self.assertEqual(len(active), 1)
self.assertEqual(active[0]["count"], 2)
self.assertIsNone(active[0]["dismissed_at"])
stats = self.registry.stats()[0]
self.assertEqual(stats["occurrences"], 2)
self.assertEqual(stats["dismissals"], 1)
def test_resolve_deletes_and_keeps_stats(self):
self.registry.raise_notice("retention_unmet", params={"needed_mb": 1})
self.listener.reset_mock()
self.registry.resolve("retention_unmet")
self.registry.resolve("retention_unmet")
self.registry.raise_notice("skipped_detections", scope="front_door", params={})
self.assertEqual(self.registry.active(), [])
self.assertEqual(self.registry.stats()[0]["occurrences"], 1)
self.listener.assert_called_once()
history = self.registry.active(include_dismissed=True)
self.assertEqual(history[0]["count"], 2)
self.assertIsNotNone(history[0]["dismissed_at"])
def test_state_notice_cannot_be_dismissed(self):
self.registry.raise_notice("retention_unmet", params={})
def test_dismiss_counts_once(self):
self.registry.raise_notice("detector_stuck", scope="ov", params={})
self.assertEqual(
self.registry.dismiss("retention_unmet"), DismissResult.not_dismissable
)
self.assertEqual(len(self.registry.active()), 1)
self.assertTrue(self.registry.dismiss("detector_stuck:ov"))
self.assertTrue(self.registry.dismiss("detector_stuck:ov"))
self.assertEqual(self.registry.stats()[0]["dismissals"], 1)
def test_dismiss_unknown_id(self):
self.assertEqual(self.registry.dismiss("nope"), DismissResult.not_found)
self.assertFalse(self.registry.dismiss("nope"))
def test_dismissed_hidden_unless_requested(self):
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
self.registry.dismiss("detector_stuck")
self.registry.raise_notice("detector_stuck", scope="ov", params={})
self.registry.dismiss("detector_stuck:ov")
self.assertEqual(self.registry.active(), [])
self.assertEqual(len(self.registry.active(include_dismissed=True)), 1)
def test_startup_hides_state_notices_until_confirmed(self):
self.registry.raise_notice("retention_unmet", params={"needed_mb": 1})
def test_resolve_deletes_and_keeps_stats(self):
self.registry.raise_notice(
"detector_stuck", scope="ov", params={"detector": "ov"}
"model_download_failed", scope="yolo/model.onnx", params={}
)
self.listener.reset_mock()
self.registry.mark_state_notices_unconfirmed()
ids = [n["id"] for n in self.registry.active()]
self.assertEqual(ids, ["detector_stuck:ov"])
# the producer confirms the same episode: visible again, no new occurrence
self.registry.raise_notice("retention_unmet", params={"needed_mb": 1})
ids = [n["id"] for n in self.registry.active()]
self.assertEqual(ids, ["retention_unmet", "detector_stuck:ov"])
self.assertEqual(
[s for s in self.registry.stats() if s["kind"] == "retention_unmet"][0][
"occurrences"
],
1,
)
def test_unconfirmed_across_two_startups_is_deleted(self):
self.registry.raise_notice("retention_unmet", params={})
self.registry.mark_state_notices_unconfirmed()
self.registry.mark_state_notices_unconfirmed()
self.registry.resolve("model_download_failed", "yolo/model.onnx")
self.registry.resolve("model_download_failed", "yolo/model.onnx")
self.assertEqual(self.registry.active(include_dismissed=True), [])
self.assertEqual(self.registry.stats()[0]["occurrences"], 1)
self.listener.assert_called_once()
def test_resolve_kind_drops_every_scope_of_that_kind(self):
self.registry.raise_notice("detector_stuck", params={})
self.registry.raise_notice("detector_stuck", scope="ov", params={})
self.registry.raise_notice("skipped_detections", scope="garage", params={})
self.registry.resolve_kind("detector_stuck")
ids = [n["id"] for n in self.registry.active()]
self.assertEqual(ids, ["skipped_detections:garage"])
def test_active_fills_in_each_link(self):
self.registry.raise_notice(
"update_available", scope="0.19.1", params={"version": "0.19.1"}
)
self.registry.raise_notice(
"model_download_failed", scope="yolo/model.onnx", params={}
)
links = {n["kind"]: n["link"] for n in self.registry.active()}
self.assertEqual(
links,
{
"update_available": "https://github.com/blakeblackshear/frigate/releases/tag/v0.19.1",
"model_download_failed": None,
},
)
def test_replay_camera_is_ignored(self):
self.registry.raise_notice(
"ffmpeg_crash_loop", scope="_replay_front_door", params={}
"skipped_detections", scope="_replay_front_door", params={}
)
self.assertEqual(self.registry.active(), [])
@@ -208,22 +206,26 @@ class TestNoticeRegistry(unittest.TestCase):
self.listener.assert_not_called()
def test_active_sorts_by_severity_then_recency(self):
self.registry.raise_notice("detector_stuck", params={"detector": "ov"})
self.registry.raise_notice("retention_unmet", params={})
self.registry.raise_notice("detector_stuck", scope="ov", params={})
self.registry.raise_notice(
"model_download_failed", scope="yolo/model.onnx", params={}
)
ids = [n["id"] for n in self.registry.active()]
self.assertEqual(ids, ["retention_unmet", "detector_stuck"])
self.assertEqual(
ids, ["model_download_failed:yolo/model.onnx", "detector_stuck:ov"]
)
def test_listener_exception_does_not_propagate(self):
self.registry.subscribe(MagicMock(side_effect=RuntimeError("boom")))
self.registry.raise_notice("retention_unmet", params={})
self.registry.raise_notice("detector_stuck", scope="ov", params={})
self.assertEqual(len(self.registry.active()), 1)
def test_resolve_camera_drops_only_that_camera(self):
self.registry.raise_notice("ffmpeg_crash_loop", scope="front_door", params={})
self.registry.raise_notice("ffmpeg_crash_loop", scope="garage", params={})
self.registry.raise_notice("skipped_detections", scope="front_door", params={})
self.registry.raise_notice("skipped_detections", scope="garage", params={})
self.registry.raise_notice(
"model_download_failed", scope="front_door", params={}
)
@@ -232,5 +234,190 @@ class TestNoticeRegistry(unittest.TestCase):
ids = sorted(n["id"] for n in self.registry.active())
self.assertEqual(
ids, ["ffmpeg_crash_loop:garage", "model_download_failed:front_door"]
ids, ["model_download_failed:front_door", "skipped_detections:garage"]
)
def test_resolve_camera_drops_that_cameras_check_dismissals(self):
for check_id in (
"stream:front_door:0:probe",
"config:detect:fps-greater-than-five:camera.front_door",
"config:detect:fps-greater-than-five:global",
"stream:garage:0:probe",
):
self.assertTrue(self.registry.dismiss(check_id))
self.registry.resolve_camera("front_door")
ids = sorted(check["id"] for check in self.registry.dismissed_checks())
self.assertEqual(
ids,
["config:detect:fps-greater-than-five:global", "stream:garage:0:probe"],
)
def test_camera_named_global_keeps_global_check_dismissals(self):
self.registry.dismiss("config:detect:fps-greater-than-five:global")
self.registry.dismiss("config:detect:fps-greater-than-five:camera.global")
self.registry.resolve_camera("global")
ids = [check["id"] for check in self.registry.dismissed_checks()]
self.assertEqual(ids, ["config:detect:fps-greater-than-five:global"])
def test_purge_dismissed_keeps_active_notices_and_counts(self):
self.registry.raise_notice("detector_stuck", scope="ov", params={})
self.registry.raise_notice("skipped_detections", scope="garage", params={})
self.registry.dismiss("detector_stuck:ov")
self.registry.dismiss("config:detect:fps-greater-than-five:camera.garage")
self.assertEqual(self.registry.purge_dismissed(), 2)
ids = [n["id"] for n in self.registry.active(include_dismissed=True)]
self.assertEqual(ids, ["skipped_detections:garage"])
self.assertEqual(self.registry.dismissed_checks(), [])
dismissals = {s["kind"]: s["dismissals"] for s in self.registry.stats()}
self.assertEqual(dismissals["detector_stuck"], 1)
class TestApply(RegistryTestCase):
def test_each_action_reaches_its_method(self):
self.registry.apply(
{
"action": "raise",
"kind": "detector_stuck",
"scope": "ov",
"params": {"detector": "ov"},
}
)
self.assertEqual(
[n["id"] for n in self.registry.active()], ["detector_stuck:ov"]
)
self.registry.apply(
{"action": "resolve", "kind": "detector_stuck", "scope": "ov"}
)
self.assertEqual(self.registry.active(), [])
for scope in ("a", "b"):
self.registry.apply(
{"action": "raise", "kind": "detector_stuck", "scope": scope}
)
self.registry.apply({"action": "resolve_kind", "kind": "detector_stuck"})
self.assertEqual(self.registry.active(), [])
def test_malformed_updates_are_logged_and_ignored(self):
with self.assertLogs("frigate.notices.registry", level="WARNING") as logs:
self.registry.apply({"action": "raise"})
self.registry.apply({"action": "explode", "kind": "detector_stuck"})
self.assertEqual(len(logs.output), 2)
self.assertEqual(self.registry.active(), [])
@patch.dict(NOTICE_KINDS, TEST_KINDS)
class TestLifecycleKnobs(RegistryTestCase):
def test_batched_repeats_wait_for_the_flush(self):
self.registry.raise_notice("batched", params={"n": 1})
self.listener.reset_mock()
for n in (2, 3, 4):
self.registry.raise_notice("batched", params={"n": n})
self.assertEqual(self.registry.active()[0]["count"], 1)
self.listener.assert_not_called()
self.registry.flush()
notice = self.registry.active()[0]
self.assertEqual(notice["count"], 4)
self.assertEqual(notice["params"], {"n": 4})
self.assertEqual(self.registry.stats()[0]["occurrences"], 4)
self.listener.assert_called_once()
def test_flush_with_nothing_held_is_a_no_op(self):
self.registry.raise_notice("batched")
self.listener.reset_mock()
self.registry.flush()
self.listener.assert_not_called()
def test_repeats_of_a_resolved_notice_are_dropped(self):
self.registry.raise_notice("batched")
self.registry.raise_notice("batched")
self.registry.resolve("batched")
self.listener.reset_mock()
self.registry.flush()
self.assertEqual(self.registry.active(include_dismissed=True), [])
self.listener.assert_not_called()
def test_a_new_row_starts_without_stale_repeats(self):
self.registry.raise_notice("batched")
self.registry.raise_notice("batched")
self.registry.resolve("batched")
self.registry.raise_notice("batched")
self.registry.flush()
self.assertEqual(self.registry.active()[0]["count"], 1)
def test_a_dismissed_notice_reopens_at_the_count(self):
self.registry.raise_notice("batched")
self.registry.dismiss("batched")
self.registry.raise_notice("batched")
self.registry.flush()
self.assertEqual(self.registry.active(), [])
self.registry.raise_notice("batched")
self.registry.flush()
notice = self.registry.active()[0]
self.assertEqual(notice["count"], BATCHED.reopen_at_count)
self.assertIsNone(notice["dismissed_at"])
def test_a_notice_dismissed_past_the_count_stays_dismissed(self):
for _ in range(3):
self.registry.raise_notice("batched")
self.registry.flush()
self.registry.dismiss("batched")
self.registry.raise_notice("batched")
self.registry.flush()
self.assertEqual(self.registry.active(), [])
self.assertEqual(self.registry.active(include_dismissed=True)[0]["count"], 4)
def test_keep_latest_drops_the_oldest_rows(self):
for index, scope in enumerate(("a", "b", "c")):
self._raise_at(1000.0 + index, "pruned", scope=scope)
ids = sorted(n["id"] for n in self.registry.active())
self.assertEqual(ids, ["pruned:b", "pruned:c"])
class TestCatalogLifecycles(RegistryTestCase):
def test_a_new_release_replaces_the_old_update_notice(self):
for index, version in enumerate(("0.19.0", "0.19.1")):
self._raise_at(
1000.0 + index,
"update_available",
scope=version,
params={"version": version},
)
ids = [n["id"] for n in self.registry.active()]
self.assertEqual(ids, ["update_available:0.19.1"])
def test_a_dismissed_failed_login_burst_reopens_at_five_attempts(self):
self.registry.raise_notice("failed_login", scope="1000")
self.registry.dismiss("failed_login:1000")
for _ in range(4):
self.registry.raise_notice("failed_login", scope="1000")
self.registry.flush()
burst = self.registry.active()[0]
self.assertEqual(burst["count"], 5)
self.assertIsNone(burst["dismissed_at"])
+64
View File
@@ -0,0 +1,64 @@
"""Tests for the notice raised while /dev/shm is too small."""
import unittest
from unittest.mock import patch
from frigate.stats import emitter
TOO_SMALL = {"total": 64.0, "min_shm": 180}
BIG_ENOUGH = {"total": 512.0, "min_shm": 180}
class TestShmNotice(unittest.TestCase):
def setUp(self):
raise_patch = patch.object(emitter, "raise_notice")
resolve_patch = patch.object(emitter, "resolve_notice")
self.raise_notice = raise_patch.start()
self.resolve_notice = resolve_patch.start()
self.addCleanup(raise_patch.stop)
self.addCleanup(resolve_patch.stop)
self.emitter = emitter.StatsEmitter.__new__(emitter.StatsEmitter)
self.emitter._shm_checked = False
self.emitter._shm_params = None
def test_too_small_raises_once(self):
for _ in range(3):
self.emitter._update_shm_notice(TOO_SMALL)
self.raise_notice.assert_called_once_with(
"shm_too_low", params={"total": 64.0, "min": 180}
)
self.resolve_notice.assert_not_called()
def test_first_tick_clears_a_row_the_last_run_left(self):
self.emitter._update_shm_notice(BIG_ENOUGH)
self.emitter._update_shm_notice(BIG_ENOUGH)
self.resolve_notice.assert_called_once_with("shm_too_low")
self.raise_notice.assert_not_called()
def test_a_new_requirement_updates_the_notice(self):
self.emitter._update_shm_notice(TOO_SMALL)
self.emitter._update_shm_notice({"total": 64.0, "min_shm": 200})
self.assertEqual(self.raise_notice.call_count, 2)
self.raise_notice.assert_called_with(
"shm_too_low", params={"total": 64.0, "min": 200}
)
def test_growing_shm_resolves_the_notice(self):
self.emitter._update_shm_notice(TOO_SMALL)
self.emitter._update_shm_notice(BIG_ENOUGH)
self.resolve_notice.assert_called_once_with("shm_too_low")
def test_missing_shm_is_not_a_problem(self):
self.emitter._update_shm_notice({})
self.raise_notice.assert_not_called()
self.resolve_notice.assert_called_once_with("shm_too_low")
if __name__ == "__main__":
unittest.main()
+129
View File
@@ -0,0 +1,129 @@
"""Tests for the skipped detection rate and the notice it can raise."""
import unittest
from unittest.mock import patch
from frigate.stats import emitter
from frigate.stats.emitter import SkippedDetectionsTracker
from frigate.stats.util import skipped_percent
class TestSkippedPercent(unittest.TestCase):
def test_share_of_all_frames(self):
self.assertEqual(skipped_percent(0.5, 5.0, True), 10.0)
def test_offline_camera_reports_zero(self):
self.assertEqual(skipped_percent(2.0, 0.0, True), 0.0)
def test_disabled_camera_reports_zero(self):
self.assertEqual(skipped_percent(2.0, 5.0, False), 0.0)
class TestSkippedDetectionsTracker(unittest.TestCase):
def _cameras(self, pct: float) -> dict:
return {"front_door": {"skipped_pct": pct}}
def test_below_threshold_never_qualifies(self):
tracker = SkippedDetectionsTracker()
for tick in range(10):
self.assertEqual(tracker.update(self._cameras(4.9), tick * 15.0), [])
def test_qualifies_once_after_the_hold(self):
tracker = SkippedDetectionsTracker()
results = [
tracker.update(self._cameras(10.0), tick * 15.0) for tick in range(8)
]
# ticks at 0, 15, 30, and 45 s are inside the hold; 60 s qualifies
self.assertEqual(results[:4], [[], [], [], []])
self.assertEqual(results[4], ["front_door"])
self.assertEqual(results[5:], [[], [], []])
def test_a_dip_restarts_the_hold(self):
tracker = SkippedDetectionsTracker()
for now, pct in ((0.0, 10.0), (15.0, 10.0), (30.0, 10.0), (45.0, 2.0)):
self.assertEqual(tracker.update(self._cameras(pct), now), [])
self.assertEqual(tracker.update(self._cameras(10.0), 60.0), [])
self.assertEqual(tracker.update(self._cameras(10.0), 120.0), ["front_door"])
def test_recovery_then_relapse_is_a_new_episode(self):
tracker = SkippedDetectionsTracker()
tracker.update(self._cameras(10.0), 0.0)
self.assertEqual(tracker.update(self._cameras(10.0), 60.0), ["front_door"])
tracker.update(self._cameras(0.0), 75.0)
tracker.update(self._cameras(10.0), 90.0)
self.assertEqual(tracker.update(self._cameras(10.0), 150.0), ["front_door"])
def test_cameras_are_tracked_separately(self):
tracker = SkippedDetectionsTracker()
tracker.update({"a": {"skipped_pct": 10.0}, "b": {"skipped_pct": 0.0}}, 0.0)
qualified = tracker.update(
{"a": {"skipped_pct": 10.0}, "b": {"skipped_pct": 10.0}}, 60.0
)
self.assertEqual(qualified, ["a"])
def test_a_removed_camera_starts_a_new_episode(self):
tracker = SkippedDetectionsTracker()
tracker.update(self._cameras(10.0), 0.0)
tracker.update({}, 15.0)
tracker.update(self._cameras(10.0), 30.0)
self.assertEqual(tracker.update(self._cameras(10.0), 60.0), [])
self.assertEqual(tracker.update(self._cameras(10.0), 90.0), ["front_door"])
class TestEmitterNotices(unittest.TestCase):
def setUp(self):
raise_patch = patch.object(emitter, "raise_notice")
resolve_patch = patch.object(emitter, "resolve_notice")
flush_patch = patch.object(emitter, "flush_notices")
self.raise_notice = raise_patch.start()
resolve_patch.start()
self.flush_notices = flush_patch.start()
self.addCleanup(raise_patch.stop)
self.addCleanup(resolve_patch.stop)
self.addCleanup(flush_patch.stop)
def _emitter(self) -> emitter.StatsEmitter:
stats_emitter = emitter.StatsEmitter.__new__(emitter.StatsEmitter)
stats_emitter.skipped_detections = SkippedDetectionsTracker()
stats_emitter._shm_checked = False
stats_emitter._shm_params = None
return stats_emitter
def _stats(self, uptime: int, pct: float) -> dict:
return {
"service": {"uptime": uptime, "storage": {"/dev/shm": {}}},
"cameras": {"front_door": {"skipped_pct": pct}},
}
def test_qualified_camera_raises_a_notice(self):
stats_emitter = self._emitter()
stats_emitter._update_notices(self._stats(300, 12.5), 0.0)
stats_emitter._update_notices(self._stats(360, 12.5), 60.0)
self.raise_notice.assert_called_once_with(
"skipped_detections", scope="front_door", params={"pct": 12.5}
)
def test_startup_window_is_ignored(self):
stats_emitter = self._emitter()
stats_emitter._update_notices(self._stats(30, 50.0), 0.0)
stats_emitter._update_notices(self._stats(90, 50.0), 60.0)
self.raise_notice.assert_not_called()
def test_every_tick_flushes_held_repeats(self):
self._emitter()._update_notices(self._stats(30, 0.0), 0.0)
self.flush_notices.assert_called_once_with()
+26 -26
View File
@@ -34,44 +34,46 @@ class TestIsNewerVersion(unittest.TestCase):
class TestUpdateNotice(unittest.TestCase):
def setUp(self):
raise_patch = patch.object(emitter, "raise_notice")
resolve_patch = patch.object(emitter, "resolve_kind")
self.raise_notice = raise_patch.start()
self.resolve_kind = resolve_patch.start()
self.addCleanup(raise_patch.stop)
self.addCleanup(resolve_patch.stop)
def _emitter(self, latest: str) -> emitter.StatsEmitter:
stats_emitter = emitter.StatsEmitter.__new__(emitter.StatsEmitter)
stats_emitter.config = MagicMock()
stats_emitter.stats_tracking = {"latest_frigate_version": latest}
stats_emitter.notice_registry = MagicMock()
return stats_emitter
def test_newer_release_raises(self):
stats_emitter = self._emitter("0.19.0")
with patch.object(emitter, "VERSION", "0.18.1-abcdef"):
stats_emitter._check_update_notice()
self._emitter("0.19.0")._check_update_notice()
stats_emitter.notice_registry.raise_notice.assert_called_once_with(
"update_available", params={"version": "0.19.0"}
self.raise_notice.assert_called_once_with(
"update_available", scope="0.19.0", params={"version": "0.19.0"}
)
stats_emitter.notice_registry.resolve.assert_not_called()
self.resolve_kind.assert_not_called()
def test_current_or_disabled_resolves(self):
for latest in ("0.18.1", "disabled"):
stats_emitter = self._emitter(latest)
with self.subTest(latest=latest):
self.resolve_kind.reset_mock()
with patch.object(emitter, "VERSION", "0.18.1-abcdef"):
stats_emitter._check_update_notice()
with patch.object(emitter, "VERSION", "0.18.1-abcdef"):
self._emitter(latest)._check_update_notice()
stats_emitter.notice_registry.raise_notice.assert_not_called()
stats_emitter.notice_registry.resolve.assert_called_once_with(
"update_available"
)
self.raise_notice.assert_not_called()
self.resolve_kind.assert_called_once_with("update_available")
def test_a_failed_lookup_leaves_the_notice_alone(self):
stats_emitter = self._emitter("unknown")
with patch.object(emitter, "VERSION", "0.18.1-abcdef"):
stats_emitter._check_update_notice()
self._emitter("unknown")._check_update_notice()
stats_emitter.notice_registry.raise_notice.assert_not_called()
stats_emitter.notice_registry.resolve.assert_not_called()
self.raise_notice.assert_not_called()
self.resolve_kind.assert_not_called()
def test_refresh_updates_tracking_on_a_thread_then_checks(self):
stats_emitter = self._emitter("0.18.0")
@@ -89,7 +91,7 @@ class TestUpdateNotice(unittest.TestCase):
self.assertEqual(
stats_emitter.stats_tracking["latest_frigate_version"], "0.19.0"
)
stats_emitter.notice_registry.raise_notice.assert_called_once()
self.raise_notice.assert_called_once()
def test_failed_refresh_keeps_the_last_known_version(self):
stats_emitter = self._emitter("0.20.0")
@@ -105,10 +107,10 @@ class TestUpdateNotice(unittest.TestCase):
self.assertEqual(
stats_emitter.stats_tracking["latest_frigate_version"], "0.20.0"
)
stats_emitter.notice_registry.raise_notice.assert_called_once_with(
"update_available", params={"version": "0.20.0"}
self.raise_notice.assert_called_once_with(
"update_available", scope="0.20.0", params={"version": "0.20.0"}
)
stats_emitter.notice_registry.resolve.assert_not_called()
self.resolve_kind.assert_not_called()
def test_disabled_version_check_still_clears_the_notice(self):
stats_emitter = self._emitter("0.20.0")
@@ -124,6 +126,4 @@ class TestUpdateNotice(unittest.TestCase):
self.assertEqual(
stats_emitter.stats_tracking["latest_frigate_version"], "disabled"
)
stats_emitter.notice_registry.resolve.assert_called_once_with(
"update_available"
)
self.resolve_kind.assert_called_once_with("update_available")
+7 -13
View File
@@ -556,10 +556,9 @@ class TestHttp(unittest.TestCase):
assert Recordings.get(Recordings.id == rec_k2_id)
assert Recordings.get(Recordings.id == rec_k3_id)
def test_unmet_retention_raises_notice(self):
registry = MagicMock()
def test_unmet_retention_sets_the_live_flag(self):
config = FrigateConfig(**self.minimal_config)
storage = StorageMaintainer(config, MagicMock(), registry)
storage = StorageMaintainer(config, MagicMock())
storage.camera_storage_stats = {
"front_door": {"needs_refresh": False, "usage": 10, "bandwidth": 10}
}
@@ -567,22 +566,17 @@ class TestHttp(unittest.TestCase):
with patch.object(storage, "expected_hourly_bandwidth", return_value=100.0):
storage.reduce_storage_consumption()
registry.raise_notice.assert_called_once()
kind = registry.raise_notice.call_args.args[0]
params = registry.raise_notice.call_args.kwargs["params"]
self.assertEqual(kind, "retention_unmet")
self.assertEqual(params["needed_mb"], 100.0)
self.assertEqual(params["cleared_mb"], 0.0)
self.assertTrue(storage.retention_unmet)
def test_clean_run_resolves_notice(self):
registry = MagicMock()
def test_clean_run_clears_the_live_flag(self):
config = FrigateConfig(**self.minimal_config)
storage = StorageMaintainer(config, MagicMock(), registry)
storage = StorageMaintainer(config, MagicMock())
storage.retention_unmet = True
with patch.object(storage, "check_storage_needs_cleanup", return_value=False):
storage._maintain_once()
registry.resolve.assert_called_once_with("retention_unmet")
self.assertFalse(storage.retention_unmet)
def _insert_mock_event(
+10 -8
View File
@@ -14,25 +14,27 @@ class TestDetectorStuckNotice(unittest.TestCase):
return detector
def test_stuck_restart_raises_notice(self):
registry = MagicMock()
detector = self._stuck_detector()
watchdog = FrigateWatchdog({"ov": detector}, MagicMock(), registry)
watchdog = FrigateWatchdog({"ov": detector}, MagicMock())
with patch("frigate.watchdog.datetime") as mock_datetime:
with (
patch("frigate.watchdog.datetime") as mock_datetime,
patch("frigate.watchdog.raise_notice") as raise_notice,
):
mock_datetime.datetime.now.return_value.timestamp.return_value = 100.0
watchdog._check_detectors()
detector.start_or_restart.assert_called_once()
registry.raise_notice.assert_called_once_with(
raise_notice.assert_called_once_with(
"detector_stuck", scope="ov", params={"detector": "ov"}
)
def test_healthy_detector_raises_nothing(self):
registry = MagicMock()
detector = self._stuck_detector()
detector.detection_start.value = 0.0
watchdog = FrigateWatchdog({"ov": detector}, MagicMock(), registry)
watchdog = FrigateWatchdog({"ov": detector}, MagicMock())
watchdog._check_detectors()
with patch("frigate.watchdog.raise_notice") as raise_notice:
watchdog._check_detectors()
registry.raise_notice.assert_not_called()
raise_notice.assert_not_called()
+5 -1
View File
@@ -1,10 +1,13 @@
from enum import Enum
from typing import TypedDict
from typing import TYPE_CHECKING, TypedDict
from frigate.camera import CameraMetrics
from frigate.data_processing.types import DataProcessorMetrics
from frigate.object_detection.base import ObjectDetectProcess
if TYPE_CHECKING:
from frigate.storage import StorageMaintainer
class StatsTrackingTypes(TypedDict):
camera_metrics: dict[str, CameraMetrics]
@@ -14,6 +17,7 @@ class StatsTrackingTypes(TypedDict):
latest_frigate_version: str
last_updated: int
processes: dict[str, int]
storage_maintainer: "StorageMaintainer | None"
class ModelStatusTypesEnum(str, Enum):
-2
View File
@@ -31,7 +31,6 @@ DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yml")
DETECTOR_DEVICE_FIELDS = {
"cpu": "num_threads",
"rknn": "num_cores",
"deepstack": "api_url",
"degirum": "location",
"zmq": "endpoint",
}
@@ -40,7 +39,6 @@ DETECTOR_DEVICE_FIELDS = {
# detectors that use them are being reworked, so they are dropped rather than
# carried over.
DROPPED_DETECTOR_OPTIONS = {
"deepstack": ["api_timeout", "api_key"],
"degirum": ["zoo", "token"],
"zmq": ["request_timeout_ms", "linger_ms"],
}
+7 -18
View File
@@ -7,7 +7,8 @@ from pathlib import Path
import requests
from frigate.comms.inter_process import InterProcessRequestor
from frigate.const import UPDATE_MODEL_STATE, UPDATE_NOTICE
from frigate.const import UPDATE_MODEL_STATE
from frigate.notices import raise_notice, resolve_notice
from frigate.types import ModelStatusTypesEnum
from frigate.util.file import FileLock
@@ -64,26 +65,14 @@ class ModelDownloader:
return f"{self.model_name}/{file_name}"
def _report_failure(self, file_name: str, error: str) -> None:
self.requestor.send_data(
UPDATE_NOTICE,
{
"action": "raise",
"kind": "model_download_failed",
"scope": self._notice_scope(file_name),
"params": {"file": file_name, "error": error},
},
raise_notice(
"model_download_failed",
scope=self._notice_scope(file_name),
params={"file": file_name, "model": self.model_name, "error": error},
)
def _resolve_failure(self, file_name: str) -> None:
self.requestor.send_data(
UPDATE_NOTICE,
{
"action": "resolve",
"kind": "model_download_failed",
"scope": self._notice_scope(file_name),
"params": {},
},
)
resolve_notice("model_download_failed", self._notice_scope(file_name))
def _send_state(self, file_name: str, state: ModelStatusTypesEnum) -> None:
self.requestor.send_data(
+35
View File
@@ -269,6 +269,41 @@ def delete_event_thumbnail(event: Event) -> bool:
return True
### Training Images
TRAINING_IMAGE_EXTENSIONS = (".webp", ".png", ".jpg", ".jpeg")
def trim_oldest_files(folder: str, max_files: int) -> None:
"""Delete the oldest training images until at most max_files remain."""
try:
names = os.listdir(folder)
except OSError:
return
files: list[tuple[float, str]] = []
for name in names:
if not name.lower().endswith(TRAINING_IMAGE_EXTENSIONS):
continue
path = os.path.join(folder, name)
# the UI can move or delete an image between listdir and stat
try:
files.append((os.path.getctime(path), path))
except OSError:
continue
files.sort(reverse=True)
for _, path in files[max_files:]:
try:
os.unlink(path)
except OSError:
logger.debug("Unable to delete training image %s", path)
### File Locking
-60
View File
@@ -29,7 +29,6 @@ from frigate.const import (
STREAM_TYPE_MAIN,
STREAM_TYPE_SUB,
STREAM_TYPE_TO_ROLE,
UPDATE_NOTICE,
)
from frigate.log import LogPipe
from frigate.util.builtin import EventsPerSecond, get_record_segment_time
@@ -44,11 +43,6 @@ logger = logging.getLogger(__name__)
RECORD_GRACE_SECONDS = 90
# a camera whose ffmpeg restarts this many times in the trailing hour is in a
# crash loop; it recovers once the capture thread stays up this long
CRASH_LOOP_RESTARTS = 5
CRASH_LOOP_RECOVERY_S = 600
def capture_frames(
ffmpeg_process: sp.Popen[Any],
@@ -146,8 +140,6 @@ class CameraWatchdog(threading.Thread):
self.stop_event = stop_event
self.sleeptime = self.config.ffmpeg.retry_interval
self.reconnect_timestamps = deque()
self.crash_loop_raised = False
self._crash_loop_sent_restarts = 0
self.stalls = stalls
self.reconnects = reconnects
self.detection_frame = detection_frame
@@ -197,53 +189,6 @@ class CameraWatchdog(threading.Thread):
self._last_record_status: dict[str, str] = {}
self._last_status_update_time: float = 0.0
def _update_crash_loop_notice(self, now: float) -> None:
"""Raise the crash loop notice as restarts pile up, resolve once stable.
Called on every 1 second tick, so raises are sent only when the restart
count changes; the registry treats repeats of a state notice as updates.
"""
restarts = len(self.reconnect_timestamps)
alive = self.capture_thread is not None and self.capture_thread.is_alive()
if not alive and restarts >= CRASH_LOOP_RESTARTS:
if self.crash_loop_raised and restarts == self._crash_loop_sent_restarts:
return
self.requestor.send_data(
UPDATE_NOTICE,
{
"action": "raise",
"kind": "ffmpeg_crash_loop",
"scope": self.config.name,
"params": {"restarts": restarts},
},
)
self.crash_loop_raised = True
self._crash_loop_sent_restarts = restarts
elif (
self.crash_loop_raised
and alive
and (
restarts == 0
or now - self.reconnect_timestamps[-1] >= CRASH_LOOP_RECOVERY_S
)
):
self._resolve_crash_loop()
def _resolve_crash_loop(self) -> None:
self.requestor.send_data(
UPDATE_NOTICE,
{
"action": "resolve",
"kind": "ffmpeg_crash_loop",
"scope": self.config.name,
"params": {},
},
)
self.crash_loop_raised = False
self._crash_loop_sent_restarts = 0
def _send_detect_status(self, status: str, now: float) -> None:
"""Send detect status only if changed or retry_interval has elapsed."""
if (
@@ -434,9 +379,6 @@ class CameraWatchdog(threading.Thread):
# cameras without a sub stream never get a record_sub topic
if self.config.record.sub.enabled:
self._send_record_status(STREAM_TYPE_SUB, "disabled", now)
if self.crash_loop_raised:
self._resolve_crash_loop()
self.was_enabled = enabled
continue
@@ -514,7 +456,6 @@ class CameraWatchdog(threading.Thread):
self.logger.error(
f"Ffmpeg process crashed unexpectedly for {self.config.name}."
)
self._update_crash_loop_notice(now)
if can_restart:
self.reset_capture_thread(terminate=False)
last_restart_time = now
@@ -544,7 +485,6 @@ class CameraWatchdog(threading.Thread):
# process is running normally
self._send_detect_status("online", now)
self.fps_overflow_count = 0
self._update_crash_loop_notice(now)
for p in self.ffmpeg_other_processes:
poll = p["process"].poll()
+2 -8
View File
@@ -7,7 +7,7 @@ from collections.abc import Callable
from dataclasses import dataclass, field
from multiprocessing.synchronize import Event as MpEvent
from frigate.notices.registry import NoticeRegistry
from frigate.notices import raise_notice
from frigate.object_detection.base import ObjectDetectProcess
from frigate.util.process import FrigateProcess
from frigate.util.services import restart_frigate
@@ -45,12 +45,10 @@ class FrigateWatchdog(threading.Thread):
self,
detectors: dict[str, ObjectDetectProcess],
stop_event: MpEvent,
notice_registry: NoticeRegistry | None = None,
):
super().__init__(name="frigate_watchdog")
self.detectors = detectors
self.stop_event = stop_event
self.notice_registry = notice_registry
self._monitored: list[MonitoredProcess] = []
def register(
@@ -126,11 +124,7 @@ class FrigateWatchdog(threading.Thread):
"Detection appears to be stuck. Restarting detection process..."
)
detector.start_or_restart()
if self.notice_registry is not None:
self.notice_registry.raise_notice(
"detector_stuck", scope=name, params={"detector": name}
)
raise_notice("detector_stuck", scope=name, params={"detector": name})
elif (
detector.detect_process is not None
and not detector.detect_process.is_alive()
+2 -1
View File
@@ -331,7 +331,8 @@ def build_access_map(
for method in route.methods:
if method in ("HEAD", "OPTIONS"):
continue
access_map[(route.path, method.lower())] = {
# the OpenAPI paths drop convertors such as :path, like path_format
access_map[(route.path_format, method.lower())] = {
"level": level,
"roles": roles,
"flag": flag,
+4 -1
View File
@@ -55,8 +55,11 @@ export class FrigateApp {
});
await this.ws.install(this.page, statsFactory(overrides?.stats));
await this.media.install();
await this.api.install(overrides);
// media goes last so its per-event routes win over the broader
// `**/api/events**` list route, which otherwise answers thumbnail and
// snapshot requests with the events JSON
await this.media.install();
}
/** Navigate to a page. Always call installDefaults() first. */
+3
View File
@@ -52,6 +52,9 @@ export function deepMerge<T extends Record<string, unknown>>(
export const BASE_CONFIG = {
...configSnapshot,
version: "0.15.0-test",
// injected by the /config endpoint rather than the Pydantic model, so it
// is absent from the snapshot
plus: { enabled: false },
cameras: {
...configSnapshot.cameras,
front_door: {
+2
View File
@@ -16,6 +16,7 @@ function cameraStats(_name: string) {
pid: 102,
process_fps: 5.0,
skipped_fps: 0,
skipped_pct: 0,
connection_quality: "excellent" as const,
expected_fps: 5,
reconnects_last_hour: 0,
@@ -75,6 +76,7 @@ export const BASE_STATS = {
uptime: 86400,
latest_version: "0.15.0",
version: "0.15.0-test",
retention_unmet: false,
},
camera_fps: 15.0,
process_fps: 15.0,
+7 -7
View File
@@ -49,7 +49,7 @@ export interface ApiMockOverrides {
};
users?: { username: string; role: string }[];
notices?: unknown[];
noticeStats?: unknown[];
dismissedChecks?: unknown[];
/** camera name to the ffprobe entries returned for `paths=camera:<name>` */
ffprobe?: Record<string, unknown[]>;
}
@@ -234,13 +234,12 @@ export class ApiMocker {
return route.fulfill({ json: entries });
});
// Notices. The stats route is registered after the list route so it wins
// for /api/notices/stats; the list glob does not match a sub-path anyway.
// Notices
await this.page.route("**/api/notices", (route) =>
route.fulfill({ json: overrides?.notices ?? [] }),
);
await this.page.route("**/api/notices/stats", (route) =>
route.fulfill({ json: overrides?.noticeStats ?? [] }),
await this.page.route("**/api/notices/dismissed_checks", (route) =>
route.fulfill({ json: overrides?.dismissedChecks ?? [] }),
);
// Users. GET lists them; POST/PUT (create, password) just succeed, so
@@ -324,8 +323,9 @@ export class MediaMocker {
}),
);
// Event thumbnails
await this.page.route("**/api/events/*/thumbnail.jpg**", (route) =>
// Event thumbnails. The explore grid and detail dialog request .webp,
// everything else requests .jpg.
await this.page.route("**/api/events/*/thumbnail.{jpg,webp}**", (route) =>
route.fulfill({
contentType: "image/png",
body: PLACEHOLDER_PNG,
+284
View File
@@ -0,0 +1,284 @@
/**
* Command menu tests -- HIGH tier.
*
* Ctrl+K and "/" open a search box over pages, cameras, camera groups,
* settings sections and actions. Text fields keep their own keys, recent
* commands survive a reload, and viewers never see admin entries.
* The menu is desktop only.
*/
import { test, expect } from "../fixtures/frigate-test";
import type { FrigateApp } from "../fixtures/frigate-test";
import { getMonacoVisibleText } from "../helpers/monaco";
import {
restrictedProfile,
viewerProfile,
} from "../fixtures/mock-data/profile";
const SAMPLE_CONFIG = "mqtt:\n host: mqtt\n";
function menu(app: FrigateApp) {
return app.page.locator("[cmdk-root]");
}
function commands(app: FrigateApp, text: string | RegExp) {
return app.page.locator("[cmdk-item]", { hasText: text });
}
/**
* Reads the recent command ids straight out of idb-keyval's store, which is
* where useUserPersistence writes under a key namespaced by username. The
* write is fire and forget, so a test that reloads has to wait for it first.
*/
function storedRecents(app: FrigateApp) {
return app.page.evaluate(
() =>
new Promise<string[]>((resolve) => {
const request = indexedDB.open("keyval-store");
request.onerror = () => resolve([]);
request.onsuccess = () => {
const read = request.result
.transaction("keyval", "readonly")
.objectStore("keyval")
.get("command-menu-recent:admin");
read.onerror = () => resolve([]);
read.onsuccess = () => resolve(read.result ?? []);
};
}),
);
}
async function openMenu(app: FrigateApp) {
await app.page.keyboard.press("Control+k");
await expect(menu(app)).toBeVisible();
}
test.describe("Command menu - opening @high", () => {
test.skip(
({ frigateApp }) => frigateApp.isMobile,
"The command menu is desktop only",
);
test("Ctrl+K opens the menu and closes it again", async ({ frigateApp }) => {
await frigateApp.goto("/");
await openMenu(frigateApp);
await expect(menu(frigateApp).locator("[cmdk-group-heading]")).toHaveText([
"Pages",
"Cameras",
"Camera groups",
"Settings",
"Actions",
]);
await frigateApp.page.keyboard.press("Control+k");
await expect(menu(frigateApp)).toBeHidden();
});
test("slash opens the menu without typing itself", async ({ frigateApp }) => {
await frigateApp.goto("/review");
await frigateApp.page.keyboard.press("/");
await expect(menu(frigateApp)).toBeVisible();
await expect(frigateApp.page.locator("[cmdk-input]")).toHaveValue("");
});
test("the config editor keeps Ctrl+K and slash", async ({ frigateApp }) => {
await frigateApp.installDefaults({ configRaw: SAMPLE_CONFIG });
await frigateApp.goto("/config");
await expect(frigateApp.page.locator(".monaco-editor").first()).toBeVisible(
{ timeout: 15_000 },
);
await frigateApp.page.locator(".monaco-editor").first().click();
await frigateApp.page.keyboard.type("/comment");
await expect
.poll(() => getMonacoVisibleText(frigateApp.page), { timeout: 10_000 })
.toContain("/comment");
await expect(menu(frigateApp)).toHaveCount(0);
// Ctrl+K opens a chord in Monaco, so the menu leaves it alone. Escape
// cancels the pending chord, which would otherwise eat the next key.
await frigateApp.page.keyboard.press("Control+k");
await expect(menu(frigateApp)).toHaveCount(0);
await frigateApp.page.keyboard.press("Escape");
});
});
test.describe("Command menu - navigating @high", () => {
test.skip(
({ frigateApp }) => frigateApp.isMobile,
"The command menu is desktop only",
);
test("a camera entry jumps to its live view", async ({ frigateApp }) => {
await frigateApp.goto("/review");
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("backy");
await expect(commands(frigateApp, "Garage")).toHaveCount(0);
await commands(frigateApp, "Backyard").filter({ hasText: "Live" }).click();
await expect(frigateApp.page).toHaveURL(/\/#backyard$/);
await expect(menu(frigateApp)).toBeHidden();
});
test("a camera entry deep links into review", async ({ frigateApp }) => {
await frigateApp.goto("/");
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("front door");
await commands(frigateApp, "Front Door")
.filter({ hasText: "Review" })
.click();
await expect(frigateApp.page).toHaveURL(/\/review\?cameras=front_door$/);
});
test("a camera group opens the live grid filtered to it", async ({
frigateApp,
}) => {
await frigateApp.goto("/review");
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("outdoor");
await commands(frigateApp, "outdoor").click();
await expect(frigateApp.page).toHaveURL(/\/\?group=outdoor$/);
});
test("repeated settings names are told apart by their group", async ({
frigateApp,
}) => {
await frigateApp.goto("/");
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("object detection");
const matches = commands(frigateApp, "Object detection");
await expect(matches).toHaveCount(2);
await expect(
matches.filter({ hasText: "Global configuration" }),
).toHaveCount(1);
await expect(
matches.filter({ hasText: "Camera configuration" }),
).toHaveCount(1);
await matches.filter({ hasText: "Camera configuration" }).click();
await expect(frigateApp.page).toHaveURL(/\/settings\?page=cameraDetect$/);
});
test("a command run once comes back under Recent", async ({ frigateApp }) => {
await frigateApp.goto("/");
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("export");
await commands(frigateApp, "Export").first().click();
await expect(frigateApp.page).toHaveURL(/\/export$/);
await expect
.poll(() => storedRecents(frigateApp))
.toEqual(["page-/export"]);
// Recents live in IndexedDB, so they outlive a full reload.
await frigateApp.goto("/");
await openMenu(frigateApp);
await expect(
menu(frigateApp).locator("[cmdk-group-heading]").first(),
).toHaveText("Recent");
await expect(
frigateApp.page.locator('[cmdk-item][data-value^="recent-"]'),
).toHaveText(["Export"]);
});
});
test.describe("Command menu - actions @high", () => {
test.skip(
({ frigateApp }) => frigateApp.isMobile,
"The command menu is desktop only",
);
test("the theme action flips the root class", async ({ frigateApp }) => {
await frigateApp.goto("/");
const isDark = () =>
frigateApp.page.evaluate(() =>
document.documentElement.classList.contains("dark"),
);
const before = await isDark();
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("toggle light");
await commands(frigateApp, "Toggle light and dark mode").click();
await expect.poll(isDark).toBe(!before);
});
test("restart asks for confirmation first", async ({ frigateApp }) => {
await frigateApp.goto("/");
await openMenu(frigateApp);
await frigateApp.page.keyboard.type("restart");
await commands(frigateApp, "Restart Frigate").click();
const confirm = frigateApp.page.getByRole("alertdialog");
await expect(confirm).toBeVisible();
await confirm.getByRole("button", { name: /cancel/i }).click();
await expect(confirm).toBeHidden();
});
});
test.describe("Command menu - permissions @high", () => {
test.skip(
({ frigateApp }) => frigateApp.isMobile,
"The command menu is desktop only",
);
test("viewers get cameras but no admin entries", async ({ frigateApp }) => {
await frigateApp.installDefaults({ profile: viewerProfile() });
await frigateApp.goto("/");
await openMenu(frigateApp);
await expect(commands(frigateApp, "Front Door").first()).toBeVisible();
await expect(commands(frigateApp, "UI settings")).toHaveCount(1);
await expect(commands(frigateApp, "Restart Frigate")).toHaveCount(0);
await expect(commands(frigateApp, "Configuration Editor")).toHaveCount(0);
await expect(commands(frigateApp, "Motion tuner")).toHaveCount(0);
});
test("a custom role only reaches groups holding its cameras", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
profile: restrictedProfile(["garage"], { role: "restricted" }),
});
await frigateApp.goto("/");
await openMenu(frigateApp);
const input = frigateApp.page.locator("[cmdk-input]");
const group = (name: string) =>
frigateApp.page.locator(`[cmdk-item][data-value="group-${name}"]`);
await input.fill("garage");
await expect(commands(frigateApp, "Garage").first()).toBeVisible();
await expect(group("default")).toHaveCount(1);
// "outdoor" holds no camera this role may see
await input.fill("outdoor");
await expect(group("outdoor")).toHaveCount(0);
// nor may a hidden camera's name pull a group up through its search terms
await input.fill("backyard");
await expect(group("default")).toHaveCount(0);
await expect(commands(frigateApp, "Backyard")).toHaveCount(0);
});
});
test.describe("Command menu - mobile @high @mobile", () => {
test.skip(
({ frigateApp }) => !frigateApp.isMobile,
"Desktop is covered above",
);
test("the menu is not mounted on a phone", async ({ frigateApp }) => {
await frigateApp.goto("/");
await frigateApp.page.keyboard.press("Control+k");
await expect(menu(frigateApp)).toHaveCount(0);
await frigateApp.page.keyboard.press("/");
await expect(menu(frigateApp)).toHaveCount(0);
});
});
+214
View File
@@ -0,0 +1,214 @@
/**
* App shell error boundaries.
*
* Both failures are forced through the mock layer rather than through test
* hooks in the app. A non-array payload reaches a component that treats it as
* a list and throws on the first render; aborting a page's asset request
* reproduces what an open tab sees after Frigate is updated underneath it.
*/
import { test, expect, type FrigateApp } from "../fixtures/frigate-test";
import { grantClipboardPermissions, readClipboard } from "../helpers/clipboard";
/** Exports builds its list with `rawExports.filter(...)`. */
async function breakExportsPage(app: FrigateApp) {
await app.page.route("**/api/exports**", (route) =>
route.fulfill({ json: { unexpected: true } }),
);
await app.goto("/export");
await expect(app.page.getByTestId("error-panel")).toBeVisible({
timeout: 10_000,
});
}
/**
* `useStats` runs `Object.entries(stats.detectors)` and only the status bar
* and bottom bar call it, so null detectors throw in one chrome component.
*/
async function breakStatusbar(app: FrigateApp) {
await app.installDefaults({ stats: { detectors: null } });
await app.goto("/");
}
/** GeneralSettings and the status bar both read profiles, so both throw. */
async function breakAllDesktopChrome(app: FrigateApp) {
await app.page.route("**/api/profiles**", (route) =>
route.fulfill({
json: { profiles: { broken: true }, active_profile: "default" },
}),
);
await app.goto("/");
}
test.describe("Error boundaries - page failure @high", () => {
// React mirrors the caught error to console.error, and the panel shows the
// TypeError message on purpose.
test.use({
expectedErrors: [
/is not a function|An error occurred in the|The above error occurred/,
],
});
test("a thrown page renders a recovery panel, not a blank screen", async ({
frigateApp,
}) => {
await breakExportsPage(frigateApp);
const panel = frigateApp.page.getByTestId("error-panel");
await expect(panel.getByText("This page stopped working")).toBeVisible();
await expect(panel.getByRole("button", { name: "Reload" })).toBeVisible();
await expect(panel.getByTestId("error-panel-message")).toContainText(
"is not a function",
);
// Contained to the route: the panel sits in the page container and the
// chrome boundary never trips, so navigation stays usable.
await expect(
frigateApp.page.locator("#pageRoot [data-testid='error-panel']"),
).toBeVisible();
await expect(frigateApp.page.getByTestId("error-strip")).toHaveCount(0);
await expect(frigateApp.page.locator('a[href="/"]').first()).toBeVisible();
});
test("Copy details puts a triageable report on the clipboard", async ({
frigateApp,
context,
}) => {
await grantClipboardPermissions(context);
await breakExportsPage(frigateApp);
await frigateApp.page
.getByTestId("error-panel")
.getByRole("button", { name: "Copy details" })
.click();
await expect
.poll(() => readClipboard(frigateApp.page), { timeout: 5_000 })
.toContain("Frigate UI crash report");
const report = await readClipboard(frigateApp.page);
expect(report).toContain("Version: 0.15.0-test");
expect(report).toMatch(/^Page: http/m);
expect(report).toContain("is not a function");
});
test("navigating away drops the panel", async ({ frigateApp }) => {
await breakExportsPage(frigateApp);
await frigateApp.page.locator('a[href="/"]').first().click();
await expect(frigateApp.page).toHaveURL(/\/$/);
await expect(frigateApp.page.getByTestId("error-panel")).toHaveCount(0);
await expect(
frigateApp.page.locator("[data-camera='front_door']"),
).toBeVisible({ timeout: 10_000 });
});
test("@mobile the panel leaves the bottom bar reachable", async ({
frigateApp,
}) => {
test.skip(!frigateApp.isMobile, "Mobile-only assertion");
await breakExportsPage(frigateApp);
await expect(
frigateApp.page
.getByTestId("error-panel")
.getByRole("button", { name: "Reload" }),
).toBeVisible();
await expect(
frigateApp.page.locator('a[href="/review"]').first(),
).toBeVisible();
});
});
test.describe("Error boundaries - chrome failure @high", () => {
test.use({
expectedErrors: [
/is not a function|An error occurred in the|The above error occurred/,
],
});
test("a thrown status bar leaves the sidebar usable", async ({
frigateApp,
}) => {
test.skip(frigateApp.isMobile, "The status bar is desktop chrome");
await breakStatusbar(frigateApp);
const strip = frigateApp.page.getByTestId("error-strip");
await expect(strip).toBeVisible({ timeout: 10_000 });
await expect(strip).toHaveCount(1);
await expect(strip.getByRole("button", { name: "Reload" })).toBeVisible();
// Each chrome component owns a boundary, so the sidebar outlives the
// status bar and the user can still navigate out.
await expect(frigateApp.page.locator("aside")).toBeVisible();
await expect(
frigateApp.page.locator('a[href="/review"]').first(),
).toBeVisible();
await expect(frigateApp.page.getByTestId("error-panel")).toHaveCount(0);
});
test("chrome failures never reach the page content", async ({
frigateApp,
}) => {
test.skip(frigateApp.isMobile, "Desktop chrome");
await breakAllDesktopChrome(frigateApp);
await expect(
frigateApp.page.getByTestId("error-strip").first(),
).toBeVisible({ timeout: 10_000 });
await expect(frigateApp.page.getByTestId("error-panel")).toHaveCount(0);
await expect(
frigateApp.page.locator("[data-camera='front_door']"),
).toBeVisible({ timeout: 10_000 });
});
});
test.describe("Error boundaries - clipboard refused @high", () => {
test.use({
expectedErrors: [
/is not a function|An error occurred in the|The above error occurred/,
],
});
test("a refused clipboard write reports the failure", async ({
frigateApp,
}) => {
// copy-to-clipboard treats a false return from execCommand as a failure
// and falls back to a prompt, which Playwright dismisses on its own.
await frigateApp.page.addInitScript(() => {
document.execCommand = () => false;
});
await breakExportsPage(frigateApp);
await frigateApp.page
.getByTestId("error-panel")
.getByRole("button", { name: "Copy details" })
.click();
await expect(
frigateApp.page.getByText("Could not copy details to clipboard"),
).toBeVisible();
});
});
test.describe("Error boundaries - stale assets @high", () => {
test.use({
expectedErrors: [
/Failed to fetch dynamically imported module|Importing a module script failed|net::ERR_FAILED|An error occurred in the|The above error occurred/,
],
});
test("a missing page chunk asks for a reload", async ({ frigateApp }) => {
await frigateApp.page.route(
/\/assets\/Exports-[^/]+\.js(\?.*)?$/,
(route) => route.abort("failed"),
);
await frigateApp.goto("/");
await frigateApp.page.locator('a[href="/export"]').first().click();
const panel = frigateApp.page.getByTestId("error-panel");
await expect(panel).toBeVisible({ timeout: 10_000 });
await expect(panel.getByText("Reload required")).toBeVisible();
await expect(panel.getByRole("button", { name: "Reload" })).toBeVisible();
});
});
+68
View File
@@ -263,3 +263,71 @@ test.describe("Explore — mobile @high @mobile", () => {
await expect(searchInput).toBeFocused();
});
});
// ---------------------------------------------------------------------------
// Frigate+ submission — desktop only
// The detail dialog's previous/next arrows only render on desktop.
// ---------------------------------------------------------------------------
test.describe("Explore — Frigate+ submission (desktop) @high", () => {
test.skip(
({ frigateApp }) => frigateApp.isMobile,
"Detail dialog navigation arrows are desktop-only",
);
test("in-flight submission does not mark the next tracked object as submitted", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({ config: { plus: { enabled: true } } });
const page = frigateApp.page;
// Hold the submission open so it is still in flight while the user moves
// on to the next tracked object.
let releaseSubmission: () => void = () => {};
const submissionHeld = new Promise<void>((resolve) => {
releaseSubmission = resolve;
});
let submissions = 0;
await page.route("**/api/events/*/plus", async (route) => {
submissions += 1;
await submissionHeld;
await route.fulfill({ json: { success: true } });
});
await frigateApp.goto("/explore?labels=person");
const firstResult = page.locator("[data-start]").first();
await expect(firstResult).toBeVisible({ timeout: 10_000 });
await firstResult.click();
// The label being confirmed is rendered in a <code> tag inside the
// "Is this object a <label>?" question.
const dialog = page.getByRole("dialog");
await expect(dialog.locator("code")).toHaveText("person");
await dialog.getByRole("button", { name: "Yes", exact: true }).click();
await expect.poll(() => submissions, { timeout: 5_000 }).toBe(1);
await page.getByRole("button", { name: "Next tracked object" }).click();
await expect(dialog.locator("code")).toHaveText("car");
const submissionLanded = page.waitForResponse(/\/api\/events\/.*\/plus/);
releaseSubmission();
await submissionLanded;
// two frames is enough for React to flush the response handler's state
// updates, so the assertions below can't pass by racing ahead of them
await page.evaluate(
() =>
new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(resolve)),
),
);
// The car was never submitted, so its question must be untouched.
expect(submissions).toBe(1);
await expect(dialog.getByText("Submitted")).toHaveCount(0);
await expect(
dialog.getByRole("button", { name: "Yes", exact: true }),
).toBeVisible();
});
});
+207
View File
@@ -0,0 +1,207 @@
/**
* Debug Replay range selection from History -- HIGH tier.
*
* Covers the "Select from Timeline" flow that Debug Replay shares with
* Export. The other half of the same report, a loading spinner latched
* after Cancel, needs real media: the vod mock serves an empty playlist.
*/
import { test, expect, type FrigateApp } from "../fixtures/frigate-test";
// the selection is seeded around the playback position, so land near the
// live edge
const playbackTime = Math.floor(Date.now() / 1000) - 300;
async function openRecordingView(frigateApp: FrigateApp) {
// The recording view pulls these while the timeline renders; the preview
// server 500s on them, which the error collector would flag.
await frigateApp.page.route("**/api/*/recordings**", (route) =>
route.fulfill({ json: [] }),
);
await frigateApp.page.route("**/api/recordings/unavailable**", (route) =>
route.fulfill({ json: [] }),
);
// inert here; the 0.19 recording view fetches coverage and needs an
// object, so this has to follow the broad recordings route to win
await frigateApp.page.route("**/api/*/recordings/coverage**", (route) =>
route.fulfill({
json: {
spans: [
{
start_time: playbackTime - 3600,
end_time: playbackTime + 600,
streams: ["main"],
},
],
codecs_compatible: true,
streams: {
main: {
video_codec: "h264",
audio_rate: null,
audio_codec: null,
has_audio: false,
bitrate: 2_000_000,
},
},
},
}),
);
await frigateApp.goto(`/review?timestamp=front_door_${playbackTime}`);
}
// desktop reaches Debug Replay through the Actions menu, mobile through
// the settings drawer; both render the same form
async function openDebugReplayForm(frigateApp: FrigateApp) {
if (frigateApp.isMobile) {
await frigateApp.page
.getByRole("button", { name: /filters/i })
.first()
.click({ timeout: 15_000 });
await frigateApp.page
.getByRole("button", { name: /^debug replay$/i })
.click();
} else {
await frigateApp.page
.getByRole("button", { name: /actions/i })
.click({ timeout: 15_000 });
await frigateApp.page
.getByRole("menuitem", { name: /debug replay/i })
.click();
}
const form = frigateApp.page.getByRole("dialog");
await expect(form).toBeVisible({ timeout: 5_000 });
return form;
}
async function selectRangeFromTimeline(frigateApp: FrigateApp) {
const form = await openDebugReplayForm(frigateApp);
await form.getByText("From Timeline").click();
await form.getByRole("button", { name: "Select", exact: true }).click();
await expect(form).toBeHidden({ timeout: 5_000 });
await expect(frigateApp.page.locator(".export-start")).toHaveText(
/\d{1,2}:\d{2}/,
{ timeout: 5_000 },
);
}
// moving the playhead between the two selections is what makes the second
// range differ from the first
async function reselectRange(frigateApp: FrigateApp) {
await selectRangeFromTimeline(frigateApp);
await frigateApp.page
.getByRole("button", { name: /^cancel$/i })
.click({ timeout: 5_000 });
await expect(frigateApp.page.locator(".export-start")).toHaveCount(0);
const segments = frigateApp.page.locator(".segment[data-segment-id]");
const count = await segments.count();
await segments.nth(Math.min(20, count - 1)).click({ force: true });
await selectRangeFromTimeline(frigateApp);
}
// the loop flipped this label between the two ranges ~25 times a second
async function countHandleLabelChanges(frigateApp: FrigateApp) {
return frigateApp.page.evaluate(async () => {
const handle = document.querySelector(".export-start");
if (!handle) {
return -1;
}
let changes = 0;
let last = handle.textContent;
const observer = new MutationObserver(() => {
if (handle.textContent !== last) {
changes += 1;
last = handle.textContent;
}
});
observer.observe(handle, {
subtree: true,
childList: true,
characterData: true,
});
await new Promise((resolve) => setTimeout(resolve, 1500));
observer.disconnect();
return changes;
});
}
test.describe("Debug Replay from History @high", () => {
test("a reselected range lands once and stays put", async ({
frigateApp,
}) => {
if (frigateApp.isMobile) {
test.skip();
return;
}
const pageErrors: string[] = [];
frigateApp.page.on("pageerror", (err) => pageErrors.push(err.message));
await frigateApp.installDefaults();
await openRecordingView(frigateApp);
await reselectRange(frigateApp);
expect(await countHandleLabelChanges(frigateApp)).toBe(0);
expect(
pageErrors.filter((message) => /Maximum update depth/i.test(message)),
).toHaveLength(0);
});
test("dragging a handle after a reselect moves it", async ({
frigateApp,
}) => {
if (frigateApp.isMobile) {
test.skip();
return;
}
await frigateApp.installDefaults();
await openRecordingView(frigateApp);
await reselectRange(frigateApp);
const start = frigateApp.page.locator(".export-start");
const before = (await start.textContent()) ?? "";
const box = await start.boundingBox();
if (!box) {
throw new Error("export start handle has no bounding box");
}
const x = box.x + box.width / 2;
const y = box.y + box.height / 2;
await frigateApp.page.mouse.move(x, y);
await frigateApp.page.mouse.down();
await frigateApp.page.mouse.move(x, y - 90, { steps: 12 });
await frigateApp.page.mouse.up();
await expect(start).not.toHaveText(before, { timeout: 5_000 });
});
});
test.describe("Debug Replay from History — mobile @high @mobile", () => {
test("a reselected range lands once and stays put", async ({
frigateApp,
}) => {
if (!frigateApp.isMobile) {
test.skip();
return;
}
const pageErrors: string[] = [];
frigateApp.page.on("pageerror", (err) => pageErrors.push(err.message));
await frigateApp.installDefaults();
await openRecordingView(frigateApp);
await reselectRange(frigateApp);
expect(await countHandleLabelChanges(frigateApp)).toBe(0);
expect(
pageErrors.filter((message) => /Maximum update depth/i.test(message)),
).toHaveLength(0);
});
});
+418 -81
View File
@@ -5,34 +5,35 @@
*/
import { test, expect } from "../fixtures/frigate-test";
import { viewerProfile } from "../fixtures/mock-data/profile";
const NOW = Math.floor(Date.now() / 1000);
// the fixture detector runs at 75.5 ms, above the live warning threshold
const QUIET_STATS = { detectors: { cpu: { inference_speed: 10 } } };
const STATE_NOTICE = {
id: "ffmpeg_crash_loop:front_door",
kind: "ffmpeg_crash_loop",
mode: "state",
const ERROR_NOTICE = {
id: "model_download_failed:yolo/model.onnx",
kind: "model_download_failed",
severity: "error",
category: "camera",
scope: "front_door",
params: { restarts: 6 },
category: "model",
scope: "yolo/model.onnx",
params: { file: "model.onnx", model: "yolo", error: "timeout" },
link: null,
first_seen: NOW - 600,
last_seen: NOW,
count: 1,
count: 2,
dismissed_at: null,
};
const EVENT_NOTICE = {
id: "detector_stuck",
kind: "detector_stuck",
mode: "event",
severity: "warning",
category: "detector",
scope: null,
params: { detector: "ov" },
link: "/system#general",
first_seen: NOW - 7200,
last_seen: NOW - 60,
count: 3,
@@ -45,16 +46,7 @@ test.describe("System — Health tab @medium", () => {
}) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [STATE_NOTICE, EVENT_NOTICE],
noticeStats: [
{
kind: "ffmpeg_crash_loop",
occurrences: 14,
dismissals: 0,
first_seen: NOW - 86400 * 20,
last_seen: NOW,
},
],
notices: [ERROR_NOTICE, EVENT_NOTICE],
});
await frigateApp.goto("/system");
@@ -67,11 +59,13 @@ test.describe("System — Health tab @medium", () => {
const rows = frigateApp.page.locator("[data-testid^='health-problem-']");
await expect(rows).toHaveCount(2);
await expect(rows.nth(0)).toHaveAttribute("data-severity", "error");
await expect(rows.nth(0)).toContainText("ffmpeg has crashed 6 times");
await expect(rows.nth(0)).toContainText("14 times since");
await expect(rows.nth(0)).toContainText(
"Downloading model.onnx for yolo failed: timeout",
);
await expect(rows.nth(0)).toContainText("2 times");
await expect(
rows.nth(0).getByRole("button", { name: "Dismiss" }),
).toHaveCount(0);
).toBeVisible();
await expect(rows.nth(1)).toContainText("Detector ov was restarted");
await expect(rows.nth(1)).toContainText("3 times");
await expect(
@@ -104,7 +98,10 @@ test.describe("System — Health tab @medium", () => {
req.url().includes("/api/notices/detector_stuck/dismiss") &&
req.method() === "POST",
);
await frigateApp.page.getByRole("button", { name: "Dismiss" }).click();
await frigateApp.page
.getByTestId("health-problem-notice:detector_stuck")
.getByRole("button", { name: "Dismiss" })
.click();
await request;
await expect(
@@ -133,13 +130,13 @@ test.describe("System — Health tab @medium", () => {
stats: QUIET_STATS,
notices: [
{
id: "update_available",
id: "update_available:0.19.0",
kind: "update_available",
mode: "state",
severity: "info",
category: "system",
scope: null,
scope: "0.19.0",
params: { version: "0.19.0" },
link: "https://github.com/blakeblackshear/frigate/releases/tag/v0.19.0",
first_seen: NOW - 3600,
last_seen: NOW,
count: 1,
@@ -150,7 +147,7 @@ test.describe("System — Health tab @medium", () => {
await frigateApp.goto("/system#health");
const row = frigateApp.page.getByTestId(
"health-problem-notice:update_available",
"health-problem-notice:update_available:0.19.0",
);
await expect(row).toBeVisible({ timeout: 15_000 });
await expect(row).toHaveAttribute("data-severity", "info");
@@ -159,7 +156,229 @@ test.describe("System — Health tab @medium", () => {
"href",
"https://github.com/blakeblackshear/frigate/releases/tag/v0.19.0",
);
await expect(row.getByRole("button", { name: "Dismiss" })).toBeVisible();
});
test("the filter shows dismissed notices without a Dismiss button", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [EVENT_NOTICE],
});
await frigateApp.page.route(
(url) =>
url.pathname.endsWith("/api/notices") &&
url.searchParams.get("include_dismissed") === "true",
(route) =>
route.fulfill({
json: [EVENT_NOTICE, { ...ERROR_NOTICE, dismissed_at: NOW - 120 }],
}),
);
await frigateApp.goto("/system#health");
await frigateApp.page.getByRole("button", { name: "Filter" }).click();
const showDismissed = frigateApp.page.getByRole("switch", {
name: "Show dismissed",
});
await expect(showDismissed).toHaveAttribute("aria-checked", "false");
await showDismissed.click();
const row = frigateApp.page.getByTestId(
"health-problem-notice:model_download_failed:yolo/model.onnx",
);
await expect(row).toBeVisible({ timeout: 15_000 });
await expect(row).toContainText("Dismissed");
await expect(row.getByRole("button", { name: "Dismiss" })).toHaveCount(0);
await showDismissed.click();
await expect(row).toHaveCount(0);
});
test("clear dismissed deletes the dismissed rows after confirming", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [EVENT_NOTICE],
});
// the history loses its dismissed row once the DELETE lands
let cleared = false;
await frigateApp.page.route(
(url) =>
url.pathname.endsWith("/api/notices") &&
url.searchParams.get("include_dismissed") === "true",
(route) =>
route.fulfill({
json: cleared
? [EVENT_NOTICE]
: [EVENT_NOTICE, { ...ERROR_NOTICE, dismissed_at: NOW - 120 }],
}),
);
await frigateApp.page.route("**/api/notices/dismissed", (route) => {
cleared = true;
return route.fulfill({ json: { success: true } });
});
await frigateApp.goto("/system#health");
await frigateApp.page.getByRole("button", { name: "Filter" }).click();
await frigateApp.page
.getByRole("switch", { name: "Show dismissed" })
.click();
const row = frigateApp.page.getByTestId(
"health-problem-notice:model_download_failed:yolo/model.onnx",
);
await expect(row).toBeVisible({ timeout: 15_000 });
await frigateApp.page.keyboard.press("Escape");
await frigateApp.page
.getByRole("button", { name: "Clear dismissed" })
.click();
const request = frigateApp.page.waitForRequest(
(req) =>
req.url().endsWith("/api/notices/dismissed") &&
req.method() === "DELETE",
);
await frigateApp.page
.getByRole("alertdialog")
.getByRole("button", { name: "Clear dismissed" })
.click();
await request;
await expect(row).toHaveCount(0);
await expect(
frigateApp.page.getByText("No dismissed notices"),
).toBeVisible();
});
test("severity switches hide notices of that severity", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [ERROR_NOTICE, EVENT_NOTICE],
});
await frigateApp.goto("/system#health");
const rows = frigateApp.page.locator("[data-testid^='health-problem-']");
await expect(rows).toHaveCount(2, { timeout: 15_000 });
await frigateApp.page.getByRole("button", { name: "Filter" }).click();
await frigateApp.page.getByRole("switch", { name: "Warning" }).click();
await expect(rows).toHaveCount(1);
await expect(rows.first()).toHaveAttribute("data-severity", "error");
await frigateApp.page.getByRole("switch", { name: "Error" }).click();
await expect(rows).toHaveCount(0);
await expect(
frigateApp.page.getByText("No notices match the filter"),
).toBeVisible();
});
test("failed login bursts are per user and show their attempt count", async ({
frigateApp,
}) => {
const burst = (user: string, start: number, count: number) => ({
id: `failed_login:${user}:${start}`,
kind: "failed_login",
severity: "warning",
category: "system",
scope: `${user}:${start}`,
params: { user },
link: "/logs",
first_seen: start,
last_seen: start + 60,
count,
dismissed_at: null,
});
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [burst("admin", NOW - 60, 7), burst("ghost", NOW - 3600, 1)],
});
await frigateApp.goto("/system#health");
const attack = frigateApp.page.getByTestId(
`health-problem-notice:failed_login:admin:${NOW - 60}`,
);
await expect(attack).toBeVisible({ timeout: 15_000 });
await expect(attack).toContainText("Failed login attempts for admin");
await expect(attack).toContainText("7 times");
await expect(attack).not.toContainText(String(NOW - 60));
await expect(
attack.getByRole("link", { name: "Open settings" }),
).toHaveAttribute("href", "/logs");
await expect(
frigateApp.page.getByTestId(
`health-problem-notice:failed_login:ghost:${NOW - 3600}`,
),
).toContainText("Failed login attempt for ghost");
});
test("skipped detections notice links to camera stats", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [
{
id: "skipped_detections:front_door",
kind: "skipped_detections",
severity: "warning",
category: "camera",
scope: "front_door",
params: { pct: 12.5 },
link: "/system#cameras",
first_seen: NOW - 600,
last_seen: NOW - 600,
count: 1,
dismissed_at: null,
},
],
});
await frigateApp.goto("/system#health");
const row = frigateApp.page.getByTestId(
"health-problem-notice:skipped_detections:front_door",
);
await expect(row).toBeVisible({ timeout: 15_000 });
await expect(row).toContainText("skipped 12.5% of frames");
await expect(
row.getByRole("link", { name: "Open settings" }),
).toHaveAttribute("href", "/system#cameras");
});
test("shm notice links to storage metrics", async ({ frigateApp }) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [
{
id: "shm_too_low",
kind: "shm_too_low",
severity: "warning",
category: "system",
scope: null,
params: { total: 64, min: 180 },
link: "/system#storage",
first_seen: NOW - 600,
last_seen: NOW - 600,
count: 1,
dismissed_at: null,
},
],
});
await frigateApp.goto("/system#health");
const row = frigateApp.page.getByTestId(
"health-problem-notice:shm_too_low",
);
await expect(row).toBeVisible({ timeout: 15_000 });
await expect(row).toContainText(
"/dev/shm allocation (64 MB) should be increased to at least 180 MB",
);
await expect(
row.getByRole("link", { name: "Open settings" }),
).toHaveAttribute("href", "/system#storage");
});
});
@@ -169,13 +388,13 @@ test.describe("System — Health tab mobile @medium @mobile", () => {
test("notices render at mobile viewport", async ({ frigateApp }) => {
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [STATE_NOTICE],
notices: [ERROR_NOTICE],
});
await frigateApp.goto("/system#health");
await expect(
frigateApp.page.getByTestId(
"health-problem-notice:ffmpeg_crash_loop:front_door",
"health-problem-notice:model_download_failed:yolo/model.onnx",
),
).toBeVisible({ timeout: 15_000 });
});
@@ -298,14 +517,17 @@ test.describe("System — Health hardware pane @medium", () => {
).toHaveAttribute("data-state", "warning");
});
test("face recognition row reflects the runtime device", async ({
test("slow face recognition on the CPU warns when an accelerator exists", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
config: { face_recognition: { enabled: true } },
stats: {
...QUIET_STATS,
embeddings: { devices: { face_recognition: "CPU" } },
embeddings: {
face_recognition_speed: 800,
devices: { face_recognition: "CPU" },
},
},
});
await frigateApp.goto("/system#health");
@@ -321,6 +543,27 @@ test.describe("System — Health hardware pane @medium", () => {
);
});
test("fast face recognition on the CPU is ok", async ({ frigateApp }) => {
await frigateApp.installDefaults({
config: { face_recognition: { enabled: true } },
stats: {
...QUIET_STATS,
embeddings: {
face_recognition_speed: 40,
devices: { face_recognition: "CPU" },
},
},
});
await frigateApp.goto("/system#health");
const row = frigateApp.page.getByTestId(
"hardware-row-enrichment:face_recognition",
);
await expect(row).toHaveAttribute("data-state", "ok", { timeout: 15_000 });
await expect(row).toContainText("CPU");
await expect(row).not.toContainText("although an accelerator");
});
test("explicit GPU that loaded on CUDA is ok despite the probe", async ({
frigateApp,
}) => {
@@ -415,36 +658,24 @@ test.describe("System — Health hardware pane @medium", () => {
});
test.describe("System — Health notices sources @medium", () => {
test("live offline camera and config checks render with links", async ({
frigateApp,
}) => {
test("config checks render with links", async ({ frigateApp }) => {
await frigateApp.installDefaults({
config: {
cameras: {
garage: { detect: { width: 2560, height: 1440, fps: 10 } },
},
},
stats: { ...QUIET_STATS, cameras: { front_door: { camera_fps: 0 } } },
stats: QUIET_STATS,
});
await frigateApp.goto("/system#health");
const rows = frigateApp.page.locator("[data-testid^='health-problem-']");
await expect(rows.first()).toBeVisible({ timeout: 15_000 });
await expect(rows.filter({ hasText: "Front Door is offline" })).toHaveCount(
1,
);
await expect(
frigateApp.page.getByText(
"This detect resolution is higher than recommended",
),
).toBeVisible();
await expect(
rows
.filter({ hasText: "Front Door is offline" })
.getByRole("link", { name: "Open settings" }),
).toHaveAttribute("href", "/logs");
).toBeVisible({ timeout: 15_000 });
const fpsRow = frigateApp.page.getByTestId(
"health-problem-config:detect:fps-greater-than-five:garage",
"health-problem-config:detect:fps-greater-than-five:camera.garage",
);
await expect(fpsRow).toHaveAttribute("data-severity", "info");
await expect(
@@ -452,6 +683,28 @@ test.describe("System — Health notices sources @medium", () => {
).toHaveAttribute("href", "/settings?page=cameraDetect&camera=garage");
});
test("status bar problems stay out of the list", async ({ frigateApp }) => {
test.skip(frigateApp.isMobile, "Status bar is desktop-only");
await frigateApp.installDefaults({
stats: {
service: { retention_unmet: true },
cameras: { front_door: { camera_fps: 0 } },
},
});
await frigateApp.goto("/system#health");
// the status bar shows a problem, so stats have loaded
await expect(
frigateApp.page.getByText("Front Door is offline"),
).toBeVisible({ timeout: 15_000 });
await expect(
frigateApp.page.locator("[data-testid^='health-problem-']"),
).toHaveCount(0);
await expect(
frigateApp.page.getByText("Your Frigate installation is healthy"),
).toBeVisible();
});
// the fixture's cameras all have a record role with recording off, so
// dropping the role isolates the gate on record.enabled
const NO_RECORD_ROLE = {
@@ -553,43 +806,11 @@ test.describe("System — Health notices sources @medium", () => {
await expect(rows).toHaveCount(2);
await expect(
frigateApp.page.getByTestId(
"health-problem-config:detect:detect-resolution-high:garage",
"health-problem-config:detect:detect-resolution-high:camera.garage",
),
).toBeVisible();
});
test("registry rows sort ahead of live rows and keep Dismiss", async ({
frigateApp,
}) => {
await frigateApp.installDefaults({
notices: [
{
id: "detector_stuck:cpu",
kind: "detector_stuck",
mode: "event",
severity: "warning",
category: "detector",
scope: "cpu",
params: { detector: "cpu" },
first_seen: NOW - 60,
last_seen: NOW - 60,
count: 1,
dismissed_at: null,
},
],
// the default fixture's slow cpu detector is the live warning here
});
await frigateApp.goto("/system#health");
const rows = frigateApp.page.locator("[data-severity='warning']");
await expect(rows.first()).toBeVisible({ timeout: 15_000 });
await expect(rows.nth(0)).toContainText("Detector cpu was restarted");
await expect(
rows.nth(0).getByRole("button", { name: "Dismiss" }),
).toBeVisible();
await expect(rows.nth(1)).toContainText("Cpu is slow");
});
test("empty state when stats, config, and registry are clean", async ({
frigateApp,
}) => {
@@ -797,7 +1018,7 @@ test.describe("System — Health notices sources @medium", () => {
await frigateApp.goto("/system#health");
const row = frigateApp.page.getByTestId(
"health-problem-config:lpr:global-disabled:garage",
"health-problem-config:lpr:global-disabled:camera.garage",
);
await expect(row).toBeVisible({ timeout: 15_000 });
await expect(
@@ -824,4 +1045,120 @@ test.describe("System — Health notices sources @medium", () => {
.click();
await expect(frigateApp.page).toHaveURL(/\/system#health/);
});
test("status bar counts undismissed notices next to the health text", async ({
frigateApp,
}) => {
test.skip(frigateApp.isMobile, "Status bar is desktop-only");
await frigateApp.installDefaults({
stats: QUIET_STATS,
notices: [EVENT_NOTICE],
});
await frigateApp.goto("/");
const notices = frigateApp.page.getByRole("link", {
name: "1 system notice",
});
await expect(notices).toBeVisible({ timeout: 15_000 });
await expect(notices).toHaveAttribute("href", "/system#health");
await expect(
frigateApp.page.getByRole("link", { name: "System is healthy" }),
).toBeVisible();
});
test("status bar shows viewers no problems or health text", async ({
frigateApp,
}) => {
test.skip(frigateApp.isMobile, "Status bar is desktop-only");
await frigateApp.installDefaults({
profile: viewerProfile(),
stats: {
cpu_usages: { "frigate.full_system": { cpu: "12.0" } },
cameras: { front_door: { camera_fps: 0 } },
},
});
await frigateApp.goto("/");
// the CPU reading comes from the same stats as the offline problem
await expect(frigateApp.page.getByText("CPU 12%")).toBeVisible({
timeout: 15_000,
});
await expect(
frigateApp.page.getByText("Front Door is offline"),
).toHaveCount(0);
await expect(frigateApp.page.getByText("System is healthy")).toHaveCount(0);
});
test("a config row can be dismissed", async ({ frigateApp }) => {
const id = "config:detect:fps-greater-than-five:camera.garage";
await frigateApp.installDefaults({
config: {
cameras: {
garage: { detect: { width: 2560, height: 1440, fps: 10 } },
},
},
stats: QUIET_STATS,
});
// the list gains the dismissal after the POST so the refetch hides the row
let dismissed = false;
await frigateApp.page.route(`**/api/notices/${id}/dismiss`, (route) => {
dismissed = true;
return route.fulfill({ json: { success: true } });
});
await frigateApp.page.route("**/api/notices/dismissed_checks", (route) =>
route.fulfill({ json: dismissed ? [{ id, dismissed_at: NOW }] : [] }),
);
await frigateApp.goto("/system#health");
const row = frigateApp.page.getByTestId(`health-problem-${id}`);
await expect(row).toBeVisible({ timeout: 15_000 });
const request = frigateApp.page.waitForRequest(
(req) =>
req.url().includes(`/api/notices/${id}/dismiss`) &&
req.method() === "POST",
);
await row.getByRole("button", { name: "Dismiss" }).click();
await request;
await expect(row).toHaveCount(0);
});
test("dismissed config rows move to the dismissed list", async ({
frigateApp,
}) => {
const id = "config:detect:fps-greater-than-five:camera.garage";
await frigateApp.installDefaults({
config: {
cameras: {
garage: { detect: { width: 2560, height: 1440, fps: 10 } },
},
},
stats: QUIET_STATS,
dismissedChecks: [{ id, dismissed_at: NOW - 120 }],
});
await frigateApp.page.route(
(url) =>
url.pathname.endsWith("/api/notices") &&
url.searchParams.get("include_dismissed") === "true",
(route) => route.fulfill({ json: [] }),
);
await frigateApp.goto("/system#health");
await expect(
frigateApp.page.getByText(
"This detect resolution is higher than recommended",
),
).toBeVisible({ timeout: 15_000 });
const row = frigateApp.page.getByTestId(`health-problem-${id}`);
await expect(row).toHaveCount(0);
await frigateApp.page.getByRole("button", { name: "Filter" }).click();
await frigateApp.page
.getByRole("switch", { name: "Show dismissed" })
.click();
await expect(row).toBeVisible();
await expect(row).toContainText("Dismissed");
await expect(row.getByRole("button", { name: "Dismiss" })).toHaveCount(0);
});
});
+1
View File
@@ -27,6 +27,7 @@
<link rel="mask-icon" href="/images/branding/favicon.svg" color="#3b82f7" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
</head>
<body>
<div id="root"></div>
+1
View File
@@ -27,6 +27,7 @@
<link rel="mask-icon" href="/images/branding/favicon.svg" color="#3b82f7" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
</head>
<body>
<div id="root"></div>
+212 -183
View File
@@ -35,11 +35,11 @@
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.2.8",
"@rjsf/core": "^6.4.1",
"@rjsf/shadcn": "^6.4.1",
"@rjsf/shadcn": "^6.5.2",
"@rjsf/utils": "^6.4.1",
"@rjsf/validator-ajv8": "^6.4.1",
"apexcharts": "^3.52.0",
"axios": "^1.13.6",
"axios": "^1.18.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
@@ -49,10 +49,10 @@
"framer-motion": "^12.38.0",
"hls.js": "^1.6.15",
"i18next": "^24.2.0",
"i18next-http-backend": "^3.0.1",
"i18next-http-backend": "^4.0.2",
"idb-keyval": "^6.2.1",
"immer": "^10.1.1",
"js-yaml": "^4.1.1",
"js-yaml": "^4.3.2",
"konva": "^10.2.3",
"lodash": "^4.18.1",
"lucide-react": "^0.577.0",
@@ -68,10 +68,10 @@
"react-grid-layout": "^2.2.2",
"react-hook-form": "^7.72.0",
"react-i18next": "^15.2.0",
"react-icons": "^5.5.0",
"react-icons": "^5.6.0",
"react-konva": "^19.2.3",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.30.3",
"react-router-dom": "^6.30.6",
"react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "3.4.4",
"remark-gfm": "^4.0.0",
@@ -109,7 +109,7 @@
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.8",
"eslint-plugin-vitest-globals": "^1.5.0",
"eslint-plugin-vitest-globals": "^1.6.1",
"fake-indexeddb": "^6.0.0",
"i18next-cli": "^1.5.11",
"jest-websocket-mock": "^2.5.0",
@@ -117,7 +117,7 @@
"monaco-editor": "^0.52.2",
"msw": "^2.3.5",
"patch-package": "^8.0.1",
"postcss": "^8.5.8",
"postcss": "^8.5.12",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5",
"tailwindcss": "^3.4.9",
@@ -4878,22 +4878,22 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
"version": "1.23.4",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz",
"integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rjsf/core": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.4.1.tgz",
"integrity": "sha512-+QaiSgQnOuO6ghIsohH2u/QcylkN+Da2968a75g/i4oARYJRYVxXDm2u3JR5aXndpMb4t4jTFrYyG8cNIv6oEg==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.5.2.tgz",
"integrity": "sha512-Fx+aVNQRYQyoY0vM8zYDZkuOiNe+5PLsxUySUdHfjljlT23mJnTCpPKMkxWJwh4UEWeSN0xjmknW9LfIwuQmOg==",
"license": "Apache-2.0",
"dependencies": {
"lodash": "^4.17.23",
"lodash-es": "^4.17.23",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"markdown-to-jsx": "^8.0.0",
"prop-types": "^15.8.1"
},
@@ -4901,14 +4901,14 @@
"node": ">=20"
},
"peerDependencies": {
"@rjsf/utils": "^6.4.x",
"@rjsf/utils": "^6.5.x",
"react": ">=18"
}
},
"node_modules/@rjsf/shadcn": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@rjsf/shadcn/-/shadcn-6.4.1.tgz",
"integrity": "sha512-WzwXW3XY7K1jo9XrBv6M41ScdHrnQDKpSxip5i1N6xCgEE6hiyX+wn7pDO689OoidvL3lWQmtnoqMdcoJvEWjw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@rjsf/shadcn/-/shadcn-6.5.2.tgz",
"integrity": "sha512-h3FsGRy07Gw4MrORlxNTuSb7icuyiUM0KXimg1jgtlYLhYvMKaHzJgdL/SrAyBWiUtXIsxuBhPU3rFxnA9Ml6Q==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-checkbox": "^1.3.3",
@@ -4925,19 +4925,19 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"lodash": "^4.17.23",
"lodash-es": "^4.17.23",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"lucide-react": "^0.548.0",
"tailwind-merge": "^3.4.0",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"uuid": "^13.0.0"
"uuid": "^14.0.0"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@rjsf/core": "^6.4.x",
"@rjsf/utils": "^6.4.x",
"@rjsf/core": "^6.5.x",
"@rjsf/utils": "^6.5.x",
"react": ">=18"
}
},
@@ -4951,9 +4951,9 @@
}
},
"node_modules/@rjsf/shadcn/node_modules/tailwind-merge": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
"integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
"license": "MIT",
"funding": {
"type": "github",
@@ -4961,16 +4961,17 @@
}
},
"node_modules/@rjsf/utils": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.4.1.tgz",
"integrity": "sha512-5NL3jwt3rIS5/WRTrKt++y40FS/ScKGVwYJ3jIrHSQHSwBdLnd4cHf2zcnA97L1Klj8I6tvS/ugh+blf/Diwuw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.5.2.tgz",
"integrity": "sha512-qBVQ5qf9BKMOQy/DjMl/IjD5s6akRDx18cgiSYunKt/CYc+kPzHyCVA2TU0rXCsigNlhgnfM8piC3LEHye6vpA==",
"license": "Apache-2.0",
"dependencies": {
"@x0k/json-schema-merge": "^1.0.2",
"@x0k/json-schema-merge": "^1.0.3",
"fast-equals": "^6.0.0",
"fast-uri": "^3.1.0",
"jsonpointer": "^5.0.1",
"lodash": "^4.17.23",
"lodash-es": "^4.17.23",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"react-is": "^18.3.1"
},
"engines": {
@@ -4980,6 +4981,15 @@
"react": ">=18"
}
},
"node_modules/@rjsf/utils/node_modules/fast-equals": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.0.0.tgz",
"integrity": "sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@rjsf/validator-ajv8": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.4.1.tgz",
@@ -6173,9 +6183,9 @@
}
},
"node_modules/@x0k/json-schema-merge": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@x0k/json-schema-merge/-/json-schema-merge-1.0.2.tgz",
"integrity": "sha512-1734qiJHNX3+cJGDMMw2yz7R+7kpbAtl5NdPs1c/0gO5kYT6s4dMbLXiIfpZNsOYhGZI3aH7FWrj4Zxz7epXNg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@x0k/json-schema-merge/-/json-schema-merge-1.0.3.tgz",
"integrity": "sha512-lerJC4sI9CNUQWdff3PnU1YJOqazD6TjMcvxZIPXUBjn4j1cUiXE0LvzhMnGYzKKr271TkvXJtH7gEwksrtn+w==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -6458,14 +6468,40 @@
}
},
"node_modules/axios": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz",
"integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/axios/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axios/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/bail": {
@@ -6483,6 +6519,19 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/baseline-browser-mapping": {
"version": "2.11.23",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz",
"integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@@ -6522,20 +6571,21 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"version": "4.28.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz",
"integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==",
"dev": true,
"funding": [
{
@@ -6553,10 +6603,11 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.5.4",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0"
"baseline-browser-mapping": "^2.11.20",
"caniuse-lite": "^1.0.30001810",
"electron-to-chromium": "^1.5.420",
"node-releases": "^2.0.54",
"update-browserslist-db": "^1.3.2"
},
"bin": {
"browserslist": "cli.js"
@@ -6658,9 +6709,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"version": "1.0.30001810",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
"dev": true,
"funding": [
{
@@ -7012,15 +7063,6 @@
"toggle-selection": "^1.0.6"
}
},
"node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.6.12"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -7383,9 +7425,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz",
"integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==",
"version": "1.5.427",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz",
"integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==",
"dev": true,
"license": "ISC"
},
@@ -7501,9 +7543,9 @@
}
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7693,10 +7735,18 @@
}
},
"node_modules/eslint-plugin-vitest-globals": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.5.0.tgz",
"integrity": "sha512-ZSsVOaOIig0oVLzRTyk8lUfBfqzWxr/J3/NFMfGGRIkGQPejJYmDH3gXmSJxAojts77uzAGB/UmVrwi2DC4LYA==",
"dev": true
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.6.1.tgz",
"integrity": "sha512-ydMvr96XRaT913a8gy8TY/LLyGB49Wmj0+x/FCbaWSpWMSaWDLG63/5zSLLrdSGXJ0PTjWpYZOZ61YBuzNyyTw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/saqqdy"
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
@@ -7975,9 +8025,9 @@
"dev": true
},
"node_modules/fast-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
"funding": [
{
"type": "github",
@@ -8040,9 +8090,10 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -8099,9 +8150,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
@@ -8136,16 +8187,16 @@
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -8492,9 +8543,9 @@
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -8876,12 +8927,12 @@
}
},
"node_modules/i18next-http-backend": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.1.tgz",
"integrity": "sha512-XT2lYSkbAtDE55c6m7CtKxxrsfuRQO3rUfHzj8ZyRtY9CkIX3aRGwXGTkUhpGWce+J8n7sfu3J0f2wTzo7Lw0A==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-4.0.2.tgz",
"integrity": "sha512-oay62dIB2kL7+WHzoUXBjWfL3+mwijD3pQkQkIEaRLhy6kjXxUhrRenV0gInwlCASAaJaDy94+XvYizK+cAGdA==",
"license": "MIT",
"dependencies": {
"cross-fetch": "4.0.0"
"engines": {
"node": ">=18"
}
},
"node_modules/i18next-resources-for-ts": {
@@ -9172,6 +9223,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -9407,9 +9459,19 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -10648,11 +10710,12 @@
"license": "MIT"
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@@ -10944,9 +11007,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"version": "3.3.19",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
"integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"funding": [
{
"type": "github",
@@ -10977,54 +11040,15 @@
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/node-fetch/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/node-fetch/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"version": "2.0.55",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz",
"integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
@@ -11605,9 +11629,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"version": "8.5.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
"funding": [
{
"type": "opencollective",
@@ -11922,9 +11946,13 @@
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/psl": {
"version": "1.9.0",
@@ -12133,9 +12161,9 @@
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz",
"integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==",
"license": "MIT",
"peerDependencies": {
"react": "*"
@@ -12282,12 +12310,12 @@
}
},
"node_modules/react-router": {
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
"version": "6.30.6",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.6.tgz",
"integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.2"
"@remix-run/router": "1.23.4"
},
"engines": {
"node": ">=14.0.0"
@@ -12297,13 +12325,13 @@
}
},
"node_modules/react-router-dom": {
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
"version": "6.30.6",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.6.tgz",
"integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.2",
"react-router": "6.30.3"
"@remix-run/router": "1.23.4",
"react-router": "6.30.6"
},
"engines": {
"node": ">=14.0.0"
@@ -13575,9 +13603,9 @@
}
},
"node_modules/tmp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
"integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13588,6 +13616,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -13859,9 +13888,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
"integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
"dev": true,
"funding": [
{
@@ -13879,8 +13908,8 @@
],
"license": "MIT",
"dependencies": {
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -13977,9 +14006,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/uuid": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
+8 -8
View File
@@ -49,11 +49,11 @@
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.2.8",
"@rjsf/core": "^6.4.1",
"@rjsf/shadcn": "^6.4.1",
"@rjsf/shadcn": "^6.5.2",
"@rjsf/utils": "^6.4.1",
"@rjsf/validator-ajv8": "^6.4.1",
"apexcharts": "^3.52.0",
"axios": "^1.13.6",
"axios": "^1.18.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
@@ -63,10 +63,10 @@
"framer-motion": "^12.38.0",
"hls.js": "^1.6.15",
"i18next": "^24.2.0",
"i18next-http-backend": "^3.0.1",
"i18next-http-backend": "^4.0.2",
"idb-keyval": "^6.2.1",
"immer": "^10.1.1",
"js-yaml": "^4.1.1",
"js-yaml": "^4.3.2",
"konva": "^10.2.3",
"lodash": "^4.18.1",
"lucide-react": "^0.577.0",
@@ -82,10 +82,10 @@
"react-grid-layout": "^2.2.2",
"react-hook-form": "^7.72.0",
"react-i18next": "^15.2.0",
"react-icons": "^5.5.0",
"react-icons": "^5.6.0",
"react-konva": "^19.2.3",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.30.3",
"react-router-dom": "^6.30.6",
"react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "3.4.4",
"remark-gfm": "^4.0.0",
@@ -123,7 +123,7 @@
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.8",
"eslint-plugin-vitest-globals": "^1.5.0",
"eslint-plugin-vitest-globals": "^1.6.1",
"fake-indexeddb": "^6.0.0",
"i18next-cli": "^1.5.11",
"jest-websocket-mock": "^2.5.0",
@@ -131,7 +131,7 @@
"monaco-editor": "^0.52.2",
"msw": "^2.3.5",
"patch-package": "^8.0.1",
"postcss": "^8.5.8",
"postcss": "^8.5.12",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5",
"tailwindcss": "^3.4.9",
@@ -7,7 +7,6 @@
"submit": "تقديم"
},
"livePlayerRequiredIOSVersion": "مطلوب نظام iOS 17.1 أو أكبر لهذا النوع من البث المباشر.",
"cameraDisabled": "الكاميرا معطلة",
"stats": {
"streamType": {
"title": "نوع الدفق:",
-1
View File
@@ -11,7 +11,6 @@
"detection": "لا توجد عمليات كشف لمراجعتها",
"motion": "لم يتم العثور على بيانات الحركة"
},
"timeline": "التسلسل الزمني",
"timeline.aria": "اختر التسلسل الزمني",
"events": {
"label": "اﻷحداث",
-1
View File
@@ -2,7 +2,6 @@
"search": "بحث",
"noExports": "لا يوجد تصديرات",
"documentTitle": "التصدير - فرايجيت",
"deleteExport": "حذف التصدير",
"deleteExport.desc": "هل أنت متأكد من رغبتك في حذف{{exportName}}؟",
"editExport": {
"title": "إعادة تسمية التصدير",
+1 -7
View File
@@ -5,13 +5,9 @@
"placeholder": "أدخل أسم لهذه المجموعة"
},
"details": {
"person": "شخص",
"subLabelScore": "نتيجة العلامة الفرعية",
"timestamp": "الطابع الزمني",
"unknown": "غير معروف",
"scoreInfo": "النتيجة الفرعية هي النتيجة المرجحة لجميع درجات الثقة المعترف بها للوجه، لذلك قد تختلف عن النتيجة الموضحة في اللقطة.",
"face": "تفاصيل الوجه",
"faceDesc": "تفاصيل الكائن المتتبع الذي أنشأ هذا الوجه"
"scoreInfo": "النتيجة الفرعية هي النتيجة المرجحة لجميع درجات الثقة المعترف بها للوجه، لذلك قد تختلف عن النتيجة الموضحة في اللقطة."
},
"documentTitle": "مكتبة الوجوه - Frigate",
"uploadFaceImage": {
@@ -20,8 +16,6 @@
},
"collections": "المجموعات",
"createFaceLibrary": {
"title": "إنشاء المجاميع",
"desc": "إنشاء مجموعة جديدة",
"new": "إضافة وجه جديد",
"nextSteps": "لبناء أساس قوي:<li>استخدم علامة التبويب \"التعرّفات الأخيرة\" لاختيار الصور والتدريب عليها لكل شخص تم اكتشافه.</li> <li>ركّز على الصور الأمامية المباشرة للحصول على أفضل النتائج؛ وتجنّب صور التدريب التي تُظهر الوجوه بزاوية.</li>"
},
-1
View File
@@ -1,5 +1,4 @@
{
"documentTitle": "بث حي - فرايجيت",
"documentTitle.withCamera": "{{camera}} - بث حي - فرايجيت",
"lowBandwidthMode": "وضع موفر للبيانات",
"twoWayTalk": {
@@ -1,6 +1,5 @@
{
"documentTitle": {
"camera": "إعدادات الكاميرا - فرايجيت",
"default": "الإعدادات - فرايجيت",
"authentication": "إعدادات المصادقة - فرايجيت",
"enrichments": "إحصاء الاعدادات",
+1 -5
View File
@@ -86,11 +86,7 @@
"gpuTemperature": "درجة حرارة الـ GPU",
"npuUsage": "معلومات وحدة معالجة الشبكة",
"npuMemory": "استخدام وحدة المعالجة العصبية",
"npuTemperature": "درجة حرارة الـ NPU",
"intelGpuWarning": {
"title": "تحذير إحصائيات معالج Intel الرسومي",
"description": "هذا خطأ برمي معروف في أدوات تقارير إحصائيات معالجات Intel الرسومية (intel_gpu_top)، حيث تتوقف الأداة عن العمل وتُظهر استهلاك المعالج الرسومي (GPU) بنسبة 0% بشكل متكرر، حتى في الحالات التي يعمل فيها تسريع العتاد وكشف الكائنات بشكل صحيح على المعالج الرسومي المدمج (iGPU). هذا ليس خطأً في برنامج فرايجيت (Frigate). يمكنك إعادة تشغيل الجهاز المضيف لحل المشكلة مؤقتاً والتأكد من أن المعالج الرسومي يعمل بشكل صحيح. علماً بأن هذا الخلل لا يؤثر على الأداء."
}
"npuTemperature": "درجة حرارة الـ NPU"
},
"title": "لمحة عامة",
"detector": {
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}

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