Compare commits

...
45 Commits
Author SHA1 Message Date
Josh HawkinsandGitHub 3941355051 add note to mqtt docs to use ID rather than friendly_name (#24441) 2026-09-24 06:31:40 -06:00
lin-xianmingandGitHub 1a278630da Fix ffmpeg default record preset in reference config (#24451)
Default was changed in b733355
2026-09-23 17:33:53 -06:00
Josh HawkinsandGitHub 9d0d8a99bb use resolved camera config in object processor to avoid race on replay stop (#24450)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
2026-09-23 15:23:20 -06:00
Nicolas MowenandGitHub bbc412763d Update keywords used in docs to match UI (#24436) 2026-09-21 18:45:08 -05:00
Josh HawkinsandGitHub ac9ac50df5 back off restarts when a recording stream goes stale (#24420)
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
The watchdog loop runs every second and the record staleness check restarted ffmpeg on every pass, so once a camera's segments went stale it got one restart per second and never had time to finish a 10 second segment. The restart is now gated on `can_restart` like the detect paths and grants 90 seconds of grace afterward. Backport of https://github.com/blakeblackshear/frigate/pull/24072, already in 0.19.
2026-09-20 12:44:47 -06:00
Josh HawkinsandGitHub 93aa6c4174 Add version/release link to docs site (#24410)
* add version/release link to docs

* link to full releases page
2026-09-20 07:40:09 -06:00
Josh HawkinsandGitHub 26e6adee88 Fix semantic search reindex (#24407)
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 semantic search reindex

sqlite-vec added the `_info` shadow table in 0.1.6 and drops it unconditionally when a vec0 table is destroyed, so `DROP TABLE` on a table written by 0.17 failed with "SQL logic error" once 0.18 moved to 0.1.9. `SqliteQueueDatabase` queues non-SELECT statements and stores the exception on the cursor it returns, and nothing read those cursors, so the failed drop and every write after it went unreported while reindex still logged "Embedded N thumbnails". `drop_embeddings_tables()` now recreates the missing `_info` stub before dropping, and writes go through `execute_write()`, which waits on the cursor so failures raise. `INSERT OR REPLACE` is gone too, since vec0 implements neither REPLACE nor UPSERT and it always failed on an id already in the table, including under the 0.1.3 build 0.17 shipped.

* use lock

* show reindex failure in status bar
2026-09-19 08:10:36 -06:00
Nicolas MowenandGitHub de416b7ae7 Remove invalid hardware acceleration step in recording troubleshooting (#24395)
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
Remove the invalid suggestion in docs
2026-09-17 13:28:51 -05:00
Josh HawkinsandGitHub 06967fec91 Fix explore paging for non-date sorts (#24392)
* fix explore paging for non-date sorts

Explore paged every sort by passing the last row's `start_time` as a `before` or `after` cursor, which only works when rows are ordered by `start_time`. For score, speed, and relevance sorts, each page dropped every match newer than that row and repeated older rows from earlier pages, so infinite scroll stopped after a few pages. `/events` and `/events/search` now accept `offset`, and Explore pages non-date sorts by offset. Date sorts keep the cursor because `useSWRInfinite` only revalidates the first page, and cursor keys for later pages follow it while offset keys don't. Score and speed sorts on `/events` break ties on `id` so offset pages stay stable.

* order search ties by id and reject negative offsets

`/events/search` sorted in Python over a query with no `ORDER BY`, so tied scores, speeds, or distances kept whatever order SQLite returned, which isn't guaranteed to match across page requests. The query is now ordered by id and the stable sorts keep that order for ties. `offset` also accepted negative values, which sliced from the end of the search results.
2026-09-17 11:14:20 -06:00
Nicolas MowenandGitHub d69107de33 Handle sub labeled objects to still show up in review filter (#24391) 2026-09-17 11:54:54 -05:00
Nicolas MowenandGitHub 04480a18b6 Revert QSV ffmpeg framerate filter (#24384)
* Update version

* Move back to QSV framerate filter

* Use standard fps filter instead
2026-09-17 11:17:45 -05:00
Josh HawkinsandGitHub b02aea03cd add field messages for recording and notifications (#24272)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
recording and notifications require enabled_in_config to be true at startup to build the correct ffmpeg commands and start the notifications worker
2026-09-13 11:53:44 -06:00
Josh HawkinsandGitHub 51171319a4 Clarify profile docs (#24267)
* Recording must always be enabled in the config to be toggled later by a profile

* add faq
2026-09-13 06:41:33 -06:00
Blake BlackshearandGitHub b1b725b80a Merge pull request #24249 from blakeblackshear/dev
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
0.18.0 Release
2026-09-12 08:09:11 -05: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
Blake BlackshearandGitHub 50a2b6729e update labels/faq (#23759) 2026-07-18 11:19:12 -06:00
203 changed files with 12355 additions and 2257 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
default_target: local
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
VERSION = 0.18.0
VERSION = 0.18.1
IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BOARDS= #Initialized empty
@@ -150,7 +150,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 -1
View File
@@ -1100,7 +1100,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 > Detectors and model** and select **Synaptics** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
@@ -286,7 +286,7 @@ ffmpeg:
# Optional: output args for detect streams (default: shown below)
detect: -threads 2 -f rawvideo -pix_fmt yuv420p
# Optional: output args for record streams (default: shown below)
record: preset-record-generic
record: preset-record-generic-audio-aac
# Optional: Time in seconds to wait before ffmpeg retries connecting to the camera. (default: shown below)
# If set too low, frigate will retry a connection to the camera's stream too frequently, using up the limited streams some cameras can allow at once
# If set too high, then if a ffmpeg crash or camera stream timeout occurs, you could potentially lose up to a maximum of retry_interval second(s) of footage
@@ -498,7 +498,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
@@ -378,10 +378,10 @@ Navigate to <NavPath path="Settings > Camera configuration > Object detection" /
Navigate to <NavPath path="Settings > Camera configuration > Objects" />.
| Field | Description |
| ---------------------------------------------- | ------------------- |
| **Objects to track** | Add `license_plate` |
| **Object filters > License Plate > Threshold** | Set to `0.7` |
| Field | Description |
| --------------------------------------------------------- | ------------------- |
| **Objects to track** | Add `license_plate` |
| **Object filters > License Plate > Confidence threshold** | Set to `0.7` |
Navigate to <NavPath path="Settings > Camera configuration > Motion detection" />.
+10 -10
View File
@@ -45,10 +45,10 @@ Any detection below `min_score` will be immediately thrown out and never tracked
Navigate to <NavPath path="Settings > Global configuration > Objects" /> to set score filters globally.
| Field | Description |
| --------------------------------------- | ---------------------------------------------------------------- |
| **Object filters > Person > Min Score** | Minimum score for a single detection to initiate tracking |
| **Object filters > Person > Threshold** | Minimum computed (median) score to be considered a true positive |
| Field | Description |
| -------------------------------------------------- | ---------------------------------------------------------------- |
| **Object filters > Person > Minimum confidence** | Minimum score for a single detection to initiate tracking |
| **Object filters > Person > Confidence threshold** | Minimum computed (median) score to be considered a true positive |
To override score filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" /> and select the camera.
@@ -103,12 +103,12 @@ Conceptually, a ratio of 1 is a square, 0.5 is a "tall skinny" box, and 2 is a "
Navigate to <NavPath path="Settings > Global configuration > Objects" /> to set shape filters globally.
| Field | Description |
| --------------------------------------- | ------------------------------------------------------------------------ |
| **Object filters > Person > Min Area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Max Area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Min Ratio** | Minimum width/height ratio of the bounding box |
| **Object filters > Person > Max Ratio** | Maximum width/height ratio of the bounding box |
| Field | Description |
| -------------------------------------------------- | ------------------------------------------------------------------------ |
| **Object filters > Person > Minimum object area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Maximum object area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Minimum aspect ratio** | Minimum width/height ratio of the bounding box |
| **Object filters > Person > Maximum aspect ratio** | Maximum width/height ratio of the bounding box |
To override shape filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" /> and select the camera.
+8 -8
View File
@@ -70,14 +70,14 @@ Object filters help reduce false positives by constraining the size, shape, and
Navigate to <NavPath path="Settings > Global configuration > Objects" />.
| Field | Description |
| --------------------------------------- | ------------------------------------------------------------------------ |
| **Object filters > Person > Min Area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Max Area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Min Ratio** | Minimum width/height ratio of the bounding box |
| **Object filters > Person > Max Ratio** | Maximum width/height ratio of the bounding box |
| **Object filters > Person > Min Score** | Minimum score for the object to initiate tracking |
| **Object filters > Person > Threshold** | Minimum computed score to be considered a true positive |
| Field | Description |
| -------------------------------------------------- | ------------------------------------------------------------------------ |
| **Object filters > Person > Minimum object area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Maximum object area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
| **Object filters > Person > Minimum aspect ratio** | Minimum width/height ratio of the bounding box |
| **Object filters > Person > Maximum aspect ratio** | Maximum width/height ratio of the bounding box |
| **Object filters > Person > Minimum confidence** | Minimum score for the object to initiate tracking |
| **Object filters > Person > Confidence threshold** | Minimum computed score to be considered a true positive |
To override filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" />.
+7 -3
View File
@@ -191,14 +191,12 @@ cameras:
detect:
enabled: false
record:
enabled: false
enabled: true
profiles:
away:
enabled: true
detect:
enabled: true
record:
enabled: true
home:
enabled: false
```
@@ -251,6 +249,12 @@ Leaving the `objects` section empty (or omitting `track`) does not clear the lis
Fields that require a Frigate restart to take effect cannot be overridden by profiles, since profiles are applied at runtime without restarting. Those fields are hidden when editing a profile override and can only be changed on the base configuration.
### Why can't a profile enable recording when it's disabled in the base config?
Frigate only sets up a camera's recording stream at startup when recording is enabled in the base config, so enabling it later from a profile has no effect. The same applies to turning recording on from the UI or MQTT.
To keep recording off by default, leave `record.enabled: true` in the base config and create a profile that sets `record.enabled: false`. Activate that profile and it will be restored automatically when Frigate starts.
### Can I schedule profiles to be enabled or disabled at certain times?
Not within Frigate itself. Frigate is an NVR, not an automation platform, so it intentionally does not include a scheduler for activating profiles. Instead, activate profiles from an automation platform that already handles time- and event-based triggers well, such as [Home Assistant](https://www.home-assistant.io/) or [Node-RED](https://nodered.org/). These integrate with Frigate and give you far more robust and flexible scheduling than a built-in scheduler could.
+1 -1
View File
@@ -291,7 +291,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.
+2 -2
View File
@@ -245,8 +245,8 @@ Triggers are best configured through the Frigate UI.
1. Navigate to <NavPath path="Settings > Enrichments > Triggers" /> and select a camera from the dropdown menu.
2. Click **Add Trigger** to create a new trigger or use the pencil icon to edit an existing one.
3. In the **Create Trigger** wizard:
- Enter a **Name** for the trigger (e.g., "Red Car Alert").
- Enter a descriptive **Friendly Name** for the trigger (e.g., "Red car on the driveway camera").
- Enter a **Name** for the trigger (e.g., "Red Car Alert"). Frigate derives the trigger's
internal **ID** from this name, which can be revealed and edited with the show/hide toggle.
- Select the **Type** (`Thumbnail` or `Description`).
- For `Thumbnail`, select an image to trigger this action when a similar thumbnail image is detected, based on the threshold.
- For `Description`, enter text to trigger this action when a similar tracked object description is detected.
+4 -4
View File
@@ -28,7 +28,7 @@ During testing, enable the Zones option for the [Debug view](/usage/live#the-sin
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
2. Under the **Zones** section, click the plus icon to add a new zone.
3. Click on the camera's latest image to create the points for the zone boundary. Click the first point again to close the polygon.
4. Configure zone options such as **Friendly name**, **Objects**, **Loitering time**, and **Inertia** in the zone editor.
4. Configure zone options such as **Name**, **Objects**, **Loitering Time**, and **Inertia** in the zone editor.
5. Press **Save** when finished.
</TabItem>
@@ -200,7 +200,7 @@ When using loitering zones, a review item will behave in the following way:
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
2. Edit or create the zone (e.g., `sidewalk`).
- Set **Loitering time** to the desired number of seconds (e.g., `4`)
- Set **Loitering Time** to the desired number of seconds (e.g., `4`)
- Under **Objects**, add the relevant object types (e.g., `person`)
</TabItem>
@@ -291,7 +291,7 @@ Accurate real-world distance measurements are required to estimate speeds. These
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
2. Create or edit a zone with exactly 4 points aligned to the ground plane.
3. In the zone editor, enter the real-world **Distances** between each pair of consecutive points.
3. In the zone editor, enable **Speed Estimation** and enter the real-world **Line A distance**, **Line B distance**, **Line C distance**, and **Line D distance** between each pair of consecutive points.
- For example, if the distance between the first and second points is 10 meters, between the second and third is 12 meters, etc.
4. Distances are measured in meters (metric) or feet (imperial), depending on the **Unit system** setting.
@@ -358,7 +358,7 @@ Zones can be configured with a minimum speed requirement, meaning an object must
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
2. Edit or create the zone with distances configured.
- Set **Speed threshold** to the desired minimum speed (e.g., `20`)
- Set **Speed Threshold** to the desired minimum speed (e.g., `20`)
- The unit is kph or mph, depending on the **Unit system** setting
</TabItem>
+2 -2
View File
@@ -54,7 +54,7 @@ An object filter mask drops any [bounding box](#bounding-box) whose bottom cente
## Min Score
The lowest score a detected object can have to be kept during tracking. Anything scoring below the minimum is assumed to be a [false positive](#false-positive) and discarded.
The lowest score a detected object can have to be kept during tracking. Anything scoring below the minimum is assumed to be a [false positive](#false-positive) and discarded. Set with `min_score` in the config, shown as **Minimum confidence** in the settings UI.
## Model
@@ -86,7 +86,7 @@ A more specific identity assigned to a [tracked object](#tracked-object-event-in
## Threshold
The median score an object must reach to be considered a true positive.
The median score an object must reach to be considered a true positive. Set with `threshold` in the config, shown as **Confidence threshold** in the settings UI.
## Top Score
+6
View File
@@ -11,6 +11,12 @@ MQTT requires a network connection to your broker. This is typically local, but
:::
:::note
Wherever a topic below includes a camera, mask, or zone name, use its `ID` from the config, not its `friendly_name`. For example, a camera with `friendly_name: "Back Yard"` and ID `back_yard` publishes to `frigate/back_yard/...`, not `frigate/Back Yard/...`.
:::
## General Frigate Topics
### `frigate/available`
+7 -1
View File
@@ -21,7 +21,13 @@ Yes. Models and metadata are stored in the `model_cache` directory within the co
### Can I keep using my Frigate+ models even if I do not renew my subscription?
Yes. Subscriptions to Frigate+ provide access to the infrastructure used to train the models. Models trained with your subscription are yours to keep and use forever. However, do note that the terms and conditions prohibit you from sharing, reselling, or creating derivative products from the models.
Yes. Subscriptions to Frigate+ provide access to the infrastructure used to train the models. Models you train during an active subscription remain licensed for your continued use even after your subscription ends — models already in your model cache will keep working indefinitely. An active subscription is required to train new models and download new versions.
### Can I use Frigate+ models commercially?
A standard subscription covers use on camera systems you own or operate, including for your business. A shop, restaurant, warehouse, or office running Frigate+ at its own locations (including multiple locations) is exactly the kind of use the subscription is for.
What the standard subscription does not cover is using Frigate+ models to provide a product or service to others. If you're deploying models at your customers' sites, bundling them with hardware you sell, or running them as part of a hosted or managed service, even if your customers never receive the model files themselves, you'll need a commercial license.
Note that professional installers are fine under standard subscriptions when each customer holds their own Frigate+ subscription. The commercial license is for cases where your license powers your customers' sites.
### Why can't I submit images to Frigate+?
+13 -13
View File
@@ -64,20 +64,20 @@ Frigate+ models generally have much higher scores than the default model provide
<ConfigTabs>
<TabItem value="ui">
Navigate to <NavPath path="Settings > Global configuration > Objects" />. Under **Object filters**, set **Min Score** and **Threshold** for each object type, then click **Save**.
Navigate to <NavPath path="Settings > Global configuration > Objects" />. Under **Object filters**, set **Minimum confidence** and **Confidence threshold** for each object type, then click **Save**.
| Object | Min Score | Threshold |
| ----------------- | --------- | --------- |
| **dog** | .7 | .9 |
| **cat** | .65 | .8 |
| **face** | .7 | |
| **package** | .65 | .9 |
| **license_plate** | .6 | |
| **amazon** | .75 | |
| **ups** | .75 | |
| **fedex** | .75 | |
| **person** | .65 | .85 |
| **car** | .65 | .85 |
| Object | Minimum confidence | Confidence threshold |
| ----------------- | ------------------ | -------------------- |
| **dog** | .7 | .9 |
| **cat** | .65 | .8 |
| **face** | .7 | |
| **package** | .65 | .9 |
| **license_plate** | .6 | |
| **amazon** | .75 | |
| **ups** | .75 | |
| **fedex** | .75 | |
| **person** | .65 | .85 |
| **car** | .65 | .85 |
</TabItem>
<TabItem value="yaml">
+9 -6
View File
@@ -65,11 +65,11 @@ Some users may find that Frigate+ models result in more false positives initiall
Frigate+ models support a more relevant set of objects for security cameras. The labels for annotation in Frigate+ are configurable by editing the camera in the Cameras section of Frigate+. Currently, the following objects are supported:
- **People**: `person`, `face`
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `license_plate`
- **People**: `person`, `face`, `baby`
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `garbage truck`, `license_plate`
- **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`, `canada_post`, `royal_mail`
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`, `possum`, `rodent`
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`, `baby_stroller`
Other object types available in the default Frigate model are not available. Additional object types will be added in future releases.
@@ -77,9 +77,12 @@ Other object types available in the default Frigate model are not available. Add
Candidate labels are also available for annotation. These labels don't have enough data to be included in the model yet, but using them will help add support sooner. You can enable these labels by editing the camera settings.
Where possible, these labels are mapped to existing labels during training. For example, any `baby` labels are mapped to `person` until support for new labels is added.
Where possible, these labels are mapped to existing labels during training. For example, any `duck` labels are mapped to `bird` until support for new labels is added.
The candidate labels are: `baby`, `bpost`, `badger`, `possum`, `rodent`, `chicken`, `groundhog`, `boar`, `hedgehog`, `tractor`, `golf cart`, `garbage truck`, `bus`, `sports ball`, `la_poste`, `lawnmower`, `heron`, `rickshaw`, `wombat`, `auspost`, `aramex`, `bobcat`, `mustelid`, `transoflex`, `airplane`, `drone`, `mountain_lion`, `crocodile`, `turkey`, `baby_stroller`, `monkey`, `coyote`, `porcupine`, `parcelforce`, `sheep`, `snake`, `helicopter`, `lizard`, `duck`, `hermes`, `cargus`, `fan_courier`, `sameday`
- **Vehicles**: `tractor`, `golf_cart`, `bus`, `airplane`, `helicopter`, `rickshaw`, `scooter`
- **Delivery Logos**: `bpost`, `auspost`, `aramex`, `transoflex`, `parcelforce`, `hermes`, `cargus`, `fan_courier`, `sameday`, `la_poste`
- **Animals**: `badger`, `chicken`, `duck`, `turkey`, `groundhog`, `boar`, `hedgehog`, `wombat`, `bobcat`, `mustelid`, `mountain_lion`, `crocodile`, `monkey`, `coyote`, `porcupine`, `sheep`, `snake`, `lizard`, `heron`, `elk`, `moose`, `pig`, `donkey`, `civet`
- **Other**: `sports_ball`, `drone`, `lawnmower`
Candidate labels are not available for automatic suggestions.
+14
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.
+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
+2 -10
View File
@@ -397,19 +397,11 @@ dmesg | grep -i -E "gpu|drm|reset|hang"
Messages like `trying reset from guc_exec_queue_timedout_job` or similar GPU reset/hang messages indicate a driver or hardware issue. Ensure your kernel and GPU drivers (especially Intel) are up to date.
#### Step 6: Verify hardware acceleration configuration
An incorrect `hwaccel_args` preset can cause ffmpeg to fail silently or consume excessive CPU, starving the detector of resources.
- After upgrading Frigate, verify your preset matches your hardware (e.g., `preset-intel-qsv-h264` instead of the deprecated `preset-vaapi`).
- For h265 cameras, use the corresponding h265 preset (e.g., `preset-intel-qsv-h265`).
- Note that `hwaccel_args` are only relevant for the detect stream. Frigate does not decode the record stream.
#### Step 7: Verify go2rtc stream configuration
#### Step 6: Verify go2rtc stream configuration
Ensure that the ffmpeg source names in your go2rtc configuration match the correct camera stream. A misconfigured stream name (e.g., copying a config from one camera to another without updating the stream reference) will cause the wrong stream to be used or the stream to fail entirely.
#### Step 8: Check system resources
#### Step 7: Check system resources
If none of the above apply, the issue may be a general resource constraint. Monitor the following on your host:
+30 -22
View File
@@ -3,6 +3,9 @@ import * as path from "node:path";
import type { Config, PluginConfig } from "@docusaurus/types";
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
// Bump when a new stable release ships
const STABLE_VERSION = "0.18";
const config: Config = {
title: "Frigate",
tagline: "NVR With Realtime Object Detection for IP Cameras",
@@ -23,17 +26,17 @@ const config: Config = {
mermaid: true,
},
i18n: {
defaultLocale: 'en',
locales: ['en'],
defaultLocale: "en",
locales: ["en"],
localeConfigs: {
en: {
label: 'English',
}
label: "English",
},
},
},
themeConfig: {
announcementBar: {
id: 'frigate_plus',
id: "frigate_plus",
content: `
<span style="margin-right: 8px; display: inline-block; animation: pulse 2s infinite;">🚀</span>
Get more relevant and accurate detections with Frigate+ models.
@@ -45,8 +48,8 @@ const config: Config = {
50% { transform: scale(1.1); }
}
</style>`,
backgroundColor: '#005f73',
textColor: '#e0fbfc',
backgroundColor: "#005f73",
textColor: "#e0fbfc",
isCloseable: false,
},
docs: {
@@ -83,15 +86,15 @@ const config: Config = {
},
},
prism: {
magicComments:[
magicComments: [
{
className: 'theme-code-block-highlighted-line',
line: 'highlight-next-line',
block: {start: 'highlight-start', end: 'highlight-end'},
className: "theme-code-block-highlighted-line",
line: "highlight-next-line",
block: { start: "highlight-start", end: "highlight-end" },
},
{
className: 'code-block-error-line',
line: 'highlight-error-line',
className: "code-block-error-line",
line: "highlight-error-line",
},
],
additionalLanguages: ["bash", "json"],
@@ -131,6 +134,11 @@ const config: Config = {
srcDark: "img/branding/logo-dark.svg",
},
items: [
{
href: "https://github.com/blakeblackshear/frigate/releases",
label: `${STABLE_VERSION}`,
position: "left",
},
{
to: "/",
activeBasePath: "docs",
@@ -148,19 +156,19 @@ const config: Config = {
position: "right",
},
{
type: 'localeDropdown',
position: 'right',
type: "localeDropdown",
position: "right",
dropdownItemsAfter: [
{
label: '简体中文(社区翻译)',
href: 'https://docs.frigate-cn.video',
}
]
label: "简体中文(社区翻译)",
href: "https://docs.frigate-cn.video",
},
],
},
{
href: 'https://github.com/blakeblackshear/frigate',
label: 'GitHub',
position: 'right',
href: "https://github.com/blakeblackshear/frigate",
label: "GitHub",
position: "right",
},
],
},
+20
View File
@@ -4073,6 +4073,16 @@ paths:
- type: 'null'
default: 100
title: Limit
- name: offset
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
default: 0
title: Offset
- name: after
in: query
required: false
@@ -4378,6 +4388,16 @@ paths:
- type: 'null'
default: 50
title: Limit
- name: offset
in: query
required: false
schema:
anyOf:
- type: integer
minimum: 0
- type: 'null'
default: 0
title: Offset
- name: cameras
in: query
required: false
@@ -14,6 +14,7 @@ class EventsQueryParams(BaseModel):
zone: str | None = "all"
zones: str | None = "all"
limit: int | None = 100
offset: int | None = Field(0, ge=0)
after: float | None = None
before: float | None = None
time_range: str | None = DEFAULT_TIME_RANGE
@@ -55,6 +56,7 @@ class EventsSearchQueryParams(BaseModel):
deprecated=True,
)
limit: int | None = 50
offset: int | None = Field(0, ge=0)
cameras: str | None = "all"
labels: str | None = "all"
sub_labels: str | None = "all"
+11 -2
View File
@@ -129,6 +129,7 @@ def events(
zones = zone
limit = params.limit
offset = params.offset
after = params.after
before = params.before
time_range = params.time_range
@@ -361,11 +362,15 @@ def events(
else:
order_by = Event.start_time.desc()
# offset paging needs a stable order when scores or speeds tie
tiebreaker = [Event.id] if sort and sort.startswith(("score", "speed")) else []
events = (
Event.select(*selected_columns)
.where(reduce(operator.and_, clauses))
.order_by(order_by)
.order_by(order_by, *tiebreaker)
.limit(limit)
.offset(offset)
.dicts()
.iterator()
)
@@ -518,6 +523,7 @@ def events_search(
search_type = params.search_type
include_thumbnails = params.include_thumbnails
limit = params.limit
offset = params.offset
sort = params.sort
# Filters
@@ -824,6 +830,9 @@ def events_search(
if search_results:
events_query = events_query.where(Event.id << list(search_results.keys()))
# sorts below are stable, so this orders ties for offset paging
events_query = events_query.order_by(Event.id)
# Fetch events and process them in a single pass
processed_events = []
for event in events_query.dicts():
@@ -881,7 +890,7 @@ def events_search(
processed_events.sort(key=lambda x: x["start_time"], reverse=True)
# Limit the number of events returned
processed_events = processed_events[:limit]
processed_events = processed_events[offset:][:limit]
return JSONResponse(content=processed_events)
+2 -1
View File
@@ -69,6 +69,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,
PlaybackSourceEnum,
validate_ffmpeg_args,
@@ -1012,7 +1013,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
+20 -11
View File
@@ -43,6 +43,22 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=[Tags.review])
def get_label_clause(label: str, include_audio: bool = True):
"""Build a clause matching a label within a review segment's data.
Verified objects are stored with a `-verified` suffix (eg. `person-verified`)
so that variant is matched as well.
"""
clause = (ReviewSegment.data["objects"].cast("text") % f'*"{label}"*') | (
ReviewSegment.data["objects"].cast("text") % f'*"{label}-verified"*'
)
if include_audio:
clause |= ReviewSegment.data["audio"].cast("text") % f'*"{label}"*'
return clause
@router.get(
"/review",
response_model=list[ReviewSegmentResponse],
@@ -92,10 +108,7 @@ async def review(
filtered_labels = labels.split(",")
for label in filtered_labels:
label_clauses.append(
(ReviewSegment.data["objects"].cast("text") % f'*"{label}"*')
| (ReviewSegment.data["audio"].cast("text") % f'*"{label}"*')
)
label_clauses.append(get_label_clause(label))
clauses.append(reduce(operator.or_, label_clauses))
if zones != "all":
@@ -236,10 +249,7 @@ async def review_summary(
filtered_labels = labels.split(",")
for label in filtered_labels:
label_clauses.append(
(ReviewSegment.data["objects"].cast("text") % f'*"{label}"*')
| (ReviewSegment.data["audio"].cast("text") % f'*"{label}"*')
)
label_clauses.append(get_label_clause(label))
clauses.append(reduce(operator.or_, label_clauses))
if zones != "all":
# use matching so segments with multiple zones
@@ -337,9 +347,8 @@ async def review_summary(
filtered_labels = labels.split(",")
for label in filtered_labels:
label_clauses.append(
ReviewSegment.data["objects"].cast("text") % f'*"{label}"*'
)
label_clauses.append(get_label_clause(label, include_audio=False))
clauses.append(reduce(operator.or_, label_clauses))
# Find the time range of available data
+6 -4
View File
@@ -103,12 +103,13 @@ class CameraActivityManager:
all_objects: list[dict[str, Any]] = []
for camera in new_activity.keys():
if camera not in self.config.cameras:
camera_config = self.config.cameras.get(camera)
if camera_config is None:
continue
# handle cameras that were added dynamically
if camera not in self.camera_all_object_counts:
self.__init_camera(self.config.cameras[camera])
self.__init_camera(camera_config)
new_objects = new_activity[camera].get("objects", [])
all_objects.extend(new_objects)
@@ -233,12 +234,13 @@ class AudioActivityManager:
now = datetime.datetime.now().timestamp()
for camera in new_activity.keys():
if camera not in self.config.cameras:
camera_config = self.config.cameras.get(camera)
if camera_config is None:
continue
# handle cameras that were added dynamically
if camera not in self.current_audio_detections:
self.__init_camera(self.config.cameras[camera])
self.__init_camera(camera_config)
new_detections = new_activity[camera].get("detections", [])
if self.compare_audio_activity(camera, new_detections, now):
+76 -15
View File
@@ -1,8 +1,10 @@
import logging
import sqlite3
import threading
from typing import Any
import regex
from peewee import DatabaseError
from playhouse.sqliteq import SqliteQueueDatabase
logger = logging.getLogger(__name__)
@@ -17,6 +19,7 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
self.load_vec_extension: bool = load_vec_extension
# no extension necessary, sqlite will load correctly for each platform
self.sqlite_vec_path = "/usr/local/lib/vec0"
self.upsert_lock = threading.Lock()
super().__init__(*args, **kwargs)
def _connect(self, *args: Any, **kwargs: Any) -> sqlite3.Connection:
@@ -53,6 +56,22 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
conn.create_function("REGEXP", 2, regexp)
def execute_write(self, sql: str, params: Any = None) -> None:
"""Run a write and wait for it, so that failures are raised here.
SqliteQueueDatabase hands non-SELECT statements to a writer thread and
stores any exception on the cursor it returns, so callers that ignore
that cursor never learn the write failed.
"""
self.execute_sql(sql, params).fetchall()
def _table_exists(self, table: str) -> bool:
cursor = self.execute_sql(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
(table,),
)
return cursor.fetchone() is not None
def _delete_embeddings(self, table: str, event_ids: list[str]) -> None:
"""Delete embeddings for the given events, if the table exists.
@@ -63,17 +82,17 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
return
# the embeddings tables are only created once semantic search has run
cursor = self.execute_sql(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
(table,),
)
if cursor.fetchone() is None:
if not self._table_exists(table):
logger.debug("Skipping %s cleanup, table does not exist", table)
return
ids = ",".join(["?" for _ in event_ids])
self.execute_sql(f"DELETE FROM {table} WHERE id IN ({ids})", event_ids)
# callers treat cleanup as best effort, so log rather than propagate
try:
self.execute_write(f"DELETE FROM {table} WHERE id IN ({ids})", event_ids)
except DatabaseError:
logger.exception("Failed to delete embeddings from %s", table)
def delete_embeddings_thumbnail(self, event_ids: list[str]) -> None:
self._delete_embeddings("vec_thumbnails", event_ids)
@@ -81,25 +100,67 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
def delete_embeddings_description(self, event_ids: list[str]) -> None:
self._delete_embeddings("vec_descriptions", event_ids)
def _restore_vec_info_table(self, table: str) -> None:
"""Recreate the _info shadow table a legacy vec0 table is missing.
sqlite-vec added _info in 0.1.6 and drops it unconditionally when a
table is destroyed, so tables written by Frigate 0.17 and earlier fail
to drop. An empty stub is enough, and leaving it unseeded keeps the
table reading as pre-0.1.10 if the drop does not follow.
"""
if not self._table_exists(table) or self._table_exists(f"{table}_info"):
return
logger.debug("Restoring the %s_info shadow table before dropping", table)
self.execute_write(
f'CREATE TABLE "{table}_info" (key TEXT PRIMARY KEY, value ANY)'
)
def drop_embeddings_tables(self) -> None:
self.execute_sql("""
DROP TABLE vec_descriptions;
""")
self.execute_sql("""
DROP TABLE vec_thumbnails;
""")
for table in ("vec_descriptions", "vec_thumbnails"):
self._restore_vec_info_table(table)
self.execute_write(f"DROP TABLE IF EXISTS {table}")
def create_embeddings_tables(self) -> None:
"""Create vec0 virtual table for embeddings"""
self.execute_sql("""
self.execute_write("""
CREATE VIRTUAL TABLE IF NOT EXISTS vec_thumbnails USING vec0(
id TEXT PRIMARY KEY,
thumbnail_embedding FLOAT[768] distance_metric=cosine
);
""")
self.execute_sql("""
self.execute_write("""
CREATE VIRTUAL TABLE IF NOT EXISTS vec_descriptions USING vec0(
id TEXT PRIMARY KEY,
description_embedding FLOAT[768] distance_metric=cosine
);
""")
def upsert_embeddings(
self, table: str, column: str, embeddings: dict[str, bytes]
) -> None:
"""Write embeddings for the given event ids, replacing any that exist.
vec0 implements neither REPLACE nor UPSERT, so rows that are already
there have to be deleted first.
"""
if not embeddings:
return
event_ids = list(embeddings.keys())
ids = ",".join(["?" for _ in event_ids])
params: list[Any] = []
for event_id in event_ids:
params.extend((event_id, embeddings[event_id]))
values = ", ".join(["(?, ?)"] * len(event_ids))
# reindexing and live embedding run on separate threads, and each write
# is queued separately, so the delete and the insert have to be held
# together or an interleaved pair fails on the vec0 primary key
with self.upsert_lock:
self.execute_write(f"DELETE FROM {table} WHERE id IN ({ids})", event_ids)
self.execute_write(
f"INSERT INTO {table}(id, {column}) VALUES {values}", params
)
+37 -26
View File
@@ -199,15 +199,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."""
@@ -217,35 +222,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()
+40 -40
View File
@@ -6,9 +6,10 @@ import logging
import os
import threading
import time
from typing import Any
import numpy as np
from peewee import DoesNotExist, IntegrityError
from peewee import DatabaseError, DoesNotExist, IntegrityError
from PIL import Image
from playhouse.shortcuts import model_to_dict
@@ -207,12 +208,10 @@ class Embeddings:
embedding = self.vision_embedding([thumbnail])[0]
if upsert:
self.db.execute_sql(
"""
INSERT OR REPLACE INTO vec_thumbnails(id, thumbnail_embedding)
VALUES(?, ?)
""",
(event_id, serialize(embedding)),
self.db.upsert_embeddings(
"vec_thumbnails",
"thumbnail_embedding",
{event_id: serialize(embedding)},
)
self.image_inference_speed.update(datetime.datetime.now().timestamp() - start)
@@ -251,19 +250,12 @@ class Embeddings:
embeddings = self.vision_embedding(valid_thumbs)
if upsert:
items = []
items = {}
for i in range(len(valid_ids)):
items.append(valid_ids[i])
items.append(serialize(embeddings[i]))
items[valid_ids[i]] = serialize(embeddings[i])
self.image_eps.update()
self.db.execute_sql(
"""
INSERT OR REPLACE INTO vec_thumbnails(id, thumbnail_embedding)
VALUES {}
""".format(", ".join(["(?, ?)"] * len(valid_ids))),
items,
)
self.db.upsert_embeddings("vec_thumbnails", "thumbnail_embedding", items)
duration = datetime.datetime.now().timestamp() - start
self.image_inference_speed.update(duration / len(valid_ids))
@@ -277,12 +269,10 @@ class Embeddings:
embedding = self.text_embedding([description])[0]
if upsert:
self.db.execute_sql(
"""
INSERT OR REPLACE INTO vec_descriptions(id, description_embedding)
VALUES(?, ?)
""",
(event_id, serialize(embedding)),
self.db.upsert_embeddings(
"vec_descriptions",
"description_embedding",
{event_id: serialize(embedding)},
)
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
@@ -302,19 +292,14 @@ class Embeddings:
if upsert:
ids = list(event_descriptions.keys())
items = []
items = {}
for i in range(len(ids)):
items.append(ids[i])
items.append(serialize(embeddings[i]))
items[ids[i]] = serialize(embeddings[i])
self.text_eps.update()
self.db.execute_sql(
"""
INSERT OR REPLACE INTO vec_descriptions(id, description_embedding)
VALUES {}
""".format(", ".join(["(?, ?)"] * len(ids))),
items,
self.db.upsert_embeddings(
"vec_descriptions", "description_embedding", items
)
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
@@ -322,6 +307,17 @@ class Embeddings:
return embeddings
def reindex(self) -> None:
"""Rebuild every tracked object embedding from scratch."""
totals: dict[str, Any] = {"status": "indexing"}
try:
self._reindex(totals)
except DatabaseError:
logger.exception("Unable to reindex tracked object embeddings")
totals["status"] = "failed"
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
def _reindex(self, totals: dict[str, Any]) -> None:
logger.info("Indexing tracked object embeddings...")
self.db.drop_embeddings_tables()
@@ -346,14 +342,18 @@ class Embeddings:
batch_size = 32
current_page = 1
totals = {
"thumbnails": 0,
"descriptions": 0,
"processed_objects": total_events - 1 if total_events < batch_size else 0,
"total_objects": total_events,
"time_remaining": 0 if total_events < batch_size else -1,
"status": "indexing",
}
totals.update(
{
"thumbnails": 0,
"descriptions": 0,
"processed_objects": total_events - 1
if total_events < batch_size
else 0,
"total_objects": total_events,
"time_remaining": 0 if total_events < batch_size else -1,
"status": "indexing",
}
)
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
+2 -2
View File
@@ -121,8 +121,8 @@ PRESETS_HW_ACCEL_SCALE = {
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}",
"preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}",
FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,format=nv12",
"preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,fps={0},format=yuv420p",
"preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,fps={0},format=yuv420p",
"preset-intel-qsv-h264": "-r {0} -vf fps={0},vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
"preset-intel-qsv-h265": "-r {0} -vf fps={0},vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2},hwdownload,format=nv12",
"preset-jetson-h264": "-r {0}", # scaled in decoder
"preset-jetson-h265": "-r {0}", # scaled in decoder
+3 -2
View File
@@ -36,8 +36,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"
# Matches the setpts factor used in timelapse exports (e.g. setpts=0.04*PTS).
# Captures the floating-point factor so we can scale expected duration.
@@ -737,7 +738,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,
)
+5 -4
View File
@@ -352,8 +352,9 @@ def stats_snapshot(
total_camera_fps = total_process_fps = total_skipped_fps = total_detection_fps = 0
stats["cameras"] = {}
for name, camera_stats in camera_metrics.items():
if name not in config.cameras:
for name, camera_stats in list(camera_metrics.items()):
camera_config = config.cameras.get(name)
if camera_config is None:
continue
total_camera_fps += camera_stats.camera_fps.value
@@ -370,7 +371,7 @@ def stats_snapshot(
# Calculate connection quality based on current state
# This is computed at stats-collection time so offline cameras
# correctly show as unusable rather than excellent
expected_fps = config.cameras[name].detect.fps
expected_fps = camera_config.detect.fps
current_fps = camera_stats.camera_fps.value
reconnects = camera_stats.reconnects_last_hour.value
stalls = camera_stats.stalls_last_hour.value
@@ -398,7 +399,7 @@ def stats_snapshot(
"process_fps": round(camera_stats.process_fps.value, 2),
"skipped_fps": round(camera_stats.skipped_fps.value, 2),
"detection_fps": round(camera_stats.detection_fps.value, 2),
"detection_enabled": config.cameras[name].detect.enabled,
"detection_enabled": camera_config.detect.enabled,
"pid": pid,
"capture_pid": capture_pid,
"ffmpeg_pid": ffmpeg_pid,
+102
View File
@@ -168,6 +168,29 @@ class TestHttpApp(BaseTestHttp):
assert events[0]["id"] == id
assert events[1]["id"] == id2
def test_get_event_list_offset_pages_score_sort(self):
now = datetime.now().timestamp()
scores = [0.6, 0.9, 0.7, 0.95, 0.8]
with AuthTestClient(self.app) as client:
for i, score in enumerate(scores):
super().insert_mock_event(
f"event-{i}", start_time=now + i, data={"score": score}
)
params = {"sort": "score_desc"}
full = [e["id"] for e in client.get("/events", params=params).json()]
paged = [
e["id"]
for offset in (0, 2, 4)
for e in client.get(
"/events", params={**params, "limit": 2, "offset": offset}
).json()
]
assert full == ["event-3", "event-1", "event-4", "event-2", "event-0"]
assert paged == full
def test_get_event_list_match_multilingual_attribute(self):
event_id = "123456.zh"
attribute = "中文标签"
@@ -219,6 +242,85 @@ class TestHttpApp(BaseTestHttp):
assert len(events) == 1
assert events[0]["id"] == event_id
def test_events_search_offset_pages_score_sort(self):
now = datetime.now().timestamp()
scores = [0.6, 0.9, 0.7, 0.95, 0.8]
ids = [f"event-{i}" for i in range(len(scores))]
mock_embeddings = Mock()
mock_embeddings.search_thumbnail.return_value = [
(event_id, 0.1 * i) for i, event_id in enumerate(ids)
]
self.app.frigate_config.semantic_search.enabled = True
self.app.embeddings = mock_embeddings
with AuthTestClient(self.app) as client:
for i, score in enumerate(scores):
super().insert_mock_event(
ids[i], start_time=now + i, data={"score": score}
)
params = {
"search_type": "similarity",
"event_id": ids[0],
"sort": "score_desc",
}
paged = [
e["id"]
for offset in (0, 2, 4)
for e in client.get(
"/events/search",
params={**params, "limit": 2, "offset": offset},
).json()
]
assert paged == ["event-3", "event-1", "event-4", "event-2", "event-0"]
def test_events_search_offset_pages_orders_ties_by_id(self):
now = datetime.now().timestamp()
ids = ["event-c", "event-a", "event-b"]
mock_embeddings = Mock()
mock_embeddings.search_thumbnail.return_value = [
(event_id, 0.1) for event_id in ids
]
self.app.frigate_config.semantic_search.enabled = True
self.app.embeddings = mock_embeddings
with AuthTestClient(self.app) as client:
for i, event_id in enumerate(ids):
super().insert_mock_event(
event_id, start_time=now + i, data={"score": 0.8}
)
for sort in ("score_desc", "relevance"):
params = {
"search_type": "similarity",
"event_id": ids[0],
"sort": sort,
}
paged = [
e["id"]
for offset in (0, 1, 2)
for e in client.get(
"/events/search",
params={**params, "limit": 1, "offset": offset},
).json()
]
assert paged == ["event-a", "event-b", "event-c"]
def test_event_list_rejects_negative_offset(self):
with AuthTestClient(self.app) as client:
response = client.get("/events", params={"offset": -5})
assert response.status_code == 422
response = client.get(
"/events/search",
params={"query": "car", "offset": -5},
)
assert response.status_code == 422
def test_similarity_search_hides_unauthorized_anchor_event(self):
mock_embeddings = Mock()
self.app.frigate_config.semantic_search.enabled = True
+92
View File
@@ -240,9 +240,101 @@ class TestHttpReview(BaseTestHttp):
assert len(response_json) == 1
assert response_json[0]["id"] == id_reviewed
def test_get_review_with_label_filter_matches_verified(self):
"""Test that a label filter also matches the `-verified` variant."""
now = datetime.now().timestamp()
with AuthTestClient(self.app) as client:
super().insert_mock_review_segment(
"123456.person", now, now + 2, data={"objects": ["person"]}
)
super().insert_mock_review_segment(
"123456.verified", now, now + 2, data={"objects": ["person-verified"]}
)
super().insert_mock_review_segment(
"123456.car", now, now + 2, data={"objects": ["car"]}
)
params = {
"labels": "person",
"after": now - 1,
"before": now + 3,
}
response = client.get("/review", params=params)
assert response.status_code == 200
response_json = response.json()
assert {r["id"] for r in response_json} == {
"123456.person",
"123456.verified",
}
def test_get_review_with_label_filter_does_not_match_prefix(self):
"""Test that a label filter does not match labels that only share a prefix."""
now = datetime.now().timestamp()
with AuthTestClient(self.app) as client:
super().insert_mock_review_segment(
"123456.carrot", now, now + 2, data={"objects": ["carrot"]}
)
params = {
"labels": "car",
"after": now - 1,
"before": now + 3,
}
response = client.get("/review", params=params)
assert response.status_code == 200
assert len(response.json()) == 0
def test_get_review_with_audio_label_filter(self):
"""Test that a label filter still matches audio labels."""
now = datetime.now().timestamp()
with AuthTestClient(self.app) as client:
super().insert_mock_review_segment(
"123456.audio", now, now + 2, data={"audio": ["speech"]}
)
params = {
"labels": "speech",
"after": now - 1,
"before": now + 3,
}
response = client.get("/review", params=params)
assert response.status_code == 200
response_json = response.json()
assert len(response_json) == 1
assert response_json[0]["id"] == "123456.audio"
####################################################################################################################
################################### GET /review/summary Endpoint #################################################
####################################################################################################################
def test_get_review_summary_label_filter_matches_verified(self):
"""Test that the summary label filter also matches the `-verified` variant."""
with AuthTestClient(self.app) as client:
super().insert_mock_review_segment(
"123456.verified", data={"objects": ["person-verified"]}
)
super().insert_mock_review_segment(
"123456.car", data={"objects": ["car"]}, severity=SeverityEnum.detection
)
params = {
"cameras": "front_door",
"labels": "person",
"zones": "all",
"timezone": "utc",
}
response = client.get("/review/summary", params=params)
assert response.status_code == 200
response_json = response.json()
assert response_json["last24Hours"]["total_alert"] == 1
assert response_json["last24Hours"]["total_detection"] == 0
today_formatted = datetime.today().strftime("%Y-%m-%d")
assert response_json[today_formatted]["total_alert"] == 1
assert response_json[today_formatted]["total_detection"] == 0
def test_get_review_summary_all_filters(self):
with AuthTestClient(self.app) as client:
super().insert_mock_review_segment("123456.random")
+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()
+125 -1
View File
@@ -1,16 +1,24 @@
"""Tests for embedding cleanup on the main Frigate database.
"""Tests for embedding storage and cleanup on the main Frigate database.
Embeddings are deleted whether or not semantic search is currently enabled, so
the delete path has to tolerate databases where the vec0 tables were never
created and installs where the sqlite-vec extension is unavailable.
The write paths need the real extension, since the behavior under test belongs
to vec0 itself, so those tests are skipped when it is not installed.
"""
import os
import struct
import tempfile
import unittest
from peewee import OperationalError
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
VEC_EXTENSION_PATH = "/usr/local/lib/vec0.so"
class TestDeleteEmbeddings(unittest.TestCase):
def setUp(self) -> None:
@@ -52,6 +60,21 @@ class TestDeleteEmbeddings(unittest.TestCase):
self.assertEqual(self._thumbnail_ids(), ["b"])
def test_delete_failure_is_logged_not_raised(self) -> None:
self._create_thumbnails_table()
self.db.execute_sql(
"""
CREATE TRIGGER vec_thumbnails_no_delete BEFORE DELETE ON vec_thumbnails
BEGIN SELECT RAISE(ABORT, 'delete blocked'); END
"""
).fetchall()
with self.assertLogs("frigate.db.sqlitevecq", level="ERROR") as logs:
self.db.delete_embeddings_thumbnail(event_ids=["a"])
self.assertIn("Failed to delete embeddings", logs.output[0])
self.assertEqual(self._thumbnail_ids(), ["a", "b"])
def test_delete_skipped_without_extension(self) -> None:
self._create_thumbnails_table()
self.db.load_vec_extension = False
@@ -61,3 +84,104 @@ class TestDeleteEmbeddings(unittest.TestCase):
# the vec0 tables cannot be written without the extension
self.assertEqual(self._thumbnail_ids(), ["a", "b"])
def _vector(value: float) -> bytes:
return struct.pack("768f", *([value] * 768))
@unittest.skipUnless(
os.path.exists(VEC_EXTENSION_PATH), "sqlite-vec extension is not installed"
)
class TestEmbeddingsTableWrites(unittest.TestCase):
"""Covers the vec0 writes behind semantic search reindexing."""
def setUp(self) -> None:
self.tmp_dir = tempfile.TemporaryDirectory()
self.db = SqliteVecQueueDatabase(
os.path.join(self.tmp_dir.name, "test.db"), load_vec_extension=True
)
self.db.start()
self.db.create_embeddings_tables()
def tearDown(self) -> None:
self.db.stop()
self.db.close()
self.tmp_dir.cleanup()
def _vec_tables(self) -> list[str]:
return [
row[0]
for row in self.db.execute_sql(
"SELECT name FROM sqlite_master WHERE name LIKE 'vec_%' ORDER BY name"
)
]
def _make_legacy(self, table: str) -> None:
# sqlite-vec added the _info shadow table in 0.1.6, so tables written by
# Frigate 0.17 and earlier do not have one
self.db.execute_sql(f"DROP TABLE {table}_info").fetchall()
def _stored(self, table: str, column: str, event_id: str) -> str | None:
row = self.db.execute_sql(
f"SELECT vec_to_json({column}) FROM {table} WHERE id = ?", (event_id,)
).fetchone()
return row[0] if row else None
def test_write_error_is_raised(self) -> None:
# queued writes hide their exception in the returned cursor
with self.assertRaises(OperationalError):
self.db.execute_write("INSERT INTO vec_missing(id) VALUES ('a')")
def test_drop_tables_removes_legacy_tables(self) -> None:
self._make_legacy("vec_thumbnails")
self._make_legacy("vec_descriptions")
self.db.drop_embeddings_tables()
self.assertEqual(self._vec_tables(), [])
def test_drop_tables_without_any_tables_does_not_raise(self) -> None:
self.db.drop_embeddings_tables()
self.db.drop_embeddings_tables()
def test_upsert_replaces_existing_embedding(self) -> None:
self.db.upsert_embeddings(
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.01)}
)
self.db.upsert_embeddings(
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.99)}
)
stored = self._stored("vec_thumbnails", "thumbnail_embedding", "evt1")
self.assertTrue(stored.startswith("[0.990000"), stored)
def test_upsert_keeps_one_row_per_event(self) -> None:
for _ in range(3):
self.db.upsert_embeddings(
"vec_descriptions", "description_embedding", {"evt1": _vector(0.5)}
)
count = self.db.execute_sql(
"SELECT count(*) FROM vec_descriptions WHERE id = 'evt1'"
).fetchone()[0]
self.assertEqual(count, 1)
def test_reindex_cycle_rewrites_legacy_tables(self) -> None:
"""The 0.18 upgrade path: old vectors in, new vectors out."""
self.db.upsert_embeddings(
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.01)}
)
self._make_legacy("vec_thumbnails")
self._make_legacy("vec_descriptions")
self.db.drop_embeddings_tables()
self.db.create_embeddings_tables()
self.db.upsert_embeddings(
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.99)}
)
stored = self._stored("vec_thumbnails", "thumbnail_embedding", "evt1")
self.assertTrue(stored.startswith("[0.990000"), stored)
+30 -15
View File
@@ -24,6 +24,7 @@ from frigate.comms.event_metadata_updater import (
from frigate.comms.events_updater import EventEndSubscriber, EventUpdatePublisher
from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import (
CameraConfig,
CameraMqttConfig,
FrigateConfig,
RecordConfig,
@@ -128,8 +129,10 @@ class TrackedObjectProcessor(threading.Thread):
)
def update(camera: str, obj: TrackedObject, frame_name: str) -> None:
obj.has_snapshot = self.should_save_snapshot(camera, obj)
obj.has_clip = self.should_retain_recording(camera, obj)
obj.has_snapshot = self.should_save_snapshot(
camera_state.camera_config, obj
)
obj.has_clip = self.should_retain_recording(camera_state.camera_config, obj)
after = obj.to_dict()
message = {
"before": obj.previous,
@@ -153,8 +156,10 @@ class TrackedObjectProcessor(threading.Thread):
def end(camera: str, obj: TrackedObject, frame_name: str) -> None:
# populate has_snapshot
obj.has_snapshot = self.should_save_snapshot(camera, obj)
obj.has_clip = self.should_retain_recording(camera, obj)
obj.has_snapshot = self.should_save_snapshot(
camera_state.camera_config, obj
)
obj.has_clip = self.should_retain_recording(camera_state.camera_config, obj)
# write thumbnail to disk if it will be saved as an event
if obj.has_snapshot or obj.has_clip:
@@ -184,8 +189,8 @@ class TrackedObjectProcessor(threading.Thread):
)
def snapshot(camera: str, obj: TrackedObject) -> bool:
mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
mqtt_config: CameraMqttConfig = camera_state.camera_config.mqtt
if mqtt_config.enabled and self.should_mqtt_snapshot(mqtt_config, obj):
jpg_bytes, _ = obj.get_img_bytes(
ext="jpg",
timestamp=mqtt_config.timestamp,
@@ -238,11 +243,13 @@ class TrackedObjectProcessor(threading.Thread):
camera_state.on("camera_activity", camera_activity)
self.camera_states[camera] = camera_state
def should_save_snapshot(self, camera: str, obj: TrackedObject) -> bool:
def should_save_snapshot(
self, camera_config: CameraConfig, obj: TrackedObject
) -> bool:
if obj.false_positive:
return False
snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
snapshot_config: SnapshotsConfig = camera_config.snapshots
if not snapshot_config.enabled:
return False
@@ -261,11 +268,13 @@ class TrackedObjectProcessor(threading.Thread):
return True
def should_retain_recording(self, camera: str, obj: TrackedObject) -> bool:
def should_retain_recording(
self, camera_config: CameraConfig, obj: TrackedObject
) -> bool:
if obj.false_positive:
return False
record_config: RecordConfig = self.config.cameras[camera].record
record_config: RecordConfig = camera_config.record
# Recording is disabled
if not record_config.enabled:
@@ -281,13 +290,15 @@ class TrackedObjectProcessor(threading.Thread):
return True
def should_mqtt_snapshot(self, camera: str, obj: TrackedObject) -> bool:
def should_mqtt_snapshot(
self, mqtt_config: CameraMqttConfig, obj: TrackedObject
) -> bool:
# object never changed position
if obj.is_stationary():
return False
# if there are required zones and there is no overlap
required_zones = self.config.cameras[camera].mqtt.required_zones
required_zones = mqtt_config.required_zones
if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
logger.debug(
f"Not sending mqtt for {obj.obj_data['id']} because it did not enter required zones"
@@ -297,7 +308,11 @@ class TrackedObjectProcessor(threading.Thread):
return True
def update_mqtt_motion(
self, camera: str, frame_time: float, motion_boxes: list
self,
camera: str,
camera_config: CameraConfig,
frame_time: float,
motion_boxes: list,
) -> None:
# publish if motion is currently being detected
if motion_boxes:
@@ -312,7 +327,7 @@ class TrackedObjectProcessor(threading.Thread):
# always updated latest motion
self.last_motion_detected[camera] = frame_time
elif self.last_motion_detected.get(camera, 0) > 0:
mqtt_delay = self.config.cameras[camera].motion.mqtt_off_delay
mqtt_delay = camera_config.motion.mqtt_off_delay
# If no motion, make sure the off_delay has passed
if frame_time - self.last_motion_detected.get(camera, 0) >= mqtt_delay:
@@ -783,7 +798,7 @@ class TrackedObjectProcessor(threading.Thread):
frame_name, frame_time, current_tracked_objects, motion_boxes, regions
)
self.update_mqtt_motion(camera, frame_time, motion_boxes)
self.update_mqtt_motion(camera, camera_config, frame_time, motion_boxes)
tracked_objects = [
o.to_dict() for o in camera_state.tracked_objects.values()
+24 -7
View File
@@ -34,6 +34,8 @@ from frigate.util.process import FrigateProcess
logger = logging.getLogger(__name__)
RECORD_GRACE_SECONDS = 90
def capture_frames(
ffmpeg_process: sp.Popen[Any],
@@ -164,6 +166,7 @@ class CameraWatchdog(threading.Thread):
self.latest_invalid_segment_time: float = 0
self.latest_cache_segment_time: float = 0
self.record_enable_time: datetime | None = None
self.record_grace_until: datetime | None = None
# `valid` segments are published with the segment's start time, so the
# gap between consecutive publishes can reach 2 * segment_time. Pad the
@@ -280,6 +283,7 @@ class CameraWatchdog(threading.Thread):
self.latest_valid_segment_time = 0
self.latest_invalid_segment_time = 0
self.latest_cache_segment_time = 0
self.record_grace_until = None
self.record_enable_time = datetime.now().astimezone(UTC)
last_restart_time = datetime.now().timestamp()
continue
@@ -294,6 +298,7 @@ class CameraWatchdog(threading.Thread):
self.latest_valid_segment_time = 0
self.latest_invalid_segment_time = 0
self.latest_cache_segment_time = 0
self.record_grace_until = None
self.record_enable_time = datetime.now().astimezone(UTC)
else:
self.logger.debug(f"Disabling camera {self.config.name}")
@@ -318,6 +323,7 @@ class CameraWatchdog(threading.Thread):
self.latest_valid_segment_time = 0
self.latest_invalid_segment_time = 0
self.latest_cache_segment_time = 0
self.record_grace_until = None
self.record_enable_time = datetime.now().astimezone(UTC)
last_restart_time = datetime.now().timestamp()
self.was_record_enabled_in_config = record_enabled_in_config
@@ -404,11 +410,16 @@ class CameraWatchdog(threading.Thread):
if self.config.record.enabled and "record" in p["roles"]:
now_utc = datetime.now().astimezone(UTC)
# Check if we're within the grace period after enabling recording
# Grace period: 90 seconds allows time for ffmpeg to start and create first segment
in_grace_period = self.record_enable_time is not None and (
now_utc - self.record_enable_time
) < timedelta(seconds=90)
# ffmpeg needs time to create a first segment after
# recording is enabled and after a restart
in_grace_period = (
self.record_enable_time is not None
and (now_utc - self.record_enable_time)
< timedelta(seconds=RECORD_GRACE_SECONDS)
) or (
self.record_grace_until is not None
and now_utc < self.record_grace_until
)
latest_cache_dt = (
datetime.fromtimestamp(self.latest_cache_segment_time, tz=UTC)
@@ -445,8 +456,9 @@ class CameraWatchdog(threading.Thread):
<= self.latest_invalid_segment_time
)
invalid_stale = invalid_stale_condition
stale = cache_stale or valid_stale or invalid_stale
if cache_stale or valid_stale or invalid_stale:
if stale and can_restart:
if cache_stale:
reason = "No new recording segments were created"
elif valid_stale:
@@ -471,8 +483,13 @@ class CameraWatchdog(threading.Thread):
f"{self.config.name}/status/{role.value}", "offline"
)
self.record_grace_until = now_utc + timedelta(
seconds=RECORD_GRACE_SECONDS
)
last_restart_time = now
continue
else:
elif not stale:
self._send_record_status("online", now)
p["latest_segment_time"] = self.latest_cache_segment_time
+4 -1
View File
@@ -54,8 +54,11 @@ export class FrigateApp {
});
await this.ws.install(this.page);
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 @@ 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: {
+3 -2
View File
@@ -249,8 +249,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,
+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);
});
});
+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>
+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 @@
{}
@@ -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 @@
{}
+2 -1
View File
@@ -224,7 +224,8 @@
"ur": "اردو (урду)",
"withSystem": {
"label": "Выкарыстоўваць мову з налад сістэмы"
}
},
"be": "Беларуская"
},
"appearance": "Выгляд",
"darkMode": {
+2 -1
View File
@@ -51,7 +51,8 @@
"ur": "اردو (Urdú)",
"hr": "Hrvatski (croat)",
"bs": "Bosanski (Bosni)",
"zhHant": "繁體中文 (Xinès Tradicional)"
"zhHant": "繁體中文 (Xinès Tradicional)",
"be": "Беларуская (belarús)"
},
"system": "Sistema",
"systemMetrics": "Mètriques del sistema",
+1 -1
View File
@@ -2076,7 +2076,7 @@
},
"lpr": {
"globalDisabled": "L'enriquiment de reconeixement de matrícules ha d'estar habilitat perquè les funcions LPR funcionin en aquesta càmera.",
"vehicleNotTracked": "El reconeixement de la matrícula requereix que es faci un seguiment del 'cotxe' o de la 'motocicleta'.",
"vehicleNotTracked": "El reconeixement de la matrícula requereix un seguiment del vehicle. Habilita el 'cotxe' o un altre tipus de vehicle en Objectes per a aquesta càmera.",
"modelSizeLarge": "El model 'gran' està optimitzat per a matrícules multilínies. El model 'petit' proporciona un millor rendiment sobre 'gran' i s'ha d'utilitzar tret que la vostra regió utilitzi formats de placa multilínia."
},
"record": {
+2 -1
View File
@@ -194,7 +194,8 @@
"id": "Bahasa Indonesia (Indonesisch)",
"hr": "Hrvatski (Kroatisch)",
"bs": "Bosanski (Bosnisch)",
"zhHant": "Traditional Chinese"
"zhHant": "Traditional Chinese",
"be": "Беларуская (Belarussisch)"
},
"appearance": "Erscheinung",
"theme": {
+1 -1
View File
@@ -2116,7 +2116,7 @@
},
"lpr": {
"globalDisabled": "Die Erweiterung zur Kennzeichenerkennung muss aktiviert sein, damit die LPR-Funktionen bei dieser Kamera funktionieren.",
"vehicleNotTracked": "Für die Kennzeichenerkennung muss entweder „Auto“ oder „Motorrad“ erfasst werden. Aktivieren Sie „Auto“ oder „Motorrad“ unter „Objekte“ für diese Kamera.",
"vehicleNotTracked": "Die Kennzeichenerkennung erfordert, dass ein Fahrzeug verfolgt wird. Aktiviere ‘Auto’ oder einen anderen Fahrzeugtyp in den Objekten für diese Kamera.",
"modelSizeLarge": "Das „große“ Modell ist für mehrzeilige Kennzeichen optimiert. Das „kleine“ Modell bietet eine bessere Leistung als das „große“ und sollte verwendet werden, sofern in Ihrer Region keine mehrzeiligen Kennzeichenformate verwendet werden."
},
"record": {
+5 -1
View File
@@ -1952,7 +1952,11 @@
"modelSizeLarge": "The 'large' model is optimized for multi-line license plates. The 'small' model provides better performance over 'large' and should be used unless your region uses multi-line plate formats."
},
"record": {
"noRecordRole": "No streams have the record role defined. Recording will not function."
"noRecordRole": "No streams have the record role defined. Recording will not function.",
"profileBaseDisabled": "Recording is disabled in this camera's base config, so enabling it in a profile has no effect. Enable recording in the base config and use a profile to disable it instead."
},
"notifications": {
"profileBaseDisabled": "No cameras have notifications enabled in their base config, so enabling them in a profile has no effect. Enable notifications in the base config of at least one camera."
},
"birdseye": {
"objectsModeDetectDisabled": "Birdseye is set to 'objects' mode, but object detection is disabled for this camera. The camera will not appear in Birdseye."
+1
View File
@@ -233,6 +233,7 @@
"detectHighCpuUsage": "{{camera}} has high detect CPU usage ({{detectAvg}}%)",
"healthy": "System is healthy",
"reindexingEmbeddings": "Reindexing embeddings ({{processed}}% complete)",
"reindexEmbeddingsFailed": "Reindexing embeddings failed, check the logs",
"cameraIsOffline": "{{camera}} is offline",
"detectIsSlow": "{{detect}} is slow ({{speed}} ms)",
"detectIsVerySlow": "{{detect}} is very slow ({{speed}} ms)",
+2 -1
View File
@@ -142,7 +142,8 @@
"ur": "اردو (urdu keel)",
"hr": "Hrvatski (horvaadi keel)",
"bs": "Bosanski (bosnia keel)",
"zhHant": "繁體中文 (hiina keel traditsiooniliste hieroglüüfidega)"
"zhHant": "繁體中文 (hiina keel traditsiooniliste hieroglüüfidega)",
"be": "Беларуская (valgevene keel)"
},
"system": "Süsteem",
"systemMetrics": "Süsteemi meetrika",
+65 -1
View File
@@ -622,7 +622,11 @@
"disabled": "Objektide tuvastamine on lülitatud välja. Hetkepildid, läbivaatamisele kuuluvad objektid ja täiendavad funktsioonid, nagu näotuvastus, sõidukite numbrimärkide tuvastus ja generatiivne tehisintellekt, ei tööta."
},
"lpr": {
"vehicleNotTracked": "Sõidukite numbrimärkide tuvastus eeldab, et auto või mootorratas on jälgitav. Lülita menüüst Objektid sell kaamera jaoks sisse valikud „auto“ või „mootorratas“."
"vehicleNotTracked": "Sõidukite numbrimärkide tuvastus eeldab, et sõiduk on jälgitav. Lülita menüüst Objektid selle kaamera jaoks sisse valik „auto“ või muu sõiduki tüüp."
},
"review": {
"recordDisabled": "Salvestamine on lülitatud välja, ülevaatamseks mõeldud objektid jäävad loomata.",
"detectDisabled": "Objektide tuvastus on lülitatud välja. Ülevaatamisobjektid vajavad tuvastatud objekte, et hoiatusi ja tuvastusi kategooriatesse jaotada."
}
},
"button": {
@@ -650,5 +654,65 @@
"label": "Uus väärtus",
"reset": "Lähtesta"
}
},
"onvif": {
"autotracking": {
"zooming": {
"disabled": "Pole kasutusel",
"absolute": "Absoluutne",
"relative": "Suhteline"
}
},
"profileAuto": "Automaatne",
"profileLoading": "Laadin profiile..."
},
"modelSize": {
"small": "Väike",
"large": "Suur"
},
"logger": {
"logLevel": {
"debug": "Silumisteated",
"info": "Infoteated",
"warning": "Hoiatused",
"error": "Veateated",
"critical": "Kriitilise teated"
}
},
"birdseye": {
"cameraOrder": {
"reorderHandle": "Järjekorra muutmiseks lohista",
"saving": "Salvestan…",
"saved": "Salvestatud"
}
},
"retainMode": {
"all": "Kõik",
"motion": "Liikumine",
"active_objects": "Aktiivsed objektid"
},
"previewQuality": {
"very_high": "Väga kõrge",
"high": "Kõrge",
"medium": "Keskmine",
"low": "Madal",
"very_low": "Väga madal"
},
"ui": {
"timeFormat": {
"browser": "Brauser",
"12hour": "12 tundi",
"24hour": "24 tundi"
},
"unitSystem": {
"metric": "Meetermõõdustik",
"imperial": "Inglise mõõdustik"
}
},
"review": {
"imageSource": {
"recordings": "Salvestused",
"previews": "Eelvaated"
}
}
}
+1 -1
View File
@@ -140,7 +140,7 @@
"saveAll": "Tout enregistrer",
"savingAll": "Enregistrement de tout en cours…",
"undoAll": "Tout annuler",
"applying": "Enregistrement…",
"applying": "Enregistrement …",
"retry": "Réessayer"
},
"menu": {
+1 -1
View File
@@ -146,7 +146,7 @@
"toast": {
"started_one": "Un export a démarré. Ouverture du dossier en cours.",
"started_many": "{{count}} exports ont démarré. Ouverture du dossier en cours.",
"started_other": "{{count}} exports ont démarré. Ouverture du dossier en cours",
"started_other": "{{count}} exports ont démarré. Ouverture du dossier en cours.",
"startedNoCase_one": "Un export a démarré.",
"startedNoCase_many": "{{count}} exports ont démarré.",
"startedNoCase_other": "{{count}} exports ont démarré.",
+60 -1
View File
@@ -493,6 +493,9 @@
"max_concurrent": {
"label": "Nombre maximum d'exports simultanés",
"description": "Nombre maximum de tâches d'exportation à effectuer en même temps."
},
"chapters": {
"label": "Métadonnées des chapitres à incorporer dans les enregistrements exportés"
}
},
"preview": {
@@ -500,7 +503,8 @@
"quality": {
"label": "Prévisualisation qualité",
"description": "Niveau de qualité des prévisualisations (très_bas, bas, moyen, haut, très_haut)."
}
},
"description": "Réglages de la qualité des aperçus d'enregistrement montrés dans l'interface utilisateur."
},
"enabled_in_config": {
"label": "État original de l'enregistrement",
@@ -522,6 +526,61 @@
},
"required_zones": {
"label": "Zones requises"
},
"enabled_in_config": {
"label": "État des alertes originales",
"description": "Vérifie si les alertes étaient initialement activées dans la configuration statique."
},
"cutoff_time": {
"label": "Temps de coupure des alertes",
"description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper."
}
},
"detections": {
"label": "Configuration des détections",
"description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.",
"enabled": {
"label": "Activer les détections",
"description": "Activer ou désactiver les événements de détection pour cette caméra."
},
"labels": {
"label": "Étiquettes de détection",
"description": "Liste des étiquettes d'objets qui correspondent à des événements de détection."
},
"required_zones": {
"label": "Zones obligatoires",
"description": "Zones dans lesquelles un objet doit pénétrer pour être considéré comme une détection ; laisser vide pour autoriser n’importe quelle zone."
},
"cutoff_time": {
"label": "Délai limite de détection",
"description": "Nombre de secondes à attendre après l'absence d'activité susceptible de déclencher une détection avant d'interrompre celle-ci."
},
"enabled_in_config": {
"label": "État initial des détections",
"description": "Vérifie si les détections étaient activées initialement dans la configuration statique."
}
},
"genai": {
"label": "Configuration de l'IA générative",
"description": "Contrôle l'utilisation de l'IA générative pour la production de descriptions et de résumés des éléments d'évaluation.",
"enabled": {
"label": "Activer les descriptions de l'IA générative",
"description": "Activer ou désactiver les descriptions et les résumés générés par l'IA générative pour les éléments à examiner."
},
"alerts": {
"label": "Activer l'IA générative pour les alertes",
"description": "Utilisez l'IA générative pour générer des descriptions pour les éléments d'alerte."
},
"detections": {
"label": "Activer l'IA générative pour les détections",
"description": "Utilisez l'IA générative pour générer des descriptions pour les éléments de détection."
},
"image_source": {
"label": "Source de l'image de la revue",
"description": "Source des images envoyées à l'IA générative (« aperçu » ou « enregistrements ») ; « enregistrements » utilise des images de meilleure qualité mais plus de jetons d'IA."
},
"additional_concerns": {
"label": "Autres préoccupations"
}
}
}
+63 -2
View File
@@ -199,7 +199,10 @@
}
},
"audio_transcription": {
"label": "Transcription audio"
"label": "Transcription audio",
"enabled": {
"label": "Activer la transcription audio"
}
},
"audio": {
"label": "Détection audio",
@@ -313,6 +316,9 @@
"max_concurrent": {
"label": "Nombre maximum d'exports simultanés",
"description": "Nombre maximum de tâches d'exportation à effectuer en même temps."
},
"chapters": {
"label": "Métadonnées des chapitres à incorporer dans les enregistrements exportés"
}
},
"preview": {
@@ -320,7 +326,8 @@
"quality": {
"label": "Prévisualisation qualité",
"description": "Niveau de qualité des prévisualisations (très_bas, bas, moyen, haut, très_haut)."
}
},
"description": "Réglages de la qualité des aperçus d'enregistrement montrés dans l'interface utilisateur."
},
"enabled_in_config": {
"label": "État original de l'enregistrement",
@@ -340,6 +347,60 @@
},
"required_zones": {
"label": "Zones requises"
},
"enabled_in_config": {
"label": "État des alertes originales",
"description": "Vérifie si les alertes étaient initialement activées dans la configuration statique."
},
"cutoff_time": {
"label": "Temps de coupure des alertes",
"description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper."
}
},
"detections": {
"label": "Configuration des détections",
"description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.",
"enabled": {
"label": "Activer les détections"
},
"labels": {
"label": "Étiquettes de détection",
"description": "Liste des étiquettes d'objets qui correspondent à des événements de détection."
},
"required_zones": {
"label": "Zones obligatoires",
"description": "Zones dans lesquelles un objet doit pénétrer pour être considéré comme une détection ; laisser vide pour autoriser n’importe quelle zone."
},
"cutoff_time": {
"label": "Délai limite de détection",
"description": "Nombre de secondes à attendre après l'absence d'activité susceptible de déclencher une détection avant d'interrompre celle-ci."
},
"enabled_in_config": {
"label": "État initial des détections",
"description": "Vérifie si les détections étaient activées initialement dans la configuration statique."
}
},
"genai": {
"label": "Configuration de l'IA générative",
"description": "Contrôle l'utilisation de l'IA générative pour la production de descriptions et de résumés des éléments d'évaluation.",
"enabled": {
"label": "Activer les descriptions de l'IA générative",
"description": "Activer ou désactiver les descriptions et les résumés générés par l'IA générative pour les éléments à examiner."
},
"alerts": {
"label": "Activer l'IA générative pour les alertes",
"description": "Utilisez l'IA générative pour générer des descriptions pour les éléments d'alerte."
},
"detections": {
"label": "Activer l'IA générative pour les détections",
"description": "Utilisez l'IA générative pour générer des descriptions pour les éléments de détection."
},
"image_source": {
"label": "Source de l'image de la revue",
"description": "Source des images envoyées à l'IA générative (« aperçu » ou « enregistrements ») ; « enregistrements » utilise des images de meilleure qualité mais plus de jetons d'IA."
},
"additional_concerns": {
"label": "Autres préoccupations"
}
}
}
+1 -1
View File
@@ -37,7 +37,7 @@
"descriptionLabel": "Description"
},
"deleteCase": {
"desc": "Êtes-vous sûr de vouloir supprimer {{caseName}}?",
"desc": "Êtes-vous sûr de vouloir supprimer {{caseName}} ?",
"descKeepExports": "Les exports seront disponibles comme exports non catégorisés.",
"deleteExports": "Supprimer aussi les exports"
}
+6 -5
View File
@@ -16,7 +16,8 @@
"globalConfig": "Configuration globale - Frigate",
"cameraConfig": "Configuration de la caméra - Frigate",
"maintenance": "Maintenance - Frigate",
"profiles": "Profils - Frigate"
"profiles": "Profils - Frigate",
"detectorsAndModel": "Détecteurs et modèle - Frigate"
},
"menu": {
"ui": "Interface utilisateur",
@@ -90,7 +91,7 @@
"maintenance": "Maintenance",
"uiSettings": "Paramètres IU",
"profiles": "Profils",
"systemGo2rtcStreams": "Streams go2rtc"
"systemGo2rtcStreams": "Flux go2rtc"
},
"dialog": {
"unsavedChanges": {
@@ -1363,9 +1364,9 @@
"deleteCameraDialog": {
"title": "Supprimer la caméra",
"description": "Supprimer la caméra va supprimer de façon permanente les enregistrements, les objets suivis, et la configuration de la caméra. Tous les streams go2rtc associés à la caméra devront être supprimés manuellement.",
"selectPlaceholder": "Choisir une caméra...",
"confirmTitle": "Êtes-vous sûr?",
"confirmWarning": "Supprimer <strong>\n{{cameraName}}\n</strong> ne peut être annulé.",
"selectPlaceholder": "Choisir une caméra …",
"confirmTitle": "Êtes-vous certain ?",
"confirmWarning": "La suppression de la caméra <strong>{{cameraName}}</strong> ne peut être annulé.",
"deleteExports": "Supprimer aussi les exports de cette caméra",
"confirmButton": "Suppression permanente",
"success": "Caméra {{cameraName}} supprimée avec succès",
+29 -1
View File
@@ -455,5 +455,33 @@
"basketball_bounce": "Kosárlabda pattogás",
"sine_wave": "Szinusz hullám",
"echo": "Visszhang",
"cacophony": "Kakófónia"
"cacophony": "Kakófónia",
"squish": "Facsarás",
"drip": "Csöpögés",
"pour": "Öntés",
"fill": "Töltödés",
"spray": "permet",
"pump": "Pumpál",
"boiling": "Forr",
"sonar": "hanglokátor",
"whoosh": "suhogás",
"electronic_tuner": "Elektronikus hangoló",
"effects_unit": "Effektmodul",
"chorus_effect": "Kórus-effektus",
"bang": "Bumm",
"slap": "Pofon",
"whack": "Pofon",
"breaking": "Törés",
"whip": "Ostor",
"tearing": "Szakadás",
"ping": "Pattan",
"ding": "Csing",
"squeal": "Visítás",
"creak": "Nyikorgás",
"rustle": "Suhogás",
"whir": "Zúgás",
"shofar": "Sófar",
"clatter": "csörgés",
"sizzle": "Sistergés",
"sidetone": "Mellékhang"
}
@@ -75,5 +75,10 @@
"enabled": {
"label": "Hangról szövegre alakítás engedélyezése"
}
},
"zones": {
"speed_threshold": {
"label": "Minimum sebesség"
}
}
}
+14 -4
View File
@@ -65,7 +65,8 @@
"batchQueuedSuccess_other": "{{count}} ekspor antrian.",
"batchQueueFailed": "Gagal mengantrekan {{total}} ekspor. Kamera yang gagal: {{failedCameras}}",
"error": {
"failed": "Ekspor gagal mengantri: {{error}}"
"failed": "Ekspor gagal mengantri: {{error}}",
"noValidTimeSelected": "Rentang waktu yang dipilih tidak valid"
}
},
"case": {
@@ -114,7 +115,13 @@
"exportingButton": "Mengekspor...",
"caseNamePlaceholder": "Ekspor tinjauan - {{date}}"
},
"queueing": "Mengantrekan Ekspor..."
"queueing": "Mengantrekan Ekspor...",
"fromTimeline": {
"saveExport": "Simpan ektraksi",
"queueingExport": "Antrean Ekspor...",
"previewExport": "Pratinjau Ekspor",
"useThisRange": "Gunakan Rentang Ini"
}
},
"search": {
"saveSearch": {
@@ -138,7 +145,8 @@
}
},
"showStats": {
"label": "Tampilkan statistik streaming"
"label": "Tampilkan statistik streaming",
"desc": "Aktifkan opsi ini untuk menampilkan statistik streaming sebagai overlay pada tampilan kamera."
},
"debugView": "Tampilan Debug"
},
@@ -147,7 +155,9 @@
"label": "Bagikan Cap Waktu",
"title": "Bagikan Cap Waktu",
"custom": "Cap Waktu Kustom",
"button": "Bagikan URL Cap Waktu"
"button": "Bagikan URL Cap Waktu",
"description": "Bagikan URL bertanda waktu sesuai posisi pemutar saat ini atau pilih tanda waktu kustom. Perlu dicatat bahwa ini bukan URL bagikan publik dan hanya dapat diakses oleh pengguna yang memiliki akses ke Frigate dan kamera ini.",
"shareTitle": "Pratinjau Penunjuk Waktu Frigate : {{camera}}"
},
"confirmDelete": {
"title": "Konfirmasi Penghapusan",
+33 -2
View File
@@ -47,11 +47,42 @@
"score": "Skor",
"estimatedSpeed": "Perkiraan Kecepatan {{unit}}",
"features": {
"label": "Fitur"
"label": "Fitur",
"hasSnapshot": "Memiliki Cuplikan",
"hasVideoClip": "Memiliki Klip Video",
"submittedToFrigatePlus": {
"label": "Dikirim ke Frigate+"
}
},
"cameras": {
"all": {
"short": "Kamera"
"short": "Kamera",
"title": "Semua Kamera"
},
"label": "Filter Kamera"
},
"sort": {
"label": "Urutkan",
"dateAsc": "Tanggal (Terlama)",
"dateDesc": "Tanggal (Terbaru)",
"scoreAsc": "Skor Objek (Terendah)",
"scoreDesc": "Skor Objek (Tertinggi)",
"speedAsc": "Estimasi Kecepatan (Terendah)",
"speedDesc": "Estimasi Kecepatan (Tercepat)",
"relevance": "Relevansi"
},
"review": {
"showReviewed": "Tunjukan Pratinjau"
},
"motion": {
"showMotionOnly": "Hanya Tampilkan Gerakan"
},
"explore": {
"settings": {
"title": "Pengaturan",
"defaultView": {
"title": "Tampilan Default"
}
}
}
}
+2 -1
View File
@@ -223,7 +223,8 @@
"ur": "اردو (Urdu)",
"hr": "Hrvatski (Croato)",
"bs": "Bosanski (Bosniaco)",
"zhHant": "繁體中文 (Cinese Tradizionale)"
"zhHant": "繁體中文 (Cinese Tradizionale)",
"be": "Беларуская (Bielorusso)"
},
"darkMode": {
"label": "Modalità scura",
+1 -1
View File
@@ -2182,7 +2182,7 @@
},
"lpr": {
"globalDisabled": "Per il corretto funzionamento delle funzioni LPR (riconoscimento targhe) su questa telecamera, è necessario abilitare la funzione di arricchimento del riconoscimento delle targhe.",
"vehicleNotTracked": "Il riconoscimento della targa richiede che venga tracciato 'automobile' o 'moto'. Abilita 'automobile' o 'moto' nella sezione oggetti per questa telecamera.",
"vehicleNotTracked": "Il riconoscimento della targa richiede che il veicolo sia tracciato. Abilita 'auto' o un altro tipo di veicolo nella sezione Oggetti per questa telecamera.",
"modelSizeLarge": "Il modello 'grande' è ottimizzato per le targhe multilinea. Il modello 'piccolo' offre prestazioni migliori rispetto al modello 'grande' e dovrebbe essere utilizzato a meno che nella vostra regione non siano in vigore formati di targa multilinea."
},
"record": {
+291 -217
View File
@@ -2,16 +2,16 @@
"crying": "울음",
"snoring": "코골이",
"singing": "노래",
"yell": "비명",
"yell": "고함",
"speech": "음성",
"babbling": "옹알이",
"bicycle": "자전거",
"a_capella": "아카펠라",
"accelerating": "가속",
"accelerating": "가속음",
"accordion": "아코디언",
"acoustic_guitar": "통기타",
"car": "차량",
"motorcycle": "원동기",
"acoustic_guitar": "어쿠스틱 기타",
"car": "승용차",
"motorcycle": "오토바이",
"bus": "버스",
"train": "기차",
"boat": "보트",
@@ -22,26 +22,26 @@
"sheep": "양",
"skateboard": "스케이트보드",
"door": "문",
"mouse": "마우스",
"mouse": "생쥐",
"keyboard": "키보드",
"sink": "싱크대",
"blender": "블렌더",
"blender": "믹서기",
"clock": "시계",
"scissors": "가위",
"hair_dryer": "헤어 드라이어",
"hair_dryer": "헤어드라이어",
"toothbrush": "칫솔",
"vehicle": "탈 것",
"vehicle": "탈것",
"animal": "동물",
"bark": "짖는 소리",
"goat": "염소",
"bellow": "포효",
"whoop": "환성",
"bellow": "큰 외침",
"whoop": "환호",
"whispering": "속삭임",
"laughter": "웃음",
"snicker": "낄낄 웃음",
"snicker": "낄낄거림",
"sigh": "한숨",
"choir": "합창",
"yodeling": "요들링",
"yodeling": "요들",
"chant": "성가",
"mantra": "만트라",
"child_singing": "어린이 노래",
@@ -55,271 +55,271 @@
"wheeze": "헐떡임",
"gasp": "헐떡임",
"pant": "거친숨",
"snort": "코골이",
"snort": "코웃음",
"cough": "기침",
"throat_clearing": "목 긁는 소리",
"throat_clearing": "목 가다듬는 소리",
"sneeze": "재채기",
"sniff": "훌쩍",
"sniff": "훌쩍임",
"run": "달리기",
"shuffle": "임의 재생",
"shuffle": "발 끄는 소리",
"footsteps": "발소리",
"chewing": "씹는 소리",
"biting": "치는 소리",
"gargling": "가글",
"stomach_rumble": "배 꼬르륵",
"biting": "깨무는 소리",
"gargling": "가글 소리",
"stomach_rumble": "배 꼬르륵 소리",
"burping": "트림",
"camera": "카메라",
"hiccup": "딸꾹질",
"fart": "방귀",
"hands": "손",
"hands": "손 소리",
"finger_snapping": "손가락 튕기기",
"clapping": "박수",
"heartbeat": "심장 박동",
"heart_murmur": "심장 잡음",
"cheering": "환호",
"applause": "환호",
"applause": "박수갈채",
"chatter": "수다",
"crowd": "군중",
"crowd": "군중 소리",
"children_playing": "놀고 있는 아이들",
"pets": "반려동물",
"yip": "깽깽거림",
"howl": "하울링",
"bow_wow": "짖는 소리",
"bow_wow": "멍멍",
"growling": "으르렁거림",
"whimper_dog": "낑낑거림",
"purr": "가르릉거림",
"purr": "골골송",
"meow": "야옹",
"hiss": "하악질",
"caterwaul": "발정기 울음",
"caterwaul": "발정기 고양이 울음",
"livestock": "가축",
"clip_clop": "딸깍딸깍",
"clip_clop": "다각다각 소리",
"neigh": "말 울음소리",
"cattle": "소",
"moo": "음메",
"cowbell": "워낭 소리",
"pig": "돼지",
"oink": "꿀꿀거림",
"bleat": "메에",
"fowl": "새",
"oink": "꿀꿀",
"bleat": "매애",
"fowl": "조류",
"chicken": "닭",
"cluck": "닭 울음소리",
"cock_a_doodle_doo": "꼬꼬댁",
"cluck": "꼬꼬댁",
"cock_a_doodle_doo": "꼬끼오",
"turkey": "칠면조",
"gobble": "칠면조 울음소리",
"duck": "오리",
"quack": "오리 울음소리",
"quack": "꽥꽥",
"goose": "거위",
"honk": "거위 울음소리",
"wild_animals": "야생 동물",
"roaring_cats": "맹수 포효",
"roaring_cats": "맹수의 포효",
"roar": "포효",
"chirp": "새 울음소리",
"squawk": "지저귐",
"chirp": "새 지저귐",
"squawk": "새의 큰 울음소리",
"pigeon": "비둘기",
"coo": "비둘기 울음소리",
"coo": "구구",
"crow": "까마귀",
"caw": "까마귀 울음소리",
"caw": "까악까악",
"owl": "부엉이",
"hoot": "부엉이 울음소리",
"flapping_wings": "날갯짓",
"flapping_wings": "날갯짓 소리",
"bicycle_bell": "자전거 벨",
"tuning_fork": "소리굽쇠",
"chime": "차임벨",
"wind_chime": "풍경",
"wind_chime": "풍경 소리",
"harmonica": "하모니카",
"steel_guitar": "스틸 기타",
"tapping": "두드림",
"strum": "기타 스트로크",
"tapping": "태핑",
"strum": "스트로크",
"banjo": "밴조",
"sitar": "시타르",
"mandolin": "만돌린",
"zither": "지더",
"zither": "치터",
"ukulele": "우쿨렐레",
"lawn_mower": "잔디깎이",
"chainsaw": "전기톱",
"medium_engine": "중형 엔진",
"heavy_engine": "대형 엔진",
"engine_knocking": "엔진 노킹",
"engine_starting": "엔진 시동",
"idling": "공회전",
"alarm": "알람",
"telephone": "전화",
"telephone_bell_ringing": "전화 소리",
"medium_engine": "중형 엔진 소리",
"heavy_engine": "대형 엔진 소리",
"engine_knocking": "엔진 노킹음",
"engine_starting": "시동 소리",
"idling": "공회전 소리",
"alarm": "경보",
"telephone": "전화벨",
"telephone_bell_ringing": "전화벨 울리는 소리",
"ringtone": "벨소리",
"telephone_dialing": "전화 다이얼",
"telephone_dialing": "전화 다이얼 누르는 소리",
"dial_tone": "발신음",
"cash_register": "금전 등록기",
"cash_register": "계산대 소리",
"printer": "프린터",
"single-lens_reflex_camera": "카메라 셔터",
"tools": "도구들",
"hammer": "망치",
"jackhammer": "착암기",
"sawing": "톱질",
"filing": "연마",
"sanding": "사포질",
"power_tool": "전동 도구",
"drill": "드릴",
"explosion": "폭발",
"gunshot": "총소리",
"machine_gun": "기관총",
"fusillade": "연속 총성",
"artillery_fire": "포격",
"cap_gun": "화약 총",
"fireworks": "불꽃놀이",
"firecracker": "폭죽",
"car_alarm": "차량 경보",
"power_windows": "전동 창문",
"single-lens_reflex_camera": "DSLR 셔터음",
"tools": "공구 소리",
"hammer": "망치질 소리",
"jackhammer": "착암기 소리",
"sawing": "톱질 소리",
"filing": "줄질 소리",
"sanding": "사포질 소리",
"power_tool": "전동 공구 소리",
"drill": "드릴 소리",
"explosion": "폭발음",
"gunshot": "총성",
"machine_gun": "기관총 소리",
"fusillade": "일제 사격 소리",
"artillery_fire": "포격 소리",
"cap_gun": "화약총 소리",
"fireworks": "폭죽 터지는 소리",
"firecracker": "폭죽 소리",
"car_alarm": "자동차 경보음",
"power_windows": "전동 파워 윈도우 소리",
"dogs": "개",
"rats": "쥐",
"insect": "벌래",
"rats": "쥐들",
"insect": "곤충",
"mosquito": "모기",
"fly": "파리",
"fly": "날벌레",
"frog": "개구리",
"snake": "뱀",
"music": "음악",
"musical_instrument": "악기",
"guitar": "기타",
"electric_guitar": "전기기타",
"electric_guitar": "일렉트릭 기타",
"bass_guitar": "베이스 기타",
"piano": "피아노",
"electric_piano": "전자 피아노",
"organ": "오르간",
"drum_kit": "드럼 키트",
"drum": "드럼",
"maraca": "마라카",
"maraca": "마라카스",
"marimba": "마림바",
"orchestra": "오케스트라",
"trumpet": "트럼펫",
"trombone": "트럼본",
"trombone": "트롬본",
"violin": "바이올린",
"cello": "첼로",
"flute": "플루트",
"saxophone": "섹소폰",
"saxophone": "색소폰",
"clarinet": "클라리넷",
"harp": "하프",
"bell": "벨",
"jingle_bell": "징글 벨",
"jingle_bell": "방울 소리",
"bagpipes": "백파이프",
"pop_music": "팝뮤직",
"hip_hop_music": "힙팝 뮤직",
"pop_music": "팝 음악",
"hip_hop_music": "힙합 음악",
"beatboxing": "비트박스",
"rock_music": "록 뮤직",
"rock_music": "록 음악",
"punk_rock": "펑크 록",
"rock_and_roll": "록엔 롤",
"rock_and_roll": "로큰롤",
"rhythm_and_blues": "R&B",
"soul_music": "소울 뮤직",
"swing_music": "스윙 뮤직",
"bluegrass": "블루그레스",
"jazz": "제즈",
"soul_music": "소울 음악",
"swing_music": "스윙 음악",
"bluegrass": "블루그래스",
"jazz": "재즈",
"disco": "디스코",
"classical_music": "클레식 음악",
"classical_music": "클래식 음악",
"opera": "오페라",
"techno": "테크노",
"electronic_dance_music": "Electronic Dance Music(EDM)",
"blues": "블루즈",
"music_for_children": "유아동 음악",
"electronic_dance_music": "EDM",
"blues": "블루스",
"music_for_children": "어린이 음악",
"background_music": "배경음악(BGM)",
"soundtrack_music": "사운드트렉 음악",
"soundtrack_music": "사운드트랙",
"lullaby": "자장가",
"christmas_music": "크리스마스 음악",
"steam": "증기",
"fire": "화재",
"christmas_music": "크리스마스 캐럴",
"steam": "증기 소리",
"fire": "불소리",
"motorboat": "모터보트",
"ship": "선박",
"motor_vehicle": "자동차",
"race_car": "경주용 자동차",
"race_car": "레이싱 카",
"truck": "트럭",
"air_brake": "에어 브레이크",
"reversing_beeps": "후진 경보음",
"ice_cream_truck": "아이스크림 트럭",
"reversing_beeps": "후진 경고음",
"ice_cream_truck": "아이스크림 트럭 음악",
"emergency_vehicle": "긴급 차량",
"police_car": "경찰차",
"ambulance": "구급차",
"fire_engine": "소방차",
"rail_transport": "철도 운송",
"railroad_car": "철도 차량",
"rail_transport": "철도",
"railroad_car": "기차 칸",
"subway": "지하철",
"aircraft": "비행기",
"aircraft_engine": "항공기 엔진",
"jet_engine": "제트 엔진",
"propeller": "프로펠러",
"aircraft": "항공기",
"aircraft_engine": "항공기 엔진 소리",
"jet_engine": "제트 엔진 소리",
"propeller": "프로펠러 소리",
"helicopter": "헬리콥터",
"fixed-wing_aircraft": "고정익 항공기",
"engine": "엔진",
"light_engine": "라이트 엔진",
"fixed-wing_aircraft": "고정익 비행기",
"engine": "엔진 소리",
"light_engine": "소형 엔진 소리",
"doorbell": "초인종",
"ding-dong": "딩동",
"sliding_door": "슬라이딩 도어",
"knock": "노크",
"tap": "탭",
"cupboard_open_or_close": "찬장 열기 또는 닫기",
"drawer_open_or_close": "서랍 열기 또는 닫기",
"microwave_oven": "전자 레인지",
"sliding_door": "미닫이문",
"knock": "노크 소리",
"tap": "두드리는 소리",
"cupboard_open_or_close": "찬장 여닫는 소리",
"drawer_open_or_close": "서랍 여닫는 소리",
"microwave_oven": "전자레인지",
"water_tap": "수도꼭지",
"bathtub": "목욕통",
"toilet_flush": "변기 물내림",
"electric_toothbrush": "전동 치솔",
"vacuum_cleaner": "진공 청소기",
"zipper": "지퍼",
"coin": "동전",
"electric_shaver": "전동 면도기",
"typing": "타이핑",
"bathtub": "욕조",
"toilet_flush": "변기 물내리는 소리",
"electric_toothbrush": "전동 칫솔",
"vacuum_cleaner": "진공청소기",
"zipper": "지퍼 여닫는 소리",
"coin": "동전 부딪치는 소리",
"electric_shaver": "전기면도기",
"typing": "타자 소리",
"typewriter": "타자기",
"computer_keyboard": "컴퓨터 키보드",
"alarm_clock": "알람 시계",
"siren": "사이렌",
"civil_defense_siren": "민방위 경보 사이렌",
"civil_defense_siren": "민방위 경보",
"buzzer": "버저",
"fire_alarm": "화재 경보(기)",
"whistle": "휘슬",
"ratchet": "라쳇",
"tick": "틱",
"tick-tock": "틱톡",
"gears": "기어",
"pulleys": "도르래",
"sewing_machine": "미싱기",
"air_conditioning": "A/C",
"eruption": "분화",
"boom": "붐",
"wood": "나무",
"splinter": "가시",
"glass": "유리",
"silence": "침묵",
"sound_effect": "효과 음",
"environmental_noise": "환경 소음",
"fire_alarm": "화재경보기",
"whistle": "호루라기",
"ratchet": "라쳇 소리",
"tick": "똑딱 소리",
"tick-tock": "똑딱똑딱",
"gears": "톱니바퀴 소리",
"pulleys": "도르래 소리",
"sewing_machine": "재봉틀",
"air_conditioning": "에어컨 소리",
"eruption": "분화 소리",
"boom": "쿵 하는 소리",
"wood": "나무 부딪치는 소리",
"splinter": "나무 쪼개지는 소리",
"glass": "유리 소리",
"silence": "무음",
"sound_effect": "효과음",
"environmental_noise": "주변 환경 소음",
"white_noise": "백색 소음",
"pink_noise": "핑크 노이즈",
"television": "텔레비젼(TV)",
"radio": "라디오",
"splash": "스플레쉬",
"spray": "스프레이",
"pump": "펌프",
"arrow": "화살",
"electronic_tuner": "전자 튜너",
"smash": "스메쉬",
"clicking": "클릭킹",
"television": "텔레비전 소리",
"radio": "라디오 소리",
"splash": "첨벙거리는 소리",
"spray": "스프레이 분사음",
"pump": "펌프 소리",
"arrow": "화살 날아가는 소리",
"electronic_tuner": "전자 조율기",
"smash": "박살 나는 소리",
"clicking": "딸깍거리는 소리",
"noise": "소음",
"mains_hum": "험 노이즈",
"outside": "외부",
"mains_hum": "전원 험 노이즈",
"outside": "실외",
"reggae": "레게",
"french_horn": "프렌치 호른",
"traffic_noise": "교통 소음",
"rain": "비",
"raindrop": "빗물방울",
"rain_on_surface": "표면에 비",
"stream": "개울 하천",
"wedding_music": "웨딩 음악",
"harmonic": "하모닉",
"ocean": "오션",
"vibration": "진동",
"new-age_music": "뉴 에이지 음악",
"rain": "빗소리",
"raindrop": "빗방울 소리",
"rain_on_surface": "표면에 떨어지는 빗소리",
"stream": "개울물 소리",
"wedding_music": "결혼식 음악",
"harmonic": "하모닉스",
"ocean": "바다 소리",
"vibration": "진동음",
"new-age_music": "뉴에이지 음악",
"tubular_bells": "튜블러 벨",
"middle_eastern_music": "중동 음악",
"mechanical_fan": "기계식 팬",
"angry_music": "앵그리 뮤직",
"rattle": "방울",
"water": "물",
"waterfall": "폭포",
"mechanical_fan": "선풍기 소리",
"angry_music": "격렬한 음악",
"rattle": "덜그럭거리는 소리",
"water": "물소리",
"waterfall": "폭포 소리",
"music_of_asia": "아시아 음악",
"brass_instrument": "금관악기",
"folk_music": "포크 음악",
@@ -334,95 +334,169 @@
"flamenco": "플라멩코",
"bass_drum": "베이스 드럼",
"thunderstorm": "뇌우",
"thunder": "우뢰",
"thunder": "천둥",
"vibraphone": "비브라폰",
"synthesizer": "신디사이저",
"smoke_detector": "연기 감지기",
"busy_signal": "통화 중 신호",
"shuffling_cards": "카드 섞기",
"train_whistle": "기차 경적 소리",
"busy_signal": "통화 중 신호음",
"shuffling_cards": "카드 섞는 소리",
"train_whistle": "기차 호루라기 소리",
"traditional_music": "전통 음악",
"independent_music": "인디 음악",
"song": "노래",
"theme_music": "테마 음악",
"house_music": "하우스 뮤직",
"beep": "삑",
"ping": "핑",
"dance_music": "트랜스 음악",
"whale_vocalization": "고래 울음소리",
"house_music": "하우스 음악",
"beep": "삑 소리",
"ping": "핑 소리",
"dance_music": "댄스 음악",
"whale_vocalization": "고래 소리",
"music_of_bollywood": "볼리우드 음악",
"ska": "스카(Ska)",
"ska": "스카",
"train_horn": "기차 경적",
"wind_instrument": "관악기",
"funk": "펑키 재즈",
"glockenspiel": "카리용",
"waves": "파도",
"tabla": "판자",
"ding": "딩",
"chorus_effect": "코러스 효과",
"basketball_bounce": "농구공 튕김",
"funk": "펑크",
"glockenspiel": "글로켄슈필",
"waves": "파도 소리",
"tabla": "타블라",
"ding": "딩동 소리",
"chorus_effect": "코러스 이펙트",
"basketball_bounce": "농구공 튀기는 소리",
"air_horn": "에어혼",
"hi_hat": "하이햇",
"cacophony": "불협화음",
"cacophony": "불협화음 소음",
"drum_machine": "드럼 머신",
"drum_and_bass": "드럼 앤 베이스",
"drum_roll": "드럼 롤",
"echo": "에코",
"heavy_metal": "헤비메탈",
"progressive_rock": "프로그레시브 록",
"cricket": "크리켓(귀뚜라미)",
"music_of_latin_america": "라틴 아메리카 음악",
"cricket": "귀뚜라미",
"music_of_latin_america": "라틴 음악",
"sampler": "샘플러",
"hammond_organ": "해먼드 오르간",
"theremin": "테레민",
"shofar": "쇼파르",
"mechanisms": "메커니즘",
"pulse": "펄스",
"liquid": "액체",
"squish": "으깨다",
"effects_unit": "이펙트 유닛",
"shofar": "쇼파르(양각 나팔)",
"mechanisms": "기계 장치",
"pulse": "펄스음",
"liquid": "액체 소리",
"squish": "질척거리는 소리",
"effects_unit": "이펙터",
"christian_music": "기독교 음악",
"gospel_music": "가스펠 음악",
"gospel_music": "가스펠",
"dubstep": "덥스텝",
"double_bass": "더블베이스 (콘트라베이스)",
"wind": "바람",
"wood_block": "나무 블록",
"double_bass": "콘트라베이스",
"wind": "바람 소리",
"wood_block": "우드블록",
"country": "컨트리 음악",
"cutlery": "식기류",
"cutlery": "수저 부딪치는 소리",
"afrobeat": "아프로비트",
"trance_music": "트랜스 음악",
"percussion": "타악기",
"electronic_music": "전자 음악",
"hum": "험",
"zing": "징",
"boing": "보잉",
"crunch": "크런치",
"sine_wave": "사인파",
"gurgling": "꿀꺽꿀꺽",
"hum": "웅웅거리는 험 노이즈",
"zing": "핑 날아가는 소리",
"boing": "용수철 뿅 소리",
"crunch": "바삭거리는 소리",
"sine_wave": "사인파 톤",
"gurgling": "졸졸 흐르는 소리",
"electronic_organ": "전자 오르간",
"grunge": "그런지",
"pizzicato": "꼬집다",
"pizzicato": "피치카토",
"carnatic_music": "카르나틱 음악",
"cymbal": "심벌즈",
"harpsichord": "하프시코드",
"plucked_string_instrument": "뜯어서 연주하는 현악기",
"happy_music": "행복한 음악",
"plucked_string_instrument": "발현악기",
"happy_music": "밝은 음악",
"rustling_leaves": "바스락거리는 나뭇잎",
"wind_noise": "바람 소리",
"wind_noise": "풍절음",
"tire_squeal": "타이어 마찰음",
"buzz": "버저 소리",
"foghorn": "포그혼",
"buzz": "윙윙거리는 소리",
"foghorn": "무무표지(안개 경적)",
"vocal_music": "보컬 음악",
"chirp_tone": "짹짹거리는 소리",
"plop": "퐁당",
"keys_jangling": "딸랑거리는 열쇠 꾸러미",
"chirp_tone": "전자 짹짹음",
"plop": "퐁당 소리",
"keys_jangling": "열쇠 흔들리는 소리",
"didgeridoo": "디저리두",
"tender_music": "Tender 음악",
"tender_music": "부드러운 음악",
"electronica": "일렉트로니카",
"exciting_music": "신나는 음악",
"bang": "쾅",
"slap": "슬랩",
"video_game_music": "비디오 게임 음악",
"sad_music": "Sad 음악",
"scary_music": "스카이 음악"
"bang": "쾅 소리",
"slap": "찰싹 때리는 소리",
"video_game_music": "게임 음악",
"sad_music": "슬픈 음악",
"scary_music": "공포 음악",
"squeal": "끼익 비명/마찰음",
"clatter": "달그락거리는 소리",
"sidetone": "측음",
"slosh": "출렁거리는 소리",
"sonar": "소나(음파 탐지기)",
"patter": "톡톡 두드리는 소리",
"inside": "실내",
"rimshot": "림샷",
"dishes": "식기 달그락거리는 소리",
"crack": "쩍 갈라지는 소리",
"burst": "파열음",
"croak": "개구리 울음소리",
"gong": "징",
"steelpan": "스틸팬",
"bowed_string_instrument": "찰현악기",
"string_section": "현악 섹션",
"church_bell": "교회 종소리",
"singing_bowl": "싱잉볼",
"scratching": "스크래칭",
"psychedelic_rock": "사이케델릭 록",
"crackle": "타닥거리는 소리",
"sailboat": "돛단배",
"rowboat": "노 젓는 배",
"toot": "빵빵 소리",
"skidding": "미끄러지는 소리",
"car_passing_by": "지나가는 차 소리",
"train_wheels_squealing": "기차 바퀴 마찰음",
"dental_drill's_drill": "치과용 드릴",
"slam": "쾅 닫는 소리",
"squeak": "찍찍거리는 소리",
"chopping": "도마 칼질 소리",
"frying": "튀기는 소리",
"writing": "글씨 쓰는 소리",
"steam_whistle": "증기 호각",
"chop": "장작 패는 소리",
"chink": "짤랑거리는 소리",
"shatter": "유리 와장창 깨지는 소리",
"static": "지지직거리는 잡음",
"field_recording": "현장 녹음 소리",
"scream": "외마디 비명",
"chird": "새소리",
"change_ringing": "타종 소리",
"drip": "똑똑 떨어지는 소리",
"pour": "콸콸 붓는 소리",
"trickle": "졸졸 흐르는 소리",
"gush": "솟구치는 소리",
"fill": "물 차오르는 소리",
"stir": "휘젓는 소리",
"boiling": "보글보글 끓는 소리",
"whoosh": "쉭 지나가는 소리",
"thump": "털썩 하는 소리",
"thunk": "퍽 하는 소리",
"whack": "탁 치는 소리",
"breaking": "부서지는 소리",
"bouncing": "통통 튀는 소리",
"whip": "채찍 소리",
"flap": "펄럭이는 소리",
"scratch": "긁는 소리",
"scrape": "끼익 긁히는 소리",
"rub": "문지르는 소리",
"roll": "데굴데굴 굴러가는 소리",
"crushing": "으스러지는 소리",
"crumpling": "구겨지는 소리",
"tearing": "찢어지는 소리",
"clang": "쨍그랑 소리",
"creak": "삐걱거리는 소리",
"rustle": "바스락거리는 소리",
"whir": "붕붕거리는 소리",
"sizzle": "지글지글 소리",
"clickety_clack": "덜컹덜컹 소리",
"rumble": "우르릉거리는 소리",
"reverberation": "잔향음",
"distortion": "왜곡(디스토션)",
"throbbing": "두근거리는 맥박음"
}
+67 -61
View File
@@ -2,39 +2,39 @@
"readTheDocumentation": "문서 읽기",
"time": {
"untilForTime": "{{time}}까지",
"untilForRestart": "Frigate가 재시작될 때 까지.",
"untilForRestart": "Frigate가 재시작될 때까지.",
"10minutes": "10분",
"12hours": "12시간",
"1hour": "1시간",
"24hours": "24시간",
"30minutes": "30분",
"5minutes": "5분",
"untilRestart": "재시작 될 때까지",
"untilRestart": "재시작될 때까지",
"ago": "{{timeAgo}} 전",
"justNow": "방금",
"justNow": "방금 전",
"today": "오늘",
"yesterday": "어제",
"last7": "최근 7일",
"last14": "최근 14일",
"last30": "최근 30일",
"thisWeek": "이번 주",
"lastWeek": "저번 주",
"lastWeek": "지난 주",
"thisMonth": "이번 달",
"lastMonth": "저번 달",
"lastMonth": "지난 달",
"pm": "오후",
"am": "오전",
"yr": "{{time}}년",
"year_other": "{{time}} 년",
"mo": "{{time}}월",
"month_other": "{{time}} 월",
"year_other": "{{time}}년",
"mo": "{{time}}개월",
"month_other": "{{time}}개월",
"d": "{{time}}일",
"day_other": "{{time}} 일",
"h": "{{time}}시",
"hour_other": "{{time}} 시",
"day_other": "{{time}}일",
"h": "{{time}}시간",
"hour_other": "{{time}}시간",
"m": "{{time}}분",
"minute_other": "{{time}} 분",
"minute_other": "{{time}}분",
"s": "{{time}}초",
"second_other": "{{time}} 초",
"second_other": "{{time}}초",
"formattedTimestamp": {
"12hour": "MMM d, h:mm:ss aaa",
"24hour": "MMM d, HH:mm:ss"
@@ -68,7 +68,7 @@
"12hour": "MM-dd-yy-h-mm-ss-a",
"24hour": "MM-dd-yy-HH-mm-ss"
},
"never": "한 번도 없음",
"never": "안 함",
"inProgress": "진행 중",
"invalidStartTime": "잘못된 시작 시간",
"invalidEndTime": "잘못된 종료 시간"
@@ -76,25 +76,25 @@
"notFound": {
"title": "404",
"documentTitle": "찾을 수 없음 - Frigate",
"desc": "페이지 찾을 수 없음"
"desc": "페이지를 찾을 수 없습니다"
},
"accessDenied": {
"title": "접근 거부",
"documentTitle": "접근 거부 - Frigate",
"desc": "이 페이지 접근 권한이 없습니다."
"desc": "이 페이지를 볼 수 있는 권한이 없습니다."
},
"menu": {
"user": {
"account": "계정",
"title": "사용자",
"current": "현재 사용자:{{user}}",
"current": "현재 사용자: {{user}}",
"anonymous": "익명",
"logout": "로그아웃",
"setPassword": "비밀번호 설정"
},
"system": "시스템",
"systemMetrics": "시스템 지표",
"configuration": "설정",
"configuration": "설정 구성",
"systemLogs": "시스템 로그",
"settings": "설정",
"configurationEditor": "설정 편집기",
@@ -103,7 +103,7 @@
"en": "English (영어)",
"es": "Español (스페인어)",
"zhCN": "简体中文 (중국어 간체)",
"hi": "हिन्दी (힌두어)",
"hi": "हिन्दी (힌디어)",
"fr": "Français (프랑스어)",
"ar": "العربية (아랍어)",
"pt": "Português (포르투갈어)",
@@ -117,7 +117,7 @@
"sv": "Svenska (스웨덴어)",
"cs": "Čeština (체코어)",
"nb": "Norsk Bokmål (노르웨이어 보크몰)",
"ko": "한국어",
"ko": "한국어 (Korean)",
"vi": "Tiếng Việt (베트남어)",
"fa": "فارسی (페르시아어)",
"pl": "Polski (폴란드어)",
@@ -126,7 +126,7 @@
"el": "Ελληνικά (그리스어)",
"ro": "Română (루마니아어)",
"hu": "Magyar (헝가리어)",
"fi": "Suomi (Finnish)",
"fi": "Suomi (핀란드어)",
"da": "Dansk (덴마크어)",
"sk": "Slovenčina (슬로바키아어)",
"yue": "粵語 (광둥어)",
@@ -140,9 +140,12 @@
"id": "Bahasa Indonesia (인도네시아어)",
"ur": "اردو (우르두어)",
"withSystem": {
"label": "시스템 설정 언어 사용"
"label": "시스템 언어 설정 사용"
},
"hr": "Hrvatski (크로아티아어)"
"hr": "Hrvatski (크로아티아어)",
"zhHant": "繁體中文 (중국어 번체)",
"be": "Беларуская (벨라루스어)",
"bs": "Bosanski (보스니아어)"
},
"appearance": "화면 설정",
"darkMode": {
@@ -150,17 +153,17 @@
"light": "라이트",
"dark": "다크",
"withSystem": {
"label": "시스템 설정에 따라 설정"
"label": "시스템 설정에 따라 라이트/다크 모드 전환"
}
},
"withSystem": "시스템",
"theme": {
"label": "테마",
"blue": "파랑",
"green": "녹색",
"green": "초록",
"nord": "노드 (Nord)",
"red": "빨강",
"highcontrast": "고 대비",
"highcontrast": "고대비",
"default": "기본값"
},
"help": "도움말",
@@ -174,17 +177,17 @@
"allCameras": "모든 카메라",
"cameras": {
"title": "카메라",
"count_other": "{{count}} 카메라"
"count_other": "{{count}}대 카메라"
}
},
"review": "다시보기",
"explore": "탐색",
"export": "내보내기",
"uiPlayground": "UI 실험실",
"uiPlayground": "UI 플레이그라운드",
"faceLibrary": "얼굴 라이브러리",
"classification": "분류",
"chat": "채팅",
"actions": "작업",
"actions": "동작",
"profiles": "프로필",
"features": "기능"
},
@@ -210,14 +213,14 @@
"back": "뒤로",
"hide": "{{item}} 숨기기",
"show": "{{item}} 표시",
"ID": "아이디",
"ID": "ID",
"none": "없음",
"all": "전체",
"other": "그 외"
"other": "기타"
},
"button": {
"apply": "적용",
"reset": "리셋",
"reset": "재설정",
"done": "완료",
"enabled": "활성화됨",
"enable": "활성화",
@@ -229,38 +232,38 @@
"close": "닫기",
"copy": "복사",
"back": "뒤로",
"history": "히스토리",
"history": "기록",
"fullscreen": "전체화면",
"exitFullscreen": "전체화면 나가기",
"pictureInPicture": "화면 속 화면",
"twoWayTalk": "양방향 말하기",
"exitFullscreen": "전체화면 종료",
"pictureInPicture": "화면 속 화면 (PIP)",
"twoWayTalk": "양방향 대화",
"cameraAudio": "카메라 오디오",
"on": "켜기",
"off": "끄기",
"edit": "편집",
"copyCoordinates": "코디네이트 복사",
"on": "켜짐",
"off": "꺼짐",
"edit": "수정",
"copyCoordinates": "좌표 복사",
"delete": "삭제",
"yes": "예",
"no": "아니오",
"download": "다운로드",
"info": "정보",
"suspended": "일시 정지됨",
"suspended": "일시 중지됨",
"unsuspended": "재개",
"play": "재생",
"unselect": "선택 해제",
"export": "내보내기",
"deleteNow": "바로 삭제하기",
"deleteNow": "지금 삭제",
"next": "다음",
"add": "추가",
"undo": "실행 취소",
"copiedToClipboard": "클립보드에 복사",
"continue": "계속하기",
"copiedToClipboard": "클립보드에 복사됨",
"continue": "계속",
"modified": "수정됨",
"overridden": "재정의됨",
"resetToGlobal": "글로벌 설정으로 재설정",
"resetToDefault": "기본값으로 재설정",
"resetToGlobal": "전역 설정으로 초기화",
"resetToDefault": "기본값으로 초기화",
"saveAll": "모두 저장",
"savingAll": "모두 저장 중. …",
"savingAll": "모두 저장 중…",
"undoAll": "모두 실행 취소",
"applying": "적용 중…",
"retry": "재시도"
@@ -270,8 +273,8 @@
"save": {
"title": "저장",
"error": {
"title": "설정 저장 실패: {{errorMessage}}",
"noMessage": "설정 저장이 실패했습니다"
"title": "설정 변경 저장 실패: {{errorMessage}}",
"noMessage": "설정 변경 저장에 실패했습니다"
},
"success": "설정 변경이 성공적으로 저장되었습니다."
}
@@ -279,34 +282,37 @@
"role": {
"title": "역할",
"admin": "관리자",
"viewer": "감시자",
"desc": "관리자는 Frigate UI에 모든 접근 권한이 있습니다. 감시자는 카메라 감시, 돌아보기, 과거 영상 조회만 가능합니다."
"viewer": "뷰어",
"desc": "관리자는 Frigate UI의 모든 기능에 대한 전체 접근 권한을 가집니다. 뷰어는 카메라 실시간 화면, 다시보기 항목, 이전 녹화 영상 조회만 가능합니다."
},
"pagination": {
"label": "페이지 번호",
"label": "페이지 매김",
"previous": {
"title": "이전",
"label": "이전 페이지"
"label": "이전 페이지로 이동"
},
"next": {
"title": "다음",
"label": "다음 페이지"
"label": "다음 페이지로 이동"
},
"more": "더 많은 페이지"
"more": "페이지 더보기"
},
"selectItem": "{{item}} 선택",
"information": {
"pixels": "{{area}}px"
},
"list": {
"two": "{{0}}과 {{1}}",
"many": "{{items}} 그리고 {{last}}",
"two": "{{0}} 및 {{1}}",
"many": "{{items}} 및 {{last}}",
"separatorWithSpace": ", "
},
"field": {
"optional": "선택",
"internalID": "Frigate 내부 ID는 구성 및 데이터베이스에서 사용됩니다"
"optional": "선택 사항",
"internalID": "Frigate 설정 및 데이터베이스에서 사용되는 내부 ID"
},
"no_items": "내역 없음",
"validation_errors": "검증 오류"
"no_items": "항목 없음",
"validation_errors": "유효성 검사 오류",
"credentialField": {
"savedPlaceholder": "저장됨 — 현재 값을 유지하려면 비워 두세요"
}
}
+8 -8
View File
@@ -1,16 +1,16 @@
{
"form": {
"user": "사용자명",
"user": "사용자 이름",
"password": "비밀번호",
"login": "로그인",
"errors": {
"usernameRequired": "사용자명은 필수입니다",
"passwordRequired": "비밀번호는 필수입니다",
"rateLimit": "너무 많이 시도했습니다. 다음에 다시 시도하세요.",
"loginFailed": "로그인 실패",
"unknownError": "알려지지 않은 에러. 로그를 확인하세요.",
"webUnknownError": "알려지지 않은 에러. 콘솔 로그를 확인하세요."
"usernameRequired": "사용자 이름을 입력해야 합니다",
"passwordRequired": "비밀번호를 입력해야 합니다",
"rateLimit": "요청 횟수 제한을 초과했습니다. 잠시 후 다시 시도하세요.",
"loginFailed": "로그인에 실패했습니다",
"unknownError": "알 수 없는 오류가 발생했습니다. 로그를 확인하세요.",
"webUnknownError": "알 수 없는 오류가 발생했습니다. 콘솔 로그를 확인하세요."
},
"firstTimeLogin": "처음 로그인하시나요? 로그인 정보는 Frigate 로그에 있습니다."
"firstTimeLogin": "처음 로그인하시나요? 초기 로그인 정보는 Frigate 로그에 출력되어 있습니다."
}
}
+29 -26
View File
@@ -2,86 +2,89 @@
"group": {
"label": "카메라 그룹",
"add": "카메라 그룹 추가",
"edit": "카메라 그룹 편집",
"edit": "카메라 그룹 수정",
"delete": {
"label": "카메라 그룹 삭제",
"confirm": {
"title": "삭제 확인",
"desc": "정말로 카메라 그룹을 삭제하시겠습니까 <em>{{name}}</em>?"
"desc": "정말로 <em>{{name}}</em> 카메라 그룹을 삭제하시겠습니까?"
}
},
"name": {
"label": "이름",
"placeholder": "이름을 입력하세요…",
"errorMessage": {
"mustLeastCharacters": "카메라 그룹 이름은 최소 2자 이상 써야합니다.",
"mustLeastCharacters": "카메라 그룹 이름은 최소 2자 이상이어야 합니다.",
"exists": "이미 존재하는 카메라 그룹 이름입니다.",
"nameMustNotPeriod": "카메라 그룹 이름에 마침표(.)를 넣을 수 없습니다.",
"invalid": "설정 불가능한 카메라 그룹 이름."
"nameMustNotPeriod": "카메라 그룹 이름에는 마침표(.)를 포함할 수 없습니다.",
"invalid": "유효하지 않은 카메라 그룹 이름입니다."
}
},
"cameras": {
"label": "카메라",
"desc": "이 그룹에 넣을 카메라 선택하기."
"desc": "이 그룹에 포함할 카메라를 선택하세요."
},
"icon": "아이콘",
"success": "카메라 그룹 {{name}} 저장되었습니다.",
"success": "카메라 그룹({{name}})이(가) 저장되었습니다.",
"camera": {
"birdseye": "버드아이",
"setting": {
"label": "카메라 스트리밍 설정",
"title": "{{cameraName}} 스트리밍 설정",
"desc": "카메라 그룹 대시보드의 실시간 스트리밍 옵션을 변경하세요. <em>이 설정은 기기/브라우저에 따라 다릅니다.</em>",
"audioIsAvailable": "이 카메라는 오디오 기능을 사용할 수 있습니다",
"audioIsUnavailable": "이 카메라는 오디오 기능을 사용할 수 없습니다",
"desc": "카메라 그룹 대시보드의 실시간 스트리밍 옵션을 변경합니다. <em>이 설정은 기기 및 브라우저별로 개별 적용됩니다.</em>",
"audioIsAvailable": "이 스트림에서 오디오 기능을 사용할 수 있습니다",
"audioIsUnavailable": "이 스트림에서는 오디오 기능을 사용할 수 없습니다",
"audio": {
"tips": {
"title": "오디오를 출력하려면 카메라가 지원하거나 go2rtc에서 설정해야합니다."
"title": "오디오를 출력하려면 카메라 자체에서 오디오를 지원하고 go2rtc에 해당 스트림이 설정되어 있어야 합니다."
}
},
"stream": "스트림",
"placeholder": "스트림 선택",
"streamMethod": {
"label": "스트리밍 방식",
"placeholder": "스트리밍 방식 선택",
"placeholder": "스트리밍 방식을 선택하세요",
"method": {
"noStreaming": {
"label": "스트리밍 없음",
"desc": "카메라 이미지는 1분에 한 번만 보여지며 라이브 스트리밍은 되지 않습니다."
"desc": "카메라 이미지가 1분에 한 번씩만 갱신되며 실시간 스트리밍은 작동하지 않습니다."
},
"smartStreaming": {
"label": "스마트 스트리밍 (추천함)",
"desc": "스마트 스트리밍은 감지되는 활동이 없을 때 대역폭과 자원을 절약하기 위해 1분마다 한 번 카메라 이미지를 업데이트합니다. 활동이 감지되면, 이미지는 자동으로 라이브 스트림으로 원활하게 전환됩니다."
"label": "스마트 스트리밍 (권장)",
"desc": "대역폭과 시스템 자원을 절약하기 위해 감지된 동작이 없을 때는 1분마다 이미지를 갱신합니다. 동작이 감지되면 자동으로 매끄럽게 라이브 스트림으로 전환됩니다."
},
"continuousStreaming": {
"label": "지속적인 스트리밍",
"label": "지속적 스트리밍",
"desc": {
"title": "활동이 감지되지 않더라도 카메라 이미지가 대시보드에서 항상 실시간 스트림됩니다.",
"warning": "지속적인 스트리밍은 높은 대역폭 사용과 퍼포먼스 이슈를 발생할 수 있습니다. 사용에 주의해주세요."
"title": "동작이 감지되지 않더라도 대시보드에 카메라 화면이 표시될 때 항상 실시간 스트림으로 재생됩니다.",
"warning": "지속적 스트리밍은 네트워크 대역폭 사용량이 증가하고 성능 저하를 유발할 수 있으므로 주의하여 사용하세요."
}
}
}
},
"compatibilityMode": {
"label": "호환 모드",
"desc": "이 옵션은 카메라 라이브 스트림 화면의 색상이 왜곡 되었거나 이미지 오른쪽에 대각선이 나타날때만 사용하세요."
"desc": "카메라 라이브 스트림 화면에 색상 왜곡이 있거나 화면 우측에 대각선 줄이 나타나는 경우에만 이 옵션을 활성화하세요."
}
}
}
},
"showAll": "모든 카메라 그룹 표시",
"showLess": "간략히 표시",
"editGroups": "카메라 그룹 관리"
},
"debug": {
"options": {
"label": "설정",
"title": "옵션",
"showOptions": "옵션 보기",
"showOptions": "옵션 표시",
"hideOptions": "옵션 숨기기"
},
"boundingBox": "감지 영역 상자",
"timestamp": "시간 기록",
"boundingBox": "바운딩 박스 (객체 감지 영역)",
"timestamp": "타임스탬프",
"zones": "구역 (Zones)",
"mask": "마스크",
"motion": "움직임",
"mask": "마스크 (Mask)",
"motion": "움직임 감지 (Motion)",
"regions": "영역 (Regions)",
"paths": "경로"
"paths": "이동 경로 (Paths)"
}
}

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